diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py
index 436c6eaf8..74f556aa2 100644
--- a/daras_ai_v2/base.py
+++ b/daras_ai_v2/base.py
@@ -48,7 +48,10 @@
from daras_ai_v2.exceptions import InsufficientCredits
from daras_ai_v2.fastapi_tricks import get_route_path
from daras_ai_v2.github_tools import github_url_for_file
-from daras_ai_v2.gooey_builder import render_gooey_builder
+from daras_ai_v2.gooey_builder import (
+ render_gooey_builder_inline,
+ render_gooey_builder_launcher,
+)
from daras_ai_v2.grid_layout_widget import grid_layout
from daras_ai_v2.html_spinner_widget import html_spinner
from daras_ai_v2.manage_api_keys_widget import manage_api_keys
@@ -94,6 +97,7 @@
)
from widgets.publish_form import clear_publish_form
from widgets.saved_workflow import render_saved_workflow_preview
+from widgets.sidebar import sidebar_layout, use_sidebar
from widgets.workflow_image import (
render_change_notes_input,
render_workflow_photo_uploader,
@@ -427,6 +431,9 @@ def render(self):
with header_placeholder:
self._render_header()
+ def render_sidebar(self):
+ self._render_gooey_builder()
+
def _render_header(self):
from widgets.workflow_image import CIRCLE_IMAGE_WORKFLOWS
@@ -1195,8 +1202,6 @@ def render_selected_tab(self):
self.render_deleted_output()
return
- self._render_gooey_builder()
-
with gui.styled(INPUT_OUTPUT_COLS_CSS):
input_col, output_col = gui.columns([3, 2], gap="medium")
with input_col:
@@ -1225,54 +1230,65 @@ def render_selected_tab(self):
self._saved_tab()
def _render_gooey_builder(self):
- update_gui_state: dict | None = gui.session_state.pop("update_gui_state", None)
- if update_gui_state:
- new_state = (
- {
- k: v
- for k, v in gui.session_state.items()
- if k in self.fields_to_save()
- }
- | {
- "--has-request-changed": True,
- }
- | update_gui_state
- )
- gui.session_state.clear()
- gui.session_state.update(new_state)
-
- enable_bot_builder = (
- self.request.user
- and not self.request.user.is_anonymous
- and (
- self.request.user.is_admin()
- or self.current_workspace.enable_bot_builder
- )
- )
+ sidebar_ref = use_sidebar("builder-sidebar", self.request.session)
- if not enable_bot_builder:
+ # close the sidebar for other tabs
+ if self.tab != RecipeTabs.run and self.tab != RecipeTabs.preview:
+ if sidebar_ref.is_open or sidebar_ref.is_mobile_open:
+ sidebar_ref.set_open(False)
+ sidebar_ref.set_mobile_open(False)
+ raise gui.RerunException()
return
- render_gooey_builder(
- page_slug=self.slug_versions[-1],
- builder_state=dict(
- status=dict(
- error_msg=gui.session_state.get(StateKeys.error_msg),
- run_status=gui.session_state.get(StateKeys.run_status),
- run_time=gui.session_state.get(StateKeys.run_time),
- ),
- request=extract_model_fields(
- model=self.RequestModel, state=gui.session_state
- ),
- response=extract_model_fields(
- model=self.ResponseModel, state=gui.session_state
- ),
- metadata=dict(
- title=self.current_pr.title,
- description=self.current_pr.notes,
- ),
- ),
- )
+ # render the launcher if the sidebar is not open
+ if not sidebar_ref.is_open and not sidebar_ref.is_mobile_open:
+ current_workspace = self.current_workspace if self.request.user else None
+ render_gooey_builder_launcher(
+ self.request,
+ current_workspace=current_workspace,
+ is_fab_button=True,
+ )
+ else: # open the sidebar for the builder
+ with gui.div(className="w-100 h-100"):
+ update_gui_state: dict | None = gui.session_state.pop(
+ "update_gui_state", None
+ )
+ if update_gui_state:
+ new_state = (
+ {
+ k: v
+ for k, v in gui.session_state.items()
+ if k in self.fields_to_save()
+ }
+ | {
+ "--has-request-changed": True,
+ }
+ | update_gui_state
+ )
+ gui.session_state.clear()
+ gui.session_state.update(new_state)
+
+ render_gooey_builder_inline(
+ page_slug=self.slug_versions[-1],
+ builder_state=dict(
+ status=dict(
+ error_msg=gui.session_state.get(StateKeys.error_msg),
+ run_status=gui.session_state.get(StateKeys.run_status),
+ run_time=gui.session_state.get(StateKeys.run_time),
+ ),
+ request=extract_model_fields(
+ model=self.RequestModel, state=gui.session_state
+ ),
+ response=extract_model_fields(
+ model=self.ResponseModel, state=gui.session_state
+ ),
+ metadata=dict(
+ title=self.current_pr.title,
+ description=self.current_pr.notes,
+ ),
+ ),
+ sidebar_ref=sidebar_ref,
+ )
def _render_version_history(self):
versions = self.current_pr.versions.all()
diff --git a/daras_ai_v2/gooey_builder.py b/daras_ai_v2/gooey_builder.py
index 7bd55a71e..866c6ff06 100644
--- a/daras_ai_v2/gooey_builder.py
+++ b/daras_ai_v2/gooey_builder.py
@@ -2,17 +2,105 @@
from bots.models import BotIntegration
from daras_ai_v2 import settings
+from widgets.sidebar import SidebarRef, use_sidebar
+from starlette.requests import Request
+from workspaces.models import Workspace
-def render_gooey_builder(page_slug: str, builder_state: dict):
+DEFAULT_GOOEY_BUILDER_PHOTO_URL = "https://storage.googleapis.com/dara-c1b52.appspot.com/daras_ai/media/63bdb560-b891-11f0-b9bc-02420a00014a/generate-ai-abstract-symbol-artificial-intelligence-colorful-stars-icon-vector%201.jpg"
+
+
+def can_launch_gooey_builder(
+ request: Request, current_workspace: Workspace | None
+) -> bool:
+ if not request.user or request.user.is_anonymous:
+ return False
+ if request.user.is_admin():
+ return True
+ return current_workspace and current_workspace.enable_bot_builder
+
+
+def render_gooey_builder_launcher(
+ request: Request,
+ current_workspace: Workspace | None = None,
+ is_fab_button: bool = False,
+):
+ if not can_launch_gooey_builder(request, current_workspace):
+ return
+
+ sidebar_ref = use_sidebar("builder-sidebar", request.session)
+ try:
+ bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
+ except BotIntegration.DoesNotExist:
+ return
+ branding = bi.get_web_widget_branding()
+ photo_url = branding.get(
+ "photoUrl",
+ DEFAULT_GOOEY_BUILDER_PHOTO_URL,
+ )
+ branding["showPoweredByGooey"] = False
+ if is_fab_button:
+ with gui.styled("& .gooey-builder-open-button:hover { scale: 1.2; }"):
+ with gui.div(
+ className="w-100 position-absolute",
+ style={"bottom": "24px", "left": "16px", "zIndex": "1000"},
+ ):
+ gooey_builder_open_button = gui.button(
+ label=f"",
+ className="btn btn-secondary border-0 d-none d-md-block p-0 gooey-builder-open-button",
+ style={
+ "width": "56px",
+ "height": "56px",
+ "borderRadius": "50%",
+ "boxShadow": "#0000001a 0 1px 4px, #0003 0 2px 12px",
+ },
+ )
+ if gooey_builder_open_button:
+ sidebar_ref.set_open(True)
+ raise gui.RerunException()
+ else:
+ gooey_builder_mobile_open_button = gui.button(
+ label=f"
",
+ className="border-0 m-0 btn btn-secondary rounded-pill d-md-none gooey-builder-open-button p-0",
+ style={
+ "width": "36px",
+ "height": "36px",
+ "borderRadius": "50%",
+ },
+ )
+ if gooey_builder_mobile_open_button:
+ sidebar_ref.set_mobile_open(True)
+ raise gui.RerunException()
+
+
+def render_gooey_builder_inline(
+ page_slug: str, builder_state: dict, sidebar_ref: SidebarRef
+):
if not settings.GOOEY_BUILDER_INTEGRATION_ID:
return
+ # hidden button to trigger the onClose event passed in the widget config
+ gui.tag(
+ "button",
+ type="submit",
+ name="onCloseGooeyBuilder",
+ value="yes",
+ hidden=True,
+ id="onClose",
+ )
+
+ if gui.session_state.pop("onCloseGooeyBuilder", None):
+ sidebar_ref.set_open(False)
+ sidebar_ref.set_mobile_open(False)
+ raise gui.RerunException()
+
bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
config = bi.get_web_widget_config(
hostname="gooey.ai", target="#gooey-builder-embed"
)
+ config["mode"] = "inline"
+ config["showRunLink"] = True
branding = config.setdefault("branding", {})
branding["showPoweredByGooey"] = False
@@ -25,7 +113,7 @@ def render_gooey_builder(page_slug: str, builder_state: dict):
gui.html(
# language=html
f"""
-