From e54334e95182689e03051915935577abe97ee6ea Mon Sep 17 00:00:00 2001 From: Kaustubh Maske Patil <37668193+nikochiko@users.noreply.github.com> Date: Mon, 5 Aug 2024 18:07:13 +0530 Subject: [PATCH 01/27] Disable /js/ handle --- handles/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/handles/models.py b/handles/models.py index e3371dbac..59f20b591 100644 --- a/handles/models.py +++ b/handles/models.py @@ -28,6 +28,7 @@ "about", "blog", "sales", + "js", ] COMMON_EMAIL_DOMAINS = [ "gmail.com", From 6a58c85b4da5c14dc104d37cfcd9d2aa061c05ad Mon Sep 17 00:00:00 2001 From: Kaustubh Maske Patil <37668193+nikochiko@users.noreply.github.com> Date: Mon, 5 Aug 2024 18:15:47 +0530 Subject: [PATCH 02/27] Disallow handles that might conflict with common asset routes --- handles/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/handles/models.py b/handles/models.py index 59f20b591..b027736a0 100644 --- a/handles/models.py +++ b/handles/models.py @@ -29,6 +29,9 @@ "blog", "sales", "js", + "css", + "assets", + "favicon.ico", ] COMMON_EMAIL_DOMAINS = [ "gmail.com", From 5027f359cdb504606efa1fddf9c78872c050d743 Mon Sep 17 00:00:00 2001 From: Dev Aggarwal Date: Tue, 6 Aug 2024 15:37:45 +0530 Subject: [PATCH 03/27] fix TypeError: Object of type CalledFunctionResponse is not JSON serializable --- routers/api.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/routers/api.py b/routers/api.py index b9da2d1f1..171daa34c 100644 --- a/routers/api.py +++ b/routers/api.py @@ -6,11 +6,11 @@ from types import SimpleNamespace import gooey_gui as gui -from fastapi import APIRouter from fastapi import Depends from fastapi import Form from fastapi import HTTPException from fastapi import Response +from fastapi.encoders import jsonable_encoder from fastapi.exceptions import RequestValidationError from furl import furl from pydantic import BaseModel, Field @@ -42,7 +42,6 @@ from daras_ai_v2.fastapi_tricks import fastapi_request_form from functions.models import CalledFunctionResponse from gooeysite.bg_db_conn import get_celery_result_db_safe - from routers.custom_api_router import CustomAPIRouter app = CustomAPIRouter() @@ -408,11 +407,13 @@ def build_sync_api_response( else: # return updated state return JSONResponse( - dict( - id=run_id, - url=web_url, - created_at=sr.created_at.isoformat(), - output=sr.api_output(), + jsonable_encoder( + dict( + id=run_id, + url=web_url, + created_at=sr.created_at.isoformat(), + output=sr.api_output(), + ), ), ) From 04429898ebebd12ebed23cdad318fb9232be30a4 Mon Sep 17 00:00:00 2001 From: Dev Aggarwal Date: Thu, 8 Aug 2024 04:09:20 +0530 Subject: [PATCH 04/27] Fix url routing to serve 404 page instead of 403 when static page not found - use blob.exists() to check if static file exists otherwise raise http exception 404 - avoid rendering a handle page with a sub-path (e.g. js/webflow.js) - Refactor `serve_static_file` to handle URLs more robustly - Replace custom exceptions with `HTTPException` in `render_recipe_page` and `url_shortener` functions - Extend `HandleAdmin` search fields with user fields and add filters --- handles/admin.py | 11 ++++++++++- routers/root.py | 15 ++++++++++----- routers/static_pages.py | 34 ++++++++++++++++++---------------- url_shortener/routers.py | 4 ++-- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/handles/admin.py b/handles/admin.py index cf90e811f..1a86567ee 100644 --- a/handles/admin.py +++ b/handles/admin.py @@ -1,8 +1,17 @@ from django.contrib import admin +from app_users.admin import AppUserAdmin from .models import Handle @admin.register(Handle) class HandleAdmin(admin.ModelAdmin): - search_fields = ["name", "redirect_url"] + search_fields = ["name", "redirect_url"] + [ + f"user__{field}" for field in AppUserAdmin.search_fields + ] + readonly_fields = ["user", "created_at", "updated_at"] + + list_filter = [ + ("user", admin.EmptyFieldListFilter), + ("redirect_url", admin.EmptyFieldListFilter), + ] diff --git a/routers/root.py b/routers/root.py index 33763b539..d353bddd4 100644 --- a/routers/root.py +++ b/routers/root.py @@ -579,9 +579,9 @@ def chat_lib_route(request: Request, integration_id: str, integration_name: str def recipe_or_handle_or_static( request: Request, page_slug=None, run_slug=None, example_id=None, path=None ): - path = furl(request.url).pathstr.lstrip("/") + parts = request.url.path.strip("/").split("/") - parts = path.strip("/").split("/") + # try to render a recipe page if len(parts) in {1, 2}: try: example_id = parts[1].split("-")[-1] or None @@ -591,12 +591,16 @@ def recipe_or_handle_or_static( return render_recipe_page(request, parts[0], RecipeTabs.run, example_id) except RecipePageNotFound: pass + + # try to render a handle page + if len(parts) == 1: try: return render_handle_page(request, parts[0]) except Handle.DoesNotExist: pass - return serve_static_file(request, path) + # try to serve a static file + return serve_static_file(request) def render_handle_page(request: Request, name: str): @@ -614,8 +618,9 @@ def render_handle_page(request: Request, name: str): raise HTTPException(status_code=404) -class RecipePageNotFound(Exception): - pass +class RecipePageNotFound(HTTPException): + def __init__(self) -> None: + super().__init__(status_code=404) def render_recipe_page( diff --git a/routers/static_pages.py b/routers/static_pages.py index 3969dbf8c..d6e2301ae 100644 --- a/routers/static_pages.py +++ b/routers/static_pages.py @@ -4,12 +4,13 @@ import gooey_gui as gui import requests +from fastapi import HTTPException from starlette.requests import Request from starlette.responses import ( - Response, RedirectResponse, HTMLResponse, PlainTextResponse, + Response, ) from starlette.status import HTTP_308_PERMANENT_REDIRECT, HTTP_401_UNAUTHORIZED @@ -24,20 +25,20 @@ app = CustomAPIRouter() -def serve_static_file(request: Request, path: str): +def serve_static_file(request: Request) -> Response | None: bucket = gcs_bucket() - if path.endswith("/"): - # relative css/js paths dont work with trailing slashes - return RedirectResponse( - os.path.join("/", path.strip("/")), status_code=HTTP_308_PERMANENT_REDIRECT - ) - - path = path or "index" - gcs_path = os.path.join(settings.GS_STATIC_PATH, path) + relpath = request.url.path.strip("/") or "index" + gcs_path = os.path.join(settings.GS_STATIC_PATH, relpath) # if the path has no extension, try to serve a .html file - if not os.path.splitext(gcs_path)[1]: + if not os.path.splitext(relpath)[1]: + # relative css/js paths in html won't work if a trailing slash is present in the url + if request.url.path.lstrip("/").endswith("/"): + return RedirectResponse( + os.path.join("/", relpath), + status_code=HTTP_308_PERMANENT_REDIRECT, + ) html_url = bucket.blob(gcs_path + ".html").public_url r = requests.get(html_url) if r.ok: @@ -51,12 +52,13 @@ def serve_static_file(request: Request, path: str): ) return HTMLResponse(html, status_code=r.status_code) - url = bucket.blob(gcs_path).public_url - r = requests.head(url) - if r.ok: - return RedirectResponse(url, status_code=HTTP_308_PERMANENT_REDIRECT) + blob = bucket.blob(gcs_path) + if blob.exists(): + return RedirectResponse( + blob.public_url, status_code=HTTP_308_PERMANENT_REDIRECT + ) - return Response(status_code=r.status_code) + raise HTTPException(status_code=404) @gui.route(app, "/internal/webflow-upload/") diff --git a/url_shortener/routers.py b/url_shortener/routers.py index dfa2c25cc..29496a2ad 100644 --- a/url_shortener/routers.py +++ b/url_shortener/routers.py @@ -1,5 +1,5 @@ from django.db.models import F -from fastapi import Request +from fastapi import Request, HTTPException from fastapi.responses import RedirectResponse from fastapi.responses import Response @@ -15,7 +15,7 @@ def url_shortener(hashid: str, request: Request): try: surl = ShortenedURL.objects.get_by_hashid(hashid) except ShortenedURL.DoesNotExist: - return Response(status_code=404) + raise HTTPException(status_code=404) # ensure that the url is not disabled and has not exceeded max clicks if surl.disabled or (surl.max_clicks and surl.clicks >= surl.max_clicks): return Response(status_code=410, content="This link has expired") From 4a9bb4c2f44aefc7335c2248dfd11f1a700fde55 Mon Sep 17 00:00:00 2001 From: Alexander Metzger Date: Tue, 6 Aug 2024 13:22:00 -0700 Subject: [PATCH 05/27] test secret --- explore.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/explore.py b/explore.py index cd4ec3d00..8cb254b9f 100644 --- a/explore.py +++ b/explore.py @@ -11,6 +11,9 @@ TITLE = "Explore" DESCRIPTION = "DISCOVER YOUR FIELD’S FAVORITE AI WORKFLOWS" +# test secret +leak = "sk-b53e818ad76d62bf81153702dff99dc406dfc36bc0cf5d272b331df6f2e09665" + def render(): heading(title=TITLE, description=DESCRIPTION) From ede582d5a859726ff04fe371e9066f9dffb393f7 Mon Sep 17 00:00:00 2001 From: Alexander Metzger Date: Tue, 6 Aug 2024 13:31:55 -0700 Subject: [PATCH 06/27] more test secrets; and one in commit message: abcd1234-abcd-1234-abcd-1234567890ab --- explore.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/explore.py b/explore.py index 8cb254b9f..770d79c9f 100644 --- a/explore.py +++ b/explore.py @@ -12,7 +12,28 @@ DESCRIPTION = "DISCOVER YOUR FIELD’S FAVORITE AI WORKFLOWS" # test secret -leak = "sk-b53e818ad76d62bf81153702dff99dc406dfc36bc0cf5d272b331df6f2e09665" +gooey_like = "sk-b53e818ad76d62bf81153702dff99dc406dfc36bc0cf5d27" +access_key_id_like = "AKIAIOSFODNN7EXAMPLE" +secret_key_like = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" +api_key_like = "AIzaSyA9w6cC6-BQJdFgWexAEXAMPLEAPIKEY" +service_account_like = { + "type": "service_account", + "project_id": "my-project", + "private_key_id": "abcd1234abcd1234abcd1234abcd1234abcd1234", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCUabcdEFGHIJKLMNOPQRSTUV123456\n-----END PRIVATE KEY-----\n", + "client_email": "my-service-account@my-project.iam.gserviceaccount.com", + "client_id": "12345678901234567890", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/my-service-account%40my-project.iam.gserviceaccount.com", +} +subscription_id_like = "12345678-1234-1234-1234-123456789012" +client_id_like = "abcd1234-abcd-1234-abcd-1234567890ab" +client_secret_like = "eK7M4sQ~8X9LAbCDExAMPLETK_tKyrQ6Rp_H" +tenant_id_like = "abcd1234-abcd-1234-abcd-1234567890ab" +server_api_token_like = "f31c243b-8e2a-40e8-94d4-8c17aa7EXAMPLETOKEN" +account_api_token_like = "b9b5b6d1-9f7c-4a5a-b87d-2e9ea0EXAMPLETOKEN" def render(): From 01c503400ade5ea65805bf52b1f59621db5657f3 Mon Sep 17 00:00:00 2001 From: Alexander Metzger Date: Thu, 8 Aug 2024 12:55:16 -0700 Subject: [PATCH 07/27] remove test secrets --- explore.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/explore.py b/explore.py index 770d79c9f..cd4ec3d00 100644 --- a/explore.py +++ b/explore.py @@ -11,30 +11,6 @@ TITLE = "Explore" DESCRIPTION = "DISCOVER YOUR FIELD’S FAVORITE AI WORKFLOWS" -# test secret -gooey_like = "sk-b53e818ad76d62bf81153702dff99dc406dfc36bc0cf5d27" -access_key_id_like = "AKIAIOSFODNN7EXAMPLE" -secret_key_like = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" -api_key_like = "AIzaSyA9w6cC6-BQJdFgWexAEXAMPLEAPIKEY" -service_account_like = { - "type": "service_account", - "project_id": "my-project", - "private_key_id": "abcd1234abcd1234abcd1234abcd1234abcd1234", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCUabcdEFGHIJKLMNOPQRSTUV123456\n-----END PRIVATE KEY-----\n", - "client_email": "my-service-account@my-project.iam.gserviceaccount.com", - "client_id": "12345678901234567890", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/my-service-account%40my-project.iam.gserviceaccount.com", -} -subscription_id_like = "12345678-1234-1234-1234-123456789012" -client_id_like = "abcd1234-abcd-1234-abcd-1234567890ab" -client_secret_like = "eK7M4sQ~8X9LAbCDExAMPLETK_tKyrQ6Rp_H" -tenant_id_like = "abcd1234-abcd-1234-abcd-1234567890ab" -server_api_token_like = "f31c243b-8e2a-40e8-94d4-8c17aa7EXAMPLETOKEN" -account_api_token_like = "b9b5b6d1-9f7c-4a5a-b87d-2e9ea0EXAMPLETOKEN" - def render(): heading(title=TITLE, description=DESCRIPTION) From d77e4e6480df41af7af3553a855ec066676dfca4 Mon Sep 17 00:00:00 2001 From: Alexander Metzger Date: Thu, 8 Aug 2024 13:00:25 -0700 Subject: [PATCH 08/27] pre-commit hook --- .pre-commit-config.yaml | 6 +++++- README.md | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 65af59112..1bc20673c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v4.6.0 hooks: - id: end-of-file-fixer - id: check-yaml @@ -13,3 +13,7 @@ repos: entry: poetry run black language: system types: [python] +- repo: https://github.com/gitleaks/gitleaks + rev: v8.18.4 + hooks: + - id: gitleaks diff --git a/README.md b/README.md index b3ddddba0..b3db76c39 100644 --- a/README.md +++ b/README.md @@ -208,3 +208,7 @@ docker run \ ### 📐 Code Formatting Use black - https://pypi.org/project/black + +### 💣 Secret Scanning + +Gitleaks will automatically run pre-commit (see `pre-commit-config.yaml` for details) to prevent commits with secrets in the first place. To test this without committing, run `pre-commit` from the terminal. To skip this check for false positives, use `SKIP=gitleaks git commit -m "message"` to commit changes. From ec84087d8e6bc48285c1fad38def9c6ffa0b4a90 Mon Sep 17 00:00:00 2001 From: Alexander Metzger Date: Thu, 8 Aug 2024 13:16:28 -0700 Subject: [PATCH 09/27] github action --- .github/workflows/gitleaks.yml | 15 +++++++++++++++ .gitleaksignore | 21 +++++++++++++++++++++ README.md | 4 +++- scripts/create_gitleaks_baseline.py | 18 ++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/gitleaks.yml create mode 100644 .gitleaksignore create mode 100644 scripts/create_gitleaks_baseline.py diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml new file mode 100644 index 000000000..79ffcb1a2 --- /dev/null +++ b/.github/workflows/gitleaks.yml @@ -0,0 +1,15 @@ +name: gitleaks +on: [pull_request, push, workflow_dispatch] +jobs: + scan: + name: gitleaks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE}} + GITLEAKS_NOTIFY_USER_LIST: '@sandergi' diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 000000000..022ac599f --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,21 @@ +4749e3ef005e8ddc6562d1bd82a00e752a7e94e3:explore.py:aws-access-token:16 +4749e3ef005e8ddc6562d1bd82a00e752a7e94e3:explore.py:private-key:23 +4749e3ef005e8ddc6562d1bd82a00e752a7e94e3:explore.py:generic-api-key:32 +b0c80dac8e22faafa319d5466947df8723dfaa4a:daras_ai_v2/img_model_settings_widgets.py:generic-api-key:372 +8670036e722f40530dbff3e0e7573e9b5aac85c9:routers/slack.py:slack-webhook-url:73 +b6ad1fc0168832711adcff07287907660f3305fb:bots/location.py:generic-api-key:12 +8c05ec8320a866304842fb5f4df76e0698f1031f:bots/analysis.py:generic-api-key:5 +1c03d569dd30bb9703e4ff968a57a05eb405e398:bots/signals.py:generic-api-key:11 +5e3dd6cf0da20b3e5b1daaca41ad126bc489fbf3:static/js/auth.js:generic-api-key:2 +87e443addbbc49746ab3088307a59b3e2fc2d177:recipes/CompareText2Img.py:generic-api-key:97 +1f109a743b1781c7a21c1b0ca6a3f880f7f7dc84:recipes/CompareText2Img.py:generic-api-key:77 +d18d8b9bb18a9ff8248b16b26f0455f7826ce23a:recipes/CompareText2Img.py:generic-api-key:85 +5471a8ac2d60026b24f21b51ae6f11db8acd160c:pages/CompareText2Img.py:generic-api-key:92 +5471a8ac2d60026b24f21b51ae6f11db8acd160c:daras_ai_v2/img_model_settings_widgets.py:generic-api-key:90 +6fca6072032e4f34d7d571e7de8e0ff05f7a487b:static/js/auth.js:generic-api-key:2 +2292469b22d97263c7c59cf49fae7281ce96a39c:pages/CompareText2Img.py:generic-api-key:137 +aae9d67ed6330a3eb2ede41d5ceeca52a8f0daf4:static/js/auth.js:gcp-api-key:2 +d5866242d107743ab5eebeb284e7e5ee2426d941:pages/SocialLookupEmail.py:generic-api-key:181 +73bef8c3be7682fed0b99ceb6770f599eabbbd08:daras_ai_v2/send_email.py:generic-api-key:25 +fa3f7982fa1527838c2073d2542c83887cc6ebbd:pages/EmailFaceInpainting.py:generic-api-key:189 +e1c218882d288ca1df0225654aae8dd10027e9d0:political_example.py:jwt:30 diff --git a/README.md b/README.md index b3db76c39..299c84839 100644 --- a/README.md +++ b/README.md @@ -211,4 +211,6 @@ Use black - https://pypi.org/project/black ### 💣 Secret Scanning -Gitleaks will automatically run pre-commit (see `pre-commit-config.yaml` for details) to prevent commits with secrets in the first place. To test this without committing, run `pre-commit` from the terminal. To skip this check for false positives, use `SKIP=gitleaks git commit -m "message"` to commit changes. +Gitleaks will automatically run pre-commit (see `pre-commit-config.yaml` for details) to prevent commits with secrets in the first place. To test this without committing, run `pre-commit` from the terminal. To skip this check, use `SKIP=gitleaks git commit -m "message"` to commit changes. Preferably, label false positives with the `#gitleaks:allow` comment instead of skipping the check. + +Gitleaks will also run in the CI pipeline as a GitHub action on push and pull request (can also be manually triggered in the actions tab on GitHub). To update the baseline of ignored secrets, run `python ./scripts/create_gitleaks_baseline.py` from the venv and commit the changes to `.gitleaksignore`. diff --git a/scripts/create_gitleaks_baseline.py b/scripts/create_gitleaks_baseline.py new file mode 100644 index 000000000..8e5979185 --- /dev/null +++ b/scripts/create_gitleaks_baseline.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +import subprocess +import json + +# create a baseline file +subprocess.run( + ["gitleaks", "detect", "--report-path", "gitleaks-baseline.json"], +) + +# parse the baseline file +with open("gitleaks-baseline.json") as f: + baseline = json.load(f) + +# output list of "Fingerprint"s to .gitleaksignore +with open(".gitleaksignore", "w") as f: + for leak in baseline: + f.write(leak["Fingerprint"] + "\n") From 41ae9b4f4eb7561c5ea0ab5cd80d555c2e9b89a6 Mon Sep 17 00:00:00 2001 From: anish-work Date: Mon, 5 Aug 2024 17:50:39 +0530 Subject: [PATCH 10/27] replace enableVideoLipsync with autoPlayResponses --- daras_ai_v2/bot_integration_widgets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daras_ai_v2/bot_integration_widgets.py b/daras_ai_v2/bot_integration_widgets.py index 4958f389d..c1f55cc14 100644 --- a/daras_ai_v2/bot_integration_widgets.py +++ b/daras_ai_v2/bot_integration_widgets.py @@ -375,7 +375,7 @@ def web_widget_config(bi: BotIntegration, user: AppUser | None): mode="inline", showSources=True, enablePhotoUpload=False, - enableLipsyncVideo=False, + autoPlayResponses=True, enableAudioMessage=True, branding=( dict(showPoweredByGooey=True) @@ -397,8 +397,8 @@ def web_widget_config(bi: BotIntegration, user: AppUser | None): config["enableAudioMessage"] = gui.checkbox( "Enable Audio Message", value=config["enableAudioMessage"] ) - config["enableLipsyncVideo"] = gui.checkbox( - "Enable Lipsync Video", value=config["enableLipsyncVideo"] + config["autoPlayResponses"] = gui.checkbox( + "Auto-play responses", value=config["autoPlayResponses"] ) # config["branding"]["showPoweredByGooey"] = gui.checkbox( # "Show Powered By Gooey", value=config["branding"]["showPoweredByGooey"] From e74bf8f4db52ec12bcfe3fac5c00fbfd2cffafe3 Mon Sep 17 00:00:00 2001 From: Alexander Metzger Date: Mon, 29 Apr 2024 23:01:48 -0700 Subject: [PATCH 11/27] new seamless version! --- daras_ai_v2/asr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daras_ai_v2/asr.py b/daras_ai_v2/asr.py index c09102fe4..266c6d827 100644 --- a/daras_ai_v2/asr.py +++ b/daras_ai_v2/asr.py @@ -230,7 +230,7 @@ def supports_auto_detect(self) -> bool: AsrModels.vakyansh_bhojpuri: "Harveenchadha/vakyansh-wav2vec2-bhojpuri-bhom-60", AsrModels.nemo_english: "https://objectstore.e2enetworks.net/indic-asr-public/checkpoints/conformer/english_large_data_fixed.nemo", AsrModels.nemo_hindi: "https://objectstore.e2enetworks.net/indic-asr-public/checkpoints/conformer/stt_hi_conformer_ctc_large_v2.nemo", - AsrModels.seamless_m4t: "facebook/hf-seamless-m4t-large", + AsrModels.seamless_m4t: "facebook/seamless-m4t-v2-large", AsrModels.mms_1b_all: "facebook/mms-1b-all", } From cb30353a7e84df14ad5f45fe53ff6905ebf37466 Mon Sep 17 00:00:00 2001 From: Dev Aggarwal Date: Tue, 13 Aug 2024 00:32:08 +0530 Subject: [PATCH 12/27] Update ASR script to use Seamless M4T v2 and deprecate v1 - Replace SEAMLESS_SUPPORTED with SEAMLESS_v2_ASR_SUPPORTED - Update AsrModels Enum to include seamless_m4t_v2 and mark seamless_m4t as deprecated - Modify references to use seamless_m4t_v2 model_id and supported languages - Adjust code for selecting Seamless M4T v2 in relevant functions and scripts - Auto detect is not technically supported by seamless, so don't allow it --- daras_ai_v2/asr.py | 46 +++++++++++++++++++++++++++----------------- recipes/VideoBots.py | 2 +- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/daras_ai_v2/asr.py b/daras_ai_v2/asr.py index 266c6d827..96b5f2e70 100644 --- a/daras_ai_v2/asr.py +++ b/daras_ai_v2/asr.py @@ -75,16 +75,15 @@ "fa", "pl", "pt", "ro", "ru", "sr", "sk", "sl", "es", "sw", "sv", "tl", "ta", "th", "tr", "uk", "ur", "vi", "cy" } # fmt: skip -# See page 14 of https://scontent-sea1-1.xx.fbcdn.net/v/t39.2365-6/369747868_602316515432698_2401716319310287708_n.pdf?_nc_cat=106&ccb=1-7&_nc_sid=3c67a6&_nc_ohc=_5cpNOcftdYAX8rCrVo&_nc_ht=scontent-sea1-1.xx&oh=00_AfDVkx7XubifELxmB_Un-yEYMJavBHFzPnvTbTlalbd_1Q&oe=65141B39 +# https://huggingface.co/facebook/seamless-m4t-v2-large#supported-languages # For now, below are listed the languages that support ASR. Note that Seamless only accepts ISO 639-3 codes. -SEAMLESS_SUPPORTED = { - "afr", "amh", "arb", "ary", "arz", "asm", "ast", "azj", "bel", "ben", "bos", "bul", "cat", "ceb", "ces", "ckb", - "cmn", "cym", "dan", "deu", "ell", "eng", "est", "eus", "fin", "fra", "gaz", "gle", "glg", "guj", "heb", "hin", - "hrv", "hun", "hye", "ibo", "ind", "isl", "ita", "jav", "jpn", "kam", "kan", "kat", "kaz", "kea", "khk", "khm", - "kir", "kor", "lao", "lit", "ltz", "lug", "luo", "lvs", "mai", "mal", "mar", "mkd", "mlt", "mni", "mya", "nld", - "nno", "nob", "npi", "nya", "oci", "ory", "pan", "pbt", "pes", "pol", "por", "ron", "rus", "slk", "slv", "sna", - "snd", "som", "spa", "srp", "swe", "swh", "tam", "tel", "tgk", "tgl", "tha", "tur", "ukr", "urd", "uzn", "vie", - "xho", "yor", "yue", "zlm", "zul" +SEAMLESS_v2_ASR_SUPPORTED = { + "afr", "amh", "arb", "ary", "arz", "asm", "azj", "bel", "ben", "bos", "bul", "cat", "ceb", "ces", "ckb", "cmn", + "cmn-Hant", "cym", "dan", "deu", "ell", "eng", "est", "eus", "fin", "fra", "fuv", "gaz", "gle", "glg", "guj", "heb", + "hin", "hrv", "hun", "hye", "ibo", "ind", "isl", "ita", "jav", "jpn", "kan", "kat", "kaz", "khk", "khm", "kir", + "kor", "lao", "lit", "lug", "luo", "lvs", "mai", "mal", "mar", "mkd", "mlt", "mni", "mya", "nld", "nno", "nob", + "npi", "nya", "ory", "pan", "pbt", "pes", "pol", "por", "ron", "rus", "slk", "slv", "sna", "snd", "som", "spa", + "srp", "swe", "swh", "tam", "tel", "tgk", "tgl", "tha", "tur", "ukr", "urd", "uzn", "vie", "yor", "yue", "zul", } # fmt: skip AZURE_SUPPORTED = { @@ -199,7 +198,8 @@ } # fmt: skip # https://translation.ghananlp.org/api-details#api=ghananlp-translation-webservice-api -GHANA_NLP_SUPPORTED = { 'en': 'English', 'tw': 'Twi', 'gaa': 'Ga', 'ee': 'Ewe', 'fat': 'Fante', 'dag': 'Dagbani', 'gur': 'Gurene', 'yo': 'Yoruba', 'ki': 'Kikuyu', 'luo': 'Luo', 'mer': 'Kimeru' } # fmt: skip +GHANA_NLP_SUPPORTED = {'en': 'English', 'tw': 'Twi', 'gaa': 'Ga', 'ee': 'Ewe', 'fat': 'Fante', 'dag': 'Dagbani', + 'gur': 'Gurene', 'yo': 'Yoruba', 'ki': 'Kikuyu', 'luo': 'Luo', 'mer': 'Kimeru'} # fmt: skip GHANA_NLP_MAXLEN = 500 @@ -215,11 +215,22 @@ class AsrModels(Enum): usm = "Chirp / USM (Google V2)" deepgram = "Deepgram" azure = "Azure Speech" - seamless_m4t = "Seamless M4T (Facebook Research)" + seamless_m4t_v2 = "Seamless M4T v2 (Facebook Research)" mms_1b_all = "Massively Multilingual Speech (MMS) (Facebook Research)" + seamless_m4t = "Seamless M4T [Deprecated] (Facebook Research)" + def supports_auto_detect(self) -> bool: - return self not in {self.azure, self.gcp_v1, self.mms_1b_all} + return self not in { + self.azure, + self.gcp_v1, + self.mms_1b_all, + self.seamless_m4t_v2, + } + + @classmethod + def _deprecated(cls): + return {cls.seamless_m4t} asr_model_ids = { @@ -230,7 +241,7 @@ def supports_auto_detect(self) -> bool: AsrModels.vakyansh_bhojpuri: "Harveenchadha/vakyansh-wav2vec2-bhojpuri-bhom-60", AsrModels.nemo_english: "https://objectstore.e2enetworks.net/indic-asr-public/checkpoints/conformer/english_large_data_fixed.nemo", AsrModels.nemo_hindi: "https://objectstore.e2enetworks.net/indic-asr-public/checkpoints/conformer/stt_hi_conformer_ctc_large_v2.nemo", - AsrModels.seamless_m4t: "facebook/seamless-m4t-v2-large", + AsrModels.seamless_m4t_v2: "facebook/seamless-m4t-v2-large", AsrModels.mms_1b_all: "facebook/mms-1b-all", } @@ -248,7 +259,7 @@ def supports_auto_detect(self) -> bool: AsrModels.gcp_v1: GCP_V1_SUPPORTED, AsrModels.usm: CHIRP_SUPPORTED, AsrModels.deepgram: DEEPGRAM_SUPPORTED, - AsrModels.seamless_m4t: SEAMLESS_SUPPORTED, + AsrModels.seamless_m4t_v2: SEAMLESS_v2_ASR_SUPPORTED, AsrModels.azure: AZURE_SUPPORTED, AsrModels.mms_1b_all: MMS_SUPPORTED, } @@ -783,15 +794,14 @@ def run_asr( return "\n".join( f"Speaker {chunk['speaker']}: {chunk['text']}" for chunk in chunks ) - elif selected_model == AsrModels.seamless_m4t: + elif selected_model == AsrModels.seamless_m4t_v2: data = call_celery_task( - "seamless", + "seamless.asr", pipeline=dict( - model_id=asr_model_ids[AsrModels.seamless_m4t], + model_id=asr_model_ids[AsrModels.seamless_m4t_v2], ), inputs=dict( audio=audio_url, - task="ASR", src_lang=language, ), ) diff --git a/recipes/VideoBots.py b/recipes/VideoBots.py index 46ce36ef5..ef4bfd39f 100644 --- a/recipes/VideoBots.py +++ b/recipes/VideoBots.py @@ -1475,7 +1475,7 @@ def infer_asr_model_and_language( elif "bho" in user_lang: asr_model = AsrModels.vakyansh_bhojpuri elif "sw" in user_lang: - asr_model = AsrModels.seamless_m4t + asr_model = AsrModels.seamless_m4t_v2 asr_lang = "swh" else: asr_model = default From 3cc3c3badab2189911d8eacb83d894b4e1a7d789 Mon Sep 17 00:00:00 2001 From: anish-work Date: Tue, 13 Aug 2024 00:52:14 +0530 Subject: [PATCH 13/27] functions: ux clean-up --- daras_ai_v2/base.py | 6 +++--- daras_ai_v2/prompt_vars.py | 11 +++++++++-- .../0002_alter_calledfunction_trigger.py | 18 ++++++++++++++++++ functions/models.py | 4 ++-- functions/recipe_functions.py | 13 +++++++++++-- recipes/BulkRunner.py | 5 +++-- 6 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 functions/migrations/0002_alter_calledfunction_trigger.py diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py index 8e3610a28..29afc5314 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, @@ -1483,10 +1483,11 @@ def _render_input_col(self): self.render_form_v2() placeholder = gui.div() + gui.write("---") with gui.expander("⚙️ Settings"): + self.render_settings() if self.functions_in_settings: functions_input(self.request.user) - self.render_settings() with placeholder: self.render_variables() @@ -1501,7 +1502,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/prompt_vars.py b/daras_ai_v2/prompt_vars.py index 7a74c7116..dc0976f31 100644 --- a/daras_ai_v2/prompt_vars.py +++ b/daras_ai_v2/prompt_vars.py @@ -19,6 +19,13 @@ def variables_input( ): from daras_ai_v2.workflow_url_input import del_button + def render_title_desc(): + gui.write(label) + gui.caption( + "Variables let you pass custom parameters to your workflow. Access a variable in your instruction prompt with {{ }} eg. {{ my_variable }}. Learn more.", + unsafe_allow_html=True, + ) + # find all variables in the prompts env = jinja2.sandbox.SandboxedEnvironment() template_var_names = set() @@ -56,7 +63,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 +100,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: 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..ac0803f8f 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( @@ -134,6 +134,7 @@ def render_function_input(list_key: str, del_key: str, d: dict): del_key=del_key, current_user=current_user, ) + col2.node.children[0].props["className"] += " col-12" if gui.checkbox( f"##### {field_title_desc(BasePage.RequestModel, key)}", @@ -141,11 +142,19 @@ 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=None, key=key, render_inputs=render_function_input, ) + gui.button("➕ Add Function", key=f"--{key}:add", type="tertiary") + gui.write("---") else: gui.session_state.pop(key, None) diff --git a/recipes/BulkRunner.py b/recipes/BulkRunner.py index 2cbde30e0..ff7a9dfa3 100644 --- a/recipes/BulkRunner.py +++ b/recipes/BulkRunner.py @@ -619,7 +619,7 @@ def read_df_any(f_url: str) -> "pd.DataFrame": def list_view_editor( *, - add_btn_label: str, + add_btn_label: str = None, key: str, render_labels: typing.Callable = None, render_inputs: typing.Callable[[str, str, dict], None], @@ -658,5 +658,6 @@ 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: + gui.button(add_btn_label, key=add_key) return new_lst From 05a721fae5ae0a8aee31be8c75485c2695951938 Mon Sep 17 00:00:00 2001 From: anish-work Date: Tue, 13 Aug 2024 01:59:42 +0530 Subject: [PATCH 14/27] fix: sticky submit bar zIndex issue --- daras_ai_v2/base.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py index 8e3610a28..82887cd1a 100644 --- a/daras_ai_v2/base.py +++ b/daras_ai_v2/base.py @@ -1308,9 +1308,7 @@ def get_credits_click_url(self): return "/account/" def get_submit_container_props(self): - return dict( - className="position-sticky bottom-0 bg-white", style=dict(zIndex=100) - ) + return dict(className="position-sticky bottom-0 bg-white") def render_submit_button(self, key="--submit-1"): with gui.div(**self.get_submit_container_props()): From fd3ac84d0d400ea806064d50dccd07f8cddb7bdc Mon Sep 17 00:00:00 2001 From: Kaustubh Maske Patil <37668193+nikochiko@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:53:13 +0530 Subject: [PATCH 15/27] add safety checker for face inpainting --- recipes/FaceInpainting.py | 5 ++++- recipes/Img2Img.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/recipes/FaceInpainting.py b/recipes/FaceInpainting.py index 1e97a3fbe..8770740fb 100644 --- a/recipes/FaceInpainting.py +++ b/recipes/FaceInpainting.py @@ -252,7 +252,10 @@ def render_usage_guide(self): def run(self, state: dict): if not self.request.user.disable_safety_checker: yield "Running safety checker..." - safety_checker(image=state["input_image"]) + safety_checker( + text=state["text_prompt"], + image=state["input_image"], + ) yield "Extracting Face..." diff --git a/recipes/Img2Img.py b/recipes/Img2Img.py index d41deb1f5..65f208a92 100644 --- a/recipes/Img2Img.py +++ b/recipes/Img2Img.py @@ -158,6 +158,7 @@ def run(self, state: dict) -> typing.Iterator[str | None]: yield "Generating Image..." if not self.request.user.disable_safety_checker: + yield "Running safety checker..." safety_checker(text=request.text_prompt, image=request.input_image) if request.selected_model == Img2ImgModels.instruct_pix2pix.name: From d424854bd877321001995c7cb93bac4bceb4f897 Mon Sep 17 00:00:00 2001 From: Dev Aggarwal Date: Tue, 13 Aug 2024 23:57:23 +0530 Subject: [PATCH 16/27] Refactor self-hosted LLM handling; add new models Remove default system message; deprecate SEA-LION-7B-Instruct and add Llama3 8B CPT SEA-LIONv2 Instruct and Sarvam 2B models. Update _run_self_hosted_llm function to handle text inputs. Refactor _run_chat_model and _run_text_model to use updated function. --- daras_ai_v2/language_model.py | 78 ++++++++++++++++++++++++----------- scripts/init_llm_pricing.py | 20 +++++++++ 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/daras_ai_v2/language_model.py b/daras_ai_v2/language_model.py index dffe49060..533be6410 100644 --- a/daras_ai_v2/language_model.py +++ b/daras_ai_v2/language_model.py @@ -32,7 +32,6 @@ ) from functions.recipe_functions import LLMTools -DEFAULT_SYSTEM_MSG = "You are an intelligent AI assistant. Follow the instructions as closely as possible." DEFAULT_JSON_PROMPT = ( "Please respond directly in JSON format. " "Don't output markdown or HTML, instead print the JSON object directly without formatting." @@ -308,11 +307,26 @@ class LargeLanguageModels(Enum): ) sea_lion_7b_instruct = LLMSpec( - label="SEA-LION-7B-Instruct (aisingapore)", + label="SEA-LION-7B-Instruct [Deprecated] (aisingapore)", model_id="aisingapore/sea-lion-7b-instruct", llm_api=LLMApis.self_hosted, context_window=2048, price=1, + is_deprecated=True, + ) + llama3_8b_cpt_sea_lion_v2_instruct = LLMSpec( + label="Llama3 8B CPT SEA-LIONv2 Instruct (aisingapore)", + model_id="aisingapore/llama3-8b-cpt-sea-lionv2-instruct", + llm_api=LLMApis.self_hosted, + context_window=8192, + price=1, + ) + sarvam_2b = LLMSpec( + label="Sarvam 2B (sarvamai)", + model_id="sarvamai/sarvam-2b-v0.5", + llm_api=LLMApis.self_hosted, + context_window=2048, + price=1, ) # https://platform.openai.com/docs/models/gpt-3 @@ -452,7 +466,6 @@ def run_language_model( if prompt and not messages: # convert text prompt to chat messages messages = [ - format_chat_entry(role=CHATML_ROLE_SYSTEM, content=DEFAULT_SYSTEM_MSG), format_chat_entry(role=CHATML_ROLE_USER, content=prompt), ] if not model.is_vision_model: @@ -599,6 +612,17 @@ def _run_text_model( temperature=temperature, stop=stop, ) + case LLMApis.self_hosted: + return [ + _run_self_hosted_llm( + model=model, + text_inputs=prompt, + max_tokens=max_tokens, + temperature=temperature, + avoid_repetition=avoid_repetition, + stop=stop, + ) + ] case _: raise UserError(f"Unsupported text api: {api}") @@ -674,14 +698,19 @@ def _run_chat_model( stop=stop, ) case LLMApis.self_hosted: - return _run_self_hosted_chat( - model=model, - messages=messages, - max_tokens=max_tokens, - temperature=temperature, - avoid_repetition=avoid_repetition, - stop=stop, - ) + return [ + { + "role": CHATML_ROLE_ASSISTANT, + "content": _run_self_hosted_llm( + model=model, + text_inputs=messages, + max_tokens=max_tokens, + temperature=temperature, + avoid_repetition=avoid_repetition, + stop=stop, + ), + }, + ] # case LLMApis.together: # if tools: # raise UserError("Only OpenAI chat models support Tools") @@ -697,32 +726,36 @@ def _run_chat_model( raise UserError(f"Unsupported chat api: {api}") -def _run_self_hosted_chat( +def _run_self_hosted_llm( *, model: str, - messages: list[ConversationEntry], + text_inputs: list[ConversationEntry] | str, max_tokens: int, temperature: float, avoid_repetition: bool, stop: list[str] | None, -) -> list[dict]: +) -> str: from usage_costs.cost_utils import record_cost_auto from usage_costs.models import ModelSku # sea lion doesnt support system prompt - if model == LargeLanguageModels.sea_lion_7b_instruct.model_id: - for i, entry in enumerate(messages): + if ( + not isinstance(text_inputs, str) + and model == LargeLanguageModels.sea_lion_7b_instruct.model_id + ): + for i, entry in enumerate(text_inputs): if entry["role"] == CHATML_ROLE_SYSTEM: - messages[i]["role"] = CHATML_ROLE_USER - messages.insert(i + 1, dict(role=CHATML_ROLE_ASSISTANT, content="")) + text_inputs[i]["role"] = CHATML_ROLE_USER + text_inputs.insert(i + 1, dict(role=CHATML_ROLE_ASSISTANT, content="")) ret = call_celery_task( "llm.chat", pipeline=dict( model_id=model, + fallback_chat_template_from="meta-llama/Llama-2-7b-chat-hf", ), inputs=dict( - messages=messages, + text_inputs=text_inputs, max_new_tokens=max_tokens, stop_strings=stop, temperature=temperature, @@ -742,12 +775,7 @@ def _run_self_hosted_chat( quantity=usage["completion_tokens"], ) - return [ - { - "role": CHATML_ROLE_ASSISTANT, - "content": ret["generated_text"], - } - ] + return ret["generated_text"] def _run_anthropic_chat( diff --git a/scripts/init_llm_pricing.py b/scripts/init_llm_pricing.py index b93222d42..7c22b2c65 100644 --- a/scripts/init_llm_pricing.py +++ b/scripts/init_llm_pricing.py @@ -647,6 +647,26 @@ def run(): notes="Same as GPT-4o. Note that the actual cost of this model is in GPU Milliseconds", ) + llm_pricing_create( + model_id="aisingapore/llama3-8b-cpt-sea-lionv2-instruct", + model_name=LargeLanguageModels.llama3_8b_cpt_sea_lion_v2_instruct.name, + unit_cost_input=5, + unit_cost_output=15, + unit_quantity=10**6, + provider=ModelProvider.aks, + notes="Same as GPT-4o. Note that the actual cost of this model is in GPU Milliseconds", + ) + + llm_pricing_create( + model_id="sarvamai/sarvam-2b-v0.5", + model_name=LargeLanguageModels.sarvam_2b.name, + unit_cost_input=5, + unit_cost_output=15, + unit_quantity=10**6, + provider=ModelProvider.aks, + notes="Same as GPT-4o. Note that the actual cost of this model is in GPU Milliseconds", + ) + def llm_pricing_create( model_id: str, From 0b79de42fd174044b9f7e7ac67599712e8ad3ede Mon Sep 17 00:00:00 2001 From: anish-work Date: Tue, 13 Aug 2024 22:40:07 +0530 Subject: [PATCH 17/27] remove under line in settings --- daras_ai_v2/base.py | 2 +- functions/recipe_functions.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py index 29afc5314..667fe5a1c 100644 --- a/daras_ai_v2/base.py +++ b/daras_ai_v2/base.py @@ -1487,7 +1487,7 @@ def _render_input_col(self): with gui.expander("⚙️ Settings"): self.render_settings() if self.functions_in_settings: - functions_input(self.request.user) + functions_input(self.request.user, is_in_settings=True) with placeholder: self.render_variables() diff --git a/functions/recipe_functions.py b/functions/recipe_functions.py index ac0803f8f..f7138ca8d 100644 --- a/functions/recipe_functions.py +++ b/functions/recipe_functions.py @@ -109,7 +109,9 @@ def is_functions_enabled(key="functions") -> bool: return bool(gui.session_state.get(f"--enable-{key}")) -def functions_input(current_user: AppUser, key="functions"): +def functions_input( + current_user: AppUser, key="functions", is_in_settings: bool = False +): from recipes.BulkRunner import list_view_editor from daras_ai_v2.base import BasePage @@ -155,7 +157,8 @@ def render_function_input(list_key: str, del_key: str, d: dict): ) gui.button("➕ Add Function", key=f"--{key}:add", type="tertiary") - gui.write("---") + if not is_in_settings: + gui.write("---") else: gui.session_state.pop(key, None) From 40af15d7fe8d492c9d611188dd194ca254eeee7b Mon Sep 17 00:00:00 2001 From: anish-work Date: Tue, 13 Aug 2024 23:15:05 +0530 Subject: [PATCH 18/27] hide settings in /functions --- daras_ai_v2/base.py | 11 ++++++----- recipes/Functions.py | 4 ++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py index 667fe5a1c..a375d774a 100644 --- a/daras_ai_v2/base.py +++ b/daras_ai_v2/base.py @@ -1478,16 +1478,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() - gui.write("---") - with gui.expander("⚙️ Settings"): - self.render_settings() - if self.functions_in_settings: - functions_input(self.request.user, is_in_settings=True) + if self.show_settings: + with gui.expander("⚙️ Settings"): + self.render_settings() + if self.functions_in_settings: + functions_input(self.request.user, is_in_settings=True) with placeholder: self.render_variables() diff --git a/recipes/Functions.py b/recipes/Functions.py index e376bc061..cebf4d5dd 100644 --- a/recipes/Functions.py +++ b/recipes/Functions.py @@ -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( @@ -85,6 +86,9 @@ def render_form_v2(self): def render_variables(self): variables_input(template_keys=["code"], allow_add=True) + def render_settings(self): + raise NotImplementedError + def render_output(self): if error := gui.session_state.get("error"): with gui.tag("pre", className="bg-danger bg-opacity-25"): From 4e7be3656a5d78b0be2ee50c85503b6007c2605c Mon Sep 17 00:00:00 2001 From: anish-work Date: Wed, 14 Aug 2024 02:17:49 +0530 Subject: [PATCH 19/27] remove default options from functions --- daras_ai_v2/workflow_url_input.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/daras_ai_v2/workflow_url_input.py b/daras_ai_v2/workflow_url_input.py index a37929378..30500bf68 100644 --- a/daras_ai_v2/workflow_url_input.py +++ b/daras_ai_v2/workflow_url_input.py @@ -166,13 +166,19 @@ 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 } + options = ( + options_dict + if page_cls.workflow == Workflow.FUNCTIONS + else { + # root recipe + page_cls.get_root_published_run().get_app_url(): "Default", + } + | options_dict + ) + return options From 4637475b79477a4b0fcad21581d82c55ce52755b Mon Sep 17 00:00:00 2001 From: anish-work Date: Wed, 14 Aug 2024 02:23:40 +0530 Subject: [PATCH 20/27] remove render_settings from functions not needed --- recipes/Functions.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/recipes/Functions.py b/recipes/Functions.py index cebf4d5dd..129e39af3 100644 --- a/recipes/Functions.py +++ b/recipes/Functions.py @@ -86,9 +86,6 @@ def render_form_v2(self): def render_variables(self): variables_input(template_keys=["code"], allow_add=True) - def render_settings(self): - raise NotImplementedError - def render_output(self): if error := gui.session_state.get("error"): with gui.tag("pre", className="bg-danger bg-opacity-25"): From 0a6ae3b1c2ef22e93ed339255b270fb705288022 Mon Sep 17 00:00:00 2001 From: Dev Aggarwal Date: Wed, 14 Aug 2024 20:11:16 +0530 Subject: [PATCH 21/27] Standardize button labels and improve function UX - Remove emoji from "Add a Graph", "Add a Prompt", "Add an Eval", etc., button labels - Add new `add` icon in `icons.py` and use it for add buttons - Add `include_root` parameter to `workflow_url_input` and related functions - Improve `variables_input` UX with reusable description text - Refactor `list_view_editor` to center add button with type options - Update markdown for consistent capabilities sections visuals in CompareLLM and VideoBots --- daras_ai_v2/analysis_results.py | 2 +- daras_ai_v2/base.py | 2 +- daras_ai_v2/bot_integration_widgets.py | 2 +- daras_ai_v2/icons.py | 1 + daras_ai_v2/prompt_vars.py | 8 +++++--- daras_ai_v2/workflow_url_input.py | 20 ++++++++++---------- functions/recipe_functions.py | 12 ++++-------- recipes/BulkEval.py | 4 ++-- recipes/BulkRunner.py | 9 ++++++--- recipes/CompareLLM.py | 10 ++++++---- recipes/Functions.py | 9 +++++++-- recipes/Translation.py | 2 +- recipes/VideoBots.py | 7 +++---- 13 files changed, 48 insertions(+), 40 deletions(-) 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 a375d774a..e02808f61 100644 --- a/daras_ai_v2/base.py +++ b/daras_ai_v2/base.py @@ -1488,7 +1488,7 @@ def _render_input_col(self): with gui.expander("⚙️ Settings"): self.render_settings() if self.functions_in_settings: - functions_input(self.request.user, is_in_settings=True) + functions_input(self.request.user) with placeholder: self.render_variables() diff --git a/daras_ai_v2/bot_integration_widgets.py b/daras_ai_v2/bot_integration_widgets.py index 4958f389d..a1f36b859 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 dc0976f31..c315da996 100644 --- a/daras_ai_v2/prompt_vars.py +++ b/daras_ai_v2/prompt_vars.py @@ -3,17 +3,19 @@ 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, ): @@ -22,7 +24,7 @@ def variables_input( def render_title_desc(): gui.write(label) gui.caption( - "Variables let you pass custom parameters to your workflow. Access a variable in your instruction prompt with {{ }} eg. {{ my_variable }}. Learn more.", + f"{description} Learn more.", unsafe_allow_html=True, ) @@ -112,7 +114,7 @@ def render_title_desc(): ) 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 30500bf68..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) @@ -171,14 +175,10 @@ def get_published_run_options( for pr in saved_runs_and_examples } - options = ( - options_dict - if page_cls.workflow == Workflow.FUNCTIONS - else { - # root recipe + if include_root: + # include root recipe if requested + options_dict = { page_cls.get_root_published_run().get_app_url(): "Default", - } - | options_dict - ) + } | options_dict - return options + return options_dict diff --git a/functions/recipe_functions.py b/functions/recipe_functions.py index f7138ca8d..5d4c07b87 100644 --- a/functions/recipe_functions.py +++ b/functions/recipe_functions.py @@ -109,9 +109,7 @@ def is_functions_enabled(key="functions") -> bool: return bool(gui.session_state.get(f"--enable-{key}")) -def functions_input( - current_user: AppUser, key="functions", is_in_settings: bool = False -): +def functions_input(current_user: AppUser, key="functions"): from recipes.BulkRunner import list_view_editor from daras_ai_v2.base import BasePage @@ -135,6 +133,7 @@ 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" @@ -151,14 +150,11 @@ def render_function_input(list_key: str, del_key: str, d: dict): unsafe_allow_html=True, ) list_view_editor( - add_btn_label=None, + add_btn_label="Add Function", + add_btn_type="tertiary", key=key, render_inputs=render_function_input, ) - gui.button("➕ Add Function", key=f"--{key}:add", type="tertiary") - - if not is_in_settings: - 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 ff7a9dfa3..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", @@ -620,6 +621,7 @@ def read_df_any(f_url: str) -> "pd.DataFrame": def list_view_editor( *, 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], @@ -659,5 +661,6 @@ def list_view_editor( render_labels() gui.session_state[key] = new_lst if add_btn_label: - gui.button(add_btn_label, key=add_key) + 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 129e39af3..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 @@ -84,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 46ce36ef5..37fd094bb 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 From 6caea54fc73f1729f65acb5d7414c61be6675503 Mon Sep 17 00:00:00 2001 From: anish-work Date: Wed, 14 Aug 2024 13:40:09 +0530 Subject: [PATCH 22/27] don't charge credits if functions is called from another workflow --- recipes/Functions.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/recipes/Functions.py b/recipes/Functions.py index 179c13d95..4c30d5213 100644 --- a/recipes/Functions.py +++ b/recipes/Functions.py @@ -10,6 +10,7 @@ from daras_ai_v2.exceptions import raise_for_status from daras_ai_v2.field_render import field_title_desc from daras_ai_v2.prompt_vars import variables_input +from functions.models import CalledFunction class ConsoleLogs(BaseModel): @@ -83,6 +84,14 @@ def render_form_v2(self): height=300, ) + def get_price_roundoff(self, state: dict) -> float: + try: + # called from another workflow don't charge any credits + CalledFunction.objects.get(function_run=self.get_current_sr()) + return 0 + except CalledFunction.DoesNotExist: + return self.price + def render_variables(self): variables_input( template_keys=["code"], From a513df125997ad0a4ec626bb601365f1290a95a8 Mon Sep 17 00:00:00 2001 From: anish-work Date: Tue, 13 Aug 2024 21:52:17 +0530 Subject: [PATCH 23/27] price to 1 credit for functions --- recipes/Functions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/recipes/Functions.py b/recipes/Functions.py index 4c30d5213..5d415ad89 100644 --- a/recipes/Functions.py +++ b/recipes/Functions.py @@ -23,6 +23,7 @@ class FunctionsPage(BasePage): workflow = Workflow.FUNCTIONS slug_versions = ["functions", "tools", "function", "fn", "functions"] show_settings = False + price = 1 class RequestModel(BaseModel): code: str = Field( From ba66c0240125657832c0e227dfbfd916385a180c Mon Sep 17 00:00:00 2001 From: Dev Aggarwal Date: Wed, 14 Aug 2024 20:38:33 +0530 Subject: [PATCH 24/27] Add deduct_credits flag to control credit deduction after task execution - Don't deduct credits when running called functions, safety checker and admin re-runs --- bots/admin.py | 2 +- bots/models.py | 4 ++++ celeryapp/tasks.py | 4 +++- daras_ai_v2/base.py | 3 ++- daras_ai_v2/safety_checker.py | 1 + functions/recipe_functions.py | 1 + recipes/Functions.py | 10 +++++----- routers/api.py | 3 ++- 8 files changed, 19 insertions(+), 9 deletions(-) diff --git a/bots/admin.py b/bots/admin.py index 4b6a731c8..0b8b28d10 100644 --- a/bots/admin.py +++ b/bots/admin.py @@ -438,7 +438,7 @@ def rerun_tasks(self, request, queryset): page = Workflow(sr.workflow).page_cls( request=SimpleNamespace(user=AppUser.objects.get(uid=sr.uid)) ) - page.call_runner_task(sr) + page.call_runner_task(sr, deduct_credits=False) self.message_user( request, f"Started re-running {queryset.count()} tasks in the background.", diff --git a/bots/models.py b/bots/models.py index 0407569d5..51eaebc59 100644 --- a/bots/models.py +++ b/bots/models.py @@ -363,6 +363,7 @@ def submit_api_call( request_body: dict, enable_rate_limits: bool = False, parent_pr: "PublishedRun" = None, + deduct_credits: bool = True, ) -> tuple["celery.result.AsyncResult", "SavedRun"]: from routers.api import submit_api_call @@ -384,6 +385,7 @@ def submit_api_call( user=current_user, request_body=request_body, enable_rate_limits=enable_rate_limits, + deduct_credits=deduct_credits, ), ) @@ -1818,12 +1820,14 @@ def submit_api_call( current_user: AppUser, request_body: dict, enable_rate_limits: bool = False, + deduct_credits: bool = True, ) -> tuple["celery.result.AsyncResult", "SavedRun"]: return self.saved_run.submit_api_call( current_user=current_user, request_body=request_body, enable_rate_limits=enable_rate_limits, parent_pr=self, + deduct_credits=deduct_credits, ) diff --git a/celeryapp/tasks.py b/celeryapp/tasks.py index 794b5a061..2651fd80c 100644 --- a/celeryapp/tasks.py +++ b/celeryapp/tasks.py @@ -41,6 +41,7 @@ def runner_task( uid: str, channel: str, unsaved_state: dict[str, typing.Any] = None, + deduct_credits: bool = True, ) -> int: start_time = time() error_msg = None @@ -107,7 +108,8 @@ def save_on_step(yield_val: str | tuple[str, dict] = None, *, done: bool = False # run completed successfully, deduct credits else: - sr.transaction, sr.price = page.deduct_credits(gui.session_state) + if deduct_credits: + sr.transaction, sr.price = page.deduct_credits(gui.session_state) # save everything, mark run as completed finally: diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py index b22e22e8b..7ee73dcfc 100644 --- a/daras_ai_v2/base.py +++ b/daras_ai_v2/base.py @@ -1686,7 +1686,7 @@ def dump_state_to_sr(self, state: dict, sr: SavedRun): } ) - def call_runner_task(self, sr: SavedRun): + def call_runner_task(self, sr: SavedRun, deduct_credits: bool = True): from celeryapp.tasks import runner_task, post_runner_tasks chain = ( @@ -1697,6 +1697,7 @@ def call_runner_task(self, sr: SavedRun): uid=sr.uid, channel=self.realtime_channel_name(sr.run_id, sr.uid), unsaved_state=self._unsaved_state(), + deduct_credits=deduct_credits, ) | post_runner_tasks.s() ) diff --git a/daras_ai_v2/safety_checker.py b/daras_ai_v2/safety_checker.py index 841817d94..338d22614 100644 --- a/daras_ai_v2/safety_checker.py +++ b/daras_ai_v2/safety_checker.py @@ -33,6 +33,7 @@ def safety_checker_text(text_input: str): .submit_api_call( current_user=billing_account, request_body=dict(variables=dict(input=text_input)), + deduct_credits=False, ) ) diff --git a/functions/recipe_functions.py b/functions/recipe_functions.py index 5d4c07b87..b7fd36fdb 100644 --- a/functions/recipe_functions.py +++ b/functions/recipe_functions.py @@ -53,6 +53,7 @@ def call_recipe_functions( request_body=dict( variables=sr.state.get("variables", {}) | variables | fn_vars, ), + deduct_credits=False, ) CalledFunction.objects.create( diff --git a/recipes/Functions.py b/recipes/Functions.py index 5d415ad89..356381343 100644 --- a/recipes/Functions.py +++ b/recipes/Functions.py @@ -86,12 +86,12 @@ def render_form_v2(self): ) def get_price_roundoff(self, state: dict) -> float: - try: - # called from another workflow don't charge any credits - CalledFunction.objects.get(function_run=self.get_current_sr()) + if CalledFunction.objects.filter(function_run=self.get_current_sr()).exists(): return 0 - except CalledFunction.DoesNotExist: - return self.price + return super().get_price_roundoff(state) + + def additional_notes(self): + return "\nFunctions are free if called from another workflow." def render_variables(self): variables_input( diff --git a/routers/api.py b/routers/api.py index 171daa34c..9b795d426 100644 --- a/routers/api.py +++ b/routers/api.py @@ -332,6 +332,7 @@ def submit_api_call( query_params: dict, retention_policy: RetentionPolicy = None, enable_rate_limits: bool = False, + deduct_credits: bool = True, ) -> tuple[BasePage, "celery.result.AsyncResult", str, str]: # init a new page for every request self = page_cls(request=SimpleNamespace(user=user)) @@ -357,7 +358,7 @@ def submit_api_call( except ValidationError as e: raise RequestValidationError(e.raw_errors, body=gui.session_state) from e # submit the task - result = self.call_runner_task(sr) + result = self.call_runner_task(sr, deduct_credits=deduct_credits) return self, result, sr.run_id, sr.uid From 3c400792aa9d3f63ad6b1fdeff3651c62ec0d60e Mon Sep 17 00:00:00 2001 From: Kaustubh Maske Patil <37668193+nikochiko@users.noreply.github.com> Date: Thu, 15 Aug 2024 07:35:59 +0530 Subject: [PATCH 25/27] Add no cache header for downloads from GCS --- routers/static_pages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/static_pages.py b/routers/static_pages.py index d6e2301ae..0be54ada8 100644 --- a/routers/static_pages.py +++ b/routers/static_pages.py @@ -40,7 +40,7 @@ def serve_static_file(request: Request) -> Response | None: status_code=HTTP_308_PERMANENT_REDIRECT, ) html_url = bucket.blob(gcs_path + ".html").public_url - r = requests.get(html_url) + r = requests.get(html_url, headers={"Cache-Control": "no-cache"}) if r.ok: html = r.content.decode() # replace sign in button with user's name if logged in From 8b9df6a9a61b739422ba20c5885ce085b084dab2 Mon Sep 17 00:00:00 2001 From: Dev Aggarwal Date: Mon, 19 Aug 2024 18:46:04 +0530 Subject: [PATCH 26/27] fix chat urls without trailing slashes, clearer embed code for developers to be able to modify the config --- daras_ai_v2/bot_integration_widgets.py | 17 +++++++++++++---- routers/root.py | 2 ++ templates/chat_fullscreen.html | 6 +----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/daras_ai_v2/bot_integration_widgets.py b/daras_ai_v2/bot_integration_widgets.py index 4b87d70f0..2782c2eab 100644 --- a/daras_ai_v2/bot_integration_widgets.py +++ b/daras_ai_v2/bot_integration_widgets.py @@ -1,12 +1,13 @@ +import json from itertools import zip_longest from textwrap import dedent +import gooey_gui as gui from django.core.exceptions import ValidationError from django.db import transaction from django.utils.text import slugify from furl import furl -import gooey_gui as gui from app_users.models import AppUser from bots.models import BotIntegration, BotIntegrationAnalysisRun, Platform from daras_ai_v2 import settings, icons @@ -311,7 +312,7 @@ def get_bot_test_link(bi: BotIntegration) -> str | None: return None -def get_web_widget_embed_code(bi: BotIntegration) -> str: +def get_web_widget_embed_code(bi: BotIntegration, *, config: dict = None) -> str: lib_src = get_app_route_url( chat_lib_route, path_params=dict( @@ -319,11 +320,19 @@ def get_web_widget_embed_code(bi: BotIntegration) -> str: integration_name=slugify(bi.name) or "untitled", ), ).rstrip("/") + if config is None: + config = {} return dedent( - f""" + """
- + + """ + % dict(config_json=json.dumps(config), lib_src=lib_src) ).strip() diff --git a/routers/root.py b/routers/root.py index d353bddd4..62c0125ca 100644 --- a/routers/root.py +++ b/routers/root.py @@ -503,6 +503,7 @@ def chat_route( request: Request, integration_id: str = None, integration_name: str = None ): from routers.bots_api import api_hashids + from daras_ai_v2.bot_integration_widgets import get_web_widget_embed_code try: bi = BotIntegration.objects.get(id=api_hashids.decode(integration_id)[0]) @@ -514,6 +515,7 @@ def chat_route( { "request": request, "bi": bi, + "embed_code": get_web_widget_embed_code(bi, config=dict(mode="fullscreen")), "meta": raw_build_meta_tags( url=get_og_url_path(request), title=f"Chat with {bi.name}", diff --git a/templates/chat_fullscreen.html b/templates/chat_fullscreen.html index c96074da5..339f738fd 100644 --- a/templates/chat_fullscreen.html +++ b/templates/chat_fullscreen.html @@ -12,10 +12,6 @@ {% endfor %} -
- - +{{ embed_code | safe }} From 1119c84bcff0b216f2e70aa14a51f0e541f31f2d Mon Sep 17 00:00:00 2001 From: Dev Aggarwal Date: Mon, 19 Aug 2024 18:54:53 +0530 Subject: [PATCH 27/27] fix variables not being passed down from integrations api --- daras_ai_v2/bots.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/daras_ai_v2/bots.py b/daras_ai_v2/bots.py index 58a154590..90fef04ee 100644 --- a/daras_ai_v2/bots.py +++ b/daras_ai_v2/bots.py @@ -234,18 +234,6 @@ def _echo(bot, input_text): return msgs_to_save, response_audio, response_text, response_video -def _mock_api_output(input_text): - return { - "url": "https://gooey.ai?example_id=mock-api-example", - "output": { - "input_text": input_text, - "raw_input_text": input_text, - "raw_output_text": [f"echo: ```{input_text}```\nhttps://www.youtube.com/"], - "output_text": [f"echo: ```{input_text}```\nhttps://www.youtube.com/"], - }, - } - - def msg_handler(bot: BotInterface): try: _msg_handler(bot) @@ -369,20 +357,25 @@ def _process_and_send_msg( # get latest messages for context saved_msgs = bot.convo.msgs_for_llm_context() - # # mock testing - # result = _mock_api_output(input_text) + variables = (bot.saved_run.state.get("variables") or {}) | build_run_vars( + bot.convo, bot.user_msg_id + ) body = dict( input_prompt=input_text, input_audio=input_audio, input_images=input_images, input_documents=input_documents, messages=saved_msgs, - variables=build_run_vars(bot.convo, bot.user_msg_id), + variables=variables, ) if bot.user_language: body["user_language"] = bot.user_language if bot.request_overrides: body = bot.request_overrides | body + try: + variables.update(bot.request_overrides["variables"]) + except KeyError: + pass page, result, run_id, uid = submit_api_call( page_cls=bot.page_cls, user=billing_account_user,