Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
033b8b0
feat: bot builder sidebar with basic open close buttons
anish-work Oct 24, 2025
cf576c7
set deafult session state
anish-work Oct 24, 2025
380269c
only render sidebar for admin user
anish-work Oct 24, 2025
33f0be9
fix: session key setting for default open
anish-work Oct 24, 2025
836d551
widget: add onClose functionality
anish-work Nov 3, 2025
80bf66f
change circular button for bot builder
anish-work Nov 3, 2025
ece5e7f
bot_builder: hide on other tabs
anish-work Nov 3, 2025
6125a44
fix: add padding in right pane
anish-work Nov 4, 2025
fcad662
fix: move bot builder in header for mobile
anish-work Nov 4, 2025
65119e1
fix: pass builder state the same way
anish-work Nov 4, 2025
e32ed67
fix: show bot builder button for admins only
anish-work Nov 4, 2025
47b8c6e
fix: keep bot builder open in preview tab
anish-work Nov 18, 2025
2078fc3
builder - add "showRunLink" to builder widget config
anish-work Nov 24, 2025
b023d9d
fix: close side in pages other than recipe
anish-work Nov 27, 2025
8ddf643
chore: remove unused code
anish-work Dec 18, 2025
d609211
chore: move logic to _render_gooey_builder
anish-work Dec 18, 2025
34cf1e7
fix: progress bar z-index order
anish-work Dec 18, 2025
47d5e2a
fix: formatting issue
anish-work Dec 19, 2025
b0cfe27
fix: pick bot builder photoUrl from branding
anish-work Jan 2, 2026
3df1ff4
fix: check use before fetching workspace
anish-work Jan 2, 2026
b337a18
fix: remove padding from main pane
anish-work Jan 6, 2026
efbceda
fix: remove rerun in workspace
anish-work Jan 7, 2026
689d02f
fix: render gooey builder icon in header for recipe pages only
anish-work Jan 7, 2026
6e61641
fix: misc typos
anish-work Jan 9, 2026
a0a2277
fix: close both desktop and mobile sidebar states when builder is closed
anish-work Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 64 additions & 48 deletions daras_ai_v2/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand Down
96 changes: 94 additions & 2 deletions daras_ai_v2/gooey_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"<img src='{photo_url}' style='width: 56px; height: 56px; border-radius: 50%;' />",
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"<img src='{photo_url}' style='width: 36px; height: 36px; border-radius: 50%;' />",
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"
)
Comment on lines 96 to 99
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing exception handling for BotIntegration lookup.

Unlike render_gooey_builder_launcher (lines 32-35), this function doesn't catch BotIntegration.DoesNotExist. If the integration is missing, this will raise an unhandled exception.

Suggested fix
+    try:
+        bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
+    except BotIntegration.DoesNotExist:
+        return
-    bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
     config = bi.get_web_widget_config(
         hostname="gooey.ai", target="#gooey-builder-embed"
     )
🤖 Prompt for AI Agents
In @daras_ai_v2/gooey_builder.py around lines 96 - 99, The BotIntegration lookup
in the gooey_builder flow is missing exception handling: wrap the
BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID) call in a
try/except that catches BotIntegration.DoesNotExist, log or handle the missing
integration (as done in render_gooey_builder_launcher) and return an appropriate
response instead of letting the exception bubble; ensure get_web_widget_config
is only called when the BotIntegration instance (bi) exists and include the same
error handling/logging behavior used in render_gooey_builder_launcher to keep
behavior consistent.

Comment on lines 97 to 100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing exception handling for BotIntegration lookup.

Unlike render_gooey_builder_launcher (lines 33-35), this function doesn't catch BotIntegration.DoesNotExist. If the integration is missing, this will raise an unhandled exception instead of gracefully returning.

🔧 Proposed fix to add exception handling
-    bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
+    try:
+        bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
+    except BotIntegration.DoesNotExist:
+        return
     config = bi.get_web_widget_config(
         hostname="gooey.ai", target="#gooey-builder-embed"
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
config = bi.get_web_widget_config(
hostname="gooey.ai", target="#gooey-builder-embed"
)
try:
bi = BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID)
except BotIntegration.DoesNotExist:
return
config = bi.get_web_widget_config(
hostname="gooey.ai", target="#gooey-builder-embed"
)
🤖 Prompt for AI Agents
In @daras_ai_v2/gooey_builder.py around lines 97 - 100, Wrap the BotIntegration
lookup in a try/except that catches BotIntegration.DoesNotExist (the same
pattern used in render_gooey_builder_launcher) so the code that calls
BotIntegration.objects.get(id=settings.GOOEY_BUILDER_INTEGRATION_ID) does not
raise—on exception return the same graceful fallback/response used by
render_gooey_builder_launcher instead of letting the error bubble, and only call
bi.get_web_widget_config(...) when the lookup succeeds.


config["mode"] = "inline"
config["showRunLink"] = True
branding = config.setdefault("branding", {})
branding["showPoweredByGooey"] = False

Expand All @@ -25,7 +113,7 @@ def render_gooey_builder(page_slug: str, builder_state: dict):
gui.html(
# language=html
f"""
<div id="gooey-builder-embed"></div>
<div id="gooey-builder-embed" style="height: 100%"></div>
<script id="gooey-builder-embed-script" src="{settings.WEB_WIDGET_LIB}"></script>
"""
)
Expand All @@ -42,8 +130,12 @@ def render_gooey_builder(page_slug: str, builder_state: dict):
GooeyEmbed.setGooeyBuilderVariables = (value) => {
config.payload.variables = value;
};

GooeyEmbed.setGooeyBuilderVariables(variables);

config.onClose = function() {
document.getElementById("onClose").click();
};
GooeyEmbed.mount(config);
}

Expand Down
Loading
Loading