diff --git a/daras_ai_v2/analysis_results.py b/daras_ai_v2/analysis_results.py index 5bd519982..6e43e3585 100644 --- a/daras_ai_v2/analysis_results.py +++ b/daras_ai_v2/analysis_results.py @@ -69,7 +69,7 @@ def render_analysis_results_page( gui.session_state.setdefault("selected_graphs", graphs) selected_graphs = list_view_editor( - add_btn_label="➕ Add a Graph", + add_btn_label="Add a Graph", key="selected_graphs", render_inputs=partial(render_inputs, results), ) diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py index 82887cd1a..b22e22e8b 100644 --- a/daras_ai_v2/base.py +++ b/daras_ai_v2/base.py @@ -136,7 +136,7 @@ class BasePage: class RequestModel(BaseModel): functions: list[RecipeFunction] | None = Field( - title="🧩 Functions", + title="🧩 Developer Tools and Functions", ) variables: dict[str, typing.Any] = Field( None, @@ -1476,15 +1476,17 @@ def update_flag_for_run(self, run_id: str, uid: str, is_flagged: bool): # Functions in every recipe feels like overkill for now, hide it in settings functions_in_settings = True + show_settings = True def _render_input_col(self): self.render_form_v2() placeholder = gui.div() - with gui.expander("⚙️ Settings"): - if self.functions_in_settings: - functions_input(self.request.user) - self.render_settings() + if self.show_settings: + with gui.expander("⚙️ Settings"): + self.render_settings() + if self.functions_in_settings: + functions_input(self.request.user) with placeholder: self.render_variables() @@ -1499,7 +1501,6 @@ def _render_input_col(self): def render_variables(self): if not self.functions_in_settings: - gui.write("---") functions_input(self.request.user) variables_input( template_keys=self.template_keys, allow_add=is_functions_enabled() diff --git a/daras_ai_v2/bot_integration_widgets.py b/daras_ai_v2/bot_integration_widgets.py index c1f55cc14..4b87d70f0 100644 --- a/daras_ai_v2/bot_integration_widgets.py +++ b/daras_ai_v2/bot_integration_widgets.py @@ -94,7 +94,7 @@ def render_workflow_url_input(key: str, del_key: str | None, d: dict): input_analysis_runs.append(dict(saved_run=sr, published_run=None)) list_view_editor( - add_btn_label="➕ Add", + add_btn_label="Add", key="analysis_urls", render_inputs=render_workflow_url_input, flatten_dict_key="url", diff --git a/daras_ai_v2/icons.py b/daras_ai_v2/icons.py index 91150051a..c862da919 100644 --- a/daras_ai_v2/icons.py +++ b/daras_ai_v2/icons.py @@ -14,6 +14,7 @@ company = '' copy = '' preview = '' +add = '' code = '' chat = '' diff --git a/daras_ai_v2/prompt_vars.py b/daras_ai_v2/prompt_vars.py index 7a74c7116..c315da996 100644 --- a/daras_ai_v2/prompt_vars.py +++ b/daras_ai_v2/prompt_vars.py @@ -3,22 +3,31 @@ from datetime import datetime from types import SimpleNamespace +import gooey_gui as gui import jinja2 import jinja2.meta import jinja2.sandbox -import gooey_gui as gui +from daras_ai_v2 import icons def variables_input( *, template_keys: typing.Iterable[str], label: str = "###### ⌥ Variables", + description: str = "Variables let you pass custom parameters to your workflow. Access a variable in your instruction prompt with Jinja, e.g. `{{ my_variable }}`\n ", key: str = "variables", allow_add: bool = False, ): from daras_ai_v2.workflow_url_input import del_button + def render_title_desc(): + gui.write(label) + gui.caption( + f"{description} Learn more.", + unsafe_allow_html=True, + ) + # find all variables in the prompts env = jinja2.sandbox.SandboxedEnvironment() template_var_names = set() @@ -56,7 +65,7 @@ def variables_input( continue if not title_shown: - gui.write(label) + render_title_desc() title_shown = True col1, col2 = gui.columns([11, 1], responsive=False) @@ -93,7 +102,7 @@ def variables_input( if allow_add: if not title_shown: - gui.write(label) + render_title_desc() gui.newline() col1, col2, _ = gui.columns([6, 2, 4], responsive=False) with col1: @@ -105,7 +114,7 @@ def variables_input( ) with col2: gui.button( - ' Add', + f"{icons.add} Add", key=var_add_key, type="tertiary", ) diff --git a/daras_ai_v2/workflow_url_input.py b/daras_ai_v2/workflow_url_input.py index a37929378..5c5f7b2e9 100644 --- a/daras_ai_v2/workflow_url_input.py +++ b/daras_ai_v2/workflow_url_input.py @@ -21,6 +21,7 @@ def workflow_url_input( del_key: str = None, current_user: AppUser | None = None, allow_none: bool = False, + include_root: bool = True ) -> tuple[typing.Type[BasePage], SavedRun, PublishedRun | None] | None: init_workflow_selector(internal_state, key) @@ -38,7 +39,9 @@ def workflow_url_input( else: internal_state["workflow"] = page_cls.workflow with col1: - options = get_published_run_options(page_cls, current_user=current_user) + options = get_published_run_options( + page_cls, current_user=current_user, include_root=include_root + ) options.update(internal_state.get("--added_workflows", {})) with gui.div(className="pt-1"): url = gui.selectbox( @@ -143,6 +146,7 @@ def url_to_runs( def get_published_run_options( page_cls: typing.Type[BasePage], current_user: AppUser | None = None, + include_root: bool = True, ) -> dict[str, str]: # approved examples pr_query = Q(is_approved_example=True, visibility=PublishedRunVisibility.PUBLIC) @@ -166,13 +170,15 @@ def get_published_run_options( pr.updated_at, # newer first ), ) - - options = { - # root recipe - page_cls.get_root_published_run().get_app_url(): "Default", - } | { + options_dict = { pr.get_app_url(): get_title_breadcrumbs(page_cls, pr.saved_run, pr).h1_title for pr in saved_runs_and_examples } - return options + if include_root: + # include root recipe if requested + options_dict = { + page_cls.get_root_published_run().get_app_url(): "Default", + } | options_dict + + return options_dict diff --git a/functions/migrations/0002_alter_calledfunction_trigger.py b/functions/migrations/0002_alter_calledfunction_trigger.py new file mode 100644 index 000000000..a373cbc25 --- /dev/null +++ b/functions/migrations/0002_alter_calledfunction_trigger.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2024-08-12 12:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('functions', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='calledfunction', + name='trigger', + field=models.IntegerField(choices=[(1, 'Before'), (2, 'After')]), + ), + ] diff --git a/functions/models.py b/functions/models.py index 0901fb109..be1be7a5e 100644 --- a/functions/models.py +++ b/functions/models.py @@ -13,8 +13,8 @@ class _TriggerData(typing.NamedTuple): class FunctionTrigger(_TriggerData, GooeyEnum): - pre = _TriggerData(label="Pre", db_value=1) - post = _TriggerData(label="Post", db_value=2) + pre = _TriggerData(label="Before", db_value=1) + post = _TriggerData(label="After", db_value=2) class RecipeFunction(BaseModel): diff --git a/functions/recipe_functions.py b/functions/recipe_functions.py index b43ea16e6..5d4c07b87 100644 --- a/functions/recipe_functions.py +++ b/functions/recipe_functions.py @@ -117,7 +117,7 @@ def render_function_input(list_key: str, del_key: str, d: dict): from daras_ai_v2.workflow_url_input import workflow_url_input from recipes.Functions import FunctionsPage - col1, col2 = gui.columns([2, 10], responsive=False) + col1, col2 = gui.columns([3, 9], responsive=True) with col1: col1.node.props["className"] += " pt-1" d["trigger"] = enum_selector( @@ -133,7 +133,9 @@ def render_function_input(list_key: str, del_key: str, d: dict): internal_state=d, del_key=del_key, current_user=current_user, + include_root=False, ) + col2.node.children[0].props["className"] += " col-12" if gui.checkbox( f"##### {field_title_desc(BasePage.RequestModel, key)}", @@ -141,12 +143,18 @@ def render_function_input(list_key: str, del_key: str, d: dict): value=key in gui.session_state, ): gui.session_state.setdefault(key, [{}]) + with gui.div(className="d-flex align-items-center"): + gui.write("###### Functions") + gui.caption( + "Functions give your workflow the ability run Javascript code (with webcalls!) allowing it execute logic, use common JS libraries or make external API calls before or after the workflow runs. Learn more.", + unsafe_allow_html=True, + ) list_view_editor( - add_btn_label="➕ Add Function", + add_btn_label="Add Function", + add_btn_type="tertiary", key=key, render_inputs=render_function_input, ) - gui.write("---") else: gui.session_state.pop(key, None) diff --git a/recipes/BulkEval.py b/recipes/BulkEval.py index ce25e512d..7f92a53e1 100644 --- a/recipes/BulkEval.py +++ b/recipes/BulkEval.py @@ -239,7 +239,7 @@ def render_inputs(key: str, del_key: str, d: EvalPrompt): gui.write("##### " + field_title_desc(self.RequestModel, "eval_prompts")) list_view_editor( - add_btn_label="➕ Add a Prompt", + add_btn_label="Add a Prompt", key="eval_prompts", render_inputs=render_inputs, ) @@ -261,7 +261,7 @@ def render_agg_inputs(key: str, del_key: str, d: AggFunction): gui.html("
") gui.write("##### " + field_title_desc(self.RequestModel, "agg_functions")) list_view_editor( - add_btn_label="➕ Add an Aggregation", + add_btn_label="Add an Aggregation", key="agg_functions", render_inputs=render_agg_inputs, ) diff --git a/recipes/BulkRunner.py b/recipes/BulkRunner.py index 2cbde30e0..f700effac 100644 --- a/recipes/BulkRunner.py +++ b/recipes/BulkRunner.py @@ -8,6 +8,7 @@ import gooey_gui as gui from bots.models import Workflow, SavedRun from daras_ai.image_input import upload_file_from_bytes +from daras_ai_v2 import icons from daras_ai_v2.base import BasePage from daras_ai_v2.breadcrumbs import get_title_breadcrumbs from daras_ai_v2.doc_search_settings_widgets import ( @@ -97,7 +98,7 @@ def preview_image(self, state: dict) -> str | None: def render_form_v2(self): gui.write(f"##### {field_title_desc(self.RequestModel, 'run_urls')}") run_urls = list_view_editor( - add_btn_label="➕ Add a Workflow", + add_btn_label="Add a Workflow", key="run_urls", render_inputs=self.render_run_url_inputs, flatten_dict_key="url", @@ -246,7 +247,7 @@ def render_form_v2(self): gui.write("---") gui.write(f"##### {field_title_desc(self.RequestModel, 'eval_urls')}") list_view_editor( - add_btn_label="➕ Add an Eval", + add_btn_label="Add an Eval", key="eval_urls", render_inputs=self.render_eval_url_inputs, flatten_dict_key="url", @@ -619,7 +620,8 @@ def read_df_any(f_url: str) -> "pd.DataFrame": def list_view_editor( *, - add_btn_label: str, + add_btn_label: str = None, + add_btn_type: str = "secondary", key: str, render_labels: typing.Callable = None, render_inputs: typing.Callable[[str, str, dict], None], @@ -658,5 +660,7 @@ def list_view_editor( with label_placeholder: render_labels() gui.session_state[key] = new_lst - gui.button(add_btn_label, key=add_key) + if add_btn_label: + with gui.center(): + gui.button(f"{icons.add} {add_btn_label}", key=add_key, type=add_btn_type) return new_lst diff --git a/recipes/CompareLLM.py b/recipes/CompareLLM.py index cc8c93313..513421146 100644 --- a/recipes/CompareLLM.py +++ b/recipes/CompareLLM.py @@ -2,9 +2,9 @@ import random import typing -from pydantic import BaseModel, Field - import gooey_gui as gui +from pydantic import BaseModel + from bots.models import Workflow from daras_ai_v2.base import BasePage from daras_ai_v2.enum_selector_widget import enum_multiselect @@ -12,7 +12,6 @@ run_language_model, LargeLanguageModels, SUPERSCRIPT, - ResponseFormatType, ) from daras_ai_v2.language_model_settings_widgets import ( language_model_settings, @@ -78,11 +77,14 @@ def render_form_v2(self): enum_multiselect( LargeLanguageModels, - label="#### 🤗 Compare Language Models", + label="#### 🧠 Language Models", key="selected_models", checkboxes=False, ) + gui.markdown("#### 💪 Capabilities") + # -- functions will render here in parent -- + def validate_form_v2(self): assert gui.session_state["input_prompt"], "Please enter a Prompt" assert gui.session_state["selected_models"], "Please select at least one model" diff --git a/recipes/Functions.py b/recipes/Functions.py index e376bc061..179c13d95 100644 --- a/recipes/Functions.py +++ b/recipes/Functions.py @@ -1,9 +1,9 @@ import typing +import gooey_gui as gui import requests from pydantic import BaseModel, Field -import gooey_gui as gui from bots.models import Workflow from daras_ai_v2 import settings from daras_ai_v2.base import BasePage @@ -21,6 +21,7 @@ class FunctionsPage(BasePage): title = "Functions" workflow = Workflow.FUNCTIONS slug_versions = ["functions", "tools", "function", "fn", "functions"] + show_settings = False class RequestModel(BaseModel): code: str = Field( @@ -83,7 +84,12 @@ def render_form_v2(self): ) def render_variables(self): - variables_input(template_keys=["code"], allow_add=True) + variables_input( + template_keys=["code"], + allow_add=True, + description="Pass custom parameters to your function and access the parent workflow data. " + "Variables will be passed down as the first argument to your anonymous JS function.", + ) def render_output(self): if error := gui.session_state.get("error"): diff --git a/recipes/Translation.py b/recipes/Translation.py index dada4cf2a..b8061beb2 100644 --- a/recipes/Translation.py +++ b/recipes/Translation.py @@ -90,7 +90,7 @@ def related_workflows(self) -> list: def render_form_v2(self): gui.write("###### Source Texts") list_view_editor( - add_btn_label="➕ Add Text", + add_btn_label="Add Text", key="texts", render_inputs=render_text_input, flatten_dict_key="text", diff --git a/recipes/VideoBots.py b/recipes/VideoBots.py index ef4bfd39f..2e47aaae1 100644 --- a/recipes/VideoBots.py +++ b/recipes/VideoBots.py @@ -343,7 +343,7 @@ def render_form_v2(self): accept=["audio/*", "application/*", "video/*", "text/*"], ) - gui.markdown("#### Capabilities") + gui.markdown("#### 💪 Capabilities") if gui.checkbox( "##### 🗣️ Text to Speech & Lipsync", value=bool(gui.session_state.get("tts_provider")), @@ -425,9 +425,7 @@ def render_form_v2(self): if gui.checkbox( "##### 🩻 Photo & Document Intelligence", - value=bool( - gui.session_state.get("document_model"), - ), + value=bool(gui.session_state.get("document_model")), ): if settings.AZURE_FORM_RECOGNIZER_KEY: doc_model_descriptions = azure_form_recognizer_models() @@ -439,6 +437,7 @@ def render_form_v2(self): options=doc_model_descriptions, format_func=lambda x: f"{doc_model_descriptions[x]} ({x})", ) + gui.write("---") else: gui.session_state["document_model"] = None