From ea48c93eda7772c50b999bf3ea1c870270f4e31a Mon Sep 17 00:00:00 2001 From: Chengwei Ouyang Date: Wed, 19 Feb 2025 14:31:33 +0800 Subject: [PATCH 01/17] fix project root calc --- servers/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servers/base.py b/servers/base.py index d2fefaa2..1a77895a 100755 --- a/servers/base.py +++ b/servers/base.py @@ -68,7 +68,7 @@ def compute_root_path(root_type, project_root=None, headers=None): user_id = headers["x-myshell-openapi-user-id"] project_root = os.path.join("data_cloud", f"user_{user_id}") else: - project_root = project_root + project_root = PROJECT_ROOT # assert root_type in ["workflow", "app", "app_run", "workflow_run", "comfy_workflow", "input"] root_path = None From bfa901521df9489311a90b3d65271702131bdf82 Mon Sep 17 00:00:00 2001 From: shanexi Date: Wed, 19 Feb 2025 15:21:20 +0800 Subject: [PATCH 02/17] fix: settings save --- web/apps/web/src/services/app/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/apps/web/src/services/app/index.tsx b/web/apps/web/src/services/app/index.tsx index ef53aeae..26994ad9 100755 --- a/web/apps/web/src/services/app/index.tsx +++ b/web/apps/web/src/services/app/index.tsx @@ -241,8 +241,10 @@ export const loadEnvSvc: Fetcher = () => { return APIFetch.get(loadSettingEnvFormUrl); }; -export const saveEnvSvc: Fetcher = () => { - return APIFetch.get(saveSettingEnvFormUrl); +export const saveEnvSvc: Fetcher = (params: any) => { + return APIFetch.post(saveSettingEnvFormUrl, { + body: params, + }); }; // 校验ip export const checkIp = (params: CheckIpRequest) => { From be28f537f48d927be1ff0fba73cc4117dddee088 Mon Sep 17 00:00:00 2001 From: Chengwei Ouyang Date: Sun, 23 Feb 2025 18:21:49 +0800 Subject: [PATCH 03/17] [Feat] add api widget, for test only --- assets/myshell_widget_list.json | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/assets/myshell_widget_list.json b/assets/myshell_widget_list.json index afea587d..00bfd4e3 100755 --- a/assets/myshell_widget_list.json +++ b/assets/myshell_widget_list.json @@ -1,4 +1,65 @@ { + "1892877244783509504": { + "id": "1892877244783509504", + "name": "API Widget", + "description": "API Widget", + "usage": "Tools", + "inputs": { + "properties": { + "url": { + "type": "string", + "description": "API endpoint URL", + "default": "" + }, + "method": { + "type": "string", + "description": "HTTP method", + "enum": [ + "GET", + "POST", + "PUT", + "DELETE" + ], + "default": "GET" + }, + "headers": { + "type": "string", + "description": "Request headers (JSON format)", + "default": "{}" + }, + "params": { + "type": "string", + "description": "URL parameters (JSON format)", + "default": "{}" + }, + "data": { + "type": "string", + "description": "Request body (JSON format)", + "default": "{}" + } + }, + "required": [ + "url" + ] + }, + "outputs": { + "properties": { + "status_code": { + "type": "integer", + "description": "HTTP status code" + }, + "headers": { + "type": "object", + "description": "Response headers" + }, + "body": { + "type": "string", + "description": "Response body" + } + } + }, + "widget_id": "1892877244783509504" + }, "1781991963803181056": { "id": "1781991963803181056", "name": "Crawler", From 1d55581319bec725be51059b31e2ba98550ea9cb Mon Sep 17 00:00:00 2001 From: tiancheng-myshell <158017297+tiancheng-myshell@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:33:37 +0800 Subject: [PATCH 04/17] chore: Upgrade some Git workflow actions to v4 (#299) --- .github/workflows/package-and-upload-new-linux.yaml | 2 +- .github/workflows/package-and-upload-new-mac.yaml | 2 +- .github/workflows/package-and-upload-new-windows.yaml | 4 ++-- .github/workflows/package-and-upload.yaml | 8 ++++---- .github/workflows/package-test.yaml | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/package-and-upload-new-linux.yaml b/.github/workflows/package-and-upload-new-linux.yaml index 730a345f..5e645974 100755 --- a/.github/workflows/package-and-upload-new-linux.yaml +++ b/.github/workflows/package-and-upload-new-linux.yaml @@ -67,7 +67,7 @@ jobs: python-version: 3.10.10 - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./servers/web-build diff --git a/.github/workflows/package-and-upload-new-mac.yaml b/.github/workflows/package-and-upload-new-mac.yaml index c6aa0565..3bd14c3e 100755 --- a/.github/workflows/package-and-upload-new-mac.yaml +++ b/.github/workflows/package-and-upload-new-mac.yaml @@ -67,7 +67,7 @@ jobs: python-version: 3.10.10 - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./servers/web-build diff --git a/.github/workflows/package-and-upload-new-windows.yaml b/.github/workflows/package-and-upload-new-windows.yaml index db60a60e..6f7c9956 100755 --- a/.github/workflows/package-and-upload-new-windows.yaml +++ b/.github/workflows/package-and-upload-new-windows.yaml @@ -66,7 +66,7 @@ jobs: python-version: 3.10.10 - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./servers/web-build @@ -125,7 +125,7 @@ jobs: overwrite: true - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./web-build diff --git a/.github/workflows/package-and-upload.yaml b/.github/workflows/package-and-upload.yaml index 1a164639..4520d4d9 100755 --- a/.github/workflows/package-and-upload.yaml +++ b/.github/workflows/package-and-upload.yaml @@ -56,7 +56,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./web-build @@ -94,7 +94,7 @@ jobs: python-version: 3.10.10 - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./servers/web-build @@ -172,7 +172,7 @@ jobs: python-version: 3.10.10 - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./servers/web-build @@ -222,7 +222,7 @@ jobs: python-version: 3.10.10 - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./servers/web-build diff --git a/.github/workflows/package-test.yaml b/.github/workflows/package-test.yaml index 2f1acd5b..ef42b286 100755 --- a/.github/workflows/package-test.yaml +++ b/.github/workflows/package-test.yaml @@ -57,7 +57,7 @@ jobs: python-version: 3.10.10 - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./servers/web-build @@ -127,7 +127,7 @@ jobs: python-version: 3.10.10 - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./servers/web-build @@ -169,7 +169,7 @@ jobs: python-version: 3.10.10 - name: Download web-build artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: web-build path: ./servers/web-build From 1567021e81af26ee4e9dc56b45b5286e38224424 Mon Sep 17 00:00:00 2001 From: Chengwei Ouyang Date: Sun, 23 Feb 2025 20:15:29 +0800 Subject: [PATCH 05/17] [Fix] get local file failed --- servers/base.py | 2 ++ servers/common.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/servers/base.py b/servers/base.py index 1a77895a..e8dd0fa0 100755 --- a/servers/base.py +++ b/servers/base.py @@ -68,6 +68,8 @@ def compute_root_path(root_type, project_root=None, headers=None): user_id = headers["x-myshell-openapi-user-id"] project_root = os.path.join("data_cloud", f"user_{user_id}") else: + if root_type == "file": + return "" project_root = PROJECT_ROOT # assert root_type in ["workflow", "app", "app_run", "workflow_run", "comfy_workflow", "input"] diff --git a/servers/common.py b/servers/common.py index 4b662fe5..70dfbd61 100755 --- a/servers/common.py +++ b/servers/common.py @@ -84,7 +84,7 @@ async def get_cwd(): BASE_DIR = os.getcwd() @app.get('/api/files/{filename:path}') async def get_file(filename: str, request: Request): - root_path = compute_root_path("", headers=request.headers) + root_path = compute_root_path("file", headers=request.headers) filename = filename.strip() assert "../" not in filename From c9d6c6a892ecd439d84e200eec97a3d08e8cbb95 Mon Sep 17 00:00:00 2001 From: Chengwei Ouyang Date: Mon, 24 Feb 2025 15:02:34 +0800 Subject: [PATCH 06/17] [Feat] custom widgets implemente/import/export --- .gitignore | 1 + custom_widget_info.json | 8 +- custom_widgets/widgets_status.json | 1 - proconfig/widgets/__init__.py | 42 +++++++-- proconfig/widgets/tools/dependency_checker.py | 93 +++++++++++++++++-- servers/automata.py | 9 ++ servers/base.py | 3 - 7 files changed, 132 insertions(+), 25 deletions(-) delete mode 100755 custom_widgets/widgets_status.json diff --git a/.gitignore b/.gitignore index 107143e7..fc3df129 100755 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ hf_repo launch.sh .idea/ .DS_Store +custom_widgets/*/ \ No newline at end of file diff --git a/custom_widget_info.json b/custom_widget_info.json index dac230f7..5e7a1b03 100755 --- a/custom_widget_info.json +++ b/custom_widget_info.json @@ -1,8 +1,8 @@ { - "proconfig-diffuser-imagen": { - "git": "https://github.com/wl-zhao/proconfig-diffuser-imagen.git", - "commit": "latest", + "custom_widget_demo": { + "git": "https://github.com/Cherwayway/custom_widget_demo.git", + "commit": "651f1c69cad4921d09e705802733aadae1aa9058", "branch": "main", - "description": "Simple image generation widget implemented by diffusers" + "description": "A demo widget for custom widgets" } } \ No newline at end of file diff --git a/custom_widgets/widgets_status.json b/custom_widgets/widgets_status.json deleted file mode 100755 index 9e26dfee..00000000 --- a/custom_widgets/widgets_status.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/proconfig/widgets/__init__.py b/proconfig/widgets/__init__.py index 2518cedf..e2536776 100755 --- a/proconfig/widgets/__init__.py +++ b/proconfig/widgets/__init__.py @@ -4,6 +4,7 @@ os.environ[CURRENT_PACKAGE_NAME_KEY] = "myshell" from proconfig.widgets.utils import load_module from proconfig.widgets.base import build_widgets +from proconfig.utils.widget_manager import install_widget import proconfig.widgets.imagen_widgets import proconfig.widgets.language_models @@ -14,13 +15,36 @@ import os def load_custom_widgets(): - widget_status = json.load(open("custom_widgets/widgets_status.json")) - for custom_widget in os.listdir("custom_widgets"): - if not os.path.isdir(os.path.join("custom_widgets", custom_widget)): - continue - if custom_widget in widget_status: - current_commit = widget_status[custom_widget]["current_commit"] - os.environ[CURRENT_PACKAGE_NAME_KEY] = custom_widget - load_module(os.path.join(custom_widget, current_commit), "custom_widgets", module_name=custom_widget) - + try: + with open("custom_widget_info.json", "r") as f: + widget_info = json.load(f) + except FileNotFoundError: + widget_info = {} + + # Get list of existing widget directories + existing_widgets = set(d for d in os.listdir("custom_widgets") + if os.path.isdir(os.path.join("custom_widgets", d))) + + # Install widgets from widget_info that don't exist + for widget_name, widget_data in widget_info.items(): + if widget_name not in existing_widgets: + # Extract git URL from widget_data if it's a dictionary + install_widget( + widget_data["git"], + os.path.join("custom_widgets", widget_name), + widget_data.get("commit", None), + widget_data.get("branch", "main") + ) + + # Update existing widget list, filter out non-directory files + existing_widgets = set(d for d in os.listdir("custom_widgets") + if os.path.isdir(os.path.join("custom_widgets", d)) + and not d.startswith('.') + and not d.startswith('__')) + + # Load all widgets from custom_widgets directory + for custom_widget in existing_widgets: + os.environ[CURRENT_PACKAGE_NAME_KEY] = custom_widget + load_module(custom_widget, "custom_widgets", module_name=custom_widget) + load_custom_widgets() \ No newline at end of file diff --git a/proconfig/widgets/tools/dependency_checker.py b/proconfig/widgets/tools/dependency_checker.py index d20a3140..b823cc52 100755 --- a/proconfig/widgets/tools/dependency_checker.py +++ b/proconfig/widgets/tools/dependency_checker.py @@ -3,12 +3,15 @@ import copy from proconfig.core import Automata, Workflow from proconfig.widgets import build_widgets +from proconfig.widgets.base import WIDGETS +from proconfig.core.exception import ShellException from proconfig.widgets.imagen_widgets.utils.model_manager import compute_sha256 from proconfig.utils.expressions import calc_expression, tree_map from functools import partial from proconfig.utils.misc import windows_to_linux_path, generate_comfyui_workflow_id import logging from easydict import EasyDict as edict +import subprocess PROCONFIG_PROJECT_ROOT = os.environ.get("PROCONFIG_PROJECT_ROOT", "data") @@ -78,24 +81,98 @@ def handle_model_info(ckpt_path): "image": "" } -widget_status = json.load(open("custom_widgets/widgets_status.json")) all_widget_json = json.load(open("custom_widget_info.json")) def check_missing_widgets(config, missing_widgets): - # very simple - package_name = getattr(config, "package_name", None) or "myshell" + widget_name = config.widget_name + widget_class = WIDGETS.get(widget_name) + try: + package_name = widget_class.__module__.split('.')[0] if widget_class else "myshell" + if package_name == "proconfig": + package_name = "myshell" + except Exception as e: + package_name = "myshell" + if package_name != "myshell": + # custom widget if package_name in missing_widgets: # already added + # check if the widget_name is in the used_func + if widget_name not in missing_widgets[package_name]["used_func"]: + missing_widgets[package_name]["used_func"].append(widget_name) return missing_widgets - if package_name in widget_status: - current_commit = widget_status[package_name]["current_commit"] + + widget_path = os.path.join("custom_widgets", package_name) + if not os.path.exists(widget_path): + error = { + 'error_code': 'SHELL-1113', + 'error_head': 'Widget Not Found Error', + 'msg': f"Widget {package_name} directory not found" + } + raise ShellException(**error) + + try: + result = subprocess.run( + ["git", "status", "--porcelain"], + cwd=widget_path, + capture_output=True, + text=True + ) + if result.stdout.strip(): + error = { + 'error_code': 'SHELL-1114', + 'error_head': 'Local Changes Error', + 'msg': f"Widget {package_name} has uncommitted local changes" + } + raise ShellException(**error) + except subprocess.CalledProcessError: + error = { + 'error_code': 'SHELL-1115', + 'error_head': 'Git Error', + 'msg': f"Failed to check git status for widget {package_name}" + } + raise ShellException(**error) + + # check commit id + try: + result = subprocess.run( + ["git", "rev-parse", "HEAD"], + cwd=widget_path, + capture_output=True, + text=True + ) + current_commit = result.stdout.strip() + expected_commit = all_widget_json[package_name]["commit"] + + if current_commit != expected_commit: + error = { + 'error_code': 'SHELL-1116', + 'error_head': 'Commit Mismatch Error', + 'msg': f"Widget {package_name} commit mismatch: expected {expected_commit}, got {current_commit}" + } + raise ShellException(**error) + except subprocess.CalledProcessError: + error = { + 'error_code': 'SHELL-1117', + 'error_head': 'Git Error', + 'msg': f"Failed to get current commit for widget {package_name}" + } + raise ShellException(**error) + + if package_name in all_widget_json: + current_commit = all_widget_json[package_name]["commit"] else: - # randomly pick one - current_commit = sorted(os.listdir(os.path.join("custom_widgets", package_name)))[0] + error = { + 'error_code': 'SHELL-1112', + 'error_head': 'Package Not Registered Error', + 'msg': f"{package_name} should be registered to custom_widget_info.json" + } + raise ShellException(**error) + registered = package_name in all_widget_json missing_widgets[package_name] = { "commit": current_commit, - "git": all_widget_json[package_name]["git"] if registered else None + "repo": all_widget_json[package_name]["git"] if registered else None, + "used_func": [widget_name] } return missing_widgets diff --git a/servers/automata.py b/servers/automata.py index fef39ce3..cfe68f8d 100755 --- a/servers/automata.py +++ b/servers/automata.py @@ -291,6 +291,11 @@ async def export_app(data: dict, request: Request): for key in sensitive_keys: envs.pop(key, None) + # check the local ShellAgent commit + from pygit2 import Repository + repo = Repository(os.getcwd()) + commit_id = str(repo.head.peel().id) + results = { "data": { **exported_data, @@ -299,6 +304,10 @@ async def export_app(data: dict, request: Request): "metadata": metadata, "reactflow": reactflow, "dependency": { + "shellagent_version": { + "repo": "https://github.com/myshell-ai/ShellAgent.git", + "commit": commit_id + }, "models": dependency_results["models"], "widgets": dependency_results["widgets"] }, diff --git a/servers/base.py b/servers/base.py index e8dd0fa0..1d65adfa 100755 --- a/servers/base.py +++ b/servers/base.py @@ -102,7 +102,6 @@ def compute_root_path(root_type, project_root=None, headers=None): MODEL_DIR = "models" CUSTOM_WIDGETS_DIR = "custom_widgets" -CUSTOM_WIDGETS_STATUS_PATH = os.path.join(CUSTOM_WIDGETS_DIR, "widgets_status.json") MODELS_STATUS_PATH = os.path.join(MODEL_DIR, "model_status.json") os.environ["MODEL_DIR"] = MODEL_DIR @@ -126,8 +125,6 @@ def initialize_envs(): os.makedirs(os.path.join(os.path.dirname(__file__), "web", "extensions"), exist_ok=True) if not os.path.isfile(os.environ["MODELS_STATUS_PATH"]): json.dump({}, open(os.environ["MODELS_STATUS_PATH"], "w")) - if not os.path.isfile(CUSTOM_WIDGETS_STATUS_PATH): - json.dump({}, open(CUSTOM_WIDGETS_STATUS_PATH, "w")) for k, v in env["envs"].items(): if k != "": From 5bcc380fc7bf30d47ee9dd9bf94d34fc8b4a84df Mon Sep 17 00:00:00 2001 From: shanexi Date: Mon, 24 Feb 2025 16:16:55 +0800 Subject: [PATCH 07/17] fix: Compatible with new data struct --- web/apps/web/src/services/app/type.ts | 14 +++++++++++++ .../src/stores/app/utils/automata-export.ts | 21 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/web/apps/web/src/services/app/type.ts b/web/apps/web/src/services/app/type.ts index 16e3f07d..defed13a 100755 --- a/web/apps/web/src/services/app/type.ts +++ b/web/apps/web/src/services/app/type.ts @@ -139,6 +139,20 @@ export interface ExportBotResponse { comfyui_workflows: { [key: string]: any; }; + dependencies: { + models: { + [key: string]: { + filename: string; + save_path: string; + urls: Array; + }; + }; + custom_widgets: Array<{ + name: string; + repo: string; + commit: string; + }>; + }; dependency: { models: { [key: string]: { diff --git a/web/apps/web/src/stores/app/utils/automata-export.ts b/web/apps/web/src/stores/app/utils/automata-export.ts index ff863707..6259d63a 100755 --- a/web/apps/web/src/stores/app/utils/automata-export.ts +++ b/web/apps/web/src/stores/app/utils/automata-export.ts @@ -30,12 +30,29 @@ export const checkDependency = (data: ExportBotResponse['data']) => { models: {}, widgets: {}, }; - Object.entries(data.dependency.models || {}).forEach(([key, item]) => { + Object.entries( + data.dependency.models || data.dependencies.models || {}, + ).forEach(([key, item]) => { if (isEmpty(item.urls)) { set(deps, ['models', key], item.filename); } }); - Object.entries(data.dependency.widgets || {}).forEach(([key, item]) => { + Object.entries( + data.dependency.widgets || + data.dependencies.custom_widgets.reduce<{ + [key: string]: { + git: string; + commit: string; + }; + }>((acc, cur) => { + acc[cur.name] = { + git: cur.repo, + commit: cur.commit, + }; + return acc; + }, {}) || + {}, + ).forEach(([key, item]) => { if (isEmpty(item.git) || item.git === 'None') { set(deps, ['widgets', key], key); } From 8dc2a86b19baab304dc4c0e5ff8de471fadf6c65 Mon Sep 17 00:00:00 2001 From: Chengwei Ouyang Date: Tue, 25 Feb 2025 15:48:25 +0800 Subject: [PATCH 08/17] update export custom widgets format --- proconfig/widgets/tools/dependency_checker.py | 19 ++++++++++++++++++- servers/automata.py | 5 +++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/proconfig/widgets/tools/dependency_checker.py b/proconfig/widgets/tools/dependency_checker.py index b823cc52..fd8b9370 100755 --- a/proconfig/widgets/tools/dependency_checker.py +++ b/proconfig/widgets/tools/dependency_checker.py @@ -248,7 +248,23 @@ def check_dependency_recursive(config, non_existed_models: list, missing_models: for block_config in config.blocks: check_dependency_recursive(block_config, non_existed_models, missing_models, undefined_widgets, missing_widgets, local_vars, {}, workflow_ids=workflow_ids, comfyui_workflow_ids=comfyui_workflow_ids, environ=environ) return - + +def transform_widgets_format(original_data): + if not original_data: + return {"custom_widgets": [], "used_func": []} + new_format = { + "custom_widgets": [], + "used_func": [] + } + for widget_name, widget_info in original_data.items(): + new_format["custom_widgets"].append({ + "name": widget_name, + "repo": widget_info["repo"], + "commit": widget_info["commit"] + }) + new_format["used_func"].extend(widget_info["used_func"]) + new_format["used_func"] = list(set(new_format["used_func"])) + return new_format def check_dependency(config, environ={}): # here config is a json @@ -270,6 +286,7 @@ def check_dependency(config, environ={}): check_dependency_recursive(config, non_existed_models=non_existed_models, missing_models=missing_models, undefined_widgets=undefined_widgets, missing_widgets=missing_widgets, workflow_ids=workflow_ids, comfyui_workflow_ids=comfyui_workflow_ids, local_vars={}, payload={}, environ=environ) + missing_widgets = transform_widgets_format(missing_widgets) return { "non_existed_models": non_existed_models, "models": missing_models, diff --git a/servers/automata.py b/servers/automata.py index cfe68f8d..2ca8dfa6 100755 --- a/servers/automata.py +++ b/servers/automata.py @@ -303,13 +303,14 @@ async def export_app(data: dict, request: Request): "comfyui_dependencies": comfyui_dependencies, "metadata": metadata, "reactflow": reactflow, - "dependency": { + "dependencies": { "shellagent_version": { "repo": "https://github.com/myshell-ai/ShellAgent.git", "commit": commit_id }, "models": dependency_results["models"], - "widgets": dependency_results["widgets"] + "custom_widgets": dependency_results["widgets"]["custom_widgets"], + "used_func": dependency_results["widgets"]["used_func"] }, "envs": export_env_variables(public_key, envs) }, From c1f2e9b7ad3a53d4e9a1b19b46da681f71e82512 Mon Sep 17 00:00:00 2001 From: joe Date: Tue, 25 Feb 2025 15:52:29 +0800 Subject: [PATCH 09/17] wip --- web/apps/web/src/app/app/detail/page.tsx | 2 +- web/apps/web/src/app/container.ts | 2 +- .../app/config-form/widget-config/index.tsx | 93 ++++----- .../src/components/app/node-form/index.tsx | 2 - .../app/node-form/widgets/tasks-config.tsx | 186 ++++++++++++++---- .../image-canvas/open-image-canvas.model.ts | 0 .../image-canvas/open-image-canvas.tsx | 44 ++++- .../app/plugins/image-canvas/schema.ts | 52 +++++ .../web/src/components/app/plugins/index.tsx | 2 + .../app/state-config-sheet/index.tsx | 67 +------ web/apps/web/src/services/app/index.tsx | 15 ++ web/apps/web/src/services/app/type.ts | 14 ++ .../stores/app/models/app-builder.model.ts | 31 ++- .../web/src/stores/app/models/flow.model.ts | 4 +- .../web/src/stores/app/schema-provider.tsx | 49 +---- .../src/stores/app/schema/get-state-schema.ts | 1 + web/apps/web/src/stores/app/use-app-state.tsx | 5 +- 17 files changed, 347 insertions(+), 222 deletions(-) rename web/apps/web/src/components/{ => app/plugins}/image-canvas/open-image-canvas.model.ts (100%) rename web/apps/web/src/components/{ => app/plugins}/image-canvas/open-image-canvas.tsx (67%) create mode 100644 web/apps/web/src/components/app/plugins/image-canvas/schema.ts diff --git a/web/apps/web/src/app/app/detail/page.tsx b/web/apps/web/src/app/app/detail/page.tsx index c29437ae..8bc6ee38 100755 --- a/web/apps/web/src/app/app/detail/page.tsx +++ b/web/apps/web/src/app/app/detail/page.tsx @@ -15,10 +15,10 @@ import { getNodeTypes, getEdgeTypes } from '@/components/app/constants'; import FlowHeader from '@/components/app/flow-header'; import { Header } from '@/components/app/header'; import { ComfyUIEditorModal } from '@/components/app/plugins/comfyui/widgets/comfyui-editor'; +import { ImageCanvasDialog } from '@/components/app/plugins/image-canvas/open-image-canvas'; import { ValidationDialog } from '@/components/app/validation-dialog'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; import { ListFooterExtra } from '@/components/common/list-footer-extra'; -import { ImageCanvasDialog } from '@/components/image-canvas/open-image-canvas'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { useAppState } from '@/stores/app/use-app-state'; diff --git a/web/apps/web/src/app/container.ts b/web/apps/web/src/app/container.ts index 8d8dda39..27e778f8 100755 --- a/web/apps/web/src/app/container.ts +++ b/web/apps/web/src/app/container.ts @@ -9,10 +9,10 @@ import * as Mobx from 'mobx'; import { toast } from 'react-toastify'; import { ComfyUIModel } from '@/components/app/plugins/comfyui/comfyui.model'; +import { OpenImageCanvasModel } from '@/components/app/plugins/image-canvas/open-image-canvas.model'; import { AssistantModel } from '@/components/assistant/model'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; import { FeedbackModel } from '@/components/feedback/model'; -import { OpenImageCanvasModel } from '@/components/image-canvas/open-image-canvas.model'; import { SettingsModel } from '@/components/settings/settings.model'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { FlowModel } from '@/stores/app/models/flow.model'; diff --git a/web/apps/web/src/components/app/config-form/widget-config/index.tsx b/web/apps/web/src/components/app/config-form/widget-config/index.tsx index f318b049..7e9e5c8a 100755 --- a/web/apps/web/src/components/app/config-form/widget-config/index.tsx +++ b/web/apps/web/src/components/app/config-form/widget-config/index.tsx @@ -2,19 +2,26 @@ 'use client'; -import { getDefaultValueBySchema, TValues } from '@shellagent/form-engine'; +import { + getDefaultValueBySchema, + TValues, + ISchema, +} from '@shellagent/form-engine'; +import { useInjection } from 'inversify-react'; import { isEmpty, merge } from 'lodash-es'; import { useCallback, useEffect, useMemo } from 'react'; import NodeForm from '@/components/app/node-form'; import { getPlugin } from '@/components/app/plugins'; +import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { getSchemaByWidget } from '@/stores/app/schema/get-widget-schema'; -import { useWorkflowStore } from '@/stores/workflow/workflow-provider'; export interface WidgetConfigProps { values: TValues | undefined; parent: string; onChange: (values: TValues) => void; + defaultValues?: TValues; + schema?: ISchema; } export interface CommonWidgetConfigProps extends WidgetConfigProps {} @@ -37,56 +44,23 @@ const StandardWidgetConfig: React.FC = ({ values, parent, onChange, + schema, + defaultValues, }) => { - const { loading, getWidgetSchema, widgetSchema } = useWorkflowStore( - state => ({ - loading: state.loading.getWidgetSchema, - getWidgetSchema: state.getWidgetSchema, - widgetSchema: state.widgetSchema, - }), - ); - - useEffect(() => { - if ( - values?.widget_class_name != null && - values?.widget_name != null && - !widgetSchema[[values.widget_class_name, values.widget_name].join('_')] - ) { - getWidgetSchema({ - widget_name: values.widget_class_name, - myshell_widget_name: values.widget_name, - }); - } - }, [ - values?.widget_class_name, - values?.widget_name, - getWidgetSchema, - widgetSchema, - ]); - - const schema = useMemo(() => { - const schema = getSchemaByWidget({ - ...widgetSchema[ - [values?.widget_class_name, values?.widget_name].join('_') - ], - }); - // Special process ImageCanvasWidget - if (values?.widget_class_name === 'ImageCanvasWidget') { - if (schema?.properties?.inputs?.properties?.config) { - schema.properties.inputs.properties.config['x-component'] = - 'OpenImageCanvas'; - schema.properties.inputs.properties.config['x-raw-default'] = 'ui'; - schema.properties.inputs.properties.config['x-component-props'] = {}; - } - } - return schema; - }, [widgetSchema, values?.widget_class_name]); - - const defaultValues = useMemo( - () => getDefaultValueBySchema(schema, false), - [schema], - ); - + // const appBuilder = useInjection('AppBuilderModel'); + // const key = [values?.widget_class_name, values?.widget_name].join('_'); + // const schema = useMemo(() => { + // return getSchemaByWidget({ + // ...appBuilder.widgetSchema.get(key), + // }); + // }, [appBuilder.widgetSchema, values?.widget_class_name, values?.widget_name]); + + // const defaultValues = useMemo( + // () => getDefaultValueBySchema(schema, false), + // [schema], + // ); + + // TODO 拆到model里 const handleOnChange = useCallback( (newValues: TValues) => { onChange(merge({}, defaultValues, newValues)); @@ -96,7 +70,7 @@ const StandardWidgetConfig: React.FC = ({ useEffect(() => { if ( - !loading?.[values?.widget_class_name] && + // !appBuilder.getWidgetSchemaLoading?.[key] && isEmpty(values?.inputs) && !isEmpty(defaultValues) && values @@ -108,29 +82,28 @@ const StandardWidgetConfig: React.FC = ({ } }, [ schema, - loading?.[values?.widget_class_name], + // appBuilder.getWidgetSchemaLoading[key], defaultValues, onChange, values, ]); - if (!values) { - return null; - } - return ( ); }; export const WidgetConfig: React.FC = props => { - if (props.values?.custom) { + if ( + props.values?.widget_name === 'ImageCanvasWidget' || + props.values?.widget_name === 'ComfyUIWidget' + ) { return ; } diff --git a/web/apps/web/src/components/app/node-form/index.tsx b/web/apps/web/src/components/app/node-form/index.tsx index 03817db5..7ead3205 100755 --- a/web/apps/web/src/components/app/node-form/index.tsx +++ b/web/apps/web/src/components/app/node-form/index.tsx @@ -50,7 +50,6 @@ import { TimerSelector, ContextSelector, } from './widgets'; -import { OpenImageCanvas } from '../../image-canvas/open-image-canvas'; interface NodeFormProps { values: TValues; @@ -120,7 +119,6 @@ const NodeForm = forwardRef( WorkflowSelect, TransitionConditionEditor, VariableNameInput, - OpenImageCanvas, ValidateRender, ButtonValidateRender, DialogValidateRender, diff --git a/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx b/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx index 2aafba51..0421a0d0 100755 --- a/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx @@ -1,26 +1,36 @@ import { PlusIcon } from '@heroicons/react/24/outline'; import { XMarkIcon } from '@heroicons/react/24/solid'; import { WidgetItem, NodeTypeEnum } from '@shellagent/flow-engine'; -import { Task, TaskSchema } from '@shellagent/shared/protocol/task'; +import { Task, TaskSchema, WidgetTask } from '@shellagent/shared/protocol/task'; import { customSnakeCase, getTaskDisplayName } from '@shellagent/shared/utils'; -import { Button, useFormContext, Drag } from '@shellagent/ui'; +import { Button, useFormContext, Drag, Drawer } from '@shellagent/ui'; +import { TValues, getDefaultValueBySchema } from '@shellagent/form-engine'; import { useClickAway } from 'ahooks'; +import { isNumber } from 'lodash-es'; import { Dropdown } from 'antd'; import { useInjection } from 'inversify-react'; -import { useState, useRef, useCallback } from 'react'; +import { useEffect, useState, useRef, useCallback, useMemo } from 'react'; import { useDrag, useDrop } from 'react-dnd'; +import { getNewKey } from '@shellagent/shared/utils'; +import { WidgetConfig } from '@/components/app/config-form/widget-config'; +import { EditableTitle } from '@/components/app/editable-title'; import { TaskList } from '@/components/app/task-list'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { useAppState } from '@/stores/app/use-app-state'; +import { getSchemaByWidget } from '@/stores/app/schema/get-widget-schema'; const TaskItem = ({ name, + widgetName, + widgetClassName, onDelete, onClick, index, moveTask, - draggable, // 新增参数 + draggable, + task, + showDrawer = true, }: { name: string; onDelete: () => void; @@ -31,11 +41,79 @@ const TaskItem = ({ hoverIndex: number, isDragging: boolean, ) => void; - draggable?: boolean; // 新增参数类型 + draggable?: boolean; + widgetName?: string; + widgetClassName?: string; + task: Task; + showDrawer?: boolean; }) => { const dragRef = useRef(null); const previewRef = useRef(null); + const appBuilder = useInjection('AppBuilderModel'); + const { getValues, setValue } = useFormContext(); + const { + currentStateId, + setInsideSheetOpen, + insideSheetOpen, + currentTaskName, + } = useAppState(state => state); + + const handleTitleChange = useCallback( + (value: string) => { + const { key: newKey, name: newName } = getNewKey({ + name: value, + nameKey: 'name', + values: appBuilder.nodeData[currentStateId]?.blocks, + prefix: 'Blocks', + }); + const path = `blocks.${index}`; + const values = getValues(path); + setValue(path, { + ...values, + display_name: newName, + name: newKey, + }); + }, + [index, currentStateId, getValues, setValue, appBuilder.nodeData], + ); + const handleFormChange = useCallback( + (value: TValues) => { + const path = `blocks.${index}`; + setValue(path, value); + }, + [index, setValue], + ); + + const key = `${widgetClassName}_${widgetName}`; + + // 预请求schema + useEffect(() => { + if ( + widgetClassName != null && + widgetName != null && + !appBuilder.widgetSchema.get(key) + ) { + appBuilder.getWidgetSchema({ + widget_name: widgetClassName, + myshell_widget_name: widgetName, + }); + } + }, [widgetClassName, widgetName, appBuilder.getWidgetSchema]); + + const schema = useMemo(() => { + return getSchemaByWidget({ + ...appBuilder.widgetSchema.get(key), + }); + }, [appBuilder.widgetSchema, key]); + + const defaultValues = useMemo( + () => getDefaultValueBySchema(schema, false), + [schema], + ); + // TODO default回填逻辑,拆到model里 + + console.log('schema, defaultValues', schema, defaultValues); const [{ isDragging }, drag, preview] = useDrag({ type: 'TASK', item: { index }, @@ -81,36 +159,70 @@ const TaskItem = ({ } return ( -
{ - e.stopPropagation(); - onClick(e); - }} - className={`relative group h-8 flex items-center bg-surface-container-default rounded-lg p-2 text-default font-medium cursor-pointer ${ - isDragging ? 'opacity-50' : '' - }`}> - {draggable && ( -
- -
- )} - {name} - +
{ - e.preventDefault(); e.stopPropagation(); - onDelete(); + onClick(e); }} - /> -
-
+ className={`relative group h-8 flex items-center bg-surface-container-default rounded-lg p-2 text-default font-medium cursor-pointer ${ + isDragging ? 'opacity-50' : '' + }`}> + {draggable && ( +
+ +
+ )} + {name} + { + e.preventDefault(); + e.stopPropagation(); + onDelete(); + }} + /> +
+
+ {showDrawer && ( + + setInsideSheetOpen({ + stateId: currentStateId, + open: false, + }) + } + autoFocus={false} + title={ + + }> + + + )} + ); }; @@ -118,10 +230,12 @@ const TasksConfig = ({ name, onChange, draggable, + showDrawer = true, }: { name: string; onChange: (value: Task[]) => void; draggable?: boolean; + showDrawer?: boolean; }) => { const appBuilder = useInjection('AppBuilderModel'); const btnRef = useRef(null); @@ -204,11 +318,15 @@ const TasksConfig = ({ handleItemDelete(idx)} onClick={() => handleItemClick(idx)} index={idx} moveTask={moveTask} - draggable={draggable} // 传递draggable参数 + draggable={draggable} + task={task} + showDrawer={showDrawer} /> ))}
@@ -217,7 +335,7 @@ const TasksConfig = ({ placement="bottomRight" trigger={['click']} overlayClassName="shadow-modal-default" - overlayStyle={{ borderRadius: 12, overflow: 'hidden' }} + overlayStyle={{ borderRadius: 12, overflow: 'hidden', zIndex: 999 }} getPopupContainer={() => btnRef.current || document.body} open={open} overlay={ diff --git a/web/apps/web/src/components/image-canvas/open-image-canvas.model.ts b/web/apps/web/src/components/app/plugins/image-canvas/open-image-canvas.model.ts similarity index 100% rename from web/apps/web/src/components/image-canvas/open-image-canvas.model.ts rename to web/apps/web/src/components/app/plugins/image-canvas/open-image-canvas.model.ts diff --git a/web/apps/web/src/components/image-canvas/open-image-canvas.tsx b/web/apps/web/src/components/app/plugins/image-canvas/open-image-canvas.tsx similarity index 67% rename from web/apps/web/src/components/image-canvas/open-image-canvas.tsx rename to web/apps/web/src/components/app/plugins/image-canvas/open-image-canvas.tsx index ed968a0f..7363fab2 100755 --- a/web/apps/web/src/components/image-canvas/open-image-canvas.tsx +++ b/web/apps/web/src/components/app/plugins/image-canvas/open-image-canvas.tsx @@ -7,15 +7,21 @@ import 'image-canvas/assets/react-colors-beauty.css'; // import 'image-canvas/fabric.js'; import { css } from '@emotion/react'; import { PhotoIcon } from '@heroicons/react/24/outline'; +import { getDefaultValueBySchema, TValues } from '@shellagent/form-engine'; import { AButton, Button, useFormContext } from '@shellagent/ui'; import { Modal, theme } from 'antd'; import { useInjection } from 'inversify-react'; +import { merge } from 'lodash-es'; import { observer } from 'mobx-react-lite'; import dynamic from 'next/dynamic'; -import { useEffect } from 'react'; +import { useMemo, useCallback, useEffect } from 'react'; +import { CommonWidgetConfigProps } from '@/components/app/config-form/widget-config'; +import NodeForm from '@/components/app/node-form'; + +import { OpenImageCanvasModel } from '@/components/app/plugins/image-canvas/open-image-canvas.model'; import { useSelectOptions } from '@/components/app/node-form/widgets/variable-select/use-select-options'; -import { OpenImageCanvasModel } from '@/components/image-canvas/open-image-canvas.model'; +import { defaultSchema } from './schema'; const ImageCanvas = dynamic( () => import('image-canvas').then(module => module.ImageCanvas), @@ -97,3 +103,37 @@ export const ImageCanvasDialog = observer(() => { ); }); + +export const OpenImageCanvasPlugin = observer( + ({ values, onChange, parent }) => { + const defaultValues = useMemo( + () => getDefaultValueBySchema(defaultSchema, false), + [defaultSchema], + ); + + const handleOnChange = useCallback( + (newValues: TValues) => { + onChange(merge({}, defaultValues, newValues)); + }, + [defaultValues, onChange], + ); + useEffect(() => { + onChange({ + ...values, + outputs: defaultValues.outputs, + }); + }, [values]); + + return ( + + ); + }, +); diff --git a/web/apps/web/src/components/app/plugins/image-canvas/schema.ts b/web/apps/web/src/components/app/plugins/image-canvas/schema.ts new file mode 100644 index 00000000..60ca7ddc --- /dev/null +++ b/web/apps/web/src/components/app/plugins/image-canvas/schema.ts @@ -0,0 +1,52 @@ +import { ISchema } from '@shellagent/form-engine'; + +export const defaultSchema: ISchema = { + type: 'object', + 'x-type': 'Section', + 'x-title-size': 'h4', + properties: { + inputs: { + properties: { + config: { + title: 'config', + type: 'string', + 'x-field-type': 'string', + 'x-title-size': 'h4', + 'x-type': 'Control', + 'x-layout': 'Horizontal', + 'x-component': 'OpenImageCanvas', + 'x-raw': true, + 'x-component-props': {}, + 'x-validator': [{}], + 'x-raw-default': 'ui', + }, + }, + required: ['config'], + title: 'Input', + type: 'object', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + }, + outputs: { + type: 'object', + title: 'Output', + 'x-type': 'Block', + 'x-title-size': 'h4', + 'x-collapsible': true, + properties: { + display: { + type: 'string', + default: { + image: 'string', + }, + 'x-type': 'Control', + 'x-component': 'JSONView', + 'x-component-props': { + hiddenName: true, + }, + }, + }, + }, + }, +}; diff --git a/web/apps/web/src/components/app/plugins/index.tsx b/web/apps/web/src/components/app/plugins/index.tsx index 3be1becf..6e150764 100755 --- a/web/apps/web/src/components/app/plugins/index.tsx +++ b/web/apps/web/src/components/app/plugins/index.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { CommonWidgetConfigProps } from '@/components/app/config-form/widget-config'; import { ComfyUIPlugin } from './comfyui'; +import { OpenImageCanvasPlugin } from './image-canvas/open-image-canvas'; const pluginMap = new Map>(); @@ -20,5 +21,6 @@ const getPlugin = (name: string): React.FC => { }; registerPlugin('ComfyUIWidget', ComfyUIPlugin); +registerPlugin('ImageCanvasWidget', OpenImageCanvasPlugin); export { registerPlugin, getPlugin, pluginMap }; diff --git a/web/apps/web/src/components/app/state-config-sheet/index.tsx b/web/apps/web/src/components/app/state-config-sheet/index.tsx index beba0393..70e2bf62 100755 --- a/web/apps/web/src/components/app/state-config-sheet/index.tsx +++ b/web/apps/web/src/components/app/state-config-sheet/index.tsx @@ -7,17 +7,14 @@ import { } from '@shellagent/flow-engine'; import { TValues } from '@shellagent/form-engine'; import { Button as IButtonType } from '@shellagent/shared/protocol/render-button'; -import { WidgetTask, WorkflowTask } from '@shellagent/shared/protocol/task'; +import { WidgetTask } from '@shellagent/shared/protocol/task'; import { getNewKey } from '@shellagent/shared/utils'; import { Drawer, FormRef } from '@shellagent/ui'; import clsx from 'clsx'; import { useInjection } from 'inversify-react'; -import { isNumber } from 'lodash-es'; import { useMemo, useRef, useCallback, useState } from 'react'; import { ButtonConfig } from '@/components/app/config-form/button-config'; -import { WidgetConfig } from '@/components/app/config-form/widget-config'; -import { WorkflowConfig } from '@/components/app/config-form/workflow-config'; import { EditableTitle } from '@/components/app/editable-title'; import NodeForm from '@/components/app/node-form'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; @@ -40,7 +37,6 @@ const StateConfigSheet: React.FC<{}> = () => { currentTaskName, currentButtonId, insideSheetOpen, - setInsideSheetOpen, insideSheetMode, runDrawerWidth, selectedNode, @@ -51,7 +47,6 @@ const StateConfigSheet: React.FC<{}> = () => { currentTaskName: state.currentTaskName, currentButtonId: state.currentButtonId, insideSheetOpen: state.insideSheetOpen, - setInsideSheetOpen: state.setInsideSheetOpen, insideSheetMode: state.insideSheetMode, runDrawerWidth: state.runDrawerWidth, selectedNode: state.selectedNode, @@ -119,52 +114,6 @@ const StateConfigSheet: React.FC<{}> = () => { title: 'Edit Button', }; } - if (insideSheetMode === 'workflow') { - const workflow: WorkflowTask = isNumber(currentTaskIndex) - ? appBuilder.nodeData[currentStateId]?.blocks?.[currentTaskIndex] - : {}; - - return { - children: ( - - ), - title: ( - - ), - }; - } - if (insideSheetMode === 'widget') { - const widget: WidgetTask = isNumber(currentTaskIndex) - ? appBuilder.nodeData[currentStateId]?.blocks?.[currentTaskIndex] - : {}; - - return { - children: ( - - ), - title: ( - - ), - }; - } return {}; }, [ insideSheetMode, @@ -272,20 +221,6 @@ const StateConfigSheet: React.FC<{}> = () => { onChange={onChange} ref={nodeFormRef} /> - - setInsideSheetOpen({ stateId: currentStateId, open: false }) - } - autoFocus={false} - {...drawerProps}> - {drawerProps.children} - ); diff --git a/web/apps/web/src/services/app/index.tsx b/web/apps/web/src/services/app/index.tsx index 26994ad9..12cd58ee 100755 --- a/web/apps/web/src/services/app/index.tsx +++ b/web/apps/web/src/services/app/index.tsx @@ -34,6 +34,8 @@ import { CheckIpResponse, GetIntroDisplayRequest, GetIntroDisplayResponse, + GetWidgetSchemaRequest, + GetWidgetSchemaResponse, } from './type'; import { APIFetch, baseHeaders } from '../base'; @@ -313,3 +315,16 @@ export const getMyShellWidgetList: Fetcher< params, }); }; + +// 获取widget form schema +export const fetchWidgetSchema: Fetcher< + GetWidgetSchemaResponse, + GetWidgetSchemaRequest +> = params => { + return APIFetch.post( + '/api/workflow/get_widget_schema', + { + body: params, + }, + ); +}; diff --git a/web/apps/web/src/services/app/type.ts b/web/apps/web/src/services/app/type.ts index defed13a..01b671a1 100755 --- a/web/apps/web/src/services/app/type.ts +++ b/web/apps/web/src/services/app/type.ts @@ -1,5 +1,6 @@ import { IFlow } from '@shellagent/flow-engine'; import { Automata } from '@shellagent/pro-config'; +import { JsonSchema7 } from 'node_modules/@shellagent/form-engine/src/types/jsonSchema7'; import { Config, Metadata } from '@/types/app/types'; @@ -236,3 +237,16 @@ export type GetIntroDisplayResponse = { text: string; images: string[]; }; + +// 获取widget form schema +// /api/workflow/get_widget_schema +export type GetWidgetSchemaRequest = { + widget_name: string; + myshell_widget_name: string; +}; + +export type GetWidgetSchemaResponse = { + input_schema: JsonSchema7 | Record; + output_schema: JsonSchema7; + multi_input_schema: boolean; +}; diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index e28ade89..4b0fa39a 100755 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -52,11 +52,14 @@ import { getMyShellWidgetList, releaseApp, saveApp, + fetchWidgetSchema, } from '@/services/app'; import type { GetAppFlowRequest, GetAppVersionListResponse, GetAutomataRequest, + GetWidgetSchemaResponse, + GetWidgetSchemaRequest, } from '@/services/app/type'; import { editItem, fetchList as fetchFlowList } from '@/services/home'; import type { GetListRequest, GetListResponse } from '@/services/home/type'; @@ -163,6 +166,9 @@ export class AppBuilderModel { widget_list: [], }; + @observable widgetSchema: Map = new Map(); + @observable getWidgetSchemaLoading: Record = {}; + @computed get materialList() { const backendLocalWidgets: MaterialListType = this.rawLocalWidgetList.widget_list.map(m => { @@ -172,14 +178,6 @@ export class AppBuilderModel { items: m.items.reduce((acc, i) => { acc = acc.concat( i.children.map(c => { - if (c.name === 'ComfyUIWidget') { - return { - ...c, - type: NodeTypeEnum.widget, - undraggable: true, - custom: true, - }; - } return { ...c, type: NodeTypeEnum.widget, @@ -881,4 +879,21 @@ export class AppBuilderModel { }, }); } + + @action.bound + async getWidgetSchema(params: GetWidgetSchemaRequest) { + const key = `${params.widget_name}_${params.myshell_widget_name}`; + this.getWidgetSchemaLoading[key] = true; + try { + const res = await fetchWidgetSchema(params); + this.widgetSchema.set(key, res); + } catch (error) { + this.emitter.emitter.emit( + 'message.error', + `Get ${params.widget_name} Error`, + ); + } finally { + this.getWidgetSchemaLoading[key] = false; + } + } } diff --git a/web/apps/web/src/stores/app/models/flow.model.ts b/web/apps/web/src/stores/app/models/flow.model.ts index bcab4d3a..f4b25bd1 100755 --- a/web/apps/web/src/stores/app/models/flow.model.ts +++ b/web/apps/web/src/stores/app/models/flow.model.ts @@ -5,7 +5,7 @@ import { makeAutoObservable } from 'mobx'; import { CustomEdgeData } from '@/components/app/edges'; -type SheetMode = 'button' | 'workflow' | 'widget' | ''; +type SheetMode = 'button' | 'widget' | ''; interface BaseState { currentStateId: string; @@ -26,7 +26,7 @@ export class FlowModel { currentStateId: string = ''; stateConfigSheetOpen: boolean = false; insideSheetOpen: boolean = false; - insideSheetMode: 'button' | 'workflow' | 'widget' | '' = ''; + insideSheetMode: 'button' | 'widget' | '' = ''; transitionSheetOpen: boolean = false; runDrawerWidth: number = 715; selectedNode: Node | undefined = undefined; diff --git a/web/apps/web/src/stores/app/schema-provider.tsx b/web/apps/web/src/stores/app/schema-provider.tsx index 279bf177..9bd44e19 100755 --- a/web/apps/web/src/stores/app/schema-provider.tsx +++ b/web/apps/web/src/stores/app/schema-provider.tsx @@ -1,14 +1,10 @@ import { NodeTypeEnum } from '@shellagent/flow-engine'; import { ISchema, TFieldMode } from '@shellagent/form-engine'; -import { isEmpty } from 'lodash-es'; -import { useMemo, useEffect, useState } from 'react'; +import { useMemo, useState } from 'react'; import { useContextSelector, createContext } from 'use-context-selector'; -import { JsonSchema7 } from '@/services/workflow/type'; import { getIntroSchema } from '@/stores/app/schema/get-intro-schema'; import { getStateSchema } from '@/stores/app/schema/get-state-schema'; -import { getSchemaByWidget } from '@/stores/app/schema/get-widget-schema'; -import { useWorkflowStore } from '@/stores/workflow/workflow-provider'; import { generateUUID } from '@/utils/common-helper'; import { getConditionStateSchema } from './schema/condition-state-schema'; @@ -24,7 +20,6 @@ type SchemaState = { displayName: string; fieldMode: Record; outputs?: Record; - outputSchema: JsonSchema7; formKey: string; type?: string; }; @@ -42,7 +37,6 @@ export const initState: SchemaStore = { schema: {}, fieldMode: {}, setFieldMode: () => {}, - outputSchema: {}, formKey: generateUUID(), type: NodeTypeEnum.state, }; @@ -70,7 +64,6 @@ export const SchemaProvider: React.FC = ({ children, type, }) => { - // const context = useVariableContext(state => state.context); const [fieldMode = {}, setFieldModeStorage] = useState< Record >({}); @@ -82,17 +75,6 @@ export const SchemaProvider: React.FC = ({ })); }; - const { widgetSchema, getWidgetSchema } = useWorkflowStore(state => ({ - widgetSchema: state.widgetSchema, - getWidgetSchema: state.getWidgetSchema, - })); - - useEffect(() => { - if (name && !widgetSchema[name] && type === NodeTypeEnum.widget) { - getWidgetSchema({ widget_name: name, myshell_widget_name: '' }); - } - }, [name]); - const formKey = useMemo(() => { return `${JSON.stringify({ display_name, @@ -131,20 +113,8 @@ export const SchemaProvider: React.FC = ({ if (type === NodeTypeEnum.intro) { return getIntroSchema(display_name); } - - if (!name || !display_name || isEmpty(widgetSchema?.[name])) { - return {}; - } - - return getSchemaByWidget({ - input_schema: widgetSchema?.[name]?.input_schema, - output_schema: widgetSchema?.[name]?.output_schema, - }); - }, [id, name, display_name, widgetSchema, type]); - const outputSchema = useMemo( - () => widgetSchema?.[name]?.output_schema, - [widgetSchema, name], - ); + return {}; + }, [id, name, display_name, type]); const schemaValue = useMemo( () => ({ @@ -154,21 +124,10 @@ export const SchemaProvider: React.FC = ({ schema, fieldMode, setFieldMode, - outputSchema, formKey, type, }), - [ - id, - name, - display_name, - schema, - fieldMode, - setFieldMode, - outputSchema, - formKey, - type, - ], + [id, name, display_name, schema, fieldMode, setFieldMode, formKey, type], ); return ( diff --git a/web/apps/web/src/stores/app/schema/get-state-schema.ts b/web/apps/web/src/stores/app/schema/get-state-schema.ts index 1a1cad72..0cccf3b7 100755 --- a/web/apps/web/src/stores/app/schema/get-state-schema.ts +++ b/web/apps/web/src/stores/app/schema/get-state-schema.ts @@ -20,6 +20,7 @@ export const getStateSchema = (name: string): ISchema => { 'x-component': 'TasksConfig', 'x-component-props': { draggable: false, + showDrawer: false, }, }, transition: { diff --git a/web/apps/web/src/stores/app/use-app-state.tsx b/web/apps/web/src/stores/app/use-app-state.tsx index 2d9542c7..dc6cda29 100755 --- a/web/apps/web/src/stores/app/use-app-state.tsx +++ b/web/apps/web/src/stores/app/use-app-state.tsx @@ -11,7 +11,7 @@ export type State = { currentStateId: string; stateConfigSheetOpen: boolean; insideSheetOpen: boolean; - insideSheetMode: 'button' | 'workflow' | 'widget' | ''; + insideSheetMode: 'button' | 'widget' | ''; transitionSheetOpen: boolean; newTransitionSheetOpen: boolean; runDrawerWidth: number; @@ -55,6 +55,7 @@ type Action = { setTargetInputsSheetOpen: (open: State['targetInputsSheetOpen']) => void; setRunDrawerWidth: (width: number) => void; resetState: () => void; + setCurrentTaskName: (name: string) => void; }; const initialState: State = { @@ -218,4 +219,6 @@ export const useAppState = create(set => ({ ...state, targetInputsSheetOpen: open, })), + setCurrentTaskName: (name: string) => + set(state => ({ ...state, currentTaskName: name })), })); From 6b29f2019c2c076416be7183ad7d9b6c975e3125 Mon Sep 17 00:00:00 2001 From: Chengwei Ouyang <118652142+Cherwayway@users.noreply.github.com> Date: Tue, 25 Feb 2025 12:59:49 +0800 Subject: [PATCH 10/17] update expresssion map use, and node length --- proconfig/utils/expressions.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/proconfig/utils/expressions.py b/proconfig/utils/expressions.py index ca15fd0f..ff310a32 100755 --- a/proconfig/utils/expressions.py +++ b/proconfig/utils/expressions.py @@ -129,7 +129,7 @@ def safe_eval(expression: str, context: Dict[str, Any]) -> Any: try: # Add expression length limit - if len(expression) > 100: + if len(expression) > 1000: logging.warning(f"Expression too long: {len(expression)} chars") return expression @@ -140,7 +140,7 @@ def safe_eval(expression: str, context: Dict[str, Any]) -> Any: node_count = 0 for _ in ast.walk(tree): node_count += 1 - if node_count > 10: + if node_count > 1000: logging.warning(f"Expression too complex: {node_count} nodes") return expression @@ -157,6 +157,12 @@ def check_attribute_chain(node): return node.id in safe_context or node.id in package_ctx elif isinstance(node, ast.Attribute): return check_attribute_chain(node.value) + elif isinstance(node, ast.Call): + # Support method chaining (e.g., str.replace().replace()) + return check_attribute_chain(node.func.value) if isinstance(node.func, ast.Attribute) else False + elif isinstance(node, ast.Subscript): + # Support indexing in chains (e.g., dict['key'].method()) + return check_attribute_chain(node.value) return False return check_attribute_chain(node.value) elif isinstance(node, ast.Call): @@ -172,6 +178,15 @@ def check_attribute_chain(node): # Check list comprehensions etc. return (check_node(node.elt) if hasattr(node, 'elt') else True) and \ all(check_node(generator) for generator in node.generators) + elif isinstance(node, ast.Subscript): + # Allow dictionary/list indexing operations + return check_node(node.value) and check_node(node.slice) + elif isinstance(node, ast.Index): # For Python 3.8 and below + return check_node(node.value) + elif isinstance(node, ast.Slice): # For slice operations + return (node.lower is None or check_node(node.lower)) and \ + (node.upper is None or check_node(node.upper)) and \ + (node.step is None or check_node(node.step)) return False if not check_node(tree.body): From f94cf424e1d32c88c647131902749dda1eee41c2 Mon Sep 17 00:00:00 2001 From: Chengwei Ouyang Date: Tue, 25 Feb 2025 20:03:49 +0800 Subject: [PATCH 11/17] update expression --- proconfig/utils/expressions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proconfig/utils/expressions.py b/proconfig/utils/expressions.py index ff310a32..fbab52bb 100755 --- a/proconfig/utils/expressions.py +++ b/proconfig/utils/expressions.py @@ -50,10 +50,10 @@ def localtime(seconds=None, *args, **kwargs): def is_safe_value(value: Any, depth: int = 0) -> bool: """Recursively check if value is safe""" - if depth > 10: # Prevent too deep recursion - return False + if depth > 20: # Increase depth limit from 10 to 20 + return True # Allow deeper structures instead of blocking them - if isinstance(value, (int, float, str, bool)): + if isinstance(value, (int, float, str, bool, type(None))): # Add None as safe type return True elif isinstance(value, (list, tuple)): return all(is_safe_value(x, depth + 1) for x in value) From 04a6b5f9f6526bb4dcaa834b629feef136428182 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 26 Feb 2025 16:12:26 +0800 Subject: [PATCH 12/17] Revert "wip" This reverts commit c1f2e9b7ad3a53d4e9a1b19b46da681f71e82512. --- web/apps/web/src/app/app/detail/page.tsx | 2 +- web/apps/web/src/app/container.ts | 2 +- .../app/config-form/widget-config/index.tsx | 93 +++++---- .../src/components/app/node-form/index.tsx | 2 + .../app/node-form/widgets/tasks-config.tsx | 186 ++++-------------- .../app/plugins/image-canvas/schema.ts | 52 ----- .../web/src/components/app/plugins/index.tsx | 2 - .../app/state-config-sheet/index.tsx | 67 ++++++- .../image-canvas/open-image-canvas.model.ts | 0 .../image-canvas/open-image-canvas.tsx | 44 +---- web/apps/web/src/services/app/index.tsx | 15 -- web/apps/web/src/services/app/type.ts | 14 -- .../stores/app/models/app-builder.model.ts | 31 +-- .../web/src/stores/app/models/flow.model.ts | 4 +- .../web/src/stores/app/schema-provider.tsx | 49 ++++- .../src/stores/app/schema/get-state-schema.ts | 1 - web/apps/web/src/stores/app/use-app-state.tsx | 5 +- 17 files changed, 222 insertions(+), 347 deletions(-) delete mode 100644 web/apps/web/src/components/app/plugins/image-canvas/schema.ts rename web/apps/web/src/components/{app/plugins => }/image-canvas/open-image-canvas.model.ts (100%) rename web/apps/web/src/components/{app/plugins => }/image-canvas/open-image-canvas.tsx (67%) diff --git a/web/apps/web/src/app/app/detail/page.tsx b/web/apps/web/src/app/app/detail/page.tsx index 7ad87599..6cab7e73 100644 --- a/web/apps/web/src/app/app/detail/page.tsx +++ b/web/apps/web/src/app/app/detail/page.tsx @@ -15,10 +15,10 @@ import { getNodeTypes, getEdgeTypes } from '@/components/app/constants'; import FlowHeader from '@/components/app/flow-header'; import { Header } from '@/components/app/header'; import { ComfyUIEditorModal } from '@/components/app/plugins/comfyui/widgets/comfyui-editor'; -import { ImageCanvasDialog } from '@/components/app/plugins/image-canvas/open-image-canvas'; import { ValidationDialog } from '@/components/app/validation-dialog'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; import { ListFooterExtra } from '@/components/common/list-footer-extra'; +import { ImageCanvasDialog } from '@/components/image-canvas/open-image-canvas'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { useAppState } from '@/stores/app/use-app-state'; import { ShowAlertDialog } from '@/components/chat/not-supported'; diff --git a/web/apps/web/src/app/container.ts b/web/apps/web/src/app/container.ts index 27e778f8..8d8dda39 100644 --- a/web/apps/web/src/app/container.ts +++ b/web/apps/web/src/app/container.ts @@ -9,10 +9,10 @@ import * as Mobx from 'mobx'; import { toast } from 'react-toastify'; import { ComfyUIModel } from '@/components/app/plugins/comfyui/comfyui.model'; -import { OpenImageCanvasModel } from '@/components/app/plugins/image-canvas/open-image-canvas.model'; import { AssistantModel } from '@/components/assistant/model'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; import { FeedbackModel } from '@/components/feedback/model'; +import { OpenImageCanvasModel } from '@/components/image-canvas/open-image-canvas.model'; import { SettingsModel } from '@/components/settings/settings.model'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { FlowModel } from '@/stores/app/models/flow.model'; diff --git a/web/apps/web/src/components/app/config-form/widget-config/index.tsx b/web/apps/web/src/components/app/config-form/widget-config/index.tsx index 7e9e5c8a..f318b049 100644 --- a/web/apps/web/src/components/app/config-form/widget-config/index.tsx +++ b/web/apps/web/src/components/app/config-form/widget-config/index.tsx @@ -2,26 +2,19 @@ 'use client'; -import { - getDefaultValueBySchema, - TValues, - ISchema, -} from '@shellagent/form-engine'; -import { useInjection } from 'inversify-react'; +import { getDefaultValueBySchema, TValues } from '@shellagent/form-engine'; import { isEmpty, merge } from 'lodash-es'; import { useCallback, useEffect, useMemo } from 'react'; import NodeForm from '@/components/app/node-form'; import { getPlugin } from '@/components/app/plugins'; -import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { getSchemaByWidget } from '@/stores/app/schema/get-widget-schema'; +import { useWorkflowStore } from '@/stores/workflow/workflow-provider'; export interface WidgetConfigProps { values: TValues | undefined; parent: string; onChange: (values: TValues) => void; - defaultValues?: TValues; - schema?: ISchema; } export interface CommonWidgetConfigProps extends WidgetConfigProps {} @@ -44,23 +37,56 @@ const StandardWidgetConfig: React.FC = ({ values, parent, onChange, - schema, - defaultValues, }) => { - // const appBuilder = useInjection('AppBuilderModel'); - // const key = [values?.widget_class_name, values?.widget_name].join('_'); - // const schema = useMemo(() => { - // return getSchemaByWidget({ - // ...appBuilder.widgetSchema.get(key), - // }); - // }, [appBuilder.widgetSchema, values?.widget_class_name, values?.widget_name]); - - // const defaultValues = useMemo( - // () => getDefaultValueBySchema(schema, false), - // [schema], - // ); - - // TODO 拆到model里 + const { loading, getWidgetSchema, widgetSchema } = useWorkflowStore( + state => ({ + loading: state.loading.getWidgetSchema, + getWidgetSchema: state.getWidgetSchema, + widgetSchema: state.widgetSchema, + }), + ); + + useEffect(() => { + if ( + values?.widget_class_name != null && + values?.widget_name != null && + !widgetSchema[[values.widget_class_name, values.widget_name].join('_')] + ) { + getWidgetSchema({ + widget_name: values.widget_class_name, + myshell_widget_name: values.widget_name, + }); + } + }, [ + values?.widget_class_name, + values?.widget_name, + getWidgetSchema, + widgetSchema, + ]); + + const schema = useMemo(() => { + const schema = getSchemaByWidget({ + ...widgetSchema[ + [values?.widget_class_name, values?.widget_name].join('_') + ], + }); + // Special process ImageCanvasWidget + if (values?.widget_class_name === 'ImageCanvasWidget') { + if (schema?.properties?.inputs?.properties?.config) { + schema.properties.inputs.properties.config['x-component'] = + 'OpenImageCanvas'; + schema.properties.inputs.properties.config['x-raw-default'] = 'ui'; + schema.properties.inputs.properties.config['x-component-props'] = {}; + } + } + return schema; + }, [widgetSchema, values?.widget_class_name]); + + const defaultValues = useMemo( + () => getDefaultValueBySchema(schema, false), + [schema], + ); + const handleOnChange = useCallback( (newValues: TValues) => { onChange(merge({}, defaultValues, newValues)); @@ -70,7 +96,7 @@ const StandardWidgetConfig: React.FC = ({ useEffect(() => { if ( - // !appBuilder.getWidgetSchemaLoading?.[key] && + !loading?.[values?.widget_class_name] && isEmpty(values?.inputs) && !isEmpty(defaultValues) && values @@ -82,28 +108,29 @@ const StandardWidgetConfig: React.FC = ({ } }, [ schema, - // appBuilder.getWidgetSchemaLoading[key], + loading?.[values?.widget_class_name], defaultValues, onChange, values, ]); + if (!values) { + return null; + } + return ( ); }; export const WidgetConfig: React.FC = props => { - if ( - props.values?.widget_name === 'ImageCanvasWidget' || - props.values?.widget_name === 'ComfyUIWidget' - ) { + if (props.values?.custom) { return ; } diff --git a/web/apps/web/src/components/app/node-form/index.tsx b/web/apps/web/src/components/app/node-form/index.tsx index 7ead3205..03817db5 100755 --- a/web/apps/web/src/components/app/node-form/index.tsx +++ b/web/apps/web/src/components/app/node-form/index.tsx @@ -50,6 +50,7 @@ import { TimerSelector, ContextSelector, } from './widgets'; +import { OpenImageCanvas } from '../../image-canvas/open-image-canvas'; interface NodeFormProps { values: TValues; @@ -119,6 +120,7 @@ const NodeForm = forwardRef( WorkflowSelect, TransitionConditionEditor, VariableNameInput, + OpenImageCanvas, ValidateRender, ButtonValidateRender, DialogValidateRender, diff --git a/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx b/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx index 0421a0d0..2aafba51 100755 --- a/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx +++ b/web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx @@ -1,36 +1,26 @@ import { PlusIcon } from '@heroicons/react/24/outline'; import { XMarkIcon } from '@heroicons/react/24/solid'; import { WidgetItem, NodeTypeEnum } from '@shellagent/flow-engine'; -import { Task, TaskSchema, WidgetTask } from '@shellagent/shared/protocol/task'; +import { Task, TaskSchema } from '@shellagent/shared/protocol/task'; import { customSnakeCase, getTaskDisplayName } from '@shellagent/shared/utils'; -import { Button, useFormContext, Drag, Drawer } from '@shellagent/ui'; -import { TValues, getDefaultValueBySchema } from '@shellagent/form-engine'; +import { Button, useFormContext, Drag } from '@shellagent/ui'; import { useClickAway } from 'ahooks'; -import { isNumber } from 'lodash-es'; import { Dropdown } from 'antd'; import { useInjection } from 'inversify-react'; -import { useEffect, useState, useRef, useCallback, useMemo } from 'react'; +import { useState, useRef, useCallback } from 'react'; import { useDrag, useDrop } from 'react-dnd'; -import { getNewKey } from '@shellagent/shared/utils'; -import { WidgetConfig } from '@/components/app/config-form/widget-config'; -import { EditableTitle } from '@/components/app/editable-title'; import { TaskList } from '@/components/app/task-list'; import { AppBuilderModel } from '@/stores/app/models/app-builder.model'; import { useAppState } from '@/stores/app/use-app-state'; -import { getSchemaByWidget } from '@/stores/app/schema/get-widget-schema'; const TaskItem = ({ name, - widgetName, - widgetClassName, onDelete, onClick, index, moveTask, - draggable, - task, - showDrawer = true, + draggable, // 新增参数 }: { name: string; onDelete: () => void; @@ -41,79 +31,11 @@ const TaskItem = ({ hoverIndex: number, isDragging: boolean, ) => void; - draggable?: boolean; - widgetName?: string; - widgetClassName?: string; - task: Task; - showDrawer?: boolean; + draggable?: boolean; // 新增参数类型 }) => { const dragRef = useRef(null); const previewRef = useRef(null); - const appBuilder = useInjection('AppBuilderModel'); - const { getValues, setValue } = useFormContext(); - const { - currentStateId, - setInsideSheetOpen, - insideSheetOpen, - currentTaskName, - } = useAppState(state => state); - - const handleTitleChange = useCallback( - (value: string) => { - const { key: newKey, name: newName } = getNewKey({ - name: value, - nameKey: 'name', - values: appBuilder.nodeData[currentStateId]?.blocks, - prefix: 'Blocks', - }); - const path = `blocks.${index}`; - const values = getValues(path); - setValue(path, { - ...values, - display_name: newName, - name: newKey, - }); - }, - [index, currentStateId, getValues, setValue, appBuilder.nodeData], - ); - const handleFormChange = useCallback( - (value: TValues) => { - const path = `blocks.${index}`; - setValue(path, value); - }, - [index, setValue], - ); - - const key = `${widgetClassName}_${widgetName}`; - - // 预请求schema - useEffect(() => { - if ( - widgetClassName != null && - widgetName != null && - !appBuilder.widgetSchema.get(key) - ) { - appBuilder.getWidgetSchema({ - widget_name: widgetClassName, - myshell_widget_name: widgetName, - }); - } - }, [widgetClassName, widgetName, appBuilder.getWidgetSchema]); - - const schema = useMemo(() => { - return getSchemaByWidget({ - ...appBuilder.widgetSchema.get(key), - }); - }, [appBuilder.widgetSchema, key]); - - const defaultValues = useMemo( - () => getDefaultValueBySchema(schema, false), - [schema], - ); - // TODO default回填逻辑,拆到model里 - - console.log('schema, defaultValues', schema, defaultValues); const [{ isDragging }, drag, preview] = useDrag({ type: 'TASK', item: { index }, @@ -159,70 +81,36 @@ const TaskItem = ({ } return ( - <> -
{ + e.stopPropagation(); + onClick(e); + }} + className={`relative group h-8 flex items-center bg-surface-container-default rounded-lg p-2 text-default font-medium cursor-pointer ${ + isDragging ? 'opacity-50' : '' + }`}> + {draggable && ( +
+ +
+ )} + {name} + { + e.preventDefault(); e.stopPropagation(); - onClick(e); + onDelete(); }} - className={`relative group h-8 flex items-center bg-surface-container-default rounded-lg p-2 text-default font-medium cursor-pointer ${ - isDragging ? 'opacity-50' : '' - }`}> - {draggable && ( -
- -
- )} - {name} - { - e.preventDefault(); - e.stopPropagation(); - onDelete(); - }} - /> -
-
- {showDrawer && ( - - setInsideSheetOpen({ - stateId: currentStateId, - open: false, - }) - } - autoFocus={false} - title={ - - }> - - - )} - + /> +
+
); }; @@ -230,12 +118,10 @@ const TasksConfig = ({ name, onChange, draggable, - showDrawer = true, }: { name: string; onChange: (value: Task[]) => void; draggable?: boolean; - showDrawer?: boolean; }) => { const appBuilder = useInjection('AppBuilderModel'); const btnRef = useRef(null); @@ -318,15 +204,11 @@ const TasksConfig = ({ handleItemDelete(idx)} onClick={() => handleItemClick(idx)} index={idx} moveTask={moveTask} - draggable={draggable} - task={task} - showDrawer={showDrawer} + draggable={draggable} // 传递draggable参数 /> ))}
@@ -335,7 +217,7 @@ const TasksConfig = ({ placement="bottomRight" trigger={['click']} overlayClassName="shadow-modal-default" - overlayStyle={{ borderRadius: 12, overflow: 'hidden', zIndex: 999 }} + overlayStyle={{ borderRadius: 12, overflow: 'hidden' }} getPopupContainer={() => btnRef.current || document.body} open={open} overlay={ diff --git a/web/apps/web/src/components/app/plugins/image-canvas/schema.ts b/web/apps/web/src/components/app/plugins/image-canvas/schema.ts deleted file mode 100644 index 60ca7ddc..00000000 --- a/web/apps/web/src/components/app/plugins/image-canvas/schema.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ISchema } from '@shellagent/form-engine'; - -export const defaultSchema: ISchema = { - type: 'object', - 'x-type': 'Section', - 'x-title-size': 'h4', - properties: { - inputs: { - properties: { - config: { - title: 'config', - type: 'string', - 'x-field-type': 'string', - 'x-title-size': 'h4', - 'x-type': 'Control', - 'x-layout': 'Horizontal', - 'x-component': 'OpenImageCanvas', - 'x-raw': true, - 'x-component-props': {}, - 'x-validator': [{}], - 'x-raw-default': 'ui', - }, - }, - required: ['config'], - title: 'Input', - type: 'object', - 'x-type': 'Block', - 'x-title-size': 'h4', - 'x-collapsible': true, - }, - outputs: { - type: 'object', - title: 'Output', - 'x-type': 'Block', - 'x-title-size': 'h4', - 'x-collapsible': true, - properties: { - display: { - type: 'string', - default: { - image: 'string', - }, - 'x-type': 'Control', - 'x-component': 'JSONView', - 'x-component-props': { - hiddenName: true, - }, - }, - }, - }, - }, -}; diff --git a/web/apps/web/src/components/app/plugins/index.tsx b/web/apps/web/src/components/app/plugins/index.tsx index 6e150764..3be1becf 100644 --- a/web/apps/web/src/components/app/plugins/index.tsx +++ b/web/apps/web/src/components/app/plugins/index.tsx @@ -3,7 +3,6 @@ import React from 'react'; import { CommonWidgetConfigProps } from '@/components/app/config-form/widget-config'; import { ComfyUIPlugin } from './comfyui'; -import { OpenImageCanvasPlugin } from './image-canvas/open-image-canvas'; const pluginMap = new Map>(); @@ -21,6 +20,5 @@ const getPlugin = (name: string): React.FC => { }; registerPlugin('ComfyUIWidget', ComfyUIPlugin); -registerPlugin('ImageCanvasWidget', OpenImageCanvasPlugin); export { registerPlugin, getPlugin, pluginMap }; diff --git a/web/apps/web/src/components/app/state-config-sheet/index.tsx b/web/apps/web/src/components/app/state-config-sheet/index.tsx index 70e2bf62..beba0393 100755 --- a/web/apps/web/src/components/app/state-config-sheet/index.tsx +++ b/web/apps/web/src/components/app/state-config-sheet/index.tsx @@ -7,14 +7,17 @@ import { } from '@shellagent/flow-engine'; import { TValues } from '@shellagent/form-engine'; import { Button as IButtonType } from '@shellagent/shared/protocol/render-button'; -import { WidgetTask } from '@shellagent/shared/protocol/task'; +import { WidgetTask, WorkflowTask } from '@shellagent/shared/protocol/task'; import { getNewKey } from '@shellagent/shared/utils'; import { Drawer, FormRef } from '@shellagent/ui'; import clsx from 'clsx'; import { useInjection } from 'inversify-react'; +import { isNumber } from 'lodash-es'; import { useMemo, useRef, useCallback, useState } from 'react'; import { ButtonConfig } from '@/components/app/config-form/button-config'; +import { WidgetConfig } from '@/components/app/config-form/widget-config'; +import { WorkflowConfig } from '@/components/app/config-form/workflow-config'; import { EditableTitle } from '@/components/app/editable-title'; import NodeForm from '@/components/app/node-form'; import { AppBuilderChatModel } from '@/components/chat/app-builder-chat.model'; @@ -37,6 +40,7 @@ const StateConfigSheet: React.FC<{}> = () => { currentTaskName, currentButtonId, insideSheetOpen, + setInsideSheetOpen, insideSheetMode, runDrawerWidth, selectedNode, @@ -47,6 +51,7 @@ const StateConfigSheet: React.FC<{}> = () => { currentTaskName: state.currentTaskName, currentButtonId: state.currentButtonId, insideSheetOpen: state.insideSheetOpen, + setInsideSheetOpen: state.setInsideSheetOpen, insideSheetMode: state.insideSheetMode, runDrawerWidth: state.runDrawerWidth, selectedNode: state.selectedNode, @@ -114,6 +119,52 @@ const StateConfigSheet: React.FC<{}> = () => { title: 'Edit Button', }; } + if (insideSheetMode === 'workflow') { + const workflow: WorkflowTask = isNumber(currentTaskIndex) + ? appBuilder.nodeData[currentStateId]?.blocks?.[currentTaskIndex] + : {}; + + return { + children: ( + + ), + title: ( + + ), + }; + } + if (insideSheetMode === 'widget') { + const widget: WidgetTask = isNumber(currentTaskIndex) + ? appBuilder.nodeData[currentStateId]?.blocks?.[currentTaskIndex] + : {}; + + return { + children: ( + + ), + title: ( + + ), + }; + } return {}; }, [ insideSheetMode, @@ -221,6 +272,20 @@ const StateConfigSheet: React.FC<{}> = () => { onChange={onChange} ref={nodeFormRef} /> + + setInsideSheetOpen({ stateId: currentStateId, open: false }) + } + autoFocus={false} + {...drawerProps}> + {drawerProps.children} + ); diff --git a/web/apps/web/src/components/app/plugins/image-canvas/open-image-canvas.model.ts b/web/apps/web/src/components/image-canvas/open-image-canvas.model.ts similarity index 100% rename from web/apps/web/src/components/app/plugins/image-canvas/open-image-canvas.model.ts rename to web/apps/web/src/components/image-canvas/open-image-canvas.model.ts diff --git a/web/apps/web/src/components/app/plugins/image-canvas/open-image-canvas.tsx b/web/apps/web/src/components/image-canvas/open-image-canvas.tsx similarity index 67% rename from web/apps/web/src/components/app/plugins/image-canvas/open-image-canvas.tsx rename to web/apps/web/src/components/image-canvas/open-image-canvas.tsx index 7363fab2..ed968a0f 100644 --- a/web/apps/web/src/components/app/plugins/image-canvas/open-image-canvas.tsx +++ b/web/apps/web/src/components/image-canvas/open-image-canvas.tsx @@ -7,21 +7,15 @@ import 'image-canvas/assets/react-colors-beauty.css'; // import 'image-canvas/fabric.js'; import { css } from '@emotion/react'; import { PhotoIcon } from '@heroicons/react/24/outline'; -import { getDefaultValueBySchema, TValues } from '@shellagent/form-engine'; import { AButton, Button, useFormContext } from '@shellagent/ui'; import { Modal, theme } from 'antd'; import { useInjection } from 'inversify-react'; -import { merge } from 'lodash-es'; import { observer } from 'mobx-react-lite'; import dynamic from 'next/dynamic'; -import { useMemo, useCallback, useEffect } from 'react'; +import { useEffect } from 'react'; -import { CommonWidgetConfigProps } from '@/components/app/config-form/widget-config'; -import NodeForm from '@/components/app/node-form'; - -import { OpenImageCanvasModel } from '@/components/app/plugins/image-canvas/open-image-canvas.model'; import { useSelectOptions } from '@/components/app/node-form/widgets/variable-select/use-select-options'; -import { defaultSchema } from './schema'; +import { OpenImageCanvasModel } from '@/components/image-canvas/open-image-canvas.model'; const ImageCanvas = dynamic( () => import('image-canvas').then(module => module.ImageCanvas), @@ -103,37 +97,3 @@ export const ImageCanvasDialog = observer(() => { ); }); - -export const OpenImageCanvasPlugin = observer( - ({ values, onChange, parent }) => { - const defaultValues = useMemo( - () => getDefaultValueBySchema(defaultSchema, false), - [defaultSchema], - ); - - const handleOnChange = useCallback( - (newValues: TValues) => { - onChange(merge({}, defaultValues, newValues)); - }, - [defaultValues, onChange], - ); - useEffect(() => { - onChange({ - ...values, - outputs: defaultValues.outputs, - }); - }, [values]); - - return ( - - ); - }, -); diff --git a/web/apps/web/src/services/app/index.tsx b/web/apps/web/src/services/app/index.tsx index 12cd58ee..26994ad9 100755 --- a/web/apps/web/src/services/app/index.tsx +++ b/web/apps/web/src/services/app/index.tsx @@ -34,8 +34,6 @@ import { CheckIpResponse, GetIntroDisplayRequest, GetIntroDisplayResponse, - GetWidgetSchemaRequest, - GetWidgetSchemaResponse, } from './type'; import { APIFetch, baseHeaders } from '../base'; @@ -315,16 +313,3 @@ export const getMyShellWidgetList: Fetcher< params, }); }; - -// 获取widget form schema -export const fetchWidgetSchema: Fetcher< - GetWidgetSchemaResponse, - GetWidgetSchemaRequest -> = params => { - return APIFetch.post( - '/api/workflow/get_widget_schema', - { - body: params, - }, - ); -}; diff --git a/web/apps/web/src/services/app/type.ts b/web/apps/web/src/services/app/type.ts index 01b671a1..defed13a 100644 --- a/web/apps/web/src/services/app/type.ts +++ b/web/apps/web/src/services/app/type.ts @@ -1,6 +1,5 @@ import { IFlow } from '@shellagent/flow-engine'; import { Automata } from '@shellagent/pro-config'; -import { JsonSchema7 } from 'node_modules/@shellagent/form-engine/src/types/jsonSchema7'; import { Config, Metadata } from '@/types/app/types'; @@ -237,16 +236,3 @@ export type GetIntroDisplayResponse = { text: string; images: string[]; }; - -// 获取widget form schema -// /api/workflow/get_widget_schema -export type GetWidgetSchemaRequest = { - widget_name: string; - myshell_widget_name: string; -}; - -export type GetWidgetSchemaResponse = { - input_schema: JsonSchema7 | Record; - output_schema: JsonSchema7; - multi_input_schema: boolean; -}; diff --git a/web/apps/web/src/stores/app/models/app-builder.model.ts b/web/apps/web/src/stores/app/models/app-builder.model.ts index 4b0fa39a..e28ade89 100755 --- a/web/apps/web/src/stores/app/models/app-builder.model.ts +++ b/web/apps/web/src/stores/app/models/app-builder.model.ts @@ -52,14 +52,11 @@ import { getMyShellWidgetList, releaseApp, saveApp, - fetchWidgetSchema, } from '@/services/app'; import type { GetAppFlowRequest, GetAppVersionListResponse, GetAutomataRequest, - GetWidgetSchemaResponse, - GetWidgetSchemaRequest, } from '@/services/app/type'; import { editItem, fetchList as fetchFlowList } from '@/services/home'; import type { GetListRequest, GetListResponse } from '@/services/home/type'; @@ -166,9 +163,6 @@ export class AppBuilderModel { widget_list: [], }; - @observable widgetSchema: Map = new Map(); - @observable getWidgetSchemaLoading: Record = {}; - @computed get materialList() { const backendLocalWidgets: MaterialListType = this.rawLocalWidgetList.widget_list.map(m => { @@ -178,6 +172,14 @@ export class AppBuilderModel { items: m.items.reduce((acc, i) => { acc = acc.concat( i.children.map(c => { + if (c.name === 'ComfyUIWidget') { + return { + ...c, + type: NodeTypeEnum.widget, + undraggable: true, + custom: true, + }; + } return { ...c, type: NodeTypeEnum.widget, @@ -879,21 +881,4 @@ export class AppBuilderModel { }, }); } - - @action.bound - async getWidgetSchema(params: GetWidgetSchemaRequest) { - const key = `${params.widget_name}_${params.myshell_widget_name}`; - this.getWidgetSchemaLoading[key] = true; - try { - const res = await fetchWidgetSchema(params); - this.widgetSchema.set(key, res); - } catch (error) { - this.emitter.emitter.emit( - 'message.error', - `Get ${params.widget_name} Error`, - ); - } finally { - this.getWidgetSchemaLoading[key] = false; - } - } } diff --git a/web/apps/web/src/stores/app/models/flow.model.ts b/web/apps/web/src/stores/app/models/flow.model.ts index f4b25bd1..bcab4d3a 100644 --- a/web/apps/web/src/stores/app/models/flow.model.ts +++ b/web/apps/web/src/stores/app/models/flow.model.ts @@ -5,7 +5,7 @@ import { makeAutoObservable } from 'mobx'; import { CustomEdgeData } from '@/components/app/edges'; -type SheetMode = 'button' | 'widget' | ''; +type SheetMode = 'button' | 'workflow' | 'widget' | ''; interface BaseState { currentStateId: string; @@ -26,7 +26,7 @@ export class FlowModel { currentStateId: string = ''; stateConfigSheetOpen: boolean = false; insideSheetOpen: boolean = false; - insideSheetMode: 'button' | 'widget' | '' = ''; + insideSheetMode: 'button' | 'workflow' | 'widget' | '' = ''; transitionSheetOpen: boolean = false; runDrawerWidth: number = 715; selectedNode: Node | undefined = undefined; diff --git a/web/apps/web/src/stores/app/schema-provider.tsx b/web/apps/web/src/stores/app/schema-provider.tsx index 9bd44e19..279bf177 100644 --- a/web/apps/web/src/stores/app/schema-provider.tsx +++ b/web/apps/web/src/stores/app/schema-provider.tsx @@ -1,10 +1,14 @@ import { NodeTypeEnum } from '@shellagent/flow-engine'; import { ISchema, TFieldMode } from '@shellagent/form-engine'; -import { useMemo, useState } from 'react'; +import { isEmpty } from 'lodash-es'; +import { useMemo, useEffect, useState } from 'react'; import { useContextSelector, createContext } from 'use-context-selector'; +import { JsonSchema7 } from '@/services/workflow/type'; import { getIntroSchema } from '@/stores/app/schema/get-intro-schema'; import { getStateSchema } from '@/stores/app/schema/get-state-schema'; +import { getSchemaByWidget } from '@/stores/app/schema/get-widget-schema'; +import { useWorkflowStore } from '@/stores/workflow/workflow-provider'; import { generateUUID } from '@/utils/common-helper'; import { getConditionStateSchema } from './schema/condition-state-schema'; @@ -20,6 +24,7 @@ type SchemaState = { displayName: string; fieldMode: Record; outputs?: Record; + outputSchema: JsonSchema7; formKey: string; type?: string; }; @@ -37,6 +42,7 @@ export const initState: SchemaStore = { schema: {}, fieldMode: {}, setFieldMode: () => {}, + outputSchema: {}, formKey: generateUUID(), type: NodeTypeEnum.state, }; @@ -64,6 +70,7 @@ export const SchemaProvider: React.FC = ({ children, type, }) => { + // const context = useVariableContext(state => state.context); const [fieldMode = {}, setFieldModeStorage] = useState< Record >({}); @@ -75,6 +82,17 @@ export const SchemaProvider: React.FC = ({ })); }; + const { widgetSchema, getWidgetSchema } = useWorkflowStore(state => ({ + widgetSchema: state.widgetSchema, + getWidgetSchema: state.getWidgetSchema, + })); + + useEffect(() => { + if (name && !widgetSchema[name] && type === NodeTypeEnum.widget) { + getWidgetSchema({ widget_name: name, myshell_widget_name: '' }); + } + }, [name]); + const formKey = useMemo(() => { return `${JSON.stringify({ display_name, @@ -113,8 +131,20 @@ export const SchemaProvider: React.FC = ({ if (type === NodeTypeEnum.intro) { return getIntroSchema(display_name); } - return {}; - }, [id, name, display_name, type]); + + if (!name || !display_name || isEmpty(widgetSchema?.[name])) { + return {}; + } + + return getSchemaByWidget({ + input_schema: widgetSchema?.[name]?.input_schema, + output_schema: widgetSchema?.[name]?.output_schema, + }); + }, [id, name, display_name, widgetSchema, type]); + const outputSchema = useMemo( + () => widgetSchema?.[name]?.output_schema, + [widgetSchema, name], + ); const schemaValue = useMemo( () => ({ @@ -124,10 +154,21 @@ export const SchemaProvider: React.FC = ({ schema, fieldMode, setFieldMode, + outputSchema, formKey, type, }), - [id, name, display_name, schema, fieldMode, setFieldMode, formKey, type], + [ + id, + name, + display_name, + schema, + fieldMode, + setFieldMode, + outputSchema, + formKey, + type, + ], ); return ( diff --git a/web/apps/web/src/stores/app/schema/get-state-schema.ts b/web/apps/web/src/stores/app/schema/get-state-schema.ts index 0cccf3b7..1a1cad72 100644 --- a/web/apps/web/src/stores/app/schema/get-state-schema.ts +++ b/web/apps/web/src/stores/app/schema/get-state-schema.ts @@ -20,7 +20,6 @@ export const getStateSchema = (name: string): ISchema => { 'x-component': 'TasksConfig', 'x-component-props': { draggable: false, - showDrawer: false, }, }, transition: { diff --git a/web/apps/web/src/stores/app/use-app-state.tsx b/web/apps/web/src/stores/app/use-app-state.tsx index dc6cda29..2d9542c7 100755 --- a/web/apps/web/src/stores/app/use-app-state.tsx +++ b/web/apps/web/src/stores/app/use-app-state.tsx @@ -11,7 +11,7 @@ export type State = { currentStateId: string; stateConfigSheetOpen: boolean; insideSheetOpen: boolean; - insideSheetMode: 'button' | 'widget' | ''; + insideSheetMode: 'button' | 'workflow' | 'widget' | ''; transitionSheetOpen: boolean; newTransitionSheetOpen: boolean; runDrawerWidth: number; @@ -55,7 +55,6 @@ type Action = { setTargetInputsSheetOpen: (open: State['targetInputsSheetOpen']) => void; setRunDrawerWidth: (width: number) => void; resetState: () => void; - setCurrentTaskName: (name: string) => void; }; const initialState: State = { @@ -219,6 +218,4 @@ export const useAppState = create(set => ({ ...state, targetInputsSheetOpen: open, })), - setCurrentTaskName: (name: string) => - set(state => ({ ...state, currentTaskName: name })), })); From c849146c82365fe1adec59bba67f193566b98518 Mon Sep 17 00:00:00 2001 From: Chengwei Ouyang Date: Tue, 4 Mar 2025 14:08:21 +0800 Subject: [PATCH 13/17] update engine interface for custom widgets --- proconfig/runners/runner.py | 11 +++ proconfig/widgets/custom_widgets/__init__.py | 1 + .../custom_widgets/custom_widget_caller.py | 73 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 proconfig/widgets/custom_widgets/__init__.py create mode 100644 proconfig/widgets/custom_widgets/custom_widget_caller.py diff --git a/proconfig/runners/runner.py b/proconfig/runners/runner.py index 378054f9..e0c30371 100755 --- a/proconfig/runners/runner.py +++ b/proconfig/runners/runner.py @@ -14,6 +14,7 @@ from proconfig.utils.pytree import tree_map import proconfig.utils.pytree as pytree +from proconfig.widgets.base import WIDGETS from proconfig.utils.expressions import calc_expression from proconfig.utils.misc import hash_dict from proconfig.core import Automata, Workflow, State @@ -253,6 +254,14 @@ def process_myshell_extra_inputs(self, task): "inputs": copy.deepcopy(task.inputs) } task.inputs["myshell_actual_inputs"] = myshell_actual_inputs + + def process_custom_widget_extra_inputs(self, task): + custom_widget_actual_inputs = { + "widget_name": task.widget_class_name, + "inputs": copy.deepcopy(task.inputs) + } + task.widget_class_name = "CustomAnyWidgetCallerWidget" + task.inputs["custom_widget_actual_inputs"] = custom_widget_actual_inputs def run_task(self, container, task, environ, local_vars): @@ -262,6 +271,8 @@ def run_task(self, container, task, environ, local_vars): self.process_comfy_extra_inputs(task) elif task.widget_class_name == "MyShellAnyWidgetCallerWidget": self.process_myshell_extra_inputs(task) + elif not WIDGETS.get(task.widget_class_name): + self.process_custom_widget_extra_inputs(task) if task.mode in ["widget", "comfy_workflow"]: return self.run_widget_task(container, task, environ, local_vars) diff --git a/proconfig/widgets/custom_widgets/__init__.py b/proconfig/widgets/custom_widgets/__init__.py new file mode 100644 index 00000000..1398717b --- /dev/null +++ b/proconfig/widgets/custom_widgets/__init__.py @@ -0,0 +1 @@ +from proconfig.widgets.custom_widgets.custom_widget_caller import CustomAnyWidgetCallerWidget \ No newline at end of file diff --git a/proconfig/widgets/custom_widgets/custom_widget_caller.py b/proconfig/widgets/custom_widgets/custom_widget_caller.py new file mode 100644 index 00000000..7acea249 --- /dev/null +++ b/proconfig/widgets/custom_widgets/custom_widget_caller.py @@ -0,0 +1,73 @@ +from typing import Any, Literal, Optional, List +from pydantic import Field, BaseModel + +from proconfig.widgets.base import BaseWidget, WIDGETS +from proconfig.core.exception import ShellException + +# import instructor +import os +from pydantic import BaseModel, Field +import requests +import json + +@WIDGETS.register_module() +class CustomAnyWidgetCallerWidget(BaseWidget): + NAME = "CustomAnyWidgetCallerWidget" + dynamic_schema = True + + class InputsSchema(BaseWidget.InputsSchema): + widget_name: str = Field(..., description="the widget name to call") + inputs: dict = {} + + class OutputsSchema(BaseWidget.OutputsSchema): # useless + data: str | list + + def execute(self, environ, config_ori): + # API endpoint URL + url = "https://openapi.myshell.ai/public/v1/custom_widget/run" + config = config_ori["custom_widget_actual_inputs"] + widget_name = config["widget_name"] + + # Headers for the API request + headers = { + "x-myshell-openapi-key": os.environ["MYSHELL_API_KEY"], + "Content-Type": "application/json", + **environ.get("MYSHELL_HEADERS", {}) + } + + # Request payload + data = { + "widget_name": widget_name, + "input": json.dumps(config["inputs"]) + } + + del config_ori["custom_widget_actual_inputs"] + + # print("widget inputs:", config["inputs"]) + + # Send POST request to the API + response = requests.post(url, headers=headers, json=data) + + # Parse the JSON response + json_response = response.json() + + # Extract the 'result' field and return it as a string + if json_response.get('success') and 'result' in json_response: + result = json.loads(json_response['result']) + # handle the _url + if "_url" in result: + response = requests.get(result["_url"]) + if response.status_code == 200: + return response.json() + else: + return {"error_message": "error when retrieve the result"} + return result + else: + error = { + 'error_code': 'SHELL-1102', + 'error_head': 'Widget Execution Error', + 'msg': f'widget {widget_name} failed to execute', + 'traceback': json.dumps(json_response), + } + exception = ShellException(**error) + raise exception From 5e547f645e733f4564a306e46fcff547d323b322 Mon Sep 17 00:00:00 2001 From: Chengwei Ouyang Date: Tue, 4 Mar 2025 14:14:18 +0800 Subject: [PATCH 14/17] update test/stable environ for custom widgets --- proconfig/widgets/custom_widgets/custom_widget_caller.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proconfig/widgets/custom_widgets/custom_widget_caller.py b/proconfig/widgets/custom_widgets/custom_widget_caller.py index 7acea249..c3521a27 100644 --- a/proconfig/widgets/custom_widgets/custom_widget_caller.py +++ b/proconfig/widgets/custom_widgets/custom_widget_caller.py @@ -25,6 +25,8 @@ class OutputsSchema(BaseWidget.OutputsSchema): # useless def execute(self, environ, config_ori): # API endpoint URL url = "https://openapi.myshell.ai/public/v1/custom_widget/run" + if "MYSHELL_DEPLOY" in os.environ and os.environ["MYSHELL_DEPLOY"] == "TEST": + url = "https://openapi-test.myshell.fun/public/v1/custom_widget/run" config = config_ori["custom_widget_actual_inputs"] widget_name = config["widget_name"] From 6f2d98dc34642818b5b4283bf32d19ab65c7c70b Mon Sep 17 00:00:00 2001 From: shanexi Date: Mon, 24 Feb 2025 16:16:55 +0800 Subject: [PATCH 15/17] update --- proconfig/widgets/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proconfig/widgets/__init__.py b/proconfig/widgets/__init__.py index e2536776..f49af6e9 100644 --- a/proconfig/widgets/__init__.py +++ b/proconfig/widgets/__init__.py @@ -18,6 +18,9 @@ def load_custom_widgets(): try: with open("custom_widget_info.json", "r") as f: widget_info = json.load(f) + # Remove custom_widget_demo if it exists + if "custom_widget_demo" in widget_info: + del widget_info["custom_widget_demo"] except FileNotFoundError: widget_info = {} From 4beede2f35d3b4084effcb52cfe2150d82858997 Mon Sep 17 00:00:00 2001 From: Chengwei Ouyang Date: Thu, 6 Mar 2025 14:06:10 +0800 Subject: [PATCH 16/17] update import --- proconfig/widgets/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/proconfig/widgets/__init__.py b/proconfig/widgets/__init__.py index f49af6e9..f14144bb 100644 --- a/proconfig/widgets/__init__.py +++ b/proconfig/widgets/__init__.py @@ -10,6 +10,7 @@ import proconfig.widgets.language_models import proconfig.widgets.tools import proconfig.widgets.myshell_widgets +import proconfig.widgets.custom_widgets # load custom widgets import os From 3a0e4c34e2dcdfeb727aa93336aa79ecb5cd25b9 Mon Sep 17 00:00:00 2001 From: shanexi Date: Mon, 10 Mar 2025 09:59:18 +0800 Subject: [PATCH 17/17] fix: export data structure --- web/apps/web/src/stores/app/utils/automata-export.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/apps/web/src/stores/app/utils/automata-export.ts b/web/apps/web/src/stores/app/utils/automata-export.ts index 6259d63a..5d804004 100644 --- a/web/apps/web/src/stores/app/utils/automata-export.ts +++ b/web/apps/web/src/stores/app/utils/automata-export.ts @@ -31,15 +31,15 @@ export const checkDependency = (data: ExportBotResponse['data']) => { widgets: {}, }; Object.entries( - data.dependency.models || data.dependencies.models || {}, + data.dependency?.models || data.dependencies?.models || {}, ).forEach(([key, item]) => { if (isEmpty(item.urls)) { set(deps, ['models', key], item.filename); } }); Object.entries( - data.dependency.widgets || - data.dependencies.custom_widgets.reduce<{ + data.dependency?.widgets || + data.dependencies?.custom_widgets.reduce<{ [key: string]: { git: string; commit: string;