Skip to content

Conversation

@anish-work
Copy link
Contributor

@anish-work anish-work commented Oct 29, 2025

Q/A checklist

  • I have tested my UI changes on mobile and they look acceptable
  • I have tested changes to the workflows in both the API and the UI
  • I have done a code review of my changes and looked at each line of the diff + the references of each function I have changed
  • My changes have not increased the import time of the server
How to check import time?

time python -c 'import server'

You can visualize this using tuna:

python3 -X importtime -c 'import server' 2> out.log && tuna out.log

To measure import time for a specific library:

$ time python -c 'import pandas'

________________________________________________________
Executed in    1.15 secs    fish           external
   usr time    2.22 secs   86.00 micros    2.22 secs
   sys time    0.72 secs  613.00 micros    0.72 secs

To reduce import times, import libraries that take a long time inside the functions that use them instead of at the top of the file:

def my_function():
    import pandas as pd
    ...

Legal Boilerplate

Look, I get it. The entity doing business as “Gooey.AI” and/or “Dara.network” was incorporated in the State of Delaware in 2020 as Dara Network Inc. and is gonna need some rights from me in order to utilize my contributions in this PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Dara Network Inc can use, modify, copy, and redistribute my contributions, under its choice of terms.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

📝 Walkthrough

Walkthrough

This PR introduces a sidebar UI system with Gooey builder integration. A new module widgets/sidebar.py provides sidebar state management, responsive layout helpers, and mobile-aware rendering. The gooey_builder module is enhanced with render_gooey_builder_inline() for inline sidebar rendering. The page_wrapper in routers/root.py is refactored to support two-pane layouts with sidebar integration. The workspace model gains two new boolean fields (bot_builder and restricted_function_scope), configuration constants for concurrency limits and Gooey builder icon are added to settings, and the ModelPricing field is extended with expanded model choices via migration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • widgets/sidebar.py — New module implementing sidebar state management (SidebarRef class), session persistence, responsive layout with CSS, and mobile header; requires understanding of sidebar state lifecycle and mobile behavior
  • routers/root.py page_wrapper — Significant restructuring from single-container to two-pane layout; requires careful validation of rendering order, conditional logic for mobile button injection, and pane-content composition
  • render_gooey_builder_inline() in gooey_builder.py — JS wiring for GooeyEmbed mounting, payload.variables setter, and hydration-aware loading; verify inline vs. non-inline behavior correctness
  • Integration across modules — sidebar rendering in base.py, state management in root.py, and settings additions; validate consistency of sidebar open/close behavior and builder icon usage
  • Workspace model migrations — Two new fields added; verify migration order and no data loss

Possibly related PRs

  • fix migration conflict #694: Directly related through ModelPricing.model_name migration alterations affecting the same field across related PRs

Suggested reviewers

  • devxpy

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title "Bot builder in sidebar" directly describes the main feature added across the changeset, which introduces sidebar rendering for the Gooey bot builder with related UI and state management.
Description check ✅ Passed The pull request description fully completes the provided template with all Q/A checklist items marked as complete and the legal boilerplate included.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bot_builder_sidebar

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c73660e and 95bb0b4.

📒 Files selected for processing (6)
  • daras_ai_v2/base.py (5 hunks)
  • usage_costs/migrations/0038_alter_modelpricing_model_name.py (1 hunks)
  • workspaces/admin.py (1 hunks)
  • workspaces/migrations/0013_workspace_bot_builder_and_more.py (1 hunks)
  • workspaces/models.py (1 hunks)
  • workspaces/widgets.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • daras_ai_v2/base.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • workspaces/widgets.py
  • workspaces/admin.py
  • workspaces/models.py
  • usage_costs/migrations/0038_alter_modelpricing_model_name.py
  • workspaces/migrations/0013_workspace_bot_builder_and_more.py
🪛 Ruff (0.14.6)
usage_costs/migrations/0038_alter_modelpricing_model_name.py

8-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


12-298: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

workspaces/migrations/0013_workspace_bot_builder_and_more.py

8-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


12-23: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (4)
workspaces/widgets.py (1)

35-42: LGTM! Immediate rerun ensures UI consistency.

The addition of raise gui.RerunException() after updating the workspace session ensures the UI immediately reflects the new workspace state, which is appropriate for the sidebar integration introduced in this PR.

workspaces/admin.py (1)

92-93: LGTM! Admin fields properly expose new model attributes.

The new bot_builder and restricted_function_scope fields are correctly added to the admin interface and align with the corresponding model fields introduced in this PR.

workspaces/migrations/0013_workspace_bot_builder_and_more.py (1)

1-23: LGTM! Migration correctly adds new workspace feature flags.

The migration properly adds bot_builder and restricted_function_scope BooleanFields with appropriate defaults. The structure is standard and consistent with the model changes.

workspaces/models.py (1)

158-160: LGTM! Feature flags properly added to the model.

The new bot_builder and restricted_function_scope fields are appropriately defined as BooleanFields with sensible defaults, and are clearly documented as feature flags.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (6)
daras_ai_v2/gooey_builder.py (2)

3-3: Defer heavy Django model import to reduce server import time

Importing BotIntegration at module import time can slow startup. Lazily import inside the functions instead.

Apply this diff:

-from bots.models import BotIntegration
+# Defer heavy Django model import to function scope to reduce import time

And inside both functions:

 def render_gooey_builder_inline(page_slug: str, builder_state: dict):
+    from bots.models import BotIntegration
     if not settings.GOOEY_BUILDER_INTEGRATION_ID:
         return
@@
 def render_gooey_builder(page_slug: str, builder_state: dict):
+    from bots.models import BotIntegration
     if not settings.GOOEY_BUILDER_INTEGRATION_ID:
         return

Also applies to: 11-15, 66-74


12-14: Avoid hard-coding hostname; derive at runtime or accept a parameter

Using hostname="gooey.ai" may break staging/custom hosts and Google Maps API hostname checks. Consider deriving from settings.APP_BASE_URL or passing hostname from the caller.

Example:

from furl import furl
hostname = furl(settings.APP_BASE_URL).host or "gooey.ai"
config = bi.get_web_widget_config(hostname=hostname, target="#gooey-builder-embed")

Also applies to: 16-24, 30-33

daras_ai_v2/base.py (1)

417-446: Unify default sidebar open state with routers.root to avoid first-load flicker

render_sidebar uses use_sidebar("builder-sidebar") with default default_open=True, but routers.root initializes the same key with default_open=False. Consider aligning defaults in both places (False) to prevent state desync on first load.

Would you like a small patch to centralize this default in a constant?

widgets/sidebar.py (3)

41-47: Use dict.pop instead of membership test + del (RUF051)

Simplify and avoid KeyError race.

Apply this diff:

-    if current_time - last_load_time > 0.5:
-        # Fresh page load - clear mobile state
-        mobile_key = key + ":mobile"
-        if mobile_key in session:
-            del session[mobile_key]
+    if current_time - last_load_time > 0.5:
+        # Fresh page load - clear mobile state
+        mobile_key = key + ":mobile"
+        session.pop(mobile_key, None)

127-134: Remove unused enumerate index (B007)

The loop counter i is unused.

Apply this diff:

-def sidebar_item_list(is_sidebar_open, current_url=None):
-    for i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS):
+def sidebar_item_list(is_sidebar_open, current_url=None):
+    for url, label, icon in settings.SIDEBAR_LINKS:

41-43: Fix misleading comment about time threshold

Comment says “1 second” but code uses 0.5s.

Update to: “If more than 0.5 seconds has passed since last load…”

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6172b8 and ac831ba.

📒 Files selected for processing (4)
  • daras_ai_v2/base.py (4 hunks)
  • daras_ai_v2/gooey_builder.py (1 hunks)
  • routers/root.py (4 hunks)
  • widgets/sidebar.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
daras_ai_v2/gooey_builder.py (1)
bots/models/bot_integration.py (1)
  • get_web_widget_config (565-592)
routers/root.py (4)
widgets/sidebar.py (2)
  • sidebar_layout (210-322)
  • use_sidebar (33-60)
daras_ai_v2/base.py (3)
  • render_sidebar (417-445)
  • render (368-415)
  • current_workspace (1467-1472)
routers/account.py (1)
  • explore_in_current_workspace (140-156)
workspaces/widgets.py (1)
  • global_workspace_selector (21-161)
widgets/sidebar.py (1)
routers/root.py (1)
  • anonymous_login_container (853-882)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (66-121)
  • render_gooey_builder_inline (7-63)
widgets/sidebar.py (3)
  • sidebar_layout (210-322)
  • use_sidebar (33-60)
  • set_open (21-22)
🪛 Ruff (0.14.2)
widgets/sidebar.py

46-46: Use pop instead of key in dict followed by del dict[key]

Replace if statement with .pop(..., None)

(RUF051)


128-128: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

Comment on lines +292 to +306
with (
gui.styled(side_bar_styles),
gui.div(
className="d-flex w-100 h-100 position-relative sidebar-click-container",
style={"height": "100dvh"},
onClick=dedent(
"""
if (event.target.id === "sidebar-click-container") {
document.getElementById("sidebar-hidden-btn").click();
}
"""
if not sidebar_ref.is_open
else ""
),
),
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 | 🟠 Major

Click-to-open handler never fires (id mismatch and target misuse)

The onClick handler checks event.target.id === "sidebar-click-container", but the wrapper div has no id and target is often a child. Add the id and compare against event.currentTarget.

Apply this diff:

-        gui.div(
-            className="d-flex w-100 h-100 position-relative sidebar-click-container",
-            style={"height": "100dvh"},
-            onClick=dedent(
-                """
-                if (event.target.id === "sidebar-click-container") {
-                    document.getElementById("sidebar-hidden-btn").click();
-                }
-                """
-                if not sidebar_ref.is_open
-                else ""
-            ),
-        ),
+        gui.div(
+            id="sidebar-click-container",
+            className="d-flex w-100 h-100 position-relative sidebar-click-container",
+            style={"height": "100dvh"},
+            onClick=dedent(
+                """
+                if (event.currentTarget.id === "sidebar-click-container") {
+                    document.getElementById("sidebar-hidden-btn").click();
+                }
+                """
+                if not sidebar_ref.is_open
+                else ""
+            ),
+        ),

Also applies to: 308-316

🤖 Prompt for AI Agents
In widgets/sidebar.py around lines 292-306 (and similarly 308-316) the
click-to-open handler checks event.target.id === "sidebar-click-container" but
the wrapper div has no id and event.target may be a child element; add
id="sidebar-click-container" to the wrapper div and change the JS check to
compare against event.currentTarget (e.g., event.currentTarget.id ===
"sidebar-click-container") so the handler fires correctly; apply the same id
addition and event.currentTarget comparison to the other occurrence.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
daras_ai_v2/base.py (1)

1252-1274: Do not pass entire session_state to the widget; use the filtered approach in the commented code.

This change still passes gui.session_state wholesale to the client widget (line 1254), which can expose unrelated or sensitive session data. The previous review flagged this exact issue, and the commented code (lines 1255-1274) demonstrates the safer, filtered approach.

Apply this diff to restore the filtered payload:

-        render_gooey_builder_inline(self.slug_versions[-1], gui.session_state)
-        # 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,
-        #         ),
-        #     ),
-        # )
+        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_gooey_builder_inline(self.slug_versions[-1], builder_state)
🧹 Nitpick comments (1)
daras_ai_v2/gooey_builder.py (1)

7-24: Consider documenting the expected structure and security implications of builder_state.

The builder_state parameter accepts any dict and passes it to the client widget via variables. Adding a docstring or type hint clarifying the expected structure (e.g., status/request/response/metadata) and noting that sensitive data should be filtered out would help prevent misuse.

Example:

 def render_gooey_builder_inline(page_slug: str, builder_state: dict):
+    """
+    Render the Gooey builder widget in inline mode.
+    
+    Args:
+        page_slug: The workflow page slug
+        builder_state: Filtered state dict containing only non-sensitive data
+            (e.g., status, request, response, metadata). Do not pass raw session_state.
+    """
     if not settings.GOOEY_BUILDER_INTEGRATION_ID:
         return
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac831ba and ead2b18.

📒 Files selected for processing (2)
  • daras_ai_v2/base.py (4 hunks)
  • daras_ai_v2/gooey_builder.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (70-125)
  • render_gooey_builder_inline (7-67)
widgets/sidebar.py (3)
  • sidebar_layout (210-322)
  • use_sidebar (33-60)
  • set_open (21-22)
daras_ai_v2/gooey_builder.py (1)
bots/models/bot_integration.py (1)
  • get_web_widget_config (565-592)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (python)
  • GitHub Check: test (3.10.12, 1.8.3)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
daras_ai_v2/base.py (1)

1282-1284: Do not pass entire session_state to the widget; filter to required fields only.

Passing gui.session_state wholesale to render_gooey_builder_inline can inadvertently expose unrelated or sensitive data to the client widget. The commented-out code below (lines 1286-1305) shows the safer, filtered approach.

Apply the diff from the previous review to restore a filtered payload:

 if not self.is_current_user_admin():
     return
-render_gooey_builder_inline(self.slug_versions[-1], gui.session_state)
+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_gooey_builder_inline(self.slug_versions[-1], builder_state)

This preserves the inline experience while avoiding broad session exposure.

Based on past review comments.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df49769 and df76b4d.

📒 Files selected for processing (1)
  • daras_ai_v2/base.py (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (70-125)
  • render_gooey_builder_inline (7-67)
widgets/sidebar.py (4)
  • sidebar_layout (210-322)
  • use_sidebar (33-60)
  • set_open (21-22)
  • set_mobile_open (24-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test (3.10.12, 1.8.3)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (2)
daras_ai_v2/base.py (2)

48-48: LGTM: Imports support the new sidebar functionality.

The new imports are necessary for the sidebar integration. Note that sidebar_layout is imported at line 85 but not used in this file—it's likely consumed in routers/root.py as mentioned in the AI summary.

Also applies to: 85-85


419-479: render_sidebar() is properly integrated and invoked.

Verification confirms the method is called from routers/root.py line 740 as container.render_sidebar(). The implementation correctly restricts the builder to the run tab, manages state transitions, and handles both desktop and mobile views. No issues found.

Comment on lines +1286 to +1305
# 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,
# ),
# ),
# )
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove commented code once the filtered approach is restored.

After applying the fix from the previous review to filter builder_state, remove this commented block to improve code cleanliness.

-
-        # 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,
-        #         ),
-        #     ),
-        # )
📝 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
# 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,
# ),
# ),
# )
🤖 Prompt for AI Agents
In daras_ai_v2/base.py around lines 1286 to 1305, there is a leftover commented
block calling render_gooey_builder that should be removed now that the filtered
builder_state approach is restored; delete the entire commented block (all lines
between the triple-comment markers) to clean up dead code and ensure no dangling
references remain, then run tests/lint to confirm no unused symbols or
formatting issues were introduced.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
daras_ai_v2/base.py (1)

1291-1310: Remove commented code.

As noted in the previous review, this commented block should be removed to improve code cleanliness. The inline rendering approach is now established, and keeping the old implementation as a comment adds unnecessary clutter.

Apply this diff to remove the commented code:

-
-        # 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,
-        #         ),
-        #     ),
-        # )

Based on learnings

🧹 Nitpick comments (1)
daras_ai_v2/base.py (1)

419-466: Consider adding a docstring to document sidebar integration.

The render_sidebar method is a new public method but lacks documentation explaining when it's called and its integration with the page wrapper/layout system.

Apply this diff to add documentation:

 def render_sidebar(self):
+    """
+    Render an admin-only sidebar containing the Gooey builder interface.
+    
+    This method is called by the page wrapper to render sidebar content.
+    The sidebar is only displayed on the run tab for admin users and provides
+    an inline builder interface for workflow configuration.
+    """
     if not self.is_current_user_admin():
         return
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf968b2 and 2dfeb70.

📒 Files selected for processing (1)
  • daras_ai_v2/base.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (70-125)
  • render_gooey_builder_inline (7-67)
widgets/sidebar.py (4)
  • sidebar_layout (210-325)
  • use_sidebar (33-60)
  • set_open (21-22)
  • set_mobile_open (24-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (1)
daras_ai_v2/base.py (1)

1270-1289: Security concern from past review has been addressed.

The current implementation correctly filters builder_state to only include necessary fields (status, request, response, metadata) rather than passing the entire gui.session_state. The use of extract_model_fields ensures only model-defined fields are sent to the client widget.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
routers/root.py (1)

823-828: Fix className concatenation bug (regression from past review).

Despite the past review comment indicating this was addressed, the className parameter concatenation issue remains:

  1. When the sidebar is closed (not is_builder_sidebar_open), the custom className parameter is completely dropped
  2. When the sidebar is open, there's no space between "mx-2 w-100" and the custom className

Apply this diff to properly concatenate the className in both cases:

             with gui.div(
                 id="main-content",
-                className="container-xxl "
-                if not is_builder_sidebar_open
-                else "mx-2 w-100" + className,
+                className=(
+                    "container-xxl " + className
+                    if not is_builder_sidebar_open
+                    else "mx-2 w-100 " + className
+                ),
             ):
🧹 Nitpick comments (1)
routers/root.py (1)

733-733: Consider removing redundant assignment.

The line container = page if page else None is redundant since it simply assigns page to container. You can use page directly in the subsequent code.

Apply this diff to simplify:

-    container = page if page else None
     sidebar_ref = use_sidebar("builder-sidebar", request.session, default_open=False)
     sidebar_content, pane_content = sidebar_layout(sidebar_ref)
 
     is_builder_sidebar_open = sidebar_ref.is_open
     with sidebar_content:
-        if container:
-            container.render_sidebar()
+        if page:
+            page.render_sidebar()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2dfeb70 and 377e931.

📒 Files selected for processing (1)
  • routers/root.py (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
routers/root.py (6)
widgets/sidebar.py (3)
  • sidebar_layout (210-325)
  • use_sidebar (33-60)
  • set_mobile_open (24-26)
daras_ai_v2/base.py (3)
  • render_sidebar (419-465)
  • render (370-417)
  • current_workspace (1520-1525)
app_users/models.py (2)
  • is_admin (261-262)
  • save (359-365)
daras_ai_v2/fastapi_tricks.py (1)
  • get_route_path (77-80)
routers/account.py (1)
  • explore_in_current_workspace (140-156)
workspaces/widgets.py (1)
  • global_workspace_selector (21-161)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test (3.10.12, 1.8.3)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (3)
routers/root.py (3)

48-48: LGTM!

The sidebar imports are correctly added to support the new two-pane layout functionality.


705-705: LGTM!

Correctly passes the page instance to enable page-specific sidebar rendering.


835-864: LGTM!

The mobile search button implementation correctly uses responsive classes (d-md-none) to show only on mobile devices, and the toggle logic properly manages visibility.

@contextmanager
def page_wrapper(
request: Request,
page: None = None,
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

Fix incorrect type annotation for page parameter.

The type annotation page: None = None is incorrect. This annotation means the parameter can only accept None as a value, which prevents passing actual page instances. The parameter should accept either None or a page instance.

Apply this diff to fix the type annotation:

 def page_wrapper(
     request: Request,
-    page: None = None,
+    page: typing.Optional["BasePage"] = None,
     className="",
     search_filters: typing.Optional[SearchFilters] = None,
     show_search_bar: bool = True,
 ):

Note: You may need to add from __future__ import annotations at the top of the file if forward reference resolution is needed, or import BasePage directly if circular import is not a concern.

🤖 Prompt for AI Agents
In routers/root.py around line 724, the parameter annotation `page: None = None`
is wrong because it only allows None; change it to accept a page instance or
None by annotating `page` as Optional[BasePage] (e.g., `page: Optional[BasePage]
= None`) or using a union/PEP 604 style (`page: BasePage | None = None`). Add
`from typing import Optional` (or adjust imports for union syntax) and either
import BasePage directly or add `from __future__ import annotations` to avoid
forward-reference issues; update imports accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
daras_ai_v2/base.py (1)

1291-1310: Remove the commented legacy render_gooey_builder call

The commented block with the old non‑inline render_gooey_builder(...) call is now redundant and increases noise. Since the inline path is the active implementation, it’s safe to delete this block.

-        # 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,
-        #         ),
-        #     ),
-        # )
🧹 Nitpick comments (1)
daras_ai_v2/gooey_builder.py (1)

7-68: Guard config.onClose against missing #onClose element

The inline builder wires config.onClose to document.getElementById("onClose").click(), which will throw if the element is absent (e.g., layout changes, multiple builders, or timing edge cases). A small null‑check keeps the widget from crashing when the close target isn’t present.

-    config.onClose = function() {
-        document.getElementById("onClose").click();
-    };
+    config.onClose = function() {
+        const btn = document.getElementById("onClose");
+        if (btn) btn.click();
+    };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 377e931 and aea0371.

📒 Files selected for processing (2)
  • daras_ai_v2/base.py (5 hunks)
  • daras_ai_v2/gooey_builder.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
daras_ai_v2/gooey_builder.py (1)
bots/models/bot_integration.py (2)
  • BotIntegration (163-626)
  • get_web_widget_config (565-592)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (71-126)
  • render_gooey_builder_inline (7-68)
widgets/sidebar.py (3)
  • use_sidebar (33-60)
  • set_open (21-22)
  • set_mobile_open (24-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (1)
daras_ai_v2/base.py (1)

1251-1289: Inline builder wiring and filtered state look good

The updated _render_gooey_builder now calls render_gooey_builder_inline with a filtered builder_state (status, request, response, metadata via extract_model_fields). This avoids sending full session_state to the widget while preserving necessary context for the builder. No changes needed here.

Comment on lines 419 to 466
def render_sidebar(self):
if not self.is_current_user_admin():
return

sidebar_ref = use_sidebar("builder-sidebar", self.request.session)
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

if sidebar_ref.is_open or sidebar_ref.is_mobile_open:
gui.tag(
"button",
type="submit",
name="onCloseGooeyBuilder",
value="yes",
hidden=True,
id="onClose",
) # hidden button to trigger the onClose event passed in the config

if gui.session_state.pop("onCloseGooeyBuilder", None):
sidebar_ref.set_open(False)
raise gui.RerunException()

with gui.div(className="w-100 h-100"):
self._render_gooey_builder()
else:
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='{settings.GOOEY_BUILDER_ICON}' 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()

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 | 🟠 Major

Ensure sidebar close works for both desktop and mobile states

In render_sidebar, when handling the hidden "onCloseGooeyBuilder" button, only sidebar_ref.set_open(False) is called. If the sidebar was opened via the mobile state (is_mobile_open), this leaves is_mobile_open True in the session, so the sidebar may immediately re‑open or get out of sync on mobile.

Recommend closing both flags when the builder’s onClose fires:

-        if sidebar_ref.is_open or sidebar_ref.is_mobile_open:
+        if sidebar_ref.is_open or sidebar_ref.is_mobile_open:
             gui.tag(
                 "button",
@@
-            if gui.session_state.pop("onCloseGooeyBuilder", None):
-                sidebar_ref.set_open(False)
-                raise gui.RerunException()
+            if gui.session_state.pop("onCloseGooeyBuilder", None):
+                sidebar_ref.set_open(False)
+                sidebar_ref.set_mobile_open(False)
+                raise gui.RerunException()
🤖 Prompt for AI Agents
daras_ai_v2/base.py around lines 419-466: the hidden "onCloseGooeyBuilder"
handler only calls sidebar_ref.set_open(False), leaving
sidebar_ref.is_mobile_open possibly true and causing mobile state to remain
out-of-sync; update the handler (the gui.session_state.pop branch) to also call
sidebar_ref.set_mobile_open(False) before raising gui.RerunException, and ensure
any other close-only branches in this range that call set_open(False) also clear
set_mobile_open(False) so both flags are closed when the builder onClose fires.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
routers/root.py (2)

721-729: Correct page parameter type annotation (currently only allows None)

page: None = None annotates the parameter as literally None, which hides that callers can/should pass a page instance (e.g., BasePage) and degrades type-checker/IDE help.

Consider something like:

-from contextlib import contextmanager
+from contextlib import contextmanager
+from typing import Optional
+from daras_ai_v2.base import BasePage
@@
 def page_wrapper(
     request: Request,
-    page: None = None,
+    page: Optional[BasePage] = None,

(or your preferred union/forward-reference style if circular imports are a concern).


829-835: Fix main-content className concatenation (drops custom classes when sidebar is closed)

Right now:

className="container-xxl "
if not is_builder_sidebar_open
else "mx-2 w-100" + className,
  • Ignores the passed className when the sidebar is closed.
  • Misses a separating space when the sidebar is open.

A more robust pattern:

-            with gui.div(
-                id="main-content",
-                className="container-xxl "
-                if not is_builder_sidebar_open
-                else "mx-2 w-100" + className,
-            ):
+            base_class = (
+                "container-xxl"
+                if not is_builder_sidebar_open
+                else "mx-2 w-100"
+            )
+            extra_class = f" {className}" if className else ""
+            with gui.div(
+                id="main-content",
+                className=base_class + extra_class,
+            ):

so any caller-provided classes are honored in both sidebar states with correct spacing.

🧹 Nitpick comments (1)
routers/root.py (1)

783-799: Hide Gooey builder mobile button on non-recipe pages

The admin-only mobile builder button is rendered for every page using page_wrapper, even when page is None, so on non-recipe pages tapping it can show a blank sidebar.

To avoid dangling UI, you can additionally gate on recipe pages:

-                    if request.user and request.user.is_admin:
+                    if (
+                        is_recipe_page
+                        and request.user
+                        and request.user.is_admin
+                    ):

so the button only appears where the builder actually renders.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aea0371 and 05b09e5.

📒 Files selected for processing (1)
  • routers/root.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • routers/root.py
🧬 Code graph analysis (1)
routers/root.py (2)
widgets/sidebar.py (4)
  • sidebar_layout (210-325)
  • use_sidebar (33-60)
  • set_open (21-22)
  • set_mobile_open (24-26)
daras_ai_v2/base.py (2)
  • render_sidebar (419-465)
  • render (370-417)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test (3.10.12, 1.8.3)
🔇 Additional comments (2)
routers/root.py (2)

48-48: Sidebar wiring for recipe pages looks correct

Importing sidebar_layout/use_sidebar and calling page_wrapper(request, page=page, is_recipe_page=True) in render_recipe_page cleanly ties the sidebar state to the concrete page instance; routing and meta-generation remain unchanged and look sound.

Also applies to: 705-712


841-847: Mobile search button container change looks good

Using className="d-flex d-md-none justify-content-end" for the mobile search button container keeps it mobile-only while ensuring proper flex alignment; no functional issues here.

Comment on lines +734 to 743
container = page if page else None
sidebar_ref = use_sidebar("builder-sidebar", request.session, default_open=False)
sidebar_content, pane_content = sidebar_layout(sidebar_ref)

with (
gui.div(className="header"),
gui.div(className="navbar navbar-expand-xl bg-transparent p-0 m-0"),
gui.div(className="container-xxl my-2"),
gui.div(
className="position-relative w-100 d-flex justify-content-between gap-2"
),
):
with (
gui.div(className="d-md-block"),
gui.tag("a", href="/"),
):
gui.tag(
"img",
src=settings.GOOEY_LOGO_IMG,
width="300",
height="142",
className="img-fluid logo d-none d-sm-block",
)
gui.tag(
"img",
src=settings.GOOEY_LOGO_RECT,
width="145",
height="40",
className="img-fluid logo d-sm-none",
)
is_builder_sidebar_open = sidebar_ref.is_open
if not is_recipe_page and (is_builder_sidebar_open):
sidebar_ref.set_open(False)
sidebar_ref.set_mobile_open(False)
raise gui.RerunException()

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 | 🟠 Major

Treat mobile-open sidebars as “open” and close them on non-recipe pages

is_builder_sidebar_open = sidebar_ref.is_open ignores sidebar_ref.is_mobile_open. If an admin opens the builder via the mobile button on a non-recipe page, you can end up with a visually open but empty sidebar that isn’t auto-closed by the not is_recipe_page guard.

Updating the flag to consider mobile state fixes both the guard and the layout branches that depend on it:

-    is_builder_sidebar_open = sidebar_ref.is_open
-    if not is_recipe_page and (is_builder_sidebar_open):
+    is_builder_sidebar_open = sidebar_ref.is_open or sidebar_ref.is_mobile_open
+    if not is_recipe_page and is_builder_sidebar_open:
         sidebar_ref.set_open(False)
         sidebar_ref.set_mobile_open(False)
         raise gui.RerunException()

This also makes the header and main-content sizing react consistently to mobile-open sidebars.

🤖 Prompt for AI Agents
In routers/root.py around lines 734 to 743, the code sets
is_builder_sidebar_open = sidebar_ref.is_open but ignores
sidebar_ref.is_mobile_open, causing mobile-open sidebars to remain visually open
on non-recipe pages; change the flag to consider both states (e.g.,
is_builder_sidebar_open = sidebar_ref.is_open or sidebar_ref.is_mobile_open) and
update any subsequent branches that rely on is_builder_sidebar_open so they
react to the combined state (also ensure the auto-close logic calls both
sidebar_ref.set_open(False) and sidebar_ref.set_mobile_open(False) as already
present).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
daras_ai_v2/base.py (2)

1291-1310: Remove legacy commented render_gooey_builder block

The fully commented render_gooey_builder(...) block is now redundant given the inline embed path above and adds noise. It’s also the only place in this file that references render_gooey_builder, so keeping it around can confuse future readers.

Consider deleting the entire commented block:

-        # 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,
-        #         ),
-        #     ),
-        # )

(You can then drop the render_gooey_builder import at Line 48 if it’s unused elsewhere.)


419-466: Close handler should also clear mobile sidebar state

In render_sidebar, the "onCloseGooeyBuilder" path (Line 441) only calls sidebar_ref.set_open(False). If the sidebar was opened via is_mobile_open, the mobile flag remains True in the session, so the sidebar can get out of sync or immediately re-open on mobile. The tab != run/preview branch correctly clears both flags; the onClose handler should do the same.

Recommend updating the onClose branch:

-            if gui.session_state.pop("onCloseGooeyBuilder", None):
-                sidebar_ref.set_open(False)
-                raise gui.RerunException()
+            if gui.session_state.pop("onCloseGooeyBuilder", None):
+                sidebar_ref.set_open(False)
+                sidebar_ref.set_mobile_open(False)
+                raise gui.RerunException()

Also double-check any other future close-only branches to ensure both flags are reset.

🧹 Nitpick comments (1)
workspaces/models.py (1)

177-177: Consider adding help_text for admin clarity.

The features field is a generic JSON bag. Adding a help_text would help administrators understand its intended use and expected structure.

-    features = models.JSONField(default=dict)
+    features = models.JSONField(default=dict, help_text="Workspace feature flags and configuration")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05b09e5 and c73660e.

📒 Files selected for processing (5)
  • daras_ai_v2/base.py (5 hunks)
  • usage_costs/migrations/0038_alter_modelpricing_model_name.py (1 hunks)
  • workspaces/admin.py (3 hunks)
  • workspaces/migrations/0013_workspace_features.py (1 hunks)
  • workspaces/models.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,js,ts,tsx,java,cs,cpp,c,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/devs-rules.mdc)

Format code in reverse topological order: place the main() function at the top and dependencies below it

Files:

  • daras_ai_v2/base.py
  • workspaces/models.py
  • workspaces/admin.py
  • usage_costs/migrations/0038_alter_modelpricing_model_name.py
  • workspaces/migrations/0013_workspace_features.py
🧬 Code graph analysis (2)
daras_ai_v2/base.py (2)
daras_ai_v2/gooey_builder.py (2)
  • render_gooey_builder (71-126)
  • render_gooey_builder_inline (7-68)
widgets/sidebar.py (4)
  • sidebar_layout (210-325)
  • use_sidebar (33-60)
  • set_open (21-22)
  • set_mobile_open (24-26)
workspaces/admin.py (1)
gooeysite/custom_widgets.py (1)
  • JSONEditorWidget (4-26)
🪛 GitHub Actions: Python tests
workspaces/models.py

[error] 175-177: ruff format would reformat 1 file; run 'ruff format' to fix code style issues.

🪛 Ruff (0.14.6)
workspaces/admin.py

135-137: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

usage_costs/migrations/0038_alter_modelpricing_model_name.py

8-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


12-298: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

workspaces/migrations/0013_workspace_features.py

8-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


12-18: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (6)
workspaces/admin.py (2)

135-138: LGTM!

The formfield_overrides configuration correctly maps JSONField to JSONEditorWidget, providing a better editing experience for JSON data in the admin. The static analysis warning (RUF012) about mutable class attributes is a false positive here—this is the standard Django admin pattern and the dict is not mutated at runtime.


86-108: LGTM!

The features field is appropriately placed in the admin fields list and is editable, which is correct for administrator-managed feature configuration.

workspaces/migrations/0013_workspace_features.py (1)

1-18: LGTM!

The migration is correctly generated and aligns with the model change. It properly uses default=dict (callable) to avoid mutable default issues. The static analysis warnings (RUF012) are false positives—Django migrations use class attributes for dependencies and operations by design.

daras_ai_v2/base.py (3)

48-48: Inline builder import matches new usage

Importing render_gooey_builder_inline here aligns with _render_gooey_builder’s new inline usage; the change is consistent and correct.


85-85: Sidebar hooks wired into BasePage

Bringing in use_sidebar for render_sidebar is appropriate and keeps sidebar state tied to the Django session; no issues from this change in this file.


1251-1289: Filtered builder_state with inline embed avoids leaking full session

_render_gooey_builder now:

  • Pops and applies update_gui_state in a constrained way via fields_to_save().
  • Gates access to admins only (is_current_user_admin()).
  • Calls render_gooey_builder_inline with a filtered builder_state composed of:
    • status (error_msg, run_status, run_time)
    • request / response from extract_model_fields
    • Minimal metadata (title, description)

This addresses the earlier concern about passing the entire gui.session_state to the client widget and keeps the payload scoped to what the builder needs.

Comment on lines +245 to +291
("protogen_2_2", "Protogen V2.2 (darkstorm2150)"),
("epicdream", "epiCDream [Deprecated] (epinikion)"),
("flux_1_dev", "FLUX.1 [dev]"),
("dream_shaper", "DreamShaper (Lykon)"),
("dreamlike_2", "Dreamlike Photoreal 2.0 (dreamlike.art)"),
("sd_2", "Stable Diffusion v2.1 (stability.ai)"),
("sd_1_5", "Stable Diffusion v1.5 (RunwayML)"),
("dall_e", "DALL·E 2 (OpenAI)"),
("dall_e_3", "DALL·E 3 (OpenAI)"),
("gpt_image_1", "GPT Image 1 (OpenAI)"),
("nano_banana", "Nano Banana (Google)"),
("openjourney_2", "Open Journey v2 beta [Deprecated] (PromptHero)"),
("openjourney", "Open Journey [Deprecated] (PromptHero)"),
("analog_diffusion", "Analog Diffusion [Deprecated] (wavymulder)"),
("protogen_5_3", "Protogen v5.3 [Deprecated] (darkstorm2150)"),
("jack_qiao", "Stable Diffusion v1.4 [Deprecated] (Jack Qiao)"),
(
"rodent_diffusion_1_5",
"Rodent Diffusion 1.5 [Deprecated] (NerdyRodent)",
),
("deepfloyd_if", "DeepFloyd IF [Deprecated] (stability.ai)"),
("flux_pro_kontext", "FLUX.1 Pro Kontext (fal.ai)"),
("dream_shaper", "DreamShaper (Lykon)"),
("dreamlike_2", "Dreamlike Photoreal 2.0 (dreamlike.art)"),
("sd_2", "Stable Diffusion v2.1 (stability.ai)"),
("sd_1_5", "Stable Diffusion v1.5 (RunwayML)"),
("dall_e", "Dall-E (OpenAI)"),
("gpt_image_1", "GPT Image 1 (OpenAI)"),
("nano_banana", "Nano Banana (Google)"),
("instruct_pix2pix", "✨ InstructPix2Pix (Tim Brooks)"),
("openjourney_2", "Open Journey v2 beta [Deprecated] (PromptHero)"),
("openjourney", "Open Journey [Deprecated] (PromptHero)"),
("analog_diffusion", "Analog Diffusion [Deprecated] (wavymulder)"),
("protogen_5_3", "Protogen v5.3 [Deprecated] (darkstorm2150)"),
("jack_qiao", "Stable Diffusion v1.4 [Deprecated] (Jack Qiao)"),
(
"rodent_diffusion_1_5",
"Rodent Diffusion 1.5 [Deprecated] (NerdyRodent)",
),
("sd_2", "Stable Diffusion v2.1 (stability.ai)"),
("runway_ml", "Stable Diffusion v1.5 (RunwayML)"),
("dall_e", "Dall-E (OpenAI)"),
("jack_qiao", "Stable Diffusion v1.4 [Deprecated] (Jack Qiao)"),
("wav2lip", "LipSync (wav2lip)"),
("sadtalker", "LipSync (sadtalker)"),
("twilio_voice", "Twilio Voice"),
("twilio_sms", "Twilio SMS"),
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

Duplicate choices entries for several models in tail of list

The choices list for model_name has multiple duplicate keys and labels in the image-model section (e.g., dream_shaper, dreamlike_2, sd_2, sd_1_5, dall_e, gpt_image_1, nano_banana, openjourney_2, openjourney, analog_diffusion, protogen_5_3, jack_qiao, rodent_diffusion_1_5 appear more than once).

This won’t break the migration, but it will:

  • Produce duplicate options in admin/forms.
  • Make get_model_name_display() effectively depend on the first occurrence only, which is confusing and brittle.

I’d recommend deduping these choices at the source definition (likely in usage_costs.models.ModelPricing.model_name or a shared choices constant), then regenerating the migration so that each model key appears exactly once with a single canonical label.

Comment on lines 177 to 178
features = models.JSONField(default=dict)
class Meta:
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

Add a blank line before class Meta.

The pipeline failure indicates a formatting issue. PEP 8 and ruff expect a blank line before nested class definitions within a class body.

Apply this diff to fix the formatting:

     objects = WorkspaceQuerySet.as_manager()
     features = models.JSONField(default=dict)
+
     class Meta:
📝 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
features = models.JSONField(default=dict)
class Meta:
objects = WorkspaceQuerySet.as_manager()
features = models.JSONField(default=dict)
class Meta:
🤖 Prompt for AI Agents
In workspaces/models.py around lines 177-178, there is no blank line before the
nested class Meta; add a single blank line between the fields (features =
models.JSONField(...)) and the line declaring "class Meta:" so the nested class
is separated per PEP8/ruff formatting rules; run the formatter/ruff to confirm
the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants