From 191858b602774d30151aa9b7757553909f47e325 Mon Sep 17 00:00:00 2001 From: "weirui.kwr@alibaba-inc.com" Date: Sat, 17 Aug 2024 16:24:31 +0800 Subject: [PATCH 01/47] update --- .../web/workstation/workflow_dag.py | 7 +--- .../web/workstation/workflow_node.py | 17 ++++++++ .../web/workstation/workflow_utils.py | 40 +++++++++++++++---- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/agentscope/web/workstation/workflow_dag.py b/src/agentscope/web/workstation/workflow_dag.py index 242a8b36c..c5093467b 100644 --- a/src/agentscope/web/workstation/workflow_dag.py +++ b/src/agentscope/web/workstation/workflow_dag.py @@ -16,10 +16,7 @@ WorkflowNodeType, DEFAULT_FLOW_VAR, ) -from agentscope.web.workstation.workflow_utils import ( - is_callable_expression, - kwarg_converter, -) +from agentscope.web.workstation.workflow_utils import kwarg_converter try: import networkx as nx @@ -286,8 +283,6 @@ def sanitize_node_data(raw_info: dict) -> dict: if value == "": raw_info["data"]["args"].pop(key) raw_info["data"]["source"].pop(key) - elif is_callable_expression(value): - raw_info["data"]["args"][key] = eval(value) return raw_info diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 827905a22..8806fda46 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -73,6 +73,8 @@ def __init__( Initialize nodes. Implement specific initialization logic in subclasses. """ + self._has_init = False + self.node_id = node_id self.opt_kwargs = opt_kwargs self.source_kwargs = source_kwargs @@ -81,6 +83,21 @@ def __init__( self.var_name = f"{self.node_type.name.lower()}_{self.node_id}" def __call__(self, x: dict = None): # type: ignore[no-untyped-def] + """ + Invokes the node's operations with the provided input. + + This method is designed to be called as a function. It delegates the + actual execution of the node's logic to the _execute method. + Subclasses should implement their specific logic in the + `_execute` method. + """ + # if not self._has_init: + # for key, value in self.opt_kwargs.items(): + # if + + return self._execute(x) + + def _execute(self, x: dict = None): # type: ignore[no-untyped-def] """ Performs the operations of the node. Implement specific logic in subclasses. diff --git a/src/agentscope/web/workstation/workflow_utils.py b/src/agentscope/web/workstation/workflow_utils.py index 3f2aea4b5..c35759287 100644 --- a/src/agentscope/web/workstation/workflow_utils.py +++ b/src/agentscope/web/workstation/workflow_utils.py @@ -1,16 +1,33 @@ # -*- coding: utf-8 -*- """Workflow node utils.""" +import ast +import builtins +from typing import Any -def is_callable_expression(s: str) -> bool: - """Check a expression whether a callable expression""" + +def is_callable_expression(value: str) -> bool: + """ + Check if the given string could represent a callable expression + (including lambda and built-in functions). + """ try: - # Do not detect exp like this - if s in ["input", "print"]: - return False - result = eval(s) - return callable(result) - except Exception: + # Attempt to parse the expression using the AST module + node = ast.parse(value, mode='eval') + + # Check for callable expressions + if isinstance(node.body, (ast.Call, ast.Lambda)): + return True + + if isinstance(node.body, ast.Name): + # Exclude undesired built-in functions + excluded_builtins = {"input", "print"} + if node.body.id in excluded_builtins: + return False + return node.body.id in dir(builtins) + + return False + except (SyntaxError, ValueError): return False @@ -25,6 +42,13 @@ def kwarg_converter(kwargs: dict) -> str: return ", ".join(kwarg_parts) +def convert_str_to_callable(item) -> Any: + """Convert a str to callable if it can be called.""" + if is_callable_expression(item): + return eval(item) + return item + + def deps_converter(dep_vars: list) -> str: """Convert a dep_vars list to a string.""" return f"[{', '.join(dep_vars)}]" From 77fae11fa5a89995c824da06cacf3c6ec0c93dd0 Mon Sep 17 00:00:00 2001 From: "weirui.kwr@alibaba-inc.com" Date: Sat, 17 Aug 2024 17:07:02 +0800 Subject: [PATCH 02/47] update --- src/agentscope/studio/_app.py | 13 ++++++- src/agentscope/studio/_app_online.py | 2 +- .../web/workstation/workflow_node.py | 38 ++++++++++--------- .../web/workstation/workflow_utils.py | 4 +- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/agentscope/studio/_app.py b/src/agentscope/studio/_app.py index 06d3f7762..182f444ab 100644 --- a/src/agentscope/studio/_app.py +++ b/src/agentscope/studio/_app.py @@ -682,6 +682,8 @@ def _save_workflow() -> Response: """ Save the workflow JSON data to the local user folder. """ + filename_regex = re.compile(r"^[\w\-.]+$") # Alphanumeric, _, -, . + user_login = session.get("user_login", "local_user") user_dir = os.path.join(_cache_dir, user_login) if not os.path.exists(user_dir): @@ -691,8 +693,15 @@ def _save_workflow() -> Response: overwrite = data.get("overwrite", False) filename = data.get("filename") workflow_str = data.get("workflow") - if not filename: - return jsonify({"message": "Filename is required"}) + + # Validate the filename using the regex pattern + if not filename or not filename_regex.match(filename): + return jsonify( + { + "message": "Invalid filename. Only alphanumeric characters, " + "underscores, hyphens, and dots are allowed.", + }, + ) filepath = os.path.join(user_dir, f"{filename}.json") diff --git a/src/agentscope/studio/_app_online.py b/src/agentscope/studio/_app_online.py index 6a23331b4..0ce6433e0 100644 --- a/src/agentscope/studio/_app_online.py +++ b/src/agentscope/studio/_app_online.py @@ -392,4 +392,4 @@ def set_locale() -> Response: except ValueError: print(f"Invalid port number. Using default port {PORT}.") - _app.run(host="0.0.0.0", port=PORT) + _app.run(host="127.0.0.1", port=PORT) diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 8806fda46..8020e3b88 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -27,6 +27,8 @@ kwarg_converter, deps_converter, dict_converter, + convert_str_to_callable, + is_callable_expression, ) from agentscope.service import ( bing_search, @@ -91,9 +93,11 @@ def __call__(self, x: dict = None): # type: ignore[no-untyped-def] Subclasses should implement their specific logic in the `_execute` method. """ - # if not self._has_init: - # for key, value in self.opt_kwargs.items(): - # if + if not self._has_init: + for key, value in self.opt_kwargs.items(): + if is_callable_expression(value): + self.opt_kwargs[key] = convert_str_to_callable(value) + self._has_init = True return self._execute(x) @@ -165,7 +169,7 @@ def __init__( super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) self.msg = Msg(**self.opt_kwargs) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.msg def compile(self) -> dict: @@ -194,7 +198,7 @@ def __init__( super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) self.pipeline = DialogAgent(**self.opt_kwargs) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -224,7 +228,7 @@ def __init__( super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) self.pipeline = UserAgent(**self.opt_kwargs) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -254,7 +258,7 @@ def __init__( super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) self.pipeline = TextToImageAgent(**self.opt_kwargs) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -284,7 +288,7 @@ def __init__( super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) self.pipeline = DictDialogAgent(**self.opt_kwargs) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -323,7 +327,7 @@ def __init__( **self.opt_kwargs, ) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -379,7 +383,7 @@ def __init__( self.participants = get_all_agents(self.pipeline) self.participants_var = get_all_agents(self.pipeline, return_var=True) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: with msghub(self.participants, announcement=self.announcement): x = self.pipeline(x) return x @@ -424,7 +428,7 @@ def __init__( super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) self.pipeline = placeholder - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -457,7 +461,7 @@ def __init__( super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) self.pipeline = SequentialPipeline(operators=self.dep_opts) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -496,7 +500,7 @@ def __init__( **self.opt_kwargs, ) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -537,7 +541,7 @@ def __init__( **self.opt_kwargs, ) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -585,7 +589,7 @@ def __init__( **self.opt_kwargs, ) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -662,7 +666,7 @@ def __init__( **self.opt_kwargs, ) - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -703,7 +707,7 @@ def __init__( assert len(self.dep_opts) == 1, "CopyNode can only have one parent!" self.pipeline = self.dep_opts[0] - def __call__(self, x: dict = None) -> dict: + def _execute(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: diff --git a/src/agentscope/web/workstation/workflow_utils.py b/src/agentscope/web/workstation/workflow_utils.py index c35759287..9dd9c69e0 100644 --- a/src/agentscope/web/workstation/workflow_utils.py +++ b/src/agentscope/web/workstation/workflow_utils.py @@ -13,7 +13,7 @@ def is_callable_expression(value: str) -> bool: """ try: # Attempt to parse the expression using the AST module - node = ast.parse(value, mode='eval') + node = ast.parse(value, mode="eval") # Check for callable expressions if isinstance(node.body, (ast.Call, ast.Lambda)): @@ -42,7 +42,7 @@ def kwarg_converter(kwargs: dict) -> str: return ", ".join(kwarg_parts) -def convert_str_to_callable(item) -> Any: +def convert_str_to_callable(item: str) -> Any: """Convert a str to callable if it can be called.""" if is_callable_expression(item): return eval(item) From b055bcbfe6595e28df8934530e6ec56c3807e875 Mon Sep 17 00:00:00 2001 From: "weirui.kwr@alibaba-inc.com" Date: Mon, 19 Aug 2024 19:08:52 +0800 Subject: [PATCH 03/47] fix --- src/agentscope/web/workstation/workflow_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/agentscope/web/workstation/workflow_utils.py b/src/agentscope/web/workstation/workflow_utils.py index 9dd9c69e0..0e7f731fa 100644 --- a/src/agentscope/web/workstation/workflow_utils.py +++ b/src/agentscope/web/workstation/workflow_utils.py @@ -12,6 +12,9 @@ def is_callable_expression(value: str) -> bool: (including lambda and built-in functions). """ try: + if not isinstance(value, str): + return False + # Attempt to parse the expression using the AST module node = ast.parse(value, mode="eval") From b3a7c5a33d1b909e4d886485c3f8a6d835e6e4de Mon Sep 17 00:00:00 2001 From: "weirui.kwr@alibaba-inc.com" Date: Mon, 19 Aug 2024 19:15:42 +0800 Subject: [PATCH 04/47] fix minor --- src/agentscope/studio/templates/login.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/agentscope/studio/templates/login.html b/src/agentscope/studio/templates/login.html index c395b7723..1fc27a641 100644 --- a/src/agentscope/studio/templates/login.html +++ b/src/agentscope/studio/templates/login.html @@ -7,7 +7,8 @@ rel="icon" type="image/png"> - + Date: Mon, 19 Aug 2024 20:32:22 +0800 Subject: [PATCH 05/47] fix --- .pre-commit-config.yaml | 1 + .../web/workstation/workflow_node.py | 246 ++++++++++-------- .../web/workstation/workflow_utils.py | 4 + 3 files changed, 143 insertions(+), 108 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e65b4ae1f..96ff6cd6c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -96,6 +96,7 @@ repos: --disable=W0632, --disable=W0123, --disable=C3001, + --disable=W0201, ] - repo: https://github.com/regebro/pyroma rev: "4.0" diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 8020e3b88..8723c14f6 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -94,13 +94,19 @@ def __call__(self, x: dict = None): # type: ignore[no-untyped-def] `_execute` method. """ if not self._has_init: - for key, value in self.opt_kwargs.items(): - if is_callable_expression(value): - self.opt_kwargs[key] = convert_str_to_callable(value) + self._execute_init() self._has_init = True return self._execute(x) + def _execute_init(self) -> None: + """ + Init before running. + """ + for key, value in self.opt_kwargs.items(): + if is_callable_expression(value): + self.opt_kwargs[key] = convert_str_to_callable(value) + def _execute(self, x: dict = None): # type: ignore[no-untyped-def] """ Performs the operations of the node. Implement specific logic in @@ -159,14 +165,11 @@ class MsgNode(WorkflowNode): node_type = WorkflowNodeType.MESSAGE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.msg = Msg(**self.opt_kwargs) def _execute(self, x: dict = None) -> dict: @@ -188,14 +191,11 @@ class DialogAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.pipeline = DialogAgent(**self.opt_kwargs) def _execute(self, x: dict = None) -> dict: @@ -218,14 +218,11 @@ class UserAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.pipeline = UserAgent(**self.opt_kwargs) def _execute(self, x: dict = None) -> dict: @@ -258,8 +255,12 @@ def __init__( super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) self.pipeline = TextToImageAgent(**self.opt_kwargs) - def _execute(self, x: dict = None) -> dict: - return self.pipeline(x) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() + self.pipeline = TextToImageAgent(**self.opt_kwargs) def compile(self) -> dict: return { @@ -288,8 +289,12 @@ def __init__( super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) self.pipeline = DictDialogAgent(**self.opt_kwargs) - def _execute(self, x: dict = None) -> dict: - return self.pipeline(x) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() + self.pipeline = DictDialogAgent(**self.opt_kwargs) def compile(self) -> dict: return { @@ -308,17 +313,13 @@ class ReActAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) - # Build tools + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.service_toolkit = ServiceToolkit() - for tool in dep_opts: + for tool in self.dep_opts: if not hasattr(tool, "service_func"): raise TypeError(f"{tool} must be tool!") self.service_toolkit.add(tool.service_func) @@ -366,11 +367,6 @@ def __init__( dep_opts: list, ) -> None: super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) - self.announcement = Msg( - name=self.opt_kwargs["announcement"].get("name", "Host"), - content=self.opt_kwargs["announcement"].get("content", "Welcome!"), - role="system", - ) assert len(self.dep_opts) == 1 and hasattr( self.dep_opts[0], "pipeline", @@ -379,6 +375,17 @@ def __init__( "element being an instance of PipelineBaseNode" ) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() + self.announcement = Msg( + name=self.opt_kwargs["announcement"].get("name", "Host"), + content=self.opt_kwargs["announcement"].get("content", "Welcome!"), + role="system", + ) + self.pipeline = self.dep_opts[0] self.participants = get_all_agents(self.pipeline) self.participants_var = get_all_agents(self.pipeline, return_var=True) @@ -418,14 +425,11 @@ class PlaceHolderNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.pipeline = placeholder def _execute(self, x: dict = None) -> dict: @@ -451,14 +455,11 @@ class SequentialPipelineNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.pipeline = SequentialPipeline(operators=self.dep_opts) def _execute(self, x: dict = None) -> dict: @@ -495,6 +496,12 @@ def __init__( assert ( len(self.dep_opts) == 1 ), "ForLoopPipelineNode can only contain one PipelineNode." + + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.pipeline = ForLoopPipeline( loop_body_operators=self.dep_opts[0], **self.opt_kwargs, @@ -536,6 +543,12 @@ def __init__( assert ( len(self.dep_opts) == 1 ), "WhileLoopPipelineNode can only contain one PipelineNode." + + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.pipeline = WhileLoopPipeline( loop_body_operators=self.dep_opts[0], **self.opt_kwargs, @@ -577,6 +590,12 @@ def __init__( assert ( 0 < len(self.dep_opts) <= 2 ), "IfElsePipelineNode must contain one or two PipelineNode." + + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() if len(self.dep_opts) == 1: self.pipeline = IfElsePipeline( if_body_operators=self.dep_opts[0], @@ -634,16 +653,13 @@ def __init__( assert 0 < len(self.dep_opts), ( "SwitchPipelineNode must contain at least " "one PipelineNode." ) - case_operators = {} self.case_operators_var = {} if len(self.dep_opts) == len(self.opt_kwargs["cases"]): # No default_operators provided - default_operators = placeholder self.default_var_name = "placeholder" elif len(self.dep_opts) == len(self.opt_kwargs["cases"]) + 1: # default_operators provided - default_operators = self.dep_opts.pop(-1) self.default_var_name = self.dep_vars.pop(-1) else: raise ValueError( @@ -651,13 +667,36 @@ def __init__( f"cases {self.opt_kwargs['cases']}.", ) - for key, value, var in zip( + for key, var in zip( self.opt_kwargs["cases"], - self.dep_opts, self.dep_vars, ): - case_operators[key] = value.pipeline self.case_operators_var[key] = var + + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() + case_operators = {} + + if len(self.dep_opts) == len(self.opt_kwargs["cases"]): + # No default_operators provided + default_operators = placeholder + elif len(self.dep_opts) == len(self.opt_kwargs["cases"]) + 1: + # default_operators provided + default_operators = self.dep_opts.pop(-1) + else: + raise ValueError( + f"SwitchPipelineNode deps {self.dep_opts} not matches " + f"cases {self.opt_kwargs['cases']}.", + ) + + for key, value in zip( + self.opt_kwargs["cases"], + self.dep_opts, + ): + case_operators[key] = value.pipeline self.opt_kwargs.pop("cases") self.source_kwargs.pop("cases") self.pipeline = SwitchPipeline( @@ -705,6 +744,12 @@ def __init__( ) -> None: super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) assert len(self.dep_opts) == 1, "CopyNode can only have one parent!" + + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.pipeline = self.dep_opts[0] def _execute(self, x: dict = None) -> dict: @@ -726,14 +771,11 @@ class BingSearchServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.service_func = partial(bing_search, **self.opt_kwargs) def compile(self) -> dict: @@ -754,14 +796,11 @@ class GoogleSearchServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.service_func = partial(google_search, **self.opt_kwargs) def compile(self) -> dict: @@ -782,14 +821,11 @@ class PythonServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.service_func = execute_python_code def compile(self) -> dict: @@ -808,14 +844,11 @@ class ReadTextServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.service_func = read_text_file def compile(self) -> dict: @@ -834,14 +867,11 @@ class WriteTextServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + def _execute_init(self) -> None: + """ + Init before running. + """ + super()._execute_init() self.service_func = write_text_file def compile(self) -> dict: diff --git a/src/agentscope/web/workstation/workflow_utils.py b/src/agentscope/web/workstation/workflow_utils.py index 0e7f731fa..c77439d88 100644 --- a/src/agentscope/web/workstation/workflow_utils.py +++ b/src/agentscope/web/workstation/workflow_utils.py @@ -48,7 +48,11 @@ def kwarg_converter(kwargs: dict) -> str: def convert_str_to_callable(item: str) -> Any: """Convert a str to callable if it can be called.""" if is_callable_expression(item): + print(111111) + print(item, eval(item)) return eval(item) + print(222222222) + print(item) return item From 54063b90cf3c89d9b554ab910f4d4b4d74c8d69f Mon Sep 17 00:00:00 2001 From: "weirui.kwr@alibaba-inc.com" Date: Mon, 19 Aug 2024 20:34:17 +0800 Subject: [PATCH 06/47] remove extra print --- src/agentscope/web/workstation/workflow_utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/agentscope/web/workstation/workflow_utils.py b/src/agentscope/web/workstation/workflow_utils.py index c77439d88..0e7f731fa 100644 --- a/src/agentscope/web/workstation/workflow_utils.py +++ b/src/agentscope/web/workstation/workflow_utils.py @@ -48,11 +48,7 @@ def kwarg_converter(kwargs: dict) -> str: def convert_str_to_callable(item: str) -> Any: """Convert a str to callable if it can be called.""" if is_callable_expression(item): - print(111111) - print(item, eval(item)) return eval(item) - print(222222222) - print(item) return item From df3d38c9d9b682b7506e2e132c2408b740aa854a Mon Sep 17 00:00:00 2001 From: "weirui.kwr@alibaba-inc.com" Date: Tue, 20 Aug 2024 15:16:46 +0800 Subject: [PATCH 07/47] fix --- .../web/workstation/workflow_node.py | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 8723c14f6..d49264b05 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -245,16 +245,6 @@ class TextToImageAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) - self.pipeline = TextToImageAgent(**self.opt_kwargs) - def _execute_init(self) -> None: """ Init before running. @@ -262,6 +252,9 @@ def _execute_init(self) -> None: super()._execute_init() self.pipeline = TextToImageAgent(**self.opt_kwargs) + def _execute(self, x: dict = None) -> dict: + return self.pipeline(x) + def compile(self) -> dict: return { "imports": "from agentscope.agents import TextToImageAgent", @@ -279,16 +272,6 @@ class DictDialogAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) - self.pipeline = DictDialogAgent(**self.opt_kwargs) - def _execute_init(self) -> None: """ Init before running. @@ -296,6 +279,9 @@ def _execute_init(self) -> None: super()._execute_init() self.pipeline = DictDialogAgent(**self.opt_kwargs) + def _execute(self, x: dict = None) -> dict: + return self.pipeline(x) + def compile(self) -> dict: return { "imports": "from agentscope.agents import DictDialogAgent", From d54d178d2192c06274bd4a6f6395223a8a6ae6cc Mon Sep 17 00:00:00 2001 From: qbc Date: Tue, 20 Aug 2024 18:10:03 +0800 Subject: [PATCH 08/47] support wanx (#12) --- .../html-drag-components/model-wanx.html | 40 +++++++++++++++++++ .../studio/static/js/workstation.js | 23 +++++++++++ .../studio/templates/workstation.html | 19 ++++++++- .../web/workstation/workflow_node.py | 1 + 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/agentscope/studio/static/html-drag-components/model-wanx.html diff --git a/src/agentscope/studio/static/html-drag-components/model-wanx.html b/src/agentscope/studio/static/html-drag-components/model-wanx.html new file mode 100644 index 000000000..28e8c6d46 --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/model-wanx.html @@ -0,0 +1,40 @@ +
+
+ + + + Wanx +
+ +
+
+
Wanx Configurations
+
+ +
+ +
+ +
+ +
+ +
+ + + +
+ + + +
+
\ No newline at end of file diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 946822d75..b86fced24 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -17,6 +17,7 @@ let nameToHtmlFile = { 'openai_chat': 'model-openai-chat.html', 'post_api_chat': 'model-post-api-chat.html', 'post_api_dall_e': 'model-post-api-dall-e.html', + 'dashscope_image_synthesis': 'model-wanx.html', 'Message': 'message-msg.html', 'DialogAgent': 'agent-dialogagent.html', 'UserAgent': 'agent-useragent.html', @@ -562,6 +563,28 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { addEventListenersToNumberInputs(post_api_dall_eId); break; + case 'dashscope_image_synthesis': + const dashscope_image_synthesisId = editor.addNode('dashscope_image_synthesis', 0, + 0, + pos_x, pos_y, + 'dashscope_image_synthesis', { + "args": + { + "config_name": '', + "model_name": '', + "generate_args": { + "n": 1, + "size": "", + "temperature": 0.0, + "seed": 0, + }, + "model_type": 'dashscope_image_synthesis', + "messages_key": 'prompt' + } + }, htmlSourceCode); + addEventListenersToNumberInputs(dashscope_image_synthesisId); + break; + // Message case 'Message': editor.addNode('Message', 1, 1, pos_x, diff --git a/src/agentscope/studio/templates/workstation.html b/src/agentscope/studio/templates/workstation.html index cd1897f48..fe287bea1 100644 --- a/src/agentscope/studio/templates/workstation.html +++ b/src/agentscope/studio/templates/workstation.html @@ -116,6 +116,10 @@ data-node="post_api_dall_e" draggable="true" ondragstart="drag(event)">Post API Dall-E +
  • Wanx +
  • @@ -209,7 +213,7 @@
  • - Tool + Service
  • +
  • +
    + Tool +
    +
      +
    • + Image Composition +
    • +
    +
  • diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index d49264b05..13cd06549 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -874,6 +874,7 @@ def compile(self) -> dict: "openai_chat": ModelNode, "post_api_chat": ModelNode, "post_api_dall_e": ModelNode, + 'dashscope_image_synthesis': ModelNode, "Message": MsgNode, "DialogAgent": DialogAgentNode, "UserAgent": UserAgentNode, From 6f3184ce1710928191e8c9b5745dd56a00f9f0a0 Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Tue, 3 Sep 2024 10:33:06 +0800 Subject: [PATCH 09/47] Weirui gallery patches1 (#13) Added: Gallery & logout interface Game Monkey Adventures Chinese example --- gallery/monkey.json | 249 +++++++++++++++++ src/agentscope/studio/_app.py | 24 ++ src/agentscope/studio/_app_online.py | 20 ++ .../static/workstation_templates/zh2.json | 134 +++++++++ .../static/workstation_templates/zh3.json | 144 ++++++++++ .../static/workstation_templates/zh4.json | 264 ++++++++++++++++++ src/agentscope/utils/tools.py | 4 +- .../web/workstation/workflow_node.py | 2 +- 8 files changed, 839 insertions(+), 2 deletions(-) create mode 100644 gallery/monkey.json create mode 100644 src/agentscope/studio/static/workstation_templates/zh2.json create mode 100644 src/agentscope/studio/static/workstation_templates/zh3.json create mode 100644 src/agentscope/studio/static/workstation_templates/zh4.json diff --git a/gallery/monkey.json b/gallery/monkey.json new file mode 100644 index 000000000..49db572dd --- /dev/null +++ b/gallery/monkey.json @@ -0,0 +1,249 @@ +{ + "meta": { + "index": 0, + "title": "Adventure of Monkey", + "author": "AgentScopeTeam", + "keywords": [ + "game", + "multi-modal" + ], + "category": "game", + "time": "2024-08-20", + "thumbnail": "" + }, + "drawflow": { + "Home": { + "data": { + "3": { + "id": 3, + "name": "dashscope_chat", + "data": { + "args": { + "config_name": "qwen-max", + "model_name": "qwen-max", + "temperature": 0, + "seed": 0, + "model_type": "dashscope_chat", + "messages_key": "input" + } + }, + "class": "dashscope_chat", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 29.11111111111111, + "pos_y": 28 + }, + "4": { + "id": 4, + "name": "dashscope_image_synthesis", + "data": { + "args": { + "config_name": "wanx-v1", + "model_name": "wanx-v1", + "generate_args": { + "n": 1, + "size": "1024*1024", + "temperature": 0, + "seed": 0 + }, + "model_type": "dashscope_image_synthesis", + "messages_key": "prompt", + "api_key": "" + } + }, + "class": "dashscope_image_synthesis", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 25, + "pos_y": 682 + }, + "6": { + "id": 6, + "name": "DialogAgent", + "data": { + "args": { + "name": "游戏主持人", + "sys_prompt": "你是一款文字冒险游戏,主题是孙悟空历险记。玩家将扮演孙悟空,探索奇幻的世界,面临各种选择和挑战。每当玩家做出选择时,你需要提供四个选项(A、B、C、D),每个选项都应包含不同的故事情节或冒险。确保每个选择都有可能导致不同的后果,增强互动性和趣味性。在每个回合开始时,简要描述当前的情境,并询问玩家的选择。", + "model_config_name": "qwen-max" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 962, + "pos_y": 267.8888888888889 + }, + "8": { + "id": 8, + "name": "UserAgent", + "data": { + "args": { + "name": "User" + } + }, + "class": "UserAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 963, + "pos_y": 577.1111111111111 + }, + "9": { + "id": 9, + "name": "TextToImageAgent", + "data": { + "args": { + "name": "场景描述", + "model_config_name": "wanx-v1" + } + }, + "class": "TextToImageAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 959.8888888888889, + "pos_y": 472 + }, + "11": { + "id": 11, + "name": "DialogAgent", + "data": { + "args": { + "name": "图片提示词生成器", + "sys_prompt": "任务目标:根据输入的分镜,创造一组描述性短语,旨在用于指导文生图模型生成具有相应风格和特性的高质量绘本画面。 \\n描述应该是简洁明了的关键词或短语,以逗号分隔,以确保在画面生成时能够清楚地反映绘本所描述的画面。\\n如果包含人物的话,需要当下人物的神情。比如一个好的描述可能包含:人物+神情+动作描述+场景描述。人物的主角是孙悟空,因此你的输出应该以孙悟空开头。", + "model_config_name": "qwen-max" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 961, + "pos_y": 369 + }, + "12": { + "id": 12, + "name": "WhileLoopPipeline", + "data": { + "elements": [ + "13" + ], + "args": { + "condition_func": "lambda *args: True" + } + }, + "class": "GROUP", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "20", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 851, + "pos_y": 60 + }, + "13": { + "id": 13, + "name": "SequentialPipeline", + "data": { + "elements": [ + "6", + "9", + "11", + "8" + ] + }, + "class": "GROUP", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 908, + "pos_y": 204.88888888888889 + }, + "20": { + "id": 20, + "name": "Message", + "data": { + "args": { + "name": "Hint", + "content": "游戏开始", + "url": "" + } + }, + "class": "Message", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "12", + "output": "input_1" + } + ] + } + }, + "pos_x": 410, + "pos_y": 248.88888888888889 + } + } + } + } +} \ No newline at end of file diff --git a/src/agentscope/studio/_app.py b/src/agentscope/studio/_app.py index 182f444ab..6b9bf603c 100644 --- a/src/agentscope/studio/_app.py +++ b/src/agentscope/studio/_app.py @@ -677,6 +677,30 @@ def _read_examples() -> Response: return jsonify(json=data) +@_app.route("/fetch-gallery", methods=["POST"]) +def _fetch_gallery() -> Response: + """ + Get all workflows JSON files in gallery folder. + """ + gallery_path = os.path.join(_app.root_path, "..", "..", "..", "gallery") + + if not os.path.exists(gallery_path): + return jsonify(json=[]) + + gallery_items = [] + for filename in os.listdir(gallery_path): + if filename.endswith(".json"): + file_path = os.path.join(gallery_path, filename) + try: + with open(file_path, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + gallery_items.append(data) + except (IOError, json.JSONDecodeError) as e: + print(f"Error reading {file_path}: {e}") + + return jsonify(json=gallery_items) + + @_app.route("/save-workflow", methods=["POST"]) def _save_workflow() -> Response: """ diff --git a/src/agentscope/studio/_app_online.py b/src/agentscope/studio/_app_online.py index 0ce6433e0..521283d0a 100644 --- a/src/agentscope/studio/_app_online.py +++ b/src/agentscope/studio/_app_online.py @@ -34,6 +34,7 @@ _delete_workflow, _list_workflows, _load_workflow, + _fetch_gallery, ) _app = Flask(__name__) @@ -195,6 +196,15 @@ def _home() -> str: return render_template("login.html", client_id=CLIENT_ID, ip=IP, port=PORT) +@_app.route("/logout") +def logout() -> str: + """ + Logout the user by clearing the session and redirecting to the login page. + """ + session.clear() # Clear the session + return redirect(url_for("_home")) # Redirect to the login page + + @_app.route("/oauth/callback") def oauth_callback() -> str: """ @@ -323,6 +333,16 @@ def _read_examples_online(**kwargs: Any) -> Response: return _read_examples() +@_app.route("/fetch-gallery", methods=["POST"]) +@_require_auth(fail_with_exception=True, secret_key=SECRET_KEY) +def _fetch_gallery_online(**kwargs: Any) -> Response: + # pylint: disable=unused-argument + """ + Get all workflows JSON files in gallery folder. + """ + return _fetch_gallery() + + @_app.route("/save-workflow", methods=["POST"]) @_require_auth(fail_with_exception=True, secret_key=SECRET_KEY) def _save_workflow_online(**kwargs: Any) -> Response: diff --git a/src/agentscope/studio/static/workstation_templates/zh2.json b/src/agentscope/studio/static/workstation_templates/zh2.json new file mode 100644 index 000000000..316ec7c50 --- /dev/null +++ b/src/agentscope/studio/static/workstation_templates/zh2.json @@ -0,0 +1,134 @@ +{ + "drawflow": { + "Home": { + "data": { + "2": { + "id": 2, + "name": "dashscope_chat", + "data": { + "args": { + "config_name": "qwen", + "model_name": "qwen-max", + "api_key": "", + "temperature": 0, + "seed": 0, + "model_type": "dashscope_chat", + "messages_key": "input" + } + }, + "class": "dashscope_chat", + "html": "\n
    \n
    DashScope Chat\n
    \n
    \n
    DashScope Chat 配置 (您的API key不会被储存和暴露给网站维护者)

    \n \n
    \n \n
    \n \n \n \n
    \n \n
    \n \n \n
    \n
    \n ", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 339, + "pos_y": 23 + }, + "4": { + "id": 4, + "name": "UserAgent", + "data": { + "args": { + "name": "User" + } + }, + "class": "UserAgent", + "html": "\n
    \n
    UserAgent\n \n
    \n
    \n
    \n 用户代理智能体\n
    Node ID: 4
    \n
    Variable name :agent_4
    \n
    \n

    Name

    \n
    \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 797, + "pos_y": 181 + }, + "6": { + "id": 6, + "name": "PythonService", + "data": {}, + "class": "PythonService", + "html": "\n
    \n
    PythonService\n
    \n
    \n
    在 ReActAgent 中集成 Python 解释器,以增强智能体功能
    \n
    Variable name :service_6
    \n
    \n
    \n ", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 824, + "pos_y": 620 + }, + "3": { + "id": 3, + "name": "SequentialPipeline", + "data": { + "elements": [ + "4", + "5" + ] + }, + "class": "GROUP", + "html": "\n
    \n
    SequentialPipeline\n
    \n
    \n
    用于实现顺序逻辑的Pipeline (从上往下顺序执行)\n
    Variable name :pipeline_3
    \n
    \n
    \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 643, + "pos_y": 55 + }, + "5": { + "id": 5, + "name": "ReActAgent", + "data": { + "elements": [ + "6" + ], + "args": { + "name": "Assistant", + "sys_prompt": "You are an assistant. ", + "model_config_name": "qwen", + "max_iters": 10, + "verbose": "True" + } + }, + "class": "GROUP", + "html": "\n
    \n
    ReActAgent\n \n
    \n
    \n
    \n 带有工具的 ReAct(推理和行动)智能体\n
    Node ID: 5
    \n
    Variable name :agent_5
    \n
    \n

    Name

    \n
    \n

    System prompt

    \n
    \n

    Model config name

    \n
    \n

    Tools

    \n
    Please drag and drop a Tool module into this area. Inserting other types of modules may result in errors.
    \n

    Max reasoning-acting iterations

    \n \n

    Verbose

    \n
    \n \n \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 713, + "pos_y": 241 + }, + "1": { + "id": 1, + "name": "readme", + "data": {}, + "class": "welcome", + "html": "\n
    README
    \n
    \n 📖 本例演示了使用 AgentScope 的 Sequential Pipeline 进行单轮用户-ReactAgent(带有Python解释器能力)的对话。

    使用到的模块:

    • DashScope Chat:
      - 每个多智能体应用都必须包含一个配置好的模型。
    • SequentialPipeline:
      - 使信息按预定顺序在智能体之间传递。
    • UserAgent:
      - 代表应用中的用户。
    • ReActAgent:
      - 实现 ReAct 的智能体。
    • PythonService:
      - 用于执行一段 python 代码。
    \n \n ", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 68, + "pos_y": 67 + } + } + } + } +} \ No newline at end of file diff --git a/src/agentscope/studio/static/workstation_templates/zh3.json b/src/agentscope/studio/static/workstation_templates/zh3.json new file mode 100644 index 000000000..cd884ebc0 --- /dev/null +++ b/src/agentscope/studio/static/workstation_templates/zh3.json @@ -0,0 +1,144 @@ +{ + "drawflow": { + "Home": { + "data": { + "2": { + "id": 2, + "name": "dashscope_chat", + "data": { + "args": { + "config_name": "qwen", + "model_name": "qwen-max", + "api_key": "", + "temperature": 0, + "seed": 0, + "model_type": "dashscope_chat", + "messages_key": "input" + } + }, + "class": "dashscope_chat", + "html": "\n
    \n
    DashScope Chat\n
    \n
    \n
    DashScope Chat 配置 (您的API key不会被储存和暴露给网站维护者)

    \n \n
    \n \n
    \n \n \n \n
    \n \n
    \n \n \n
    \n
    \n ", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 351, + "pos_y": 113 + }, + "3": { + "id": 3, + "name": "WhileLoopPipeline", + "data": { + "elements": [ + "4" + ], + "args": { + "condition_func": "lambda *args: True" + } + }, + "class": "GROUP", + "html": "\n
    \n
    WhileLoopPipeline\n
    \n
    \n
    实现类似 while 循环的控制流的Pipeline\n
    Variable name :pipeline_3
    \n
    \n

    Condition Function

    \n
    \n
    \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 699, + "pos_y": 151 + }, + "4": { + "id": 4, + "name": "SequentialPipeline", + "data": { + "elements": [ + "6", + "5" + ] + }, + "class": "GROUP", + "html": "\n
    \n
    SequentialPipeline\n
    \n
    \n
    用于实现顺序逻辑的Pipeline (从上往下顺序执行)\n
    Variable name :pipeline_4
    \n
    \n
    \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 750, + "pos_y": 217 + }, + "5": { + "id": 5, + "name": "UserAgent", + "data": { + "args": { + "name": "User" + } + }, + "class": "UserAgent", + "html": "\n
    \n
    UserAgent\n \n
    \n
    \n
    \n 用户代理智能体\n
    Node ID: 5
    \n
    Variable name :agent_5
    \n
    \n

    Name

    \n
    \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 766, + "pos_y": 361 + }, + "6": { + "id": 6, + "name": "DialogAgent", + "data": { + "args": { + "name": "Assistant", + "sys_prompt": "You are a helpful assistant.", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "html": "\n
    \n
    DialogAgent\n \n
    \n
    \n
    \n 应用中的对话智能体\n
    Node ID: 6
    \n
    Variable name :agent_6
    \n
    \n

    Name

    \n
    \n

    System prompt

    \n
    \n

    Model config name

    \n \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 985, + "pos_y": 468 + }, + "1": { + "id": 1, + "name": "readme", + "data": {}, + "class": "welcome", + "html": "\n
    README
    \n
    \n 📖 这是一个如何在 AgentScope 中编写用户-智能体循环对话的示例。

    使用到的模块:

    • DashScope Chat:
      - 每个多智能体应用都必须包含一个配置好的模型。
    • WhileLoopPipeline:
      - 设计用于执行while循环的重复性操作。
    • SequentialPipeline:
      - 使信息按预定顺序在智能体之间传递。
    • UserAgent:
      - 代表应用中的用户。
    • DialogAgent:
      - 应用中的对话智能体。
    更多的细节, 请参考 链接.
    \n \n ", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 68, + "pos_y": 67 + } + } + } + } +} \ No newline at end of file diff --git a/src/agentscope/studio/static/workstation_templates/zh4.json b/src/agentscope/studio/static/workstation_templates/zh4.json new file mode 100644 index 000000000..18acfe7b5 --- /dev/null +++ b/src/agentscope/studio/static/workstation_templates/zh4.json @@ -0,0 +1,264 @@ +{ + "drawflow": { + "Home": { + "data": { + "4": { + "id": 4, + "name": "MsgHub", + "data": { + "elements": [ + "5" + ], + "args": { + "announcement": { + "name": "Host", + "content": "This is a chat room and you can speak freely and briefly." + } + } + }, + "class": "GROUP", + "html": "\n
    \n
    MsgHub\n
    \n
    \n
    MsgHub用于在一组智能体之间共享信息。\n
    Variable name :hub_4
    \n
    \n

    Announcement Name

    \n
    \n

    Announcement Content

    \n
    \n
    \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "3", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 705, + "pos_y": 8 + }, + "5": { + "id": 5, + "name": "WhileLoopPipeline", + "data": { + "elements": [ + "6" + ], + "args": { + "condition_func": "lambda *args: True" + } + }, + "class": "GROUP", + "html": "\n
    \n
    WhileLoopPipeline\n
    \n
    \n
    实现类似 while 循环的控制流的Pipeline\n
    Variable name :pipeline_5
    \n
    \n

    Condition Function

    \n
    \n
    \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 827, + "pos_y": 141.5 + }, + "6": { + "id": 6, + "name": "SequentialPipeline", + "data": { + "elements": [ + "8", + "9", + "7", + "10" + ] + }, + "class": "GROUP", + "html": "\n
    \n
    SequentialPipeline\n
    \n
    \n
    用于实现顺序逻辑的Pipeline (从上往下顺序执行)\n
    Variable name :pipeline_6
    \n
    \n
    \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 893, + "pos_y": 254.75 + }, + "7": { + "id": 7, + "name": "DialogAgent", + "data": { + "args": { + "name": "Lingfeng", + "sys_prompt": "You are Lingfeng, a noble in the imperial court, known for your wisdom and strategic acumen. You often engage in complex political intrigues and have recently suspected the Queen’s adviser of treachery. Your speaking style is reminiscent of classical literature. Respond in a concise statement, no more than 30 words.", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "html": "\n
    \n
    DialogAgent\n \n
    \n
    \n
    \n 应用中的对话智能体\n
    Node ID: 7
    \n
    Variable name :agent_7
    \n
    \n

    Name

    \n
    \n

    System prompt

    \n
    \n

    Model config name

    \n \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 934, + "pos_y": 343 + }, + "8": { + "id": 8, + "name": "DialogAgent", + "data": { + "args": { + "name": "Boyu", + "sys_prompt": "You are Boyu, a friend of Lingfeng and an enthusiast of court dramas. Your speech is modern but with a flair for the dramatic, matching your love for emotive storytelling. You've been closely following Lingfeng’s political maneuvers in the imperial court through secret correspondence. Respond in a concise statement, no more than 30 words.", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "html": "\n
    \n
    DialogAgent\n \n
    \n
    \n
    \n 应用中的对话智能体\n
    Node ID: 8
    \n
    Variable name :agent_8
    \n
    \n

    Name

    \n
    \n

    System prompt

    \n
    \n

    Model config name

    \n \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 970, + "pos_y": 365 + }, + "9": { + "id": 9, + "name": "DialogAgent", + "data": { + "args": { + "name": "Haotian", + "sys_prompt": "You are Haotian, Lingfeng’s cousin who prefers the open fields to the confines of court life. As a celebrated athlete, your influence has protected Lingfeng in times of political strife. You promote physical training as a way to prepare for life's battles, often using sports metaphors in conversation. Respond in a concise statement, no more than 30 words.", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "html": "\n
    \n
    DialogAgent\n \n
    \n
    \n
    \n 应用中的对话智能体\n
    Node ID: 9
    \n
    Variable name :agent_9
    \n
    \n

    Name

    \n
    \n

    System prompt

    \n
    \n

    Model config name

    \n \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 1009, + "pos_y": 409 + }, + "10": { + "id": 10, + "name": "UserAgent", + "data": { + "args": { + "name": "King" + } + }, + "class": "UserAgent", + "html": "\n
    \n
    UserAgent\n \n
    \n
    \n
    \n 用户代理智能体\n
    Node ID: 10
    \n
    Variable name :agent_10
    \n
    \n

    Name

    \n
    \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 1043, + "pos_y": 454 + }, + "2": { + "id": 2, + "name": "dashscope_chat", + "data": { + "args": { + "config_name": "qwen", + "model_name": "qwen-max", + "api_key": "", + "temperature": 0.1, + "seed": 1, + "model_type": "dashscope_chat", + "messages_key": "input" + } + }, + "class": "dashscope_chat", + "html": "\n
    \n
    DashScope Chat\n
    \n
    \n
    DashScope Chat 配置 (您的API key不会被储存和暴露给网站维护者)

    \n \n
    \n \n
    \n \n \n \n
    \n \n
    \n \n \n
    \n
    \n ", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 358, + "pos_y": 7 + }, + "3": { + "id": 3, + "name": "Message", + "data": { + "args": { + "name": "User", + "content": "Hello every one", + "url": "" + } + }, + "class": "Message", + "html": "\n
    \n
    Message\n
    \n
    \n

    Name

    \n
    \n

    Content

    \n
    \n

    URL (Optional)

    \n
    \n
    \n
    \n ", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "4", + "output": "input_1" + } + ] + } + }, + "pos_x": 350, + "pos_y": 505 + }, + "1": { + "id": 1, + "name": "readme", + "data": {}, + "class": "welcome", + "html": "\n
    README
    \n
    \n 📖 本例演示了 AgentScope 中实现的多智能体群聊的功能。

    使用到的模块:

    • DashScope Chat:
      - 每个多智能体应用都必须包含一个配置好的模型。
    • MsgHub:
      - MsgHub是智能体像群聊一样分享信息的地方。
    • Message:
      - Message是智能体间信息流动的介质。
    • WhileLoopPipeline:
      - 设计用于执行while循环的重复性操作。
    • SequentialPipeline:
      - 使信息按预定顺序在智能体之间传递。
    • UserAgent:
      - 代表应用中的用户。
    • DialogAgent:
      - 应用中的对话智能体。
    更多的细节, 请参考 链接.
    \n \n ", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 68, + "pos_y": 67 + } + } + } + } +} \ No newline at end of file diff --git a/src/agentscope/utils/tools.py b/src/agentscope/utils/tools.py index 4e2382fc0..6e11e124c 100644 --- a/src/agentscope/utils/tools.py +++ b/src/agentscope/utils/tools.py @@ -97,7 +97,9 @@ def _guess_type_by_extension( url: str, ) -> Literal["image", "audio", "video", "file"]: """Guess the type of the file by its extension.""" - extension = url.split(".")[-1].lower() + parsed_url = urlparse(url) + path = parsed_url.path + extension = path.split(".")[-1].lower() if "." in path else "" if extension in [ "bmp", diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 13cd06549..8dd275d92 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -874,7 +874,7 @@ def compile(self) -> dict: "openai_chat": ModelNode, "post_api_chat": ModelNode, "post_api_dall_e": ModelNode, - 'dashscope_image_synthesis': ModelNode, + "dashscope_image_synthesis": ModelNode, "Message": MsgNode, "DialogAgent": DialogAgentNode, "UserAgent": UserAgentNode, From 6cb7b31bfe4247d1457024a4e8bdb2b3dcdb1a53 Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:41:34 +0800 Subject: [PATCH 10/47] fix compile(#17) --- .pre-commit-config.yaml | 1 + src/agentscope/web/workstation/workflow.py | 4 +- .../web/workstation/workflow_dag.py | 38 +- .../web/workstation/workflow_node.py | 455 +++++++++++------- 4 files changed, 322 insertions(+), 176 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 96ff6cd6c..fbd2fb365 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -97,6 +97,7 @@ repos: --disable=W0123, --disable=C3001, --disable=W0201, + --disable=C0302, ] - repo: https://github.com/regebro/pyroma rev: "4.0" diff --git a/src/agentscope/web/workstation/workflow.py b/src/agentscope/web/workstation/workflow.py index 291b9a7f4..e76414bf8 100644 --- a/src/agentscope/web/workstation/workflow.py +++ b/src/agentscope/web/workstation/workflow.py @@ -32,7 +32,7 @@ def start_workflow(config: dict) -> None: """ logger.info("Launching...") - dag = build_dag(config) + dag = build_dag(config, only_compile=False) dag.run() logger.info("Finished.") @@ -48,7 +48,7 @@ def compile_workflow(config: dict, compiled_filename: str = "main.py") -> None: """ logger.info("Compiling...") - dag = build_dag(config) + dag = build_dag(config, only_compile=True) dag.compile(compiled_filename) logger.info("Finished.") diff --git a/src/agentscope/web/workstation/workflow_dag.py b/src/agentscope/web/workstation/workflow_dag.py index c5093467b..cb1b2f323 100644 --- a/src/agentscope/web/workstation/workflow_dag.py +++ b/src/agentscope/web/workstation/workflow_dag.py @@ -49,11 +49,17 @@ class ASDiGraph(nx.DiGraph): the computation graph. """ - def __init__(self, *args, **kwargs): # type: ignore[no-untyped-def] + def __init__( + self, + only_compile: bool = True, + *args: Any, + **kwargs: Any, + ) -> None: """ Initialize the ASDiGraph instance. """ super().__init__(*args, **kwargs) + self.only_compile = only_compile self.nodes_not_in_graph = set() # Prepare the header of the file with necessary imports and any @@ -77,6 +83,9 @@ def run(self) -> None: the nodes, and then runs each node's computation sequentially using the outputs from its predecessors as inputs. """ + if self.only_compile: + raise ValueError("Workflow cannot run on compile mode!") + agentscope.init(logger_level="DEBUG") sorted_nodes = list(nx.topological_sort(self)) sorted_nodes = [ @@ -164,6 +173,7 @@ def add_as_node( node_id: str, node_info: dict, config: dict, + only_compile: bool = True, ) -> Any: """ Add a node to the graph based on provided node information and @@ -203,7 +213,12 @@ def add_as_node( for dep_node_id in deps: if not self.has_node(dep_node_id): dep_node_info = config[dep_node_id] - self.add_as_node(dep_node_id, dep_node_info, config) + self.add_as_node( + node_id=dep_node_id, + node_info=dep_node_info, + config=config, + only_compile=only_compile, + ) dep_opts.append(self.nodes[dep_node_id]["opt"]) node_opt = node_cls( @@ -211,6 +226,7 @@ def add_as_node( opt_kwargs=node_info["data"].get("args", {}), source_kwargs=node_info["data"].get("source", {}), dep_opts=dep_opts, + only_compile=only_compile, ) # Add build compiled python code @@ -286,7 +302,7 @@ def sanitize_node_data(raw_info: dict) -> dict: return raw_info -def build_dag(config: dict) -> ASDiGraph: +def build_dag(config: dict, only_compile: bool = True) -> ASDiGraph: """ Construct a Directed Acyclic Graph (DAG) from the provided configuration. @@ -303,7 +319,7 @@ def build_dag(config: dict) -> ASDiGraph: Raises: ValueError: If the resulting graph is not acyclic. """ - dag = ASDiGraph() + dag = ASDiGraph(only_compile=only_compile) for node_id, node_info in config.items(): config[node_id] = sanitize_node_data(node_info) @@ -314,7 +330,12 @@ def build_dag(config: dict) -> ASDiGraph: NODE_NAME_MAPPING[node_info["name"]].node_type == WorkflowNodeType.MODEL ): - dag.add_as_node(node_id, node_info, config) + dag.add_as_node( + node_id, + node_info, + config, + only_compile=only_compile, + ) # Add and init non-model nodes for node_id, node_info in config.items(): @@ -322,7 +343,12 @@ def build_dag(config: dict) -> ASDiGraph: NODE_NAME_MAPPING[node_info["name"]].node_type != WorkflowNodeType.MODEL ): - dag.add_as_node(node_id, node_info, config) + dag.add_as_node( + node_id, + node_info, + config, + only_compile=only_compile, + ) # Add edges for node_id, node_info in config.items(): diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 8dd275d92..301483c7f 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -70,12 +70,13 @@ def __init__( opt_kwargs: dict, source_kwargs: dict, dep_opts: list, + only_compile: bool = True, ) -> None: """ Initialize nodes. Implement specific initialization logic in subclasses. """ - self._has_init = False + self.only_compile = only_compile self.node_id = node_id self.opt_kwargs = opt_kwargs @@ -84,6 +85,12 @@ def __init__( self.dep_vars = [opt.var_name for opt in self.dep_opts] self.var_name = f"{self.node_type.name.lower()}_{self.node_id}" + # Warning: Might cause error when args is still string + if not only_compile: + for key, value in self.opt_kwargs.items(): + if is_callable_expression(value): + self.opt_kwargs[key] = convert_str_to_callable(value) + def __call__(self, x: dict = None): # type: ignore[no-untyped-def] """ Invokes the node's operations with the provided input. @@ -93,25 +100,7 @@ def __call__(self, x: dict = None): # type: ignore[no-untyped-def] Subclasses should implement their specific logic in the `_execute` method. """ - if not self._has_init: - self._execute_init() - self._has_init = True - - return self._execute(x) - - def _execute_init(self) -> None: - """ - Init before running. - """ - for key, value in self.opt_kwargs.items(): - if is_callable_expression(value): - self.opt_kwargs[key] = convert_str_to_callable(value) - - def _execute(self, x: dict = None): # type: ignore[no-untyped-def] - """ - Performs the operations of the node. Implement specific logic in - subclasses. - """ + return x @abstractmethod def compile(self) -> dict: @@ -142,8 +131,15 @@ def __init__( opt_kwargs: dict, source_kwargs: dict, dep_opts: list, + only_compile: bool = True, ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) ModelManager.get_instance().load_model_configs([self.opt_kwargs]) def compile(self) -> dict: @@ -165,14 +161,24 @@ class MsgNode(WorkflowNode): node_type = WorkflowNodeType.MESSAGE - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.msg = Msg(**self.opt_kwargs) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.msg def compile(self) -> dict: @@ -191,14 +197,24 @@ class DialogAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.pipeline = DialogAgent(**self.opt_kwargs) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -218,14 +234,24 @@ class UserAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.pipeline = UserAgent(**self.opt_kwargs) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -245,14 +271,24 @@ class TextToImageAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.pipeline = TextToImageAgent(**self.opt_kwargs) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -272,14 +308,24 @@ class DictDialogAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.pipeline = DictDialogAgent(**self.opt_kwargs) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -299,13 +345,24 @@ class ReActAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) + # Build tools self.service_toolkit = ServiceToolkit() - for tool in self.dep_opts: + for tool in dep_opts: if not hasattr(tool, "service_func"): raise TypeError(f"{tool} must be tool!") self.service_toolkit.add(tool.service_func) @@ -314,7 +371,7 @@ def _execute_init(self) -> None: **self.opt_kwargs, ) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -351,8 +408,20 @@ def __init__( opt_kwargs: dict, source_kwargs: dict, dep_opts: list, + only_compile: bool = True, ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) + self.announcement = Msg( + name=self.opt_kwargs["announcement"].get("name", "Host"), + content=self.opt_kwargs["announcement"].get("content", "Welcome!"), + role="system", + ) assert len(self.dep_opts) == 1 and hasattr( self.dep_opts[0], "pipeline", @@ -361,22 +430,11 @@ def __init__( "element being an instance of PipelineBaseNode" ) - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() - self.announcement = Msg( - name=self.opt_kwargs["announcement"].get("name", "Host"), - content=self.opt_kwargs["announcement"].get("content", "Welcome!"), - role="system", - ) - self.pipeline = self.dep_opts[0] self.participants = get_all_agents(self.pipeline) self.participants_var = get_all_agents(self.pipeline, return_var=True) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: with msghub(self.participants, announcement=self.announcement): x = self.pipeline(x) return x @@ -411,14 +469,24 @@ class PlaceHolderNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.pipeline = placeholder - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -441,14 +509,24 @@ class SequentialPipelineNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.pipeline = SequentialPipeline(operators=self.dep_opts) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -477,23 +555,24 @@ def __init__( opt_kwargs: dict, source_kwargs: dict, dep_opts: list, + only_compile: bool = True, ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) assert ( len(self.dep_opts) == 1 ), "ForLoopPipelineNode can only contain one PipelineNode." - - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() self.pipeline = ForLoopPipeline( loop_body_operators=self.dep_opts[0], **self.opt_kwargs, ) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -524,23 +603,24 @@ def __init__( opt_kwargs: dict, source_kwargs: dict, dep_opts: list, + only_compile: bool = True, ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) assert ( len(self.dep_opts) == 1 ), "WhileLoopPipelineNode can only contain one PipelineNode." - - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() self.pipeline = WhileLoopPipeline( loop_body_operators=self.dep_opts[0], **self.opt_kwargs, ) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -571,17 +651,18 @@ def __init__( opt_kwargs: dict, source_kwargs: dict, dep_opts: list, + only_compile: bool = True, ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) assert ( 0 < len(self.dep_opts) <= 2 ), "IfElsePipelineNode must contain one or two PipelineNode." - - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() if len(self.dep_opts) == 1: self.pipeline = IfElsePipeline( if_body_operators=self.dep_opts[0], @@ -594,7 +675,7 @@ def _execute_init(self) -> None: **self.opt_kwargs, ) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -634,55 +715,42 @@ def __init__( opt_kwargs: dict, source_kwargs: dict, dep_opts: list, + only_compile: bool = True, ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) assert 0 < len(self.dep_opts), ( "SwitchPipelineNode must contain at least " "one PipelineNode." ) - self.case_operators_var = {} - - if len(self.dep_opts) == len(self.opt_kwargs["cases"]): - # No default_operators provided - self.default_var_name = "placeholder" - elif len(self.dep_opts) == len(self.opt_kwargs["cases"]) + 1: - # default_operators provided - self.default_var_name = self.dep_vars.pop(-1) - else: - raise ValueError( - f"SwitchPipelineNode deps {self.dep_opts} not matches " - f"cases {self.opt_kwargs['cases']}.", - ) - - for key, var in zip( - self.opt_kwargs["cases"], - self.dep_vars, - ): - self.case_operators_var[key] = var - - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() case_operators = {} + self.case_operators_var = {} if len(self.dep_opts) == len(self.opt_kwargs["cases"]): # No default_operators provided default_operators = placeholder + self.default_var_name = "placeholder" elif len(self.dep_opts) == len(self.opt_kwargs["cases"]) + 1: # default_operators provided default_operators = self.dep_opts.pop(-1) + self.default_var_name = self.dep_vars.pop(-1) else: raise ValueError( f"SwitchPipelineNode deps {self.dep_opts} not matches " f"cases {self.opt_kwargs['cases']}.", ) - for key, value in zip( + for key, value, var in zip( self.opt_kwargs["cases"], self.dep_opts, + self.dep_vars, ): case_operators[key] = value.pipeline + self.case_operators_var[key] = var self.opt_kwargs.pop("cases") self.source_kwargs.pop("cases") self.pipeline = SwitchPipeline( @@ -691,7 +759,7 @@ def _execute_init(self) -> None: **self.opt_kwargs, ) - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -727,18 +795,19 @@ def __init__( opt_kwargs: dict, source_kwargs: dict, dep_opts: list, + only_compile: bool = True, ) -> None: - super().__init__(node_id, opt_kwargs, source_kwargs, dep_opts) + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) assert len(self.dep_opts) == 1, "CopyNode can only have one parent!" - - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() self.pipeline = self.dep_opts[0] - def _execute(self, x: dict = None) -> dict: + def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: @@ -757,11 +826,21 @@ class BingSearchServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.service_func = partial(bing_search, **self.opt_kwargs) def compile(self) -> dict: @@ -782,11 +861,21 @@ class GoogleSearchServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.service_func = partial(google_search, **self.opt_kwargs) def compile(self) -> dict: @@ -807,11 +896,21 @@ class PythonServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.service_func = execute_python_code def compile(self) -> dict: @@ -830,11 +929,21 @@ class ReadTextServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.service_func = read_text_file def compile(self) -> dict: @@ -853,11 +962,21 @@ class WriteTextServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def _execute_init(self) -> None: - """ - Init before running. - """ - super()._execute_init() + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) self.service_func = write_text_file def compile(self) -> dict: From 0d6a728cb97feff336532d95a08fbe470c75e2a1 Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:54:07 +0800 Subject: [PATCH 11/47] support meme (#20) --------- Co-authored-by: qbc --- gallery/meme.json | 352 +++++++++++++ gallery/story.json | 496 ++++++++++++++++++ .../tool-image-composition.html | 42 ++ .../static/js/dashboard-detail-dialogue.js | 11 +- .../studio/static/js/workstation.js | 20 +- .../studio/tools/image_composition.py | 310 +++++++++++ .../web/workstation/workflow_dag.py | 131 ++++- .../web/workstation/workflow_node.py | 46 +- .../web/workstation/workflow_utils.py | 49 ++ 9 files changed, 1442 insertions(+), 15 deletions(-) create mode 100644 gallery/meme.json create mode 100644 gallery/story.json create mode 100644 src/agentscope/studio/static/html-drag-components/tool-image-composition.html create mode 100644 src/agentscope/studio/tools/image_composition.py diff --git a/gallery/meme.json b/gallery/meme.json new file mode 100644 index 000000000..950dee56f --- /dev/null +++ b/gallery/meme.json @@ -0,0 +1,352 @@ +{ + "meta": { + "index": 1, + "title": "Meme Generator", + "author": "AgentScopeTeam", + "keywords": [ + "meme", + "image" + ], + "category": "tool", + "time": "2024-09-04", + "thumbnail": "" + }, + "drawflow": { + "Home": { + "data": { + "9": { + "id": 9, + "name": "TextToImageAgent", + "data": { + "args": { + "name": "图像生成器", + "model_config_name": "wanx-v1" + } + }, + "class": "TextToImageAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "10", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "17", + "output": "input_1" + } + ] + } + }, + "pos_x": 948.8888888888889, + "pos_y": 54.111111111111114 + }, + "10": { + "id": 10, + "name": "DialogAgent", + "data": { + "args": { + "name": "我的朋友", + "sys_prompt": "你是一个疯狂的meme创造者,专精于“我的朋友认为我做了什么”的备忘录。你接受一个输入,比如“加密货币交易员”,然后输出这样的描述,描述每个场景的合适图像——让图像提示极其唤起一个非常具体的陈词滥调,高情感的描述:\"一个人坐在兰博基尼里,戴着太阳镜,拿着一叠现金自拍\"", + "model_config_name": "qwen-max" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "14", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "9", + "output": "input_1" + } + ] + } + }, + "pos_x": 489, + "pos_y": 30.88888888888889 + }, + "11": { + "id": 11, + "name": "DialogAgent", + "data": { + "args": { + "name": "我的父母", + "sys_prompt": "\"你是一个疯狂的meme创造者,专精于“我的父母认为我做了什么”的备忘录。你接受一个输入,比如“加密货币交易员”,然后输出这样的描述,描述每个场景的合适图像——让图像提示极其唤起一个非常具体的陈词滥调,高情感的描述:\"一个穿西装的华尔街狼,自信地在会议室展示加密货币图表\"", + "model_config_name": "qwen-max" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "14", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "15", + "output": "input_1" + } + ] + } + }, + "pos_x": 492, + "pos_y": 466 + }, + "12": { + "id": 12, + "name": "DialogAgent", + "data": { + "args": { + "name": "实际", + "sys_prompt": "\"你是一个疯狂的meme创造者,专精于“我实际认为我做了什么”的备忘录。你接受一个输入,比如“加密货币交易员”,然后输出这样的描述,描述每个场景的合适图像——让图像提示极其唤起一个非常具体的陈词滥调,很衰很弱很惨的描述:\"一个穿着睡衣的睡眠不足的人,在凌晨3点疯狂刷新手机,看着自己的一生积蓄在骗局中蒸发\"", + "model_config_name": "qwen-max" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "14", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "16", + "output": "input_1" + } + ] + } + }, + "pos_x": 476.77777777777777, + "pos_y": 925.2222222222222 + }, + "14": { + "id": 14, + "name": "UserAgent", + "data": { + "args": { + "name": "User" + } + }, + "class": "UserAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "10", + "output": "input_1" + }, + { + "node": "11", + "output": "input_1" + }, + { + "node": "12", + "output": "input_1" + } + ] + } + }, + "pos_x": 105, + "pos_y": 554 + }, + "15": { + "id": 15, + "name": "TextToImageAgent", + "data": { + "args": { + "name": "图像生成器", + "model_config_name": "wanx-v1" + } + }, + "class": "TextToImageAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "11", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "17", + "output": "input_1" + } + ] + } + }, + "pos_x": 961, + "pos_y": 523 + }, + "16": { + "id": 16, + "name": "TextToImageAgent", + "data": { + "args": { + "name": "图像生成器", + "model_config_name": "wanx-v1" + } + }, + "class": "TextToImageAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "12", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "17", + "output": "input_1" + } + ] + } + }, + "pos_x": 976.1111111111111, + "pos_y": 979 + }, + "17": { + "id": 17, + "name": "ImageComposition", + "data": { + "args": { + "image_urls": [], + "titles": "['我的朋友认为我...', '我的父母认为我...', '实际上我...']", + "output_path": "meme.jpg", + "row": 1, + "column": 3, + "spacing": 10, + "title_height": 100, + "font_name": "PingFang" + } + }, + "class": "ImageComposition", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "9", + "input": "output_1" + }, + { + "node": "15", + "input": "output_1" + }, + { + "node": "16", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 1758, + "pos_y": 205 + }, + "18": { + "id": 18, + "name": "dashscope_chat", + "data": { + "args": { + "config_name": "qwen-max", + "model_name": "qwen-max", + "api_key": "", + "temperature": 0, + "seed": 0, + "model_type": "dashscope_chat", + "messages_key": "input" + } + }, + "class": "dashscope_chat", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 121, + "pos_y": 65 + }, + "19": { + "id": 19, + "name": "dashscope_image_synthesis", + "data": { + "args": { + "config_name": "wanx-v1", + "model_name": "wanx-v1", + "generate_args": { + "n": 1, + "size": "1024*1024", + "temperature": 0, + "seed": 0 + }, + "model_type": "dashscope_image_synthesis", + "messages_key": "prompt", + "api_key": "" + } + }, + "class": "dashscope_image_synthesis", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 124, + "pos_y": 165.75 + } + } + } + } +} \ No newline at end of file diff --git a/gallery/story.json b/gallery/story.json new file mode 100644 index 000000000..f2e0e9d02 --- /dev/null +++ b/gallery/story.json @@ -0,0 +1,496 @@ +{ + "meta": { + "index": 0, + "title": "Story", + "author": "AgentScopeTeam", + "keywords": [ + "story", + "multi-modal" + ], + "category": "tool", + "time": "2024-09-04", + "thumbnail": "" + }, + "drawflow": { + "Home": { + "data": { + "2": { + "id": 2, + "name": "dashscope_chat", + "data": { + "args": { + "config_name": "qwen", + "model_name": "qwen-max", + "api_key": "", + "temperature": 0, + "seed": 0, + "model_type": "dashscope_chat", + "messages_key": "input" + } + }, + "class": "dashscope_chat", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": -21, + "pos_y": 42 + }, + "3": { + "id": 3, + "name": "dashscope_image_synthesis", + "data": { + "args": { + "config_name": "wanx", + "model_name": "wanx-v1", + "generate_args": { + "n": 1, + "size": "1024*1024", + "temperature": 0, + "seed": 0 + }, + "model_type": "dashscope_image_synthesis", + "messages_key": "prompt", + "api_key": "" + } + }, + "class": "dashscope_image_synthesis", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": -23, + "pos_y": 153 + }, + "4": { + "id": 4, + "name": "Message", + "data": { + "args": { + "name": "Host", + "content": "请输入您想生成的绘本故事主角名字", + "url": "" + } + }, + "class": "Message", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "5", + "output": "input_1" + } + ] + } + }, + "pos_x": -23, + "pos_y": 262 + }, + "5": { + "id": 5, + "name": "UserAgent", + "data": { + "args": { + "name": "User" + } + }, + "class": "UserAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "4", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "6", + "output": "input_1" + } + ] + } + }, + "pos_x": -25, + "pos_y": 376 + }, + "6": { + "id": 6, + "name": "DialogAgent", + "data": { + "args": { + "name": "第一幕", + "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据主角名称,发挥想象,只生成第一幕的描述,不超过30个字。\n# 输出格式为\n第一幕:xxxx", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "5", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "7", + "output": "input_1" + }, + { + "node": "10", + "output": "input_1" + }, + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 341, + "pos_y": 37 + }, + "7": { + "id": 7, + "name": "DialogAgent", + "data": { + "args": { + "name": "第二幕", + "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据第一幕的内容,只生成第二幕的描述,不超过30个字。\n# 输出格式为\n第二幕:xxxx", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "6", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "8", + "output": "input_1" + }, + { + "node": "11", + "output": "input_1" + }, + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 339, + "pos_y": 156.11111111111111 + }, + "8": { + "id": 8, + "name": "DialogAgent", + "data": { + "args": { + "name": "第三幕", + "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据第二幕的内容,只生成第三幕的描述,不超过30个字。\n# 输出格式为\n第三幕:xxxx", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "7", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "9", + "output": "input_1" + }, + { + "node": "12", + "output": "input_1" + }, + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 357, + "pos_y": 271 + }, + "9": { + "id": 9, + "name": "DialogAgent", + "data": { + "args": { + "name": "第四幕", + "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据第三幕的内容,只生成第四幕的描述,不超过30个字。\n# 输出格式为\n第四幕:xxxx", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "8", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "13", + "output": "input_1" + }, + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 341, + "pos_y": 379.1111111111111 + }, + "10": { + "id": 10, + "name": "TextToImageAgent", + "data": { + "args": { + "name": "图片生成助手1", + "model_config_name": "wanx" + } + }, + "class": "TextToImageAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "6", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 714, + "pos_y": 105 + }, + "11": { + "id": 11, + "name": "TextToImageAgent", + "data": { + "args": { + "name": "图片生成助手2", + "model_config_name": "wanx" + } + }, + "class": "TextToImageAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "7", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 711.1111111111111, + "pos_y": 221 + }, + "12": { + "id": 12, + "name": "TextToImageAgent", + "data": { + "args": { + "name": "图片生成助手3", + "model_config_name": "wanx" + } + }, + "class": "TextToImageAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "8", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 722, + "pos_y": 452 + }, + "13": { + "id": 13, + "name": "TextToImageAgent", + "data": { + "args": { + "name": "图片生成助手4", + "model_config_name": "wanx" + } + }, + "class": "TextToImageAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "9", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 700.8888888888889, + "pos_y": 571 + }, + "14": { + "id": 14, + "name": "ImageComposition", + "data": { + "args": { + "image_urls": [], + "titles": "[\"第一幕\", \"第二幕\", \"第三幕\", \"第四幕\"]", + "output_path": "test.png", + "row": 2, + "column": 2, + "spacing": 10, + "title_height": 100, + "font_name": "PingFang" + } + }, + "class": "ImageComposition", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "10", + "input": "output_1" + }, + { + "node": "11", + "input": "output_1" + }, + { + "node": "12", + "input": "output_1" + }, + { + "node": "13", + "input": "output_1" + }, + { + "node": "6", + "input": "output_1" + }, + { + "node": "7", + "input": "output_1" + }, + { + "node": "8", + "input": "output_1" + }, + { + "node": "9", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 1101, + "pos_y": -17.88888888888889 + } + } + } + } +} \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/tool-image-composition.html b/src/agentscope/studio/static/html-drag-components/tool-image-composition.html new file mode 100644 index 000000000..10505a91b --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/tool-image-composition.html @@ -0,0 +1,42 @@ +
    +
    + + + + Image composition +
    + +
    +
    +
    Image composition Configurations
    +
    + +
    + + +
    + + + +
    + + + +
    + + + +
    + + + +
    + + + +
    + +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/js/dashboard-detail-dialogue.js b/src/agentscope/studio/static/js/dashboard-detail-dialogue.js index 50afad5b0..a13520ceb 100644 --- a/src/agentscope/studio/static/js/dashboard-detail-dialogue.js +++ b/src/agentscope/studio/static/js/dashboard-detail-dialogue.js @@ -133,11 +133,16 @@ function _determineFileType(url) { // Audio let audio_suffix = [".mp3", ".wav", ".wma", ".ogg", ".aac", ".flac"]; - if (img_suffix.some((suffix) => url.endsWith(suffix))) { + const parsed_url = new URL(url); + const path = parsed_url.pathname; + + const extension = path.split('.').pop().toLowerCase(); + + if (img_suffix.includes('.' + extension)) { return "image"; - } else if (video_suffix.some((suffix) => url.endsWith(suffix))) { + } else if (video_suffix.includes('.' + extension)) { return "video"; - } else if (audio_suffix.some((suffix) => url.endsWith(suffix))) { + } else if (audio_suffix.includes('.' + extension)) { return "audio"; } return "file"; diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index b86fced24..3ebc7e04b 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -36,6 +36,7 @@ let nameToHtmlFile = { 'PythonService': 'service-execute-python.html', 'ReadTextService': 'service-read-text.html', 'WriteTextService': 'service-write-text.html', + 'ImageComposition': 'tool-image-composition.html' } // Cache the loaded html files @@ -789,6 +790,22 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { pos_x, pos_y, 'WriteTextService', {}, htmlSourceCode); break; + case 'ImageComposition': + editor.addNode('ImageComposition', 1, 1, + pos_x, pos_y, 'ImageComposition', { + "args": { + "image_urls": [], + "titles": "", + "output_path": "", + "row": 1, + "column": 1, + "spacing": 10, + "title_height": 100, + "font_name": "PingFang", + } + }, htmlSourceCode); + break; + default: } } @@ -1304,7 +1321,8 @@ function checkConditions() { if (node.inputs) { for (let inputKey in node.inputs) { - if (node.inputs[inputKey].connections && + if (node.class !== "ImageComposition" && + node.inputs[inputKey].connections && node.inputs[inputKey].connections.length > 1) { Swal.fire({ title: 'Invalid Connections', diff --git a/src/agentscope/studio/tools/image_composition.py b/src/agentscope/studio/tools/image_composition.py new file mode 100644 index 000000000..2d681fd6c --- /dev/null +++ b/src/agentscope/studio/tools/image_composition.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- +"""Image composition""" +import os +import textwrap + +from typing import List, Optional, Union, Tuple, Sequence +from io import BytesIO +from urllib.parse import urlparse +from PIL import Image, ImageDraw, ImageFont +import requests +import json5 + + +def is_url(path: str) -> bool: + """ + Check if the provided path is a URL. + + Parameters: + - path: The path to be checked. + + Returns: + - bool: True if the path is a valid URL, False otherwise. + """ + try: + result = urlparse(path) + return all([result.scheme, result.netloc]) + except ValueError: + return False + + +def is_local_file(path: str) -> bool: + """ + Check if the provided path is a local file. + + Parameters: + - path: The path to be checked. + + Returns: + - bool: True if the path exists and is a file, False otherwise. + """ + return os.path.isfile(path) + + +def text_size(text: str, font: ImageFont) -> Tuple[int, int]: + """ + Calculate the size (width and height in pixels) of the given text when + rendered with the specified font. + + This is useful when you need to know how much space text will take up on + an image before actually adding it to the image. + + Parameters: + - text (str): The text string whose size is to be measured. + - font (ImageFont.ImageFont): The font in which the text is to be rendered. + + Returns: + - Tuple[int, int]: The width and height (in pixels) as a tuple. + """ + im = Image.new(mode="P", size=(0, 0)) + + draw = ImageDraw.Draw(im) + + left, top, right, bottom = draw.textbbox((0, 0), text=text, font=font) + + width = right - left + height = bottom - top + return width, height + + +def get_min_font_size( + titles: Union[list[str], str], + font_path: Optional[str], + target_width: int, + max_height: int, +) -> Tuple[ImageFont.FreeTypeFont, Union[float, int]]: + """ + Determine the smallest font size that allows text of each title to fit + within a specified width and height. + + This function iterates through a list of titles and progressively + increases the font size from a starting value until the height of the + rendered text exceeds the maximum allowed height. The smallest font size + among all titles is returned along with an ImageFont instance of that size. + + Parameters: + - titles (List[str]): A list of text strings (titles) to be measured. + - font_path (str): The path to the font file to be used. + - target_width (int): The maximum width available for the text. + - max_height (int): The maximum height available for the text. + + Returns: + - Tuple[ImageFont.ImageFont, int]: A tuple containing an ImageFont + instance of the minimum font size and the minimum font size itself as + an integer. + """ + min_font_size = float("inf") + for text in titles: + if not text.strip(): + break + font_size = 10 # Start with a small font size + while True: + try: + font = ImageFont.truetype( + font_path, + font_size if font_path else 20, + ) + except OSError as exc: + raise FileNotFoundError( + f"Font file not found: {font_path}", + ) from exc + + sample_width, _ = text_size(text, font=font) + avg_char_width = sample_width / len(text) + chars_per_line = max(1, int(target_width / avg_char_width)) + wrapped_text = textwrap.fill(text, width=chars_per_line) + _, h = text_size(wrapped_text, font=font) + if h > max_height: + font_size -= 1 + break + font_size += 1 + min_font_size = min(min_font_size, font_size) + + min_font = ImageFont.truetype( + font_path, + min_font_size if font_path else 20, + ) + return min_font, min_font_size + + +def wrap_texts_with_font( + titles: Union[list[str], str], + font: ImageFont, + target_width: int, +) -> List[str]: + """ + Wrap a list of text strings so that each line does not exceed the + target width when rendered with the specified font. + + Parameters: + - titles (List[str]): A list of text strings (titles) to be wrapped. + - font (ImageFont.ImageFont): The font in which the text will be rendered. + - target_width (int): The maximum width each line of text should occupy. + + Returns: + - List[str]: A list of the wrapped text strings. + """ + wrapped_texts = [] + for text in titles: + sample_width, _ = text_size(text, font=font) + avg_char_width = sample_width / len(text) + chars_per_line = max(1, int(target_width / avg_char_width)) + wrapped_text = textwrap.fill(text, width=chars_per_line) + wrapped_texts.append(wrapped_text) + return wrapped_texts + + +def load_images_from_string(string: str) -> Optional[Image.Image]: + """ + Load an image from a given string that represents either a local file + path or a URL. + Parameters: + - string (str): A string that represents either a local file path or a + URL to an image. + + Returns: + - Optional[Image.Image]: A PIL Image object if the image was + successfully loaded; otherwise, None. + + """ + img = None + try: + if is_local_file(string): + img = Image.open(string) + elif is_url(string): + response = requests.get(string) + img = Image.open(BytesIO(response.content)) + except Exception as e: + print(f"Failed to load image from '{string}': {e}") + return img + + +def load_images_and_titles( + image_paths: Sequence[Union[str, object]], +) -> Tuple[List[Image.Image], List[str]]: + """ + Load images from the given paths or URLs and extract titles if available. + + Parameters: + - image_paths (List[Union[str, object]]): A list of paths where each + path can be a string URL or a local file path, or an object expected to + have 'url' or 'content' attributes. + + Returns: + - Tuple[List[Image.Image], List[str]]: A tuple containing two lists: + - The first list contains PIL Image objects loaded from the paths. + - The second list contains the extracted titles if available. + """ + images = [] + msg_titles = [] + + for path in image_paths: + if isinstance(path, str): + img = load_images_from_string(path) + if not img: + print(f"Invalid path: {path}") + continue + else: + url = getattr(path, "url", None) + if isinstance(url, list) and url: + url = url[0] + if isinstance(url, str): + img = load_images_from_string(url) + if not img: + print(f"Invalid url path: {url}") + continue + else: + title = getattr(path, "content", "") + if title: + msg_titles.append(title) + else: + print(f"Invalid path object: {path}") + continue + images.append(img) + + return images, msg_titles + + +# pylint: disable=R0912 +def stitch_images_with_grid( + image_paths: List[str], + titles: Union[List[str], str] = None, + output_path: str = None, + row: int = 1, + column: int = 1, + spacing: int = 10, + title_height: int = 100, + font_name: Optional[str] = None, +) -> str: + """ + Stitch multiple images and titles into a single image, supporting + custom grid layouts. + Now supports image loading from URLs. + + Parameters: + - image_paths: List of image file paths or URLs. + - titles: List of titles corresponding to each image. + - output_path: File path for the output image. + - row: Number of rows in the grid. + - column: Number of columns in the grid. + - spacing: The size of the gap between images. + - title_height: The height of the title area. + - font_name: font_name for rendering the titles. If None, the default + font is used. + """ + if titles and isinstance(titles, str): + titles = json5.loads(titles) + + images, msg_titles = load_images_and_titles(image_paths) + if msg_titles: + titles = msg_titles + + if not titles: + titles = [" "] * len(images) + elif len(titles) < len(images): + titles.extend([" "] * (len(images) - len(titles))) + + max_width = max(i.size[0] for i in images) + spacing + max_height = max(i.size[1] for i in images) + title_height + spacing + + total_width = column * max_width + total_height = row * max_height + + combined = Image.new("RGB", (total_width, total_height), color="white") + + font, _ = get_min_font_size( + titles, + font_name, + max_width - spacing * 2, + title_height - spacing, + ) + + wrapped_texts = wrap_texts_with_font(titles, font, max_width - spacing * 2) + + for idx, image in enumerate(images): + row_idx = idx // column + col_idx = idx % column + x = col_idx * max_width + y = row_idx * max_height + title_height + spacing * 3 + + title_x = x + spacing // 2 # Add some left padding to the title + title_y = ( + row_idx * max_height + spacing + ) # Title starts at the top of its section + + # Create draw object + draw = ImageDraw.Draw(combined) + + # Draw the wrapped text + current_y = title_y + for line in wrapped_texts[idx].split("\n"): + draw.text((title_x, current_y), line, fill="black", font=font) + current_y += text_size(line, font=font)[1] + spacing + + combined.paste(image, (x, y)) + + if output_path: + combined.save(output_path) + combined.show() + + return output_path diff --git a/src/agentscope/web/workstation/workflow_dag.py b/src/agentscope/web/workstation/workflow_dag.py index cb1b2f323..1621b4d41 100644 --- a/src/agentscope/web/workstation/workflow_dag.py +++ b/src/agentscope/web/workstation/workflow_dag.py @@ -7,7 +7,7 @@ can perform certain actions when called. """ import copy -from typing import Any +from typing import Any, Optional from loguru import logger import agentscope @@ -16,7 +16,10 @@ WorkflowNodeType, DEFAULT_FLOW_VAR, ) -from agentscope.web.workstation.workflow_utils import kwarg_converter +from agentscope.web.workstation.workflow_utils import ( + kwarg_converter, + replace_flow_name, +) try: import networkx as nx @@ -133,6 +136,8 @@ def format_python_code(code: str) -> str: 0 ] = f'agentscope.init(logger_level="DEBUG", {kwarg_converter(kwargs)})' + self.update_flow_name(place="execs") + sorted_nodes = list(nx.topological_sort(self)) sorted_nodes = [ node_id @@ -196,6 +201,7 @@ def add_as_node( WorkflowNodeType.PIPELINE, WorkflowNodeType.COPY, WorkflowNodeType.SERVICE, + WorkflowNodeType.TOOL, ]: raise NotImplementedError(node_cls) @@ -270,6 +276,116 @@ def exec_node(self, node_id: str, x_in: Any = None) -> Any: ) return out_values + def update_flow_name(self, place: str = "") -> None: + """update flow name""" + node_mapping = self.sort_parents_in_node_mapping() + + if place: + for node_id in list(nx.topological_sort(self)): + node = self.nodes[node_id] + node["compile_dict"][place] = replace_flow_name( + node["compile_dict"][place], + node_mapping[node_id][1], + node_mapping[node_id][0], + ) + + def get_labels_for_node(self) -> dict: + """get input and output labels for each node""" + roots = [node for node in self.nodes() if self.in_degree(node) == 0] + + # Initialize labels dict with empty sets for parent labels + labels = {node: (set(), "") for node in self.nodes()} + + for idx, root in enumerate(roots): + # Each root node gets a label starting with its index + self.label_nodes(root, f"{idx}", labels) + + # Convert parent label sets to sorted lists + labels = { + node: (set(sorted(parents)), label) + for node, (parents, label) in labels.items() + } + return labels + + def label_nodes( + self, + current: str, + label: str, + labels: dict, + parent: Optional[str] = None, + ) -> None: + """recursively label nodes allowing for multiple parents""" + if parent: + # Add the parent label to the current node + labels[current][0].add(parent) + + # If the current node already has a label, it's from a shorter + # path, so we don't overwrite it + if not labels[current][1]: + labels[current] = (labels[current][0], label) + + # Iterate over successors and recursively assign labels + for i, successor in enumerate(self.successors(current)): + # Append the index of the successor to the label for branching + succ_label = f"{label}_{i}" + self.label_nodes(successor, succ_label, labels, parent=label) + + def predecessor_with_edge_info(self, node_id: str) -> list[str]: + """ + Get a sorted list of predecessor node IDs for a given node, + based on edge information. + + Parameters: + - node_id (str): The ID of the node for which predecessors are to + be found and sorted. + + Returns: + - List[str]: A list of predecessor node IDs sorted based on the + custom sort key derived from the 'input_key' attribute in the edge + data. + """ + + def sort_key(x: tuple) -> tuple[int, int]: + input_key_num = int(x[2]["input_key"].split("_")[1]) + node_id_parts = x[0].split("_") + main_id = int(node_id_parts[0]) + return input_key_num, main_id + + # Get all predecessor inputs via edges with order + in_edges = self.in_edges(node_id, data=True) + sorted_in_edge_list = sorted(in_edges, key=sort_key) + return [x[0] for x in sorted_in_edge_list] + + def sort_parents_in_node_mapping(self) -> dict[str, tuple[list[str], str]]: + """ + Updates the node mapping with sorted parent labels for each node. + + Returns: + - Dict[str, Tuple[List[str], str]]: An updated node mapping where + each node ID is associated with a tuple of a sorted list of parent + labels and the node's own label. + """ + updated_node_mapping = {} + + for node_id in self.nodes(): + sorted_parents_ids = self.predecessor_with_edge_info(node_id) + _, current_label = self.get_labels_for_node().get( + node_id, + (set(), ""), + ) + + sorted_parents_labels = [] + for parent_id in sorted_parents_ids: + if parent_id in self.get_labels_for_node(): + parent_label = self.get_labels_for_node()[parent_id][1] + sorted_parents_labels.append(parent_label) + updated_node_mapping[node_id] = ( + sorted_parents_labels, + current_label, + ) + + return updated_node_mapping + def sanitize_node_data(raw_info: dict) -> dict: """ @@ -352,15 +468,12 @@ def build_dag(config: dict, only_compile: bool = True) -> ASDiGraph: # Add edges for node_id, node_info in config.items(): - outputs = node_info.get("outputs", {}) - for output_key, output_val in outputs.items(): - connections = output_val.get("connections", []) + inputs = node_info.get("inputs", {}) + for input_key, input_val in inputs.items(): + connections = input_val.get("connections", []) for conn in connections: target_node_id = conn.get("node") - # Here it is assumed that the output of the connection is - # only connected to one of the inputs. If there are more - # complex connections, modify the logic accordingly - dag.add_edge(node_id, target_node_id, output_key=output_key) + dag.add_edge(target_node_id, node_id, input_key=input_key) # Check if the graph is a DAG if not nx.is_directed_acyclic_graph(dag): diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 301483c7f..d83f990d8 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -38,6 +38,7 @@ execute_python_code, ServiceToolkit, ) +from agentscope.studio.tools.image_composition import stitch_images_with_grid DEFAULT_FLOW_VAR = "flow" @@ -51,6 +52,7 @@ class WorkflowNodeType(IntEnum): SERVICE = 3 MESSAGE = 4 COPY = 5 + TOOL = 6 class WorkflowNode(ABC): @@ -184,9 +186,9 @@ def __call__(self, x: dict = None) -> dict: def compile(self) -> dict: return { "imports": "from agentscope.message import Msg", - "inits": f"{DEFAULT_FLOW_VAR} = Msg" + "inits": "", + "execs": f"{DEFAULT_FLOW_VAR} = Msg" f"({kwarg_converter(self.opt_kwargs)})", - "execs": "", } @@ -988,6 +990,45 @@ def compile(self) -> dict: } +class ImageCompositionNode(WorkflowNode): + """ + Image Composition Node + """ + + node_type = WorkflowNodeType.TOOL + + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) + self.pipeline = partial(stitch_images_with_grid, **self.opt_kwargs) + + def __call__(self, x: dict = None) -> dict: + return self.pipeline(x) + + def compile(self) -> dict: + return { + "imports": "from agentscope.studio.tools.image_composition import " + "stitch_images_with_grid\n" + "from functools import partial\n", + "inits": f"{self.var_name} = partial(stitch_images_with_grid" + f", {kwarg_converter(self.opt_kwargs)})", + "execs": f"{DEFAULT_FLOW_VAR} = {self.var_name}" + f"([{DEFAULT_FLOW_VAR}])", + } + + NODE_NAME_MAPPING = { "dashscope_chat": ModelNode, "openai_chat": ModelNode, @@ -1013,6 +1054,7 @@ def compile(self) -> dict: "PythonService": PythonServiceNode, "ReadTextService": ReadTextServiceNode, "WriteTextService": WriteTextServiceNode, + "ImageComposition": ImageCompositionNode, } diff --git a/src/agentscope/web/workstation/workflow_utils.py b/src/agentscope/web/workstation/workflow_utils.py index 0e7f731fa..70e776b6e 100644 --- a/src/agentscope/web/workstation/workflow_utils.py +++ b/src/agentscope/web/workstation/workflow_utils.py @@ -2,6 +2,7 @@ """Workflow node utils.""" import ast import builtins +import re from typing import Any @@ -63,3 +64,51 @@ def dict_converter(dictionary: dict) -> str: for key, value in dictionary.items(): result_parts.append(f'"{key}": {value}') return "{" + ", ".join(result_parts) + "}" + + +def replace_flow_name( + string: str, + output_value: str, + input_values: list, +) -> str: + """Replaces 'flow' based on its position relative to an equals sign, + treating occurrences on both sides independently.""" + + def format_replace_value(value: str) -> str: + """Concatenates 'flow_' prefix with value, or 'flow' for empty + value.""" + return f"flow_{value}" if value else "flow" + + # Prepare the replacement values + output_value_formatted = format_replace_value(output_value) + formatted_input_values = [ + format_replace_value(value) for value in input_values + ] + + # Split the string by the first equals sign, if present + parts = string.split("=", 1) + left_part = parts[0] if parts else "" + right_part = parts[1] if len(parts) > 1 else "" + + # Replace 'flow' on the left side of '=' with the output_value_formatted + left_part = re.sub( + r"\bflow\b", + output_value_formatted, + left_part, + count=1, + ) + + # Replace 'flow' on the right side of '=' with the formatted input + # values string + if "flow" in right_part: + input_values_str = ", ".join(formatted_input_values) + # Here we replace all occurrences of 'flow' on the right side + right_part = re.sub(r"\bflow\b", input_values_str, right_part) + + # Reassemble the string + if len(parts) > 1: + string = "=".join([left_part, right_part]) + else: + string = left_part # In case there was no '=' in the original string + + return string From d7498289a6621ca764ac38d1e98d05502b6f5f93 Mon Sep 17 00:00:00 2001 From: zhijianma Date: Fri, 6 Sep 2024 17:56:16 +0800 Subject: [PATCH 12/47] add text to audio (#14) --- .../html-drag-components/model-wanx.html | 2 - .../service-text-to-audio.html | 43 +++++++++ .../service-text-to-image.html | 40 +++++++++ .../studio/static/js/workstation.js | 82 +++++++++++++++-- .../static/workstation_templates/en3.json | 4 +- .../static/workstation_templates/en4.json | 2 +- .../static/workstation_templates/zh3.json | 4 +- .../static/workstation_templates/zh4.json | 2 +- .../studio/templates/workstation.html | 10 +++ .../web/workstation/workflow_node.py | 90 +++++++++++++++++-- 10 files changed, 259 insertions(+), 20 deletions(-) create mode 100644 src/agentscope/studio/static/html-drag-components/service-text-to-audio.html create mode 100644 src/agentscope/studio/static/html-drag-components/service-text-to-image.html diff --git a/src/agentscope/studio/static/html-drag-components/model-wanx.html b/src/agentscope/studio/static/html-drag-components/model-wanx.html index 28e8c6d46..9aaefbac7 100644 --- a/src/agentscope/studio/static/html-drag-components/model-wanx.html +++ b/src/agentscope/studio/static/html-drag-components/model-wanx.html @@ -35,6 +35,4 @@ min="0" max="2" step="0.1">
    - -
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html b/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html new file mode 100644 index 000000000..d4f6e2e46 --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html @@ -0,0 +1,43 @@ +
    +
    + + + + + TextToAudioService +
    + +
    +
    +
    Integrate the Text to Audio Service within ReActAgent + to enhance agent capabilities +
    + +
    + + + + + +
    + + + +
    diff --git a/src/agentscope/studio/static/html-drag-components/service-text-to-image.html b/src/agentscope/studio/static/html-drag-components/service-text-to-image.html new file mode 100644 index 000000000..9d6cdb7a4 --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/service-text-to-image.html @@ -0,0 +1,40 @@ +
    +
    + + + + + TextToImageService +
    + +
    +
    +
    Integrate the Text to Image Service within ReActAgent + to enhance agent capabilities +
    + +
    + + + + + +
    + + +
    + + +
    + + +
    diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 3ebc7e04b..81abbcc44 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -36,9 +36,23 @@ let nameToHtmlFile = { 'PythonService': 'service-execute-python.html', 'ReadTextService': 'service-read-text.html', 'WriteTextService': 'service-write-text.html', + 'TextToAudioService': 'service-text-to-audio.html', + 'TextToImageService': 'service-text-to-image.html', 'ImageComposition': 'tool-image-composition.html' } +const ModelNames48k = [ + 'sambert-zhinan-v1', + "sambert-zhiqi-v1", + "sambert-zhichu-v1", + "sambert-zhide-v1", + "sambert-zhijia-v1", + "sambert-zhiru-v1", + "sambert-zhiqian-v1", + "sambert-zhixiang-v1", + "sambert-zhiwei-v1", +] + // Cache the loaded html files let htmlCache = {}; @@ -485,8 +499,7 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { "api_key": '', "temperature": 0.0, "seed": 0, - "model_type": 'dashscope_chat', - "messages_key": 'input' + "model_type": 'dashscope_chat' } }, htmlSourceCode); @@ -504,8 +517,7 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { "api_key": '', "temperature": 0.0, "seed": 0, - "model_type": 'openai_chat', - "messages_key": 'messages' + "model_type": 'openai_chat' } }, htmlSourceCode); @@ -579,8 +591,7 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { "temperature": 0.0, "seed": 0, }, - "model_type": 'dashscope_image_synthesis', - "messages_key": 'prompt' + "model_type": 'dashscope_image_synthesis' } }, htmlSourceCode); addEventListenersToNumberInputs(dashscope_image_synthesisId); @@ -790,6 +801,28 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { pos_x, pos_y, 'WriteTextService', {}, htmlSourceCode); break; + case 'TextToAudioService': + const TextToAudioServiceID = editor.addNode('TextToAudioService', 0, 0, + pos_x, pos_y, 'TextToAudioService', { + "args": { + "model": "", + "api_key": "", + "sample_rate": "" + } + }, htmlSourceCode); + updateSampleRate(TextToAudioServiceID) + break; + case 'TextToImageService': + editor.addNode('TextToImageService', 0, 0, + pos_x, pos_y, 'TextToImageService', { + "args": { + "model": "", + "api_key": "", + "n": 1, + "size":"" + } + }, htmlSourceCode); + break; case 'ImageComposition': editor.addNode('ImageComposition', 1, 1, pos_x, pos_y, 'ImageComposition', { @@ -810,6 +843,43 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { } } +function updateSampleRate(nodeId) { + const newNode = document.getElementById(`node-${nodeId}`); + if (!newNode) { + console.error(`Node with ID node-${nodeId} not found.`); + return; + } + + const modelNameInput = newNode.querySelector('#model_name'); + function updateSampleRateValue() { + const modelName = modelNameInput ? modelNameInput.value : ''; + + if (ModelNames48k.includes(modelName)) { + sampleRate = 48000 + } else { + sampleRate = 16000 + } + + const sampleRateInput = newNode.querySelector('#sample_rate'); + + if (sampleRateInput) { + sampleRateInput.value = sampleRate; + var nodeData = editor.getNodeFromId(nodeId).data; + nodeData.args.sample_rate = sampleRate + nodeData.args.model = modelName + editor.updateNodeDataFromId(nodeId, nodeData); + + console.log(`${modelName} sample rate updated to: ${sampleRate}`); + } else { + console.log(`Sample Rate input not found.`); + } + } + + if (modelNameInput) { + modelNameInput.addEventListener('input', updateSampleRateValue); + } +} + function setupTextInputListeners(nodeId) { const newNode = document.getElementById(`node-${nodeId}`); if (newNode) { diff --git a/src/agentscope/studio/static/workstation_templates/en3.json b/src/agentscope/studio/static/workstation_templates/en3.json index d884a9a5b..dd21afda9 100644 --- a/src/agentscope/studio/static/workstation_templates/en3.json +++ b/src/agentscope/studio/static/workstation_templates/en3.json @@ -54,8 +54,8 @@ "name": "SequentialPipeline", "data": { "elements": [ - "6", - "5" + "5", + "6" ] }, "class": "GROUP", diff --git a/src/agentscope/studio/static/workstation_templates/en4.json b/src/agentscope/studio/static/workstation_templates/en4.json index ddb39b327..4259f0a88 100644 --- a/src/agentscope/studio/static/workstation_templates/en4.json +++ b/src/agentscope/studio/static/workstation_templates/en4.json @@ -67,9 +67,9 @@ "name": "SequentialPipeline", "data": { "elements": [ + "7", "8", "9", - "7", "10" ] }, diff --git a/src/agentscope/studio/static/workstation_templates/zh3.json b/src/agentscope/studio/static/workstation_templates/zh3.json index cd884ebc0..b30d60192 100644 --- a/src/agentscope/studio/static/workstation_templates/zh3.json +++ b/src/agentscope/studio/static/workstation_templates/zh3.json @@ -56,8 +56,8 @@ "name": "SequentialPipeline", "data": { "elements": [ - "6", - "5" + "5", + "6" ] }, "class": "GROUP", diff --git a/src/agentscope/studio/static/workstation_templates/zh4.json b/src/agentscope/studio/static/workstation_templates/zh4.json index 18acfe7b5..42a448b60 100644 --- a/src/agentscope/studio/static/workstation_templates/zh4.json +++ b/src/agentscope/studio/static/workstation_templates/zh4.json @@ -69,9 +69,9 @@ "name": "SequentialPipeline", "data": { "elements": [ + "7", "8", "9", - "7", "10" ] }, diff --git a/src/agentscope/studio/templates/workstation.html b/src/agentscope/studio/templates/workstation.html index fe287bea1..936fde5cb 100644 --- a/src/agentscope/studio/templates/workstation.html +++ b/src/agentscope/studio/templates/workstation.html @@ -241,6 +241,16 @@ draggable="true" ondragstart="drag(event)"> Write Text +
  • + Text to Audio +
  • +
  • + Text to Image +
  • diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index d83f990d8..a231a1a3d 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -36,6 +36,8 @@ read_text_file, write_text_file, execute_python_code, + dashscope_text_to_audio, + dashscope_text_to_image, ServiceToolkit, ) from agentscope.studio.tools.image_composition import stitch_images_with_grid @@ -808,6 +810,7 @@ def __init__( ) assert len(self.dep_opts) == 1, "CopyNode can only have one parent!" self.pipeline = self.dep_opts[0] + self.var_name = self.pipeline.var_name def __call__(self, x: dict = None) -> dict: return self.pipeline(x) @@ -847,7 +850,7 @@ def __init__( def compile(self) -> dict: return { - "imports": "from agentscope.service import ServiceFactory\n" + "imports": "from agentscope.service import ServiceToolkit\n" "from functools import partial\n" "from agentscope.service import bing_search", "inits": f"{self.var_name} = partial(bing_search," @@ -882,7 +885,7 @@ def __init__( def compile(self) -> dict: return { - "imports": "from agentscope.service import ServiceFactory\n" + "imports": "from agentscope.service import ServiceToolkit\n" "from functools import partial\n" "from agentscope.service import google_search", "inits": f"{self.var_name} = partial(google_search," @@ -917,7 +920,7 @@ def __init__( def compile(self) -> dict: return { - "imports": "from agentscope.service import ServiceFactory\n" + "imports": "from agentscope.service import ServiceToolkit\n" "from agentscope.service import execute_python_code", "inits": f"{self.var_name} = execute_python_code", "execs": "", @@ -950,7 +953,7 @@ def __init__( def compile(self) -> dict: return { - "imports": "from agentscope.service import ServiceFactory\n" + "imports": "from agentscope.service import ServiceToolkit\n" "from agentscope.service import read_text_file", "inits": f"{self.var_name} = read_text_file", "execs": "", @@ -983,13 +986,83 @@ def __init__( def compile(self) -> dict: return { - "imports": "from agentscope.service import ServiceFactory\n" + "imports": "from agentscope.service import ServiceToolkit\n" "from agentscope.service import write_text_file", "inits": f"{self.var_name} = write_text_file", "execs": "", } +class TextToAudioServiceNode(WorkflowNode): + """ + Text to Audio Service Node + """ + + node_type = WorkflowNodeType.SERVICE + + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) + self.service_func = partial(dashscope_text_to_audio, **self.opt_kwargs) + + def compile(self) -> dict: + return { + "imports": "from agentscope.service import ServiceToolkit\n" + "from functools import partial\n" + "from agentscope.service import dashscope_text_to_audio", + "inits": f"{self.var_name} = partial(dashscope_text_to_audio," + f" {kwarg_converter(self.opt_kwargs)})", + "execs": "", + } + + +class TextToImageServiceNode(WorkflowNode): + """ + Text to Image Service Node + """ + + node_type = WorkflowNodeType.SERVICE + + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) + self.service_func = partial(dashscope_text_to_image, **self.opt_kwargs) + + def compile(self) -> dict: + return { + "imports": "from agentscope.service import ServiceToolkit\n" + "from functools import partial\n" + "from agentscope.service import dashscope_text_to_image", + "inits": f"{self.var_name} = partial(dashscope_text_to_image," + f" {kwarg_converter(self.opt_kwargs)})", + "execs": "", + } + + class ImageCompositionNode(WorkflowNode): """ Image Composition Node @@ -1054,6 +1127,8 @@ def compile(self) -> dict: "PythonService": PythonServiceNode, "ReadTextService": ReadTextServiceNode, "WriteTextService": WriteTextServiceNode, + "TextToAudioService": TextToAudioServiceNode, + "TextToImageService": TextToImageServiceNode, "ImageComposition": ImageCompositionNode, } @@ -1083,8 +1158,11 @@ def get_all_agents( all_agents = [] for participant in node.pipeline.participants: + if participant.node_type == WorkflowNodeType.COPY: + participant = participant.pipeline + if participant.node_type == WorkflowNodeType.AGENT: - if participant not in seen_agents: + if participant.pipeline not in seen_agents: if return_var: all_agents.append(participant.var_name) else: From 6fc2ec88f8a6108f67d270c0c78e64c07aad326e Mon Sep 17 00:00:00 2001 From: rabZhang <515184034@qq.com> Date: Fri, 6 Sep 2024 17:56:59 +0800 Subject: [PATCH 13/47] Refine guide logout v0 (#25) --- src/agentscope/studio/static/css/base.css | 68 +++- .../studio/static/css/workstation.css | 153 +++++++++ .../studio/static/js/workstation.js | 312 ++++++++++++++++++ .../studio/templates/workstation.html | 49 ++- 4 files changed, 580 insertions(+), 2 deletions(-) diff --git a/src/agentscope/studio/static/css/base.css b/src/agentscope/studio/static/css/base.css index 98dc0b017..c6ea9a188 100644 --- a/src/agentscope/studio/static/css/base.css +++ b/src/agentscope/studio/static/css/base.css @@ -34,6 +34,7 @@ a { .page-titlebar { display: flex; + justify-content: space-between; width: 100%; height: var(--page-titlebar-height); flex-direction: row; @@ -41,4 +42,69 @@ a { border-bottom: 1px solid var(--border-color); box-sizing: border-box; padding: 0 20px; -} \ No newline at end of file +} + +.github-user-content{ + display: flex; + align-items: center; +} +.user-detail{ + display: flex; + position: relative; + cursor: pointer; +} +.user-option{ + position: relative; + display: flex;; + margin-right: 5px; +} +.user-option a{ + color: black; + text-decoration: none; + font-weight: normal; +} +.triangle-down { + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid gray; + position: relative; + transform: translateY(10px); + margin-left: 5px; +} + +.user-detail:hover .dropdown { + display: block; +} + +.dropdown { + display: none; + position: absolute; + width: 100%; + top:100%; + left: 0; + background-color: #f9f9f9; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; + white-space: nowrap; + border-radius: 0 0px 5px 5px; + overflow: hidden; +} + +.dropdown a { + color: black; + padding: 4px 8px; + text-decoration: none; + display: block; +} + +.dropdown a:hover { + background-color: #f1f1f1; +} + +.triangle-down:hover { + border-top: 5px solid #000; +} + + diff --git a/src/agentscope/studio/static/css/workstation.css b/src/agentscope/studio/static/css/workstation.css index b7fe4f555..323910042 100644 --- a/src/agentscope/studio/static/css/workstation.css +++ b/src/agentscope/studio/static/css/workstation.css @@ -839,4 +839,157 @@ pre[class*="language-"] { font-size: 16px; line-height: 1.2; resize: vertical; +} + +.tour-guide { + display: none; + z-index: 3; +} + +.tour-step { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: 10px; + border: 1px solid #ccc; + border-radius: 5px; + background-color: #fff; +} + +.tour-arrow { + position: absolute; + top: 50%; + left: -10px; + width: 0; + height: 0; + border-style: solid; + border-width: 10px 10px 10px 0; + border-color: transparent #ccc transparent transparent; + transform: translateY(-50%); +} + +.tour-content { + display: flex; + flex-direction: column; + align-items: center; + border-radius: 5px; + background-color: #fff; +} +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1; + display: none; +} + +.guide-Example{ + background: #fff; + height: fit-content; + border-radius: 4px; + border: 1px solid #fff; + color: #000; + z-index: 2; +} + +.guide-skip{ + padding: 8px 12px; + margin: 4px; + border: none; + border-radius: 4px; + background-color: var(--main-color); + color: white; + cursor: pointer; + font-size: 16px; + margin-top: 10px; +} +.notification { + position: fixed; + flex-direction: column; + padding: 10px 30px; + border: 1px solid #ccc; + border-radius: 10px; + background-color: #fff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + font-size: 14px; + line-height: 1.5; + z-index: 1000; + overflow: hidden; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + transition: right 0.5s ease-out, left 0.5s ease-out; +} + +.notification-arrow { + position: absolute; + width: 10px; + height: 10px; + background-color: #ccc; + transform: rotate(45deg); +} + +.notification-title { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + margin-bottom: 5px; +} + +.notification-content { + margin-bottom: 5px; +} + +.notification-close { + cursor: pointer; + color: #ccc; + font-size: 12px; + font-weight: bold; + transition: transform 0.5s, color 0.5s; +} +.notification-close:hover { + color: #000; + transform: rotate(180deg); +} +.notification-close:mouseout { + color: #ccc; + transform: rotate(0deg); +} +.notification-clickBotton-box{ + align-self: end; + display: flex; + gap: 5px; +} +.notification-btn{ + padding: 8px 12px; + margin: 4px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; +} +.notification-btn:hover{ + transform: translateY(-2px); +} +.notification-btn.confirmBotton{ + background-color: var(--main-color); + color: white; +} +.notification-btn.cancelBotton{ + background-color:#ccc; +} +.notification-progress{ + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 4px; + background-color: #4CAF50; + transition: width 0.1s linear; } \ No newline at end of file diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 81abbcc44..852dd814a 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -352,6 +352,10 @@ async function initializeWorkstationPage() { }); setTimeout(showSurveyModal, 30000); + + if(!localStorage.getItem('firstGuide')){ + startGuide(); + } } @@ -2252,6 +2256,10 @@ function importExample(index) { function importExample_step(index) { + if(!localStorage.getItem('firstGuide')){ + localStorage.setItem('firstGuide', 'true'); + skipGuide(); + } fetchExample(index, data => { const dataToImportStep = data.json; addHtmlAndReplacePlaceHolderBeforeImport(dataToImportStep).then(() => { @@ -2412,3 +2420,307 @@ function showSurveyModal() { function hideSurveyModal() { document.getElementById("surveyModal").style.display = "none"; } + +function startGuide(){ + const targetElement = document.querySelector('.guide-Example'); + const element = document.querySelector('.tour-guide'); + positionElementRightOf(element, targetElement); +} + +function getElementCoordinates(targetElement) { + const style = window.getComputedStyle(targetElement); + const rect = targetElement.getBoundingClientRect(); + return { + left: rect.left + (parseFloat(style.left) || 0), + top: rect.top + (parseFloat(style.top) || 0), + right: rect.right + (parseFloat(style.top) || 0), + bottom: rect.bottom + (parseFloat(style.top) || 0), + width: rect.width, + height: rect.height, + x: rect.x, + y: rect.y, + }; +} + +function positionElementRightOf(element, targetElement) { + const targetCoordinates = getElementCoordinates(targetElement); + const mask = document.querySelector(".overlay"); + mask.style.display = "block"; + element.style.position = 'absolute'; + element.style.display = 'block'; + element.style.left = `${targetCoordinates.x + targetCoordinates.right}px`; + element.style.top = `${targetCoordinates.y}px`; +} + +function skipGuide(){ + const element = document.querySelector(".tour-guide"); + const mask = document.querySelector(".overlay"); + localStorage.setItem('firstGuide', 'true'); + if(element){ + element.style.display = "none"; + element.remove(); + mask.style.display = "none"; + mask.remove(); + } +} +class Notification { + static count = 0; + static instances = []; + static clearInstances() { + Notification.count = 0; + Notification.instances = []; + } + constructor(props) { + Notification.count += 1; + Notification.instances.push(this); + this.currentIndex = Notification.count; + this.position = 'bottom-right'; + this.title = 'Notification Title'; + this.content = 'Notification Content'; + this.element = null; + this.closeBtn = true; + this.progress = false; + this.intervalTime = 3000; + this.confirmBtn = false; + this.cancelBtn = false; + this.pause = true; + this.reduceNumber = 0; + this.init(props); + } + init(props){ + this.setDefaultValues(props); + this.element = document.createElement('div'); + // init notification-box css + this.element.className = 'notification'; + // render title + this.title && this.renderTitle(this.title); + // render closeButtion + this.closeBtn && this.renderCloseButton(); + // render content + this.content && this.renderContent(this.content); + // render confirmBtn + (this.confirmBtn || this.cancelBtn) && this.renderClickButton(); + this.progress && this.renderProgressBar(); + // set position + this.setPosition(this.position); + document.body.appendChild(this.element); + setTimeout(()=>{ + this.show(); + },10) + } + // check if string is HTML + isHTMLString(string){ + const doc = new DOMParser().parseFromString(string, 'text/html'); + return Array.from(doc.body.childNodes).some(node => node.nodeType === 1); + } + // render closeButtion + renderCloseButton(){ + this.closeBtn = document.createElement('span'); + this.closeBtn.className = 'notification-close'; + this.closeBtn.innerText = 'X'; + this.closeBtn.onclick = this.destroyAll.bind(this); + this.title.appendChild(this.closeBtn); + } + // render title string or HTML + renderTitle(component){ + if(this.isHTMLString(component)){ + this.title = document.createElement('div'); + this.title.className = 'notification-title'; + this.title.innerHTML = component; + }else{ + this.title = document.createElement('div'); + this.titleText = document.createElement('div'); + this.title.className = 'notification-title'; + this.titleText.className = 'notification-titleText'; + this.titleText.innerText = component; + this.title.appendChild(this.titleText); + } + this.element.appendChild(this.title); + } + // render content string or HTML + renderContent(component){ + if(this.isHTMLString(component)){ + this.content = document.createElement('div'); + this.content.className = 'notification-content'; + this.content.innerHTML = component; + }else{ + this.content = document.createElement('div'); + this.content.className = 'notification-content'; + this.content.innerText = component; + } + this.element.appendChild(this.content); + } + // render clickbtn + renderClickButton(){ + if(this.confirmBtn || this.cancelBtn){ + this.clickBottonBox = document.createElement('div'); + this.clickBottonBox.className = 'notification-clickBotton-box'; + } + if(this.confirmBtn){ + this.confirmBotton = document.createElement('button'); + this.confirmBotton.className = 'notification-btn confirmBotton'; + this.confirmBotton.innerText = this.confirmBtn; + this.confirmBotton.onclick = this.onConfirmCallback.bind(this); + this.clickBottonBox.appendChild(this.confirmBotton); + } + if(this.cancelBtn){ + this.cancelBotton = document.createElement('button'); + this.cancelBotton.className = 'notification-btn cancelBotton'; + this.cancelBotton.innerText = this.cancelBtn; + this.cancelBotton.onclick = this.onCancelCallback.bind(this); + this.clickBottonBox.appendChild(this.cancelBotton); + } + this.element.appendChild(this.clickBottonBox); + } + // render progress bar + renderProgressBar(){ + this.progressBar = document.createElement('div'); + this.progressBar.className = 'notification-progress'; + this.element.appendChild(this.progressBar); + } + // stepProgressBar + stepProgressBar(callback){ + let startTime = performance.now(); + const step = (timestamp) => { + const progress = Math.min((timestamp + this.reduceNumber - startTime) / this.intervalTime, 1); + this.progressBar.style.width = ( 1- progress ) * 100 + '%'; + if (progress < 1 && this.pause == false) { + requestAnimationFrame(step) + }else{ + this.reduceNumber = timestamp + this.reduceNumber - startTime + } + if(progress == 1){ + this.pause == true; + this.reduceNumber = 0; + callback(); + this.removeChild(); + } + } + requestAnimationFrame(step); + } + setDefaultValues(props) { + for (const key in props) { + if (props[key] === undefined) { + return ; + } else { + this[key] = props[key]; + } + } + } + setPosition() { + switch (this.position) { + case 'top-left': + this.element.style.top = '25px'; + this.element.style.left = '-100%'; + break; + case 'top-right': + this.element.style.top = '25px'; + this.element.style.right = '-100%'; + break; + case 'bottom-right': + this.element.style.bottom = '25px'; + this.element.style.right = '-100%'; + break; + case 'bottom-left': + this.element.style.bottom = '25px'; + this.element.style.left = '-100%'; + break; + } + } + show() { + this.element.style.display = 'flex'; + switch (this.position) { + case 'top-left': + this.element.style.top = '25px'; + this.element.style.left = '25px'; + break; + case 'top-right': + this.element.style.top = '25px'; + this.element.style.right = '25px'; + break; + case 'bottom-right': + this.element.style.bottom = '25px'; + this.element.style.right = '25px'; + break; + case 'bottom-left': + this.element.style.bottom = '25px'; + this.element.style.left = '25px'; + break; + } + } + // hide() { + // // this.element.style.display = 'none'; + // } + destroyAll() { + for (const instance of Notification.instances) { + document.body.removeChild(instance.element); + } + Notification.clearInstances(); + } + removeChild() { + let removeIndex; + for (let i = 0; i < Notification.instances.length; i++) { + if (Notification.instances[i].currentIndex === this.currentIndex) { + removeIndex = i; + break; + } + } + if (removeIndex !== undefined) { + Notification.instances.splice(removeIndex, 1); + } + this.element.remove(); + } + addCloseListener() { + this.closeBtn.addEventListener('click', () => { + this.removeChild(); + }); + } + + onCancelCallback() { + if (typeof this.onCancel === 'function') { + this.onCancel(); + this.removeChild(); + } + } + + onConfirmCallback() { + if (typeof this.onConfirm === 'function') { + this.pause = !this.pause + if(!this.pause){ + this.stepProgressBar(this.onConfirm); + this.confirmBotton.innerText = 'pause' + }else{ + this.confirmBotton.innerText = this.confirmBtn + } + } + } +} + +function createNotification({ + title = 'Notification', + content = 'Notification content', + position = 'top-right', + intervalTime = 3000, + progress = true, + confirmBtn = 'Yes', + cancelBtn = 'No', + closeBtn = true, + onConfirm = () => { + }, + onCancel = () => { + + } +}) { + const notice = new Notification({ + title: title, + content: content, + position: position, + closeBtn: closeBtn, + intervalTime: intervalTime, + progress: progress, + confirmBtn: confirmBtn, + cancelBtn: cancelBtn, + onConfirm: onConfirm, + onCancel: onCancel + }); +} \ No newline at end of file diff --git a/src/agentscope/studio/templates/workstation.html b/src/agentscope/studio/templates/workstation.html index 936fde5cb..6a08845e5 100644 --- a/src/agentscope/studio/templates/workstation.html +++ b/src/agentscope/studio/templates/workstation.html @@ -43,10 +43,46 @@
    Workstation + {% if token_dict %} +
    + +
    + +
    +
    + {% endif %}
    -
  • +
  • Example
    +
    +
    +
    + Use examples Learn more + +
    +
    +
  • +
  • From 22873653aead048c43dd51b8dac60c002cd28bde Mon Sep 17 00:00:00 2001 From: zhijianma Date: Mon, 9 Sep 2024 18:12:44 +0800 Subject: [PATCH 14/47] fix: remove image_urls in gallery and multi-input for temp (#27) --- gallery/meme.json | 1 - gallery/story.json | 1 - src/agentscope/studio/static/js/workstation.js | 1 - src/agentscope/web/workstation/workflow_dag.py | 4 +++- src/agentscope/web/workstation/workflow_node.py | 8 +++++--- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/gallery/meme.json b/gallery/meme.json index 950dee56f..59637dbf3 100644 --- a/gallery/meme.json +++ b/gallery/meme.json @@ -262,7 +262,6 @@ "name": "ImageComposition", "data": { "args": { - "image_urls": [], "titles": "['我的朋友认为我...', '我的父母认为我...', '实际上我...']", "output_path": "meme.jpg", "row": 1, diff --git a/gallery/story.json b/gallery/story.json index f2e0e9d02..44085b11e 100644 --- a/gallery/story.json +++ b/gallery/story.json @@ -432,7 +432,6 @@ "name": "ImageComposition", "data": { "args": { - "image_urls": [], "titles": "[\"第一幕\", \"第二幕\", \"第三幕\", \"第四幕\"]", "output_path": "test.png", "row": 2, diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 852dd814a..85ab5c791 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -831,7 +831,6 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { editor.addNode('ImageComposition', 1, 1, pos_x, pos_y, 'ImageComposition', { "args": { - "image_urls": [], "titles": "", "output_path": "", "row": 1, diff --git a/src/agentscope/web/workstation/workflow_dag.py b/src/agentscope/web/workstation/workflow_dag.py index 1621b4d41..87ccdb227 100644 --- a/src/agentscope/web/workstation/workflow_dag.py +++ b/src/agentscope/web/workstation/workflow_dag.py @@ -110,9 +110,11 @@ def run(self) -> None: ] if not inputs: values[node_id] = self.exec_node(node_id) - elif len(inputs): + elif len(inputs) == 1: # Note: only support exec with the first predecessor now values[node_id] = self.exec_node(node_id, inputs[0]) + elif len(inputs) > 1: + values[node_id] = self.exec_node(node_id, inputs) else: raise ValueError("Too many predecessors!") diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index a231a1a3d..31d08affe 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from enum import IntEnum from functools import partial -from typing import List, Optional +from typing import List, Optional, Any from agentscope import msghub from agentscope.agents import ( @@ -95,7 +95,7 @@ def __init__( if is_callable_expression(value): self.opt_kwargs[key] = convert_str_to_callable(value) - def __call__(self, x: dict = None): # type: ignore[no-untyped-def] + def __call__(self, x: Any = None): # type: ignore[no-untyped-def] """ Invokes the node's operations with the provided input. @@ -1087,7 +1087,9 @@ def __init__( ) self.pipeline = partial(stitch_images_with_grid, **self.opt_kwargs) - def __call__(self, x: dict = None) -> dict: + def __call__(self, x: list = None) -> dict: + if isinstance(x, dict): + x = [x] return self.pipeline(x) def compile(self) -> dict: From f2a609ed7fc6c0c9756ce9b40553e5c9f85d387b Mon Sep 17 00:00:00 2001 From: qbc Date: Wed, 11 Sep 2024 16:16:33 +0800 Subject: [PATCH 15/47] Background remover (#22) --- gallery/remove_bg.json | 143 +++++++++++++ gallery/story.json | 2 +- setup.py | 2 + .../tool-image-composition.html | 3 +- .../tool-image-motion.html | 40 ++++ .../html-drag-components/tool-post.html | 47 ++++ .../tool-video-composition.html | 32 +++ .../studio/static/js/workstation.js | 96 +++++++-- .../studio/templates/workstation.html | 15 ++ .../studio/tools/image_composition.py | 43 +--- src/agentscope/studio/tools/image_motion.py | 201 ++++++++++++++++++ src/agentscope/studio/tools/utils.py | 36 ++++ .../studio/tools/video_composition.py | 181 ++++++++++++++++ src/agentscope/studio/tools/web_post.py | 153 +++++++++++++ .../web/workstation/workflow_node.py | 133 ++++++++++++ .../web/workstation/workflow_utils.py | 8 +- 16 files changed, 1074 insertions(+), 61 deletions(-) create mode 100644 gallery/remove_bg.json create mode 100644 src/agentscope/studio/static/html-drag-components/tool-image-motion.html create mode 100644 src/agentscope/studio/static/html-drag-components/tool-post.html create mode 100644 src/agentscope/studio/static/html-drag-components/tool-video-composition.html create mode 100644 src/agentscope/studio/tools/image_motion.py create mode 100644 src/agentscope/studio/tools/utils.py create mode 100644 src/agentscope/studio/tools/video_composition.py create mode 100644 src/agentscope/studio/tools/web_post.py diff --git a/gallery/remove_bg.json b/gallery/remove_bg.json new file mode 100644 index 000000000..5ab21632a --- /dev/null +++ b/gallery/remove_bg.json @@ -0,0 +1,143 @@ +{ + "meta": { + "index": 2, + "title": "Background Remover", + "author": "AgentScopeTeam", + "keywords": [ + "background", + "image" + ], + "category": "tool", + "time": "2024-09-04", + "thumbnail": "" + }, + "drawflow": { + "Home": { + "data": { + "2": { + "id": 2, + "name": "dashscope_image_synthesis", + "data": { + "args": { + "config_name": "wanx", + "model_name": "wanx-v1", + "generate_args": { + "n": 1, + "size": "1024*1024", + "temperature": 0, + "seed": 0 + }, + "model_type": "dashscope_image_synthesis", + "messages_key": "prompt", + "api_key": "" + } + }, + "class": "dashscope_image_synthesis", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": 24.88888888888889, + "pos_y": -12 + }, + "3": { + "id": 3, + "name": "TextToImageAgent", + "data": { + "args": { + "name": "人物头像生成器", + "model_config_name": "wanx" + } + }, + "class": "TextToImageAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "4", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "5", + "output": "input_1" + } + ] + } + }, + "pos_x": 555, + "pos_y": 179 + }, + "4": { + "id": 4, + "name": "Message", + "data": { + "args": { + "name": "Host", + "content": "一个英俊帅气的男生,背景是森林", + "url": "" + } + }, + "class": "Message", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "3", + "output": "input_1" + } + ] + } + }, + "pos_x": 245.11111111111111, + "pos_y": 164 + }, + "5": { + "id": 5, + "name": "Post", + "data": { + "args": { + "url": "https://api.remove.bg/v1.0/removebg", + "image_path_or_url": "", + "headers": "{\"X-Api-Key\": \"\"}", + "data": "{\"size\": \"auto\"}", + "json": "{}", + "kwargs": "{}", + "output_path": "remove_bg.png" + } + }, + "class": "Post", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "3", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 948, + "pos_y": -60 + } + } + } + } +} \ No newline at end of file diff --git a/gallery/story.json b/gallery/story.json index 44085b11e..2d3e96be6 100644 --- a/gallery/story.json +++ b/gallery/story.json @@ -1,6 +1,6 @@ { "meta": { - "index": 0, + "index": 3, "title": "Story", "author": "AgentScopeTeam", "keywords": [ diff --git a/setup.py b/setup.py index 8966431d4..778fd79a9 100644 --- a/setup.py +++ b/setup.py @@ -110,6 +110,8 @@ "flask_babel", "babel==2.15.0", "gunicorn", + "numpy", + "opencv-python-headless", ] with open("README.md", "r", encoding="UTF-8") as fh: diff --git a/src/agentscope/studio/static/html-drag-components/tool-image-composition.html b/src/agentscope/studio/static/html-drag-components/tool-image-composition.html index 10505a91b..20317d490 100644 --- a/src/agentscope/studio/static/html-drag-components/tool-image-composition.html +++ b/src/agentscope/studio/static/html-drag-components/tool-image-composition.html @@ -2,7 +2,8 @@
    - + + Image composition
    diff --git a/src/agentscope/studio/static/html-drag-components/tool-image-motion.html b/src/agentscope/studio/static/html-drag-components/tool-image-motion.html new file mode 100644 index 000000000..c0d70d0ab --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/tool-image-motion.html @@ -0,0 +1,40 @@ +
    +
    + + + + + Image Motion +
    + +
    +
    +
    Image Motion Configurations
    +
    + + +
    + + + + + +
    + + + + +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/tool-post.html b/src/agentscope/studio/static/html-drag-components/tool-post.html new file mode 100644 index 000000000..7c66162a1 --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/tool-post.html @@ -0,0 +1,47 @@ +
    +
    + + + + + Post +
    + +
    +
    +
    Post Configurations
    +
    + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + + +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/tool-video-composition.html b/src/agentscope/studio/static/html-drag-components/tool-video-composition.html new file mode 100644 index 000000000..5ca9ef09d --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/tool-video-composition.html @@ -0,0 +1,32 @@ +
    +
    + + + + + Video composition +
    + +
    +
    +
    Video composition Configurations
    +
    + + +
    + + + +
    + + + +
    + + + +
    + +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 85ab5c791..a3ca19edc 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -36,9 +36,12 @@ let nameToHtmlFile = { 'PythonService': 'service-execute-python.html', 'ReadTextService': 'service-read-text.html', 'WriteTextService': 'service-write-text.html', + 'Post': 'tool-post.html', 'TextToAudioService': 'service-text-to-audio.html', 'TextToImageService': 'service-text-to-image.html', - 'ImageComposition': 'tool-image-composition.html' + 'ImageComposition': 'tool-image-composition.html', + 'ImageMotion': 'tool-image-motion.html', + 'VideoComposition': 'tool-video-composition.html', } const ModelNames48k = [ @@ -493,7 +496,7 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { switch (name) { // Workflow-Model case 'dashscope_chat': - const dashscope_chatId = editor.addNode('dashscope_chat', 0, 0, pos_x, + editor.addNode('dashscope_chat', 0, 0, pos_x, pos_y, 'dashscope_chat', { "args": @@ -507,11 +510,10 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { } }, htmlSourceCode); - addEventListenersToNumberInputs(dashscope_chatId); break; case 'openai_chat': - const openai_chatId = editor.addNode('openai_chat', 0, 0, pos_x, + editor.addNode('openai_chat', 0, 0, pos_x, pos_y, 'openai_chat', { "args": @@ -525,11 +527,10 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { } }, htmlSourceCode); - addEventListenersToNumberInputs(openai_chatId); break; case 'post_api_chat': - const post_api_chatId = editor.addNode('post_api_chat', 0, 0, pos_x, pos_y, + editor.addNode('post_api_chat', 0, 0, pos_x, pos_y, 'post_api_chat', { "args": { @@ -549,11 +550,10 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { } }, htmlSourceCode); - addEventListenersToNumberInputs(post_api_chatId); break; case 'post_api_dall_e': - const post_api_dall_eId = editor.addNode('post_api_dall_e', 0, + editor.addNode('post_api_dall_e', 0, 0, pos_x, pos_y, 'post_api_dall_e', { @@ -577,11 +577,10 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { } }, htmlSourceCode); - addEventListenersToNumberInputs(post_api_dall_eId); break; case 'dashscope_image_synthesis': - const dashscope_image_synthesisId = editor.addNode('dashscope_image_synthesis', 0, + editor.addNode('dashscope_image_synthesis', 0, 0, pos_x, pos_y, 'dashscope_image_synthesis', { @@ -598,7 +597,6 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { "model_type": 'dashscope_image_synthesis' } }, htmlSourceCode); - addEventListenersToNumberInputs(dashscope_image_synthesisId); break; // Message @@ -721,16 +719,14 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { break; case 'ForLoopPipeline': - const ForLoopPipelineID = - editor.addNode('ForLoopPipeline', 1, 1, pos_x, pos_y, - 'GROUP', { - elements: [], - "args": { - "max_loop": 3, - "break_func": '' - } - }, htmlSourceCode); - addEventListenersToNumberInputs(ForLoopPipelineID); + editor.addNode('ForLoopPipeline', 1, 1, pos_x, pos_y, + 'GROUP', { + elements: [], + "args": { + "max_loop": 3, + "break_func": '' + } + }, htmlSourceCode); break; case 'WhileLoopPipeline': @@ -827,6 +823,7 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { } }, htmlSourceCode); break; + case 'ImageComposition': editor.addNode('ImageComposition', 1, 1, pos_x, pos_y, 'ImageComposition', { @@ -842,6 +839,44 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { }, htmlSourceCode); break; + case 'ImageMotion': + editor.addNode('ImageMotion', 1, 1, + pos_x, pos_y, 'ImageMotion', { + "args": { + "output_path": "", + "output_format": "", + "duration": "", + } + }, htmlSourceCode); + break; + + case 'VideoComposition': + editor.addNode('VideoComposition', 1, 1, + pos_x, pos_y, 'VideoComposition', { + "args": { + "output_path": "", + "target_width": "", + "target_height": "", + "fps": "", + } + }, htmlSourceCode); + break; + + case 'Post': + editor.addNode('Post', 1, 1, + pos_x, pos_y, 'Post', { + "args": { + "url": "", + "headers": '', + "data": '', + "json": '', + "kwargs": '', + "output_path": "", + "output_type": "", + } + }, htmlSourceCode); + break; + default: } } @@ -977,6 +1012,16 @@ function validateSeed(input) { input.reportValidity(); } +function validateDuration(input) { + const value = parseInt(input.value, 10); // Parse the value as an integer. + if (isNaN(value) || value < 0 || !Number.isInteger(parseFloat(input.value))) { + input.setCustomValidity('Duration must be a non-negative integer!'); + } else { + input.setCustomValidity(''); + } + input.reportValidity(); +} + document.addEventListener('input', function (event) { const input = event.target; @@ -990,6 +1035,10 @@ document.addEventListener('input', function (event) { input.getAttribute('df-args-json_args-seed') !== null) { validateSeed(input); } + + if (input.getAttribute('df-args-duration') !== null) { + validateDuration(input) + } }); var transform = ''; @@ -1380,8 +1429,8 @@ function sortElementsByPosition(inputData) { function checkConditions() { - let hasModelTypeError = true; - let hasAgentError = true; + let hasModelTypeError = false; + let hasAgentError = false; let agentModelConfigNames = new Set(); let modelConfigNames = new Set(); let isApiKeyEmpty = false; @@ -1395,6 +1444,7 @@ function checkConditions() { if (node.inputs) { for (let inputKey in node.inputs) { if (node.class !== "ImageComposition" && + node.class !== "VideoComposition" && node.inputs[inputKey].connections && node.inputs[inputKey].connections.length > 1) { Swal.fire({ diff --git a/src/agentscope/studio/templates/workstation.html b/src/agentscope/studio/templates/workstation.html index 6a08845e5..2ae823f47 100644 --- a/src/agentscope/studio/templates/workstation.html +++ b/src/agentscope/studio/templates/workstation.html @@ -311,6 +311,21 @@ draggable="true" ondragstart="drag(event)"> Image Composition
  • +
  • + Image Motion +
  • +
  • + Video Composition +
  • +
  • + Post +
  • diff --git a/src/agentscope/studio/tools/image_composition.py b/src/agentscope/studio/tools/image_composition.py index 2d681fd6c..3cd1fbb3e 100644 --- a/src/agentscope/studio/tools/image_composition.py +++ b/src/agentscope/studio/tools/image_composition.py @@ -1,44 +1,15 @@ # -*- coding: utf-8 -*- """Image composition""" -import os import textwrap from typing import List, Optional, Union, Tuple, Sequence from io import BytesIO -from urllib.parse import urlparse from PIL import Image, ImageDraw, ImageFont import requests import json5 - -def is_url(path: str) -> bool: - """ - Check if the provided path is a URL. - - Parameters: - - path: The path to be checked. - - Returns: - - bool: True if the path is a valid URL, False otherwise. - """ - try: - result = urlparse(path) - return all([result.scheme, result.netloc]) - except ValueError: - return False - - -def is_local_file(path: str) -> bool: - """ - Check if the provided path is a local file. - - Parameters: - - path: The path to be checked. - - Returns: - - bool: True if the path exists and is a file, False otherwise. - """ - return os.path.isfile(path) +from agentscope.message import Msg +from agentscope.studio.tools.utils import is_local_file, is_url def text_size(text: str, font: ImageFont) -> Tuple[int, int]: @@ -235,7 +206,7 @@ def stitch_images_with_grid( spacing: int = 10, title_height: int = 100, font_name: Optional[str] = None, -) -> str: +) -> Msg: """ Stitch multiple images and titles into a single image, supporting custom grid layouts. @@ -307,4 +278,10 @@ def stitch_images_with_grid( combined.save(output_path) combined.show() - return output_path + return Msg( + name="ImageComposition", + role="assistant", + content=output_path, + url=output_path, + echo=True, + ) diff --git a/src/agentscope/studio/tools/image_motion.py b/src/agentscope/studio/tools/image_motion.py new file mode 100644 index 000000000..3985f0508 --- /dev/null +++ b/src/agentscope/studio/tools/image_motion.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +"""Image motion""" +import shutil +import tempfile +import urllib.request +import random +import cv2 +import numpy as np +from PIL import Image +from agentscope.message import Msg +from agentscope.studio.tools.utils import is_url + + +def get_image_path_or_url(image_msg: Msg) -> str: + """Get image path or url from image message""" + if image_msg.url: + if isinstance(image_msg.url, list): + return image_msg.url[0] + else: + return image_msg.url + elif image_msg.content: + return image_msg.content + else: + raise ValueError("Image message must have content or url") + + +def load_image(image_path: str) -> np.ndarray: + """Load an image from a local path or URL.""" + if is_url(image_path): + # Create a temporary file + with tempfile.NamedTemporaryFile(delete=False) as temp: + # Download the image from the URL and save it to the temp file + with urllib.request.urlopen(image_path) as response: + temp.write(response.read()) + # Load the image using OpenCV + img = cv2.imread(temp.name) + # Delete the temp file + shutil.rmtree(temp.name, ignore_errors=True) + else: + # Load the image from a local path + img = cv2.imread(image_path) + return img + + +def create_video_or_gif_from_image( + msg: Msg, + output_path: str, + motion_style: str = "", + duration: int = 5, + fps: int = 30, + output_format: str = "mp4", +) -> Msg: + """ + Creates a video or GIF from a single image by applying a camera + movement effect. + + Args: + msg: A Msg object containing the image path or URL in its 'url' + or 'content' attribute. + output_path: The file path where the resulting video or GIF + will be saved. + motion_style: The type of camera movement to apply. Valid + options are 'ramdom', 'left', 'right', 'zoom_in', + and 'zoom_out'. If left empty, a random movement will be chosen. + duration: Duration of the video/GIF in seconds. + fps: Frames per second of the output video/GIF. + output_format: The format of the output file. Can be either + 'mp4' for video or 'gif' for GIF animation. + + Returns: + A Msg object containing details of the output video or GIF, + including the path or URL. + + Raises: + ValueError: If an unknown camera movement or output format is + specified. + """ + + def apply_camera_move( + frame: np.ndarray, + move: str, + move_amount: int, + ) -> np.ndarray: + """ + Applies a specified camera movement effect to an image frame. + + Args: + frame: The image frame (as a numpy array) to which the + camera movement effect will be applied. It should be a + BGR image (as used by OpenCV). + move: A string specifying the type of camera movement. + Valid options are 'left', 'right', 'zoom_in', + and 'zoom_out'. + move_amount: An integer specifying the amount of movement. + For left and right movements, this corresponds to the + number of pixels the image will be shifted. For + zooming, this value affects the scale factor. + + Returns: + The modified image frame (as a numpy array) with the + applied camera movement effect. + + Raises: + ValueError: If an unknown camera movement is specified. + """ + height, width, _ = frame.shape + + if move == "left": + mat = np.float32([[1, 0, -move_amount], [0, 1, 0]]) + frame = cv2.warpAffine( + frame, + mat, + (width, height), + borderMode=cv2.BORDER_REPLICATE, + ) + elif move == "right": + mat = np.float32([[1, 0, move_amount], [0, 1, 0]]) + frame = cv2.warpAffine( + frame, + mat, + (width, height), + borderMode=cv2.BORDER_REPLICATE, + ) + elif move == "zoom_out": + center = (width // 2, height // 2) + scale_factor = 1 - move_amount / 300 + mat = cv2.getRotationMatrix2D(center, 0, scale_factor) + frame = cv2.warpAffine( + frame, + mat, + (width, height), + flags=cv2.INTER_LINEAR, + borderMode=cv2.BORDER_REPLICATE, + ) + elif move == "zoom_in": + center = (width // 2, height // 2) + scale_factor = 1 + move_amount / 300 + mat = cv2.getRotationMatrix2D(center, 0, scale_factor) + frame = cv2.warpAffine( + frame, + mat, + (width, height), + flags=cv2.INTER_LINEAR, + borderMode=cv2.BORDER_REPLICATE, + ) + else: + raise ValueError("Unknown camera move") + + return frame + + camera_moves = ["left", "right", "zoom_out", "zoom_in"] + + image_path = get_image_path_or_url(msg) + img = load_image(image_path) + height, width, _ = img.shape + size = (width, height) + + frames_count = duration * fps + if not motion_style or motion_style == "random": + move = random.choice(camera_moves) + else: + move = motion_style + frames = [] + + if output_format == "mp4": + fourcc = cv2.VideoWriter_fourcc(*"mp4v") + out = cv2.VideoWriter(output_path, fourcc, fps, size) + + for i in range(frames_count): + move_amount = int((i / frames_count) * 20) + frame = apply_camera_move( + frame=img.copy(), + move=move, + move_amount=move_amount, + ) + if output_format == "gif": + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 将BGR转换为RGB + frames.append(Image.fromarray(frame)) + else: + out.write(frame) + + if output_format == "mp4": + out.release() + elif output_format == "gif": + frames[0].save( + output_path, + save_all=True, + append_images=frames[1:], + duration=1000 / fps, + loop=0, + ) + else: + raise ValueError("Unknown output format") + + return Msg( + name="ImageMotion", + role="assistant", + content=output_path, + url=output_path, + echo=True, + ) diff --git a/src/agentscope/studio/tools/utils.py b/src/agentscope/studio/tools/utils.py new file mode 100644 index 000000000..72e891e26 --- /dev/null +++ b/src/agentscope/studio/tools/utils.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" +This module provides utilities for studio tools. +""" +import os +from urllib.parse import urlparse + + +def is_url(path: str) -> bool: + """ + Check if the provided path is a URL. + + Parameters: + - path: The path to be checked. + + Returns: + - bool: True if the path is a valid URL, False otherwise. + """ + try: + result = urlparse(path) + return all([result.scheme, result.netloc]) + except ValueError: + return False + + +def is_local_file(path: str) -> bool: + """ + Check if the provided path is a local file. + + Parameters: + - path: The path to be checked. + + Returns: + - bool: True if the path exists and is a file, False otherwise. + """ + return os.path.isfile(path) diff --git a/src/agentscope/studio/tools/video_composition.py b/src/agentscope/studio/tools/video_composition.py new file mode 100644 index 000000000..9d2054387 --- /dev/null +++ b/src/agentscope/studio/tools/video_composition.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- +"""Video composition""" +import os +import tempfile +from typing import List, Optional, Union +import cv2 +import json5 +from agentscope.message import Msg + + +def load_videos_from_string(video_files: List[Union[str, Msg]]) -> List[str]: + """ + Load video file paths from a mixed list of strings and objects with + 'url' attribute. + + Args: + video_files: a list that may contain strings or other objects with + 'url' attribute. + + Returns: + A list of video file paths as strings. + """ + videos = [] + for video in video_files: + if isinstance(video, str): + videos.append(video) + else: + url = getattr(video, "url", None) + if isinstance(url, list) and url: + videos.extend(url) + elif isinstance(url, str): + videos.append(url) + return videos + + +def get_video_properties(video_path: str) -> tuple[int, int, float]: + """ + Get properties of a video file: width, height, and frames per second. + + Args: + video_path: path to the video file. + + Returns: + A tuple containing width, height and fps of the video. + """ + video_cap = cv2.VideoCapture(video_path) + width = int(video_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + fps = video_cap.get(cv2.CAP_PROP_FPS) + video_cap.release() + return width, height, fps + + +def rescale_video( + video_path: str, + output_path: str, + width: Optional[int], + height: Optional[int], + fps: Optional[int], +) -> None: + """ + Rescale video to given width, height, and fps. + If width or height is None, the original dimensions will be used. + If fps is None, the original fps will be used. + + Args: + video_path: path to the input video file. + output_path: path where the rescaled video will be saved. + width: target width for the rescaled video. + height: target height for the rescaled video. + fps: target frames per second for the rescaled video. + """ + cap = cv2.VideoCapture(video_path) + fourcc = cv2.VideoWriter_fourcc(*"mp4v") + out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) + + while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break + frame = cv2.resize(frame, (width, height)) + out.write(frame) + + cap.release() + out.release() + + +def merge_videos( + video_files: List[Union[str, Msg]] = None, + output_path: str = None, + target_width: Optional[int] = None, + target_height: Optional[int] = None, + target_fps: Optional[int] = None, +) -> Msg: + """ + Concatenate multiple video files into a single video file. + + Args: + video_files: A list of paths to the video files to be merged. Can + also be a JSON string representing a list of video file paths. + output_path: Path to save the merged video file. + target_width: Target width for the merged video, or None to use the + width of the first video. + target_height: Target height for the merged video, or None to use the + height of the first video. + target_fps: Target frames per second for the merged video, or None to + use the FPS of the first video. + + Raises: + AssertionError: If no video files are provided. + """ + assert len(video_files) > 0, "No video files to append" + + # If video_files is a JSON string, parse it to a list + if video_files and isinstance(video_files, str): + video_files = json5.loads(video_files) + + # Extract string paths for the videos + videos = load_videos_from_string(video_files) + + # If target properties are not provided, use properties of the first video + if not all([target_width, target_height, target_fps]): + ( + first_video_width, + first_video_height, + first_video_fps, + ) = get_video_properties(videos[0]) + target_width = target_width or first_video_width + target_height = target_height or first_video_height + target_fps = target_fps or first_video_fps + + # Create a temporary directory + temp_dir = tempfile.mkdtemp() + temp_files = [] + + try: + # Rescale videos and store them in temporary files + for index, video_file in enumerate(videos): + temp_file = os.path.join(temp_dir, f"temp_video_{index}.mp4") + rescale_video( + video_file, + temp_file, + target_width, + target_height, + target_fps, + ) + temp_files.append(temp_file) + + # Initialize VideoWriter to output the merged video + fourcc = cv2.VideoWriter_fourcc(*"mp4v") + out = cv2.VideoWriter( + output_path, + fourcc, + target_fps, + (target_width, target_height), + ) + + # Read frames from temporary files and write them to the output video + for temp_file in temp_files: + video_cap = cv2.VideoCapture(temp_file) + while video_cap.isOpened(): + ret, frame = video_cap.read() + if ret: + out.write(frame) + else: + break + video_cap.release() + + finally: + out.release() + for temp_file in temp_files: + os.remove(temp_file) + os.rmdir(temp_dir) + + return Msg( + "VideoComposition", + role="assistant", + content=output_path, + url=output_path, + echo=True, + ) diff --git a/src/agentscope/studio/tools/web_post.py b/src/agentscope/studio/tools/web_post.py new file mode 100644 index 000000000..77c6c1e23 --- /dev/null +++ b/src/agentscope/studio/tools/web_post.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +""" +Post for removing background from images using a web API. +""" + +import os +from typing import Union, Tuple +from io import BytesIO + +import json5 +from PIL import Image +import requests + +from agentscope.message import Msg +from agentscope.studio.tools.utils import is_url, is_local_file + + +def web_post( + url: str, + output_path: str = "", + output_type: str = "image", + msg: Msg = None, + image_path_or_url: str = None, + data: Union[str, dict] = None, + headers: dict = None, + json: dict = False, + **kwargs: dict, +) -> Msg: + """ + Send an HTTP POST request, upload an image and process the response. + + :param url: URL to send the request to + :param output_path: Path to save the output image + :param output_type: Type of the output, can be "image" or "text" or + "audio" or "video" + :param msg: Msg object containing the image URL + :param image_path_or_url: Local path or URL of the image + :param data: Data to send, can be a string or a dictionary + :param headers: Headers to send with the request + :param json: JSON data to send + :param kwargs: Additional request parameters + :return: Path to the saved output image + """ + # Parse image source + image_url, image_path = parse_image_source(msg, image_path_or_url) + + if data and isinstance(data, str): + data = json5.loads(data) + + if json and isinstance(json, str): + json = json5.loads(json) + + if headers and isinstance(headers, str): + headers = json5.loads(headers) + + # Update the data or kwargs parameters + if image_url: + if isinstance(data, dict): + data["image_url"] = image_url + elif image_path: + with open(image_path, "rb") as image_file: + image_data = image_file.read() + kwargs["files"] = { + "image_file": ( + os.path.basename(image_path), + image_data, + ), + } + + response = requests.post( + url, + data=data, + json=json, + headers=headers, + **kwargs, + ) + return process_response(response, output_path, output_type) + + +def parse_image_source(msg: Msg, image_path_or_url: str) -> Tuple[str, str]: + """ + Parse the image source from msg or image_path_or_url. + + :param msg: Msg object containing the image URL + :param image_path_or_url: Local path or URL of the image + :return: Tuple containing image_url and image_path + """ + image_url = "" + image_path = "" + + if msg and msg.url: + if isinstance(msg.url, list): + image_url = msg.url[0] + else: + image_url = msg.url + if image_path_or_url: + if is_url(image_path_or_url): + image_url = image_path_or_url + elif is_local_file(image_path_or_url): + image_path = image_path_or_url + + return image_url, image_path + + +def process_response( + response: requests.Response, + output_path: str, + output_type: str = "image", +) -> Msg: + """ + Process the HTTP response and save the image if successful. + + :param response: HTTP response object + :param output_path: Path to save the output image + :param output_type: Type of the output, can be "image" or "text" or + "audio" or "video" + :return: Path to the saved output image + """ + if response.status_code == requests.codes.ok: + if output_type == "image": + # Read the response content into a BytesIO object + img = Image.open(BytesIO(response.content)) + + # Display the image + img.show() + + # Save the image + if output_path: + img.save(output_path) + # TODO: Handle other output types + elif output_type == "text": + # Save the text content to a file + if output_path: + with open(output_path, "w", encoding="utf-8") as text_file: + text_file.write(response.text) + elif output_type in ["audio", "video"]: + # Save the audio or video content to a file + if output_path: + with open(output_path, "wb") as media_file: + media_file.write(response.content) + else: + raise ValueError(f"Unsupported output type: {output_type}") + else: + # Print the error message + print("Error:", response.status_code, response.text) + + return Msg( + name="Post", + role="assistant", + content=output_path, + url=output_path, + echo=True, + ) diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 31d08affe..35eeb7901 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Workflow node opt.""" +import ast from abc import ABC, abstractmethod from enum import IntEnum from functools import partial @@ -41,6 +42,10 @@ ServiceToolkit, ) from agentscope.studio.tools.image_composition import stitch_images_with_grid +from agentscope.studio.tools.image_motion import create_video_or_gif_from_image +from agentscope.studio.tools.video_composition import merge_videos + +from agentscope.studio.tools.web_post import web_post DEFAULT_FLOW_VAR = "flow" @@ -993,6 +998,49 @@ def compile(self) -> dict: } +class PostNode(WorkflowNode): + """Post Node""" + + node_type = WorkflowNodeType.TOOL + + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) + + if "kwargs" in self.opt_kwargs: + kwargs = ast.literal_eval(self.opt_kwargs["kwargs"].strip()) + del self.opt_kwargs["kwargs"] + self.opt_kwargs.update(**kwargs) + + self.pipeline = partial(web_post, **self.opt_kwargs) + + def __call__(self, x: dict = None) -> dict: + return self.pipeline(x) + + def compile(self) -> dict: + return { + "imports": "from agentscope.studio.tools.web_post import " + "web_post\n" + "from functools import partial", + "inits": f"{self.var_name} = partial(web_post," + f"{kwarg_converter(self.opt_kwargs)})", + "execs": f"{DEFAULT_FLOW_VAR} = {self.var_name}(msg=" + f"{DEFAULT_FLOW_VAR})", + } + + class TextToAudioServiceNode(WorkflowNode): """ Text to Audio Service Node @@ -1104,6 +1152,88 @@ def compile(self) -> dict: } +class ImageMotionNode(WorkflowNode): + """ + Image Motion Node + """ + + node_type = WorkflowNodeType.TOOL + + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) + self.pipeline = partial( + create_video_or_gif_from_image, + **self.opt_kwargs, + ) + + def __call__(self, x: dict = None) -> dict: + return self.pipeline(x) + + def compile(self) -> dict: + return { + "imports": "from agentscope.studio.tools.image_motion import " + "create_video_or_gif_from_image\n" + "from functools import partial\n", + "inits": f"{self.var_name} = partial(" + f"create_video_or_gif_from_image," + f" {kwarg_converter(self.opt_kwargs)})", + "execs": f"{DEFAULT_FLOW_VAR} = {self.var_name}(msg=" + f"{DEFAULT_FLOW_VAR})", + } + + +class VideoCompositionNode(WorkflowNode): + """ + Video Composition Node + """ + + node_type = WorkflowNodeType.TOOL + + def __init__( + self, + node_id: str, + opt_kwargs: dict, + source_kwargs: dict, + dep_opts: list, + only_compile: bool = True, + ) -> None: + super().__init__( + node_id, + opt_kwargs, + source_kwargs, + dep_opts, + only_compile, + ) + self.pipeline = partial(merge_videos, **self.opt_kwargs) + + def __call__(self, x: dict = None) -> dict: + return self.pipeline(x) + + def compile(self) -> dict: + return { + "imports": "from agentscope.studio.tools.video_composition import " + "merge_videos\n" + "from functools import partial\n", + "inits": f"{self.var_name} = partial(merge_videos" + f", {kwarg_converter(self.opt_kwargs)})", + "execs": f"{DEFAULT_FLOW_VAR} = {self.var_name}" + f"([{DEFAULT_FLOW_VAR}])", + } + + NODE_NAME_MAPPING = { "dashscope_chat": ModelNode, "openai_chat": ModelNode, @@ -1129,9 +1259,12 @@ def compile(self) -> dict: "PythonService": PythonServiceNode, "ReadTextService": ReadTextServiceNode, "WriteTextService": WriteTextServiceNode, + "Post": PostNode, "TextToAudioService": TextToAudioServiceNode, "TextToImageService": TextToImageServiceNode, "ImageComposition": ImageCompositionNode, + "ImageMotion": ImageMotionNode, + "VideoComposition": VideoCompositionNode, } diff --git a/src/agentscope/web/workstation/workflow_utils.py b/src/agentscope/web/workstation/workflow_utils.py index 70e776b6e..7ab1681c3 100644 --- a/src/agentscope/web/workstation/workflow_utils.py +++ b/src/agentscope/web/workstation/workflow_utils.py @@ -81,9 +81,11 @@ def format_replace_value(value: str) -> str: # Prepare the replacement values output_value_formatted = format_replace_value(output_value) - formatted_input_values = [ - format_replace_value(value) for value in input_values - ] + formatted_input_values = ( + [format_replace_value(value) for value in input_values] + if input_values + else ["flow"] + ) # Split the string by the first equals sign, if present parts = string.split("=", 1) From d3186c48af2764a4d35effd5ac8d4e5c7dbc6dbc Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:56:59 +0800 Subject: [PATCH 16/47] Nologin (#30) --- src/agentscope/studio/_app_online.py | 20 +++++++++++++- src/agentscope/studio/static/css/login.css | 32 ++++++++++++++++++++++ src/agentscope/studio/templates/login.html | 16 +++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/agentscope/studio/_app_online.py b/src/agentscope/studio/_app_online.py index 521283d0a..8bb9b380d 100644 --- a/src/agentscope/studio/_app_online.py +++ b/src/agentscope/studio/_app_online.py @@ -3,6 +3,8 @@ import ipaddress import json import os +import uuid +import time import secrets import tempfile from typing import Tuple, Any @@ -196,6 +198,22 @@ def _home() -> str: return render_template("login.html", client_id=CLIENT_ID, ip=IP, port=PORT) +@_app.route("/login_as_guest") +def login_as_guest() -> str: + """Render the workstation page without login.""" + user_login = f"guest_{uuid.uuid4().hex}_{int(time.time())}" + session["verification_token"] = generate_verification_token() + session["user_login"] = user_login + session["jwt_token"] = generate_jwt( + user_login=user_login, + access_token="access_token", + verification_token=session["verification_token"], + secret_key=SECRET_KEY, + version="online", + ) + return redirect(url_for("_workstation_online")) + + @_app.route("/logout") def logout() -> str: """ @@ -208,7 +226,7 @@ def logout() -> str: @_app.route("/oauth/callback") def oauth_callback() -> str: """ - Github oauth callback. + GitHub oauth callback. """ code = request.args.get("code") if not code: diff --git a/src/agentscope/studio/static/css/login.css b/src/agentscope/studio/static/css/login.css index 8ec8a7ea8..43d2cd761 100644 --- a/src/agentscope/studio/static/css/login.css +++ b/src/agentscope/studio/static/css/login.css @@ -49,6 +49,38 @@ body { cursor: not-allowed; } +#loginGuestButton { + color: #a0a0a0; + font-size: 12px; + padding: 5px 8px; + cursor: pointer; + box-shadow: none; + transition: transform 0.2s; + margin-top: 0.5rem; + display: inline-block; + width: auto; + background: none; + border: none; + text-decoration: underline; +} + +#loginGuestButton:hover { + transform: scale(1.01); + text-decoration: underline; +} + +#loginGuestButton:active { + transform: scale(1); + text-decoration: underline; +} + +#loginGuestButton:disabled { + color: #d3d3d3; + cursor: not-allowed; + text-decoration: none; + transform: none; +} + .terms { background: #fff; padding: 20px; diff --git a/src/agentscope/studio/templates/login.html b/src/agentscope/studio/templates/login.html index 1fc27a641..1df6d5ef3 100644 --- a/src/agentscope/studio/templates/login.html +++ b/src/agentscope/studio/templates/login.html @@ -72,6 +72,9 @@

    {{ _("Please log in and star the AgentScope repository") }}.

    +

    - +
    -
    - An dialog agent that can interact with users or other agents. +
    + + An dialog agent that can interact with users or other agents. +
    Node ID: ID_PLACEHOLDER
    - +
    - + + i18n="agent-dialogagent-textareaInput-placeholder" + placeholder="You're an assistant." + i18n-only="placeholder" + data-required="true">
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/agent-dictdialogagent.html b/src/agentscope/studio/static/html-drag-components/agent-dictdialogagent.html index 6d5040212..2f320cd98 100644 --- a/src/agentscope/studio/static/html-drag-components/agent-dictdialogagent.html +++ b/src/agentscope/studio/static/html-drag-components/agent-dictdialogagent.html @@ -8,39 +8,44 @@ DictDialogAgent
    - +
    - Agent that generates response in a dict format + Agent that generates response in a dict format
    Node ID: ID_PLACEHOLDER
    - +
    - + + i18n="agent-dictdialogagent-SystemPrompt-textarea" + placeholder="You're an assistant." + i18n-only="placeholder" + data-required="true">
    - +
    - +
    - +
    - +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/agent-reactagent.html b/src/agentscope/studio/static/html-drag-components/agent-reactagent.html index 83cbbf3fa..99582cd62 100644 --- a/src/agentscope/studio/static/html-drag-components/agent-reactagent.html +++ b/src/agentscope/studio/static/html-drag-components/agent-reactagent.html @@ -8,44 +8,52 @@ ReActAgent - +
    - Agent for ReAct (reasoning and acting) with tools + + Agent for ReAct (reasoning and acting) with tools +
    Node ID: ID_PLACEHOLDER
    - +
    - + + data-required="true">
    - +
    - -
    Please drag and + +
    Please drag and drop a Tool module into this area. Inserting other types of modules may result in errors.
    - +
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/agent-texttoimageagent.html b/src/agentscope/studio/static/html-drag-components/agent-texttoimageagent.html index d3ff12c51..a9f098ed3 100644 --- a/src/agentscope/studio/static/html-drag-components/agent-texttoimageagent.html +++ b/src/agentscope/studio/static/html-drag-components/agent-texttoimageagent.html @@ -6,23 +6,25 @@ - TextToImageAgent + TextToImageAgent
    - +
    - Agent for text to image generation + Agent for text to image generation
    Node ID: ID_PLACEHOLDER
    - +
    - +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/agent-useragent.html b/src/agentscope/studio/static/html-drag-components/agent-useragent.html index 8e9930449..30f793b4f 100644 --- a/src/agentscope/studio/static/html-drag-components/agent-useragent.html +++ b/src/agentscope/studio/static/html-drag-components/agent-useragent.html @@ -8,17 +8,21 @@ UserAgent - +
    - A proxy agent for user + A proxy agent for user
    Node ID: ID_PLACEHOLDER
    - - + +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/message-msg.html b/src/agentscope/studio/static/html-drag-components/message-msg.html index ca29eef48..c911c868e 100644 --- a/src/agentscope/studio/static/html-drag-components/message-msg.html +++ b/src/agentscope/studio/static/html-drag-components/message-msg.html @@ -11,17 +11,23 @@
    - - Name +
    - - Content +
    - +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/model-dashscope-chat.html b/src/agentscope/studio/static/html-drag-components/model-dashscope-chat.html index 241d38ce0..d7f878dc4 100644 --- a/src/agentscope/studio/static/html-drag-components/model-dashscope-chat.html +++ b/src/agentscope/studio/static/html-drag-components/model-dashscope-chat.html @@ -9,18 +9,18 @@
    -
    DashScope Chat Configurations (Your API key will +
    DashScope Chat Configurations (Your API key will NOT be stored and exposed to the website maintainer)

    - +
    - +
    @@ -34,11 +34,11 @@
    - +
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/model-openai-chat.html b/src/agentscope/studio/static/html-drag-components/model-openai-chat.html index 5b3847e67..f7750818a 100644 --- a/src/agentscope/studio/static/html-drag-components/model-openai-chat.html +++ b/src/agentscope/studio/static/html-drag-components/model-openai-chat.html @@ -9,18 +9,18 @@
    -
    OpenAI Chat Configurations (Your API key will NOT +
    OpenAI Chat Configurations (Your API key will NOT be stored and exposed to the website maintainer)

    - +
    - +
    @@ -36,19 +36,21 @@
    - +
    - + -
    Advanced +
    Advanced ▼
    diff --git a/src/agentscope/studio/static/html-drag-components/model-post-api-chat.html b/src/agentscope/studio/static/html-drag-components/model-post-api-chat.html index 9e4a05901..a04b9c170 100644 --- a/src/agentscope/studio/static/html-drag-components/model-post-api-chat.html +++ b/src/agentscope/studio/static/html-drag-components/model-post-api-chat.html @@ -9,42 +9,46 @@
    -
    Post API Chat Configuration
    +
    Post API Chat Configuration

    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - - + +
    diff --git a/src/agentscope/studio/static/html-drag-components/model-post-api-dall-e.html b/src/agentscope/studio/static/html-drag-components/model-post-api-dall-e.html index 7ff242764..95af1d903 100644 --- a/src/agentscope/studio/static/html-drag-components/model-post-api-dall-e.html +++ b/src/agentscope/studio/static/html-drag-components/model-post-api-dall-e.html @@ -9,15 +9,15 @@
    -
    Post API Dall-E Configurations
    +
    Post API Dall-E Configurations

    - +
    - +
    - +
    @@ -28,18 +28,22 @@
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/model-wanx.html b/src/agentscope/studio/static/html-drag-components/model-wanx.html index 9aaefbac7..12c71e01b 100644 --- a/src/agentscope/studio/static/html-drag-components/model-wanx.html +++ b/src/agentscope/studio/static/html-drag-components/model-wanx.html @@ -9,20 +9,20 @@
    -
    Wanx Configurations
    +
    Wanx Configurations

    - +
    - +

    - +
    - +
    @@ -30,9 +30,15 @@ - +
    - + + +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/pipeline-forlooppipeline.html b/src/agentscope/studio/static/html-drag-components/pipeline-forlooppipeline.html index 2808a61c5..64f15e6a7 100644 --- a/src/agentscope/studio/static/html-drag-components/pipeline-forlooppipeline.html +++ b/src/agentscope/studio/static/html-drag-components/pipeline-forlooppipeline.html @@ -9,15 +9,15 @@
    -
    A template pipeline for implementing control flow +
    A template pipeline for implementing control flow like for-loop
    - +
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/pipeline-ifelsepipeline.html b/src/agentscope/studio/static/html-drag-components/pipeline-ifelsepipeline.html index e3dafc6d1..96590eeec 100644 --- a/src/agentscope/studio/static/html-drag-components/pipeline-ifelsepipeline.html +++ b/src/agentscope/studio/static/html-drag-components/pipeline-ifelsepipeline.html @@ -9,11 +9,11 @@
    -
    A template pipeline for implementing control flow +
    A template pipeline for implementing control flow with if-else logic
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/pipeline-msghub.html b/src/agentscope/studio/static/html-drag-components/pipeline-msghub.html index 44e4d157c..ad43351e3 100644 --- a/src/agentscope/studio/static/html-drag-components/pipeline-msghub.html +++ b/src/agentscope/studio/static/html-drag-components/pipeline-msghub.html @@ -9,17 +9,24 @@
    -
    MsgHub is used to share messages among a group of +
    MsgHub is used to share messages among a group of agents
    - - + +
    - + + placeholder="Welcome to the group chat!" + i18n-only="placeholder" + i18n="pipeline-msghub-AnnouncementContent-placeholder" + >
    diff --git a/src/agentscope/studio/static/html-drag-components/pipeline-placeholder.html b/src/agentscope/studio/static/html-drag-components/pipeline-placeholder.html index 59d5af5d6..eda14248b 100644 --- a/src/agentscope/studio/static/html-drag-components/pipeline-placeholder.html +++ b/src/agentscope/studio/static/html-drag-components/pipeline-placeholder.html @@ -9,5 +9,5 @@
    -
    A placeholder that do nothing
    +
    A placeholder that do nothing
    diff --git a/src/agentscope/studio/static/html-drag-components/pipeline-sequentialpipeline.html b/src/agentscope/studio/static/html-drag-components/pipeline-sequentialpipeline.html index f89ecacdb..3837cb829 100644 --- a/src/agentscope/studio/static/html-drag-components/pipeline-sequentialpipeline.html +++ b/src/agentscope/studio/static/html-drag-components/pipeline-sequentialpipeline.html @@ -9,7 +9,7 @@
    -
    A template pipeline for implementing sequential +
    A template pipeline for implementing sequential logic (from top to bottom)
    diff --git a/src/agentscope/studio/static/html-drag-components/pipeline-switchpipeline.html b/src/agentscope/studio/static/html-drag-components/pipeline-switchpipeline.html index bfb32653c..ee73ebf69 100644 --- a/src/agentscope/studio/static/html-drag-components/pipeline-switchpipeline.html +++ b/src/agentscope/studio/static/html-drag-components/pipeline-switchpipeline.html @@ -9,17 +9,17 @@
    -
    A template pipeline for implementing control flow +
    A template pipeline for implementing control flow with switch-case logic
    - +
    - + - +
    diff --git a/src/agentscope/studio/static/html-drag-components/pipeline-whilelooppipeline.html b/src/agentscope/studio/static/html-drag-components/pipeline-whilelooppipeline.html index 5ebfc6b2c..fd3741cca 100644 --- a/src/agentscope/studio/static/html-drag-components/pipeline-whilelooppipeline.html +++ b/src/agentscope/studio/static/html-drag-components/pipeline-whilelooppipeline.html @@ -9,11 +9,11 @@
    -
    A template pipeline for implementing control flow +
    A template pipeline for implementing control flow like while-loop
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/service-bing-search.html b/src/agentscope/studio/static/html-drag-components/service-bing-search.html index 27a36ef32..5bcd5413b 100644 --- a/src/agentscope/studio/static/html-drag-components/service-bing-search.html +++ b/src/agentscope/studio/static/html-drag-components/service-bing-search.html @@ -10,7 +10,7 @@
    -
    Integrate the Bing Search service within ReActAgent +
    Integrate the Bing Search service within ReActAgent to enhance agent capabilities
    @@ -18,6 +18,6 @@
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/service-execute-python.html b/src/agentscope/studio/static/html-drag-components/service-execute-python.html index 5df8d85ed..008809395 100644 --- a/src/agentscope/studio/static/html-drag-components/service-execute-python.html +++ b/src/agentscope/studio/static/html-drag-components/service-execute-python.html @@ -10,7 +10,7 @@
    -
    Integrate the Python Interpreter within ReActAgent +
    Integrate the Python Interpreter within ReActAgent to enhance agent capabilities
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/service-google-search.html b/src/agentscope/studio/static/html-drag-components/service-google-search.html index 935ed06f6..ec06186b8 100644 --- a/src/agentscope/studio/static/html-drag-components/service-google-search.html +++ b/src/agentscope/studio/static/html-drag-components/service-google-search.html @@ -10,7 +10,7 @@
    -
    Integrate the Google Search service within +
    Integrate the Google Search service within ReActAgent to enhance agent capabilities
    diff --git a/src/agentscope/studio/static/html-drag-components/service-read-text.html b/src/agentscope/studio/static/html-drag-components/service-read-text.html index de706969a..9f2fe2d1b 100644 --- a/src/agentscope/studio/static/html-drag-components/service-read-text.html +++ b/src/agentscope/studio/static/html-drag-components/service-read-text.html @@ -10,7 +10,7 @@
    -
    Integrate the Read Text service within ReActAgent +
    Integrate the Read Text service within ReActAgent to enhance agent capabilities
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html b/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html index d4f6e2e46..0f9fa0d77 100644 --- a/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html +++ b/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html @@ -10,10 +10,10 @@
    -
    Integrate the Text to Audio Service within ReActAgent +
    Integrate the Text to Audio Service within ReActAgent to enhance agent capabilities
    - +
    @@ -38,6 +38,6 @@
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/service-text-to-image.html b/src/agentscope/studio/static/html-drag-components/service-text-to-image.html index 9d6cdb7a4..997f25e5e 100644 --- a/src/agentscope/studio/static/html-drag-components/service-text-to-image.html +++ b/src/agentscope/studio/static/html-drag-components/service-text-to-image.html @@ -10,10 +10,10 @@
    -
    Integrate the Text to Image Service within ReActAgent +
    Integrate the Text to Image Service within ReActAgent to enhance agent capabilities
    - +
    @@ -25,10 +25,10 @@
    - +
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/service-write-text.html b/src/agentscope/studio/static/html-drag-components/service-write-text.html index 719219957..ffc87c8af 100644 --- a/src/agentscope/studio/static/html-drag-components/service-write-text.html +++ b/src/agentscope/studio/static/html-drag-components/service-write-text.html @@ -10,7 +10,7 @@
    -
    Integrate the Write Text Service within ReActAgent +
    Integrate the Write Text Service within ReActAgent to enhance agent capabilities
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/tool-image-composition.html b/src/agentscope/studio/static/html-drag-components/tool-image-composition.html index 20317d490..ef59afdcc 100644 --- a/src/agentscope/studio/static/html-drag-components/tool-image-composition.html +++ b/src/agentscope/studio/static/html-drag-components/tool-image-composition.html @@ -10,33 +10,33 @@
    -
    Image composition Configurations
    +
    Image composition Configurations

    - +
    - +
    - +
    - +
    - +
    - +
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/tool-image-motion.html b/src/agentscope/studio/static/html-drag-components/tool-image-motion.html index c0d70d0ab..cd1dd42a4 100644 --- a/src/agentscope/studio/static/html-drag-components/tool-image-motion.html +++ b/src/agentscope/studio/static/html-drag-components/tool-image-motion.html @@ -10,24 +10,24 @@
    -
    Image Motion Configurations
    +
    Image Motion Configurations

    - +
    - + - +
    - +
    - +
    - +
    - +
    - +
    - +
    diff --git a/src/agentscope/studio/static/html-drag-components/welcome.html b/src/agentscope/studio/static/html-drag-components/welcome.html index 4eec3bded..9c28bff90 100644 --- a/src/agentscope/studio/static/html-drag-components/welcome.html +++ b/src/agentscope/studio/static/html-drag-components/welcome.html @@ -1,37 +1,38 @@ -
    +
    👏 Welcome!
    -

    Easy-to-use Multi-Agent Platform: +

    + Easy-to-use Multi-Agent Platform: AgentScope

    - - - - + + + +
    - + diff --git a/src/agentscope/studio/static/html/dashboard-detail-dialogue.html b/src/agentscope/studio/static/html/dashboard-detail-dialogue.html index df4360414..db4215aec 100644 --- a/src/agentscope/studio/static/html/dashboard-detail-dialogue.html +++ b/src/agentscope/studio/static/html/dashboard-detail-dialogue.html @@ -2,14 +2,16 @@
    -
    No messages available.
    +
    No messages available.
    -
    User
    +
    User
    + placeholder="Input message here ..." + i18n-only="placeholder" + i18n="dialogue-detail-dialogue-textarea">
    @@ -25,7 +27,7 @@
    From e19267d621d9f445cea66565de110fed835db1a9 Mon Sep 17 00:00:00 2001 From: myh-0521 <95087599+myh-0521@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:16:36 +0800 Subject: [PATCH 19/47] gallery_refine_style (#34) --- src/agentscope/studio/static/css/gallery.css | 88 ++++++ .../studio/static/css/workstation.css | 115 +++++++- .../studio/static/html/gallery.html | 20 ++ src/agentscope/studio/static/js/gallery.js | 249 ++++++++++++++++ src/agentscope/studio/static/js/index.js | 3 +- .../studio/static/js/workstation.js | 274 +++++++++++++++++- src/agentscope/studio/templates/index.html | 4 +- .../studio/templates/workstation.html | 47 ++- 8 files changed, 795 insertions(+), 5 deletions(-) create mode 100644 src/agentscope/studio/static/css/gallery.css create mode 100644 src/agentscope/studio/static/html/gallery.html create mode 100644 src/agentscope/studio/static/js/gallery.js diff --git a/src/agentscope/studio/static/css/gallery.css b/src/agentscope/studio/static/css/gallery.css new file mode 100644 index 000000000..5024161e4 --- /dev/null +++ b/src/agentscope/studio/static/css/gallery.css @@ -0,0 +1,88 @@ + +.two-column-layout { + display: flex; + width: 100%; + height: var(--page-content-height); +} + +.sidebar { + flex-basis: var(--page-sidebar-width); + box-sizing: border-box; + padding: 50px 10px; + border-right: 1px solid var(--border-color); + overflow-y: auto; + width: 100%; +} + + +.tabs { + display: block; + padding: 0; + margin: 50px 0 20px 0; + +} +.tab-button { + cursor: pointer; + padding: 10px; + border: none; + margin-right: 20px; + border-radius: 15px; +} +.tab-button.active { + background-color: var(--main-color-light); +} + +.tab { + display: none; +} + +.tab.active { + display: block; +} + + +.grid-container { + grid-template-columns: repeat(6, 1fr); + gap: 1rem; + padding: 1rem; + display: flex; + flex-wrap: wrap; + gap: 1rem; + padding: 1rem; +} + + +.grid-item { + background-color: #f0f0f0; + border-radius: 4px; + overflow: hidden; + width: 200px; + height: 200px; + position: relative; + display: flex; + flex-direction: column; +} + +.grid-item :hover { + transform: scale(1.05); +} + +.grid-item img { + width: 100%; + height: 61.8%; + object-fit: cover; +} + +.grid-item .caption { + width: 100%; + height: 38.2%; +} +.thumbnail { + width: 100%; + flex-grow: 1; +} + +.caption { + text-align: center; +} + diff --git a/src/agentscope/studio/static/css/workstation.css b/src/agentscope/studio/static/css/workstation.css index 323910042..fec76c390 100644 --- a/src/agentscope/studio/static/css/workstation.css +++ b/src/agentscope/studio/static/css/workstation.css @@ -111,6 +111,7 @@ body { border-radius: 10px; margin: 0 5px; cursor: pointer; + position: relative; } .menu-btn:hover { @@ -992,4 +993,116 @@ pre[class*="language-"] { height: 4px; background-color: #4CAF50; transition: width 0.1s linear; -} \ No newline at end of file +} + + + +.two-column-layout { + display: flex; + width: 100%; + height: var(--page-content-height); +} + +.sidebar { + flex-basis: var(--page-sidebar-width); + box-sizing: border-box; + padding: 50px 10px; + border-right: 1px solid var(--border-color); + overflow-y: auto; + width: 100%; +} + + +.tabs { + display: block; + padding: 0; + margin: 50px 0 20px 0; + +} +.tab-button { + cursor: pointer; + padding: 10px; + border: none; + margin-right: 20px; + border-radius: 15px; +} +.tab-button.active { + background-color: var(--main-color-light); +} + +.tab { + display: none; +} + +.tab.active { + display: block; +} + + +.grid-container { + grid-template-columns: repeat(6, 1fr); + gap: 1rem; + padding: 1rem; + display: flex; + flex-wrap: wrap; + gap: 1rem; + padding: 1rem; +} + + +.grid-item { + background-color: #f0f0f0; + border-radius: 4px; + overflow: hidden; + width: 200px; + height: 200px; + position: relative; + display: flex; + flex-direction: column; +} + +.grid-item :hover { + transform: scale(1.05); +} + +.grid-item img { + width: 100%; + height: 61.8%; + object-fit: cover; +} + +.grid-item .caption { + width: 100%; + height: 38.2%; +} + +.thumbnail { + width: 100%; + flex-grow: 1; +} + +.caption { + text-align: center; +} + +.tooltip { + visibility: hidden; + width: 120px; + background-color: black; + color: rgb(0, 0, 0); + text-align: center; + border-radius: 5px; + padding: 5px; + position: absolute; + z-index: 1; + top: 100%; + left: 50%; + margin-left: -30px; + opacity: 0; + transition: opacity 0.3s; +} + +.menu-btn:hover .tooltip { + visibility: visible; + opacity: 1; +} diff --git a/src/agentscope/studio/static/html/gallery.html b/src/agentscope/studio/static/html/gallery.html new file mode 100644 index 000000000..2609700b8 --- /dev/null +++ b/src/agentscope/studio/static/html/gallery.html @@ -0,0 +1,20 @@ +
    +
    +
    +
    +
      + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/src/agentscope/studio/static/js/gallery.js b/src/agentscope/studio/static/js/gallery.js new file mode 100644 index 000000000..56cc28f26 --- /dev/null +++ b/src/agentscope/studio/static/js/gallery.js @@ -0,0 +1,249 @@ + +showTab('tab1'); +function sendWorkflow(fileName) { + if (confirm('Are you sure you want to import this workflow?')) { + const workstationUrl = '/workstation?filename=' + encodeURIComponent(fileName); + window.location.href = workstationUrl; + } +} + +function deleteWorkflow(fileName) { + if (confirm('Workflow has been deleted?')) { + fetch('/delete-workflow', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + filename: fileName, + }) + }).then(response => response.json()) + .then(data => { + if (data.error) { + alert(data.error); + } else { + showLoadWorkflowList('tab2'); + } + }) + .catch(error => { + console.error('Error:', error); + alert('delete workflow error.'); + }); + } +} + +function showTab(tabId) { + var tabs = document.getElementsByClassName("tab"); + for (var i = 0; i < tabs.length; i++) { + tabs[i].classList.remove("active"); + tabs[i].style.display = "none"; + } + var tab = document.getElementById(tabId); + if (tab) { + tab.classList.add("active"); + tab.style.display = "block"; + + var tabButtons = document.getElementsByClassName("tab-button"); + for (var j = 0; j < tabButtons.length; j++) { + tabButtons[j].classList.remove("active"); + } + var activeTabButton = document.querySelector(`.tab-button[onclick*="${tabId}"]`); + if (activeTabButton) { + activeTabButton.classList.add("active"); + } + + if (tabId === "tab2") { + showLoadWorkflowList(tabId); + } else if (tabId === "tab1") { + showGalleryWorkflowList(tabId); + } + } +} + + +function createGridItem(workflowName, container, thumbnail, author = '', time = '', showDeleteButton = false) { + var gridItem = document.createElement('div'); + gridItem.className = 'grid-item'; + gridItem.style.borderRadius = '15px'; + gridItem.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; + + + var img = document.createElement('div'); + img.className = 'thumbnail'; + img.style.backgroundImage = `url('${thumbnail}')`; + img.style.backgroundSize = 'cover'; + img.style.backgroundPosition = 'center'; + gridItem.appendChild(img); + + var caption = document.createElement('div'); + caption.className = 'caption'; + caption.style.backgroundColor = 'white'; + + var h6 = document.createElement('h6'); + h6.textContent = workflowName; + h6.style.margin = '1px 0'; + + var pAuthor = document.createElement('p'); + pAuthor.textContent = `Author: ${author}`; + pAuthor.style.margin = '1px 0'; + pAuthor.style.fontSize = '10px'; + + var pTime = document.createElement('p'); + pTime.textContent = `Date: ${time}`; + pTime.style.margin = '1px 0'; + pTime.style.fontSize = '10px'; + + var button = document.createElement('button'); + button.textContent = 'Load'; + button.className = 'button'; + button.style.marginRight = '5px'; + button.style.backgroundColor = '#007aff'; + button.style.backgroundImage = 'linear-gradient(to right, #6e48aa, #9d50bb)'; + button.style.color = 'white'; + button.style.padding = '2px 7px'; + button.style.border = 'none'; + button.style.borderRadius = '8px'; + button.style.fontSize = '12px'; + button.style.cursor = 'pointer'; + button.style.transition = 'background 0.3s'; + + button.addEventListener('mouseover', function () { + button.style.backgroundColor = '#005bb5'; + }); + + button.addEventListener('mouseout', function () { + button.style.backgroundColor = '#007aff'; + }); + button.onclick = function (e) { + e.preventDefault(); + sendWorkflow(workflowName); + }; + + caption.appendChild(h6); + if (author) caption.appendChild(pAuthor); + if (time) caption.appendChild(pTime); + caption.appendChild(button); + + if (showDeleteButton) { + var deleteButton = document.createElement('button'); + deleteButton.textContent = 'Delete'; + deleteButton.className = 'button'; + deleteButton.style.backgroundColor = '#007aff'; + deleteButton.style.backgroundImage = 'linear-gradient(to right, #6e48aa, #9d50bb)'; + deleteButton.style.color = 'white'; + deleteButton.style.padding = '2px 3px'; + deleteButton.style.border = 'none'; + deleteButton.style.borderRadius = '8px'; + deleteButton.style.fontSize = '12px'; + deleteButton.style.cursor = 'pointer'; + deleteButton.style.transition = 'background 0.3s'; + + deleteButton.addEventListener('mouseover', function () { + deleteButton.style.backgroundColor = '#005bb5'; + }); + deleteButton.addEventListener('mouseout', function () { + deleteButton.style.backgroundColor = '#007aff'; + }); + + deleteButton.onclick = function (e) { + e.preventDefault(); + deleteWorkflow(workflowName); + }; + + caption.appendChild(deleteButton); + } + + gridItem.appendChild(caption); + container.appendChild(gridItem); + console.log('Grid item appended:', gridItem); +} + +function showGalleryWorkflowList(tabId) { + const container = document.getElementById(tabId).querySelector('.grid-container'); + container.innerHTML = ''; + + fetch('/fetch-gallery', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}) + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + console.log('Fetched gallery data:', data); + + const workflows = data.json || []; + + if (!Array.isArray(workflows)) { + console.error('The server did not return an array as expected.', data); + workflows = [workflows]; + } + + workflows.forEach(workflow => { + const meta = workflow.meta; + const title = meta.title; + const author = meta.author; + const time = meta.time; + const thumbnail = meta.thumbnail || generateThumbnailFromContent(meta); + createGridItem(title, container, thumbnail, author, time, false); + }); + }) + .catch(error => { + console.error('Error fetching gallery workflows:', error); + + }); +} + +function showLoadWorkflowList(tabId) { + fetch('/list-workflows', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}) + }) + .then(response => response.json()) + .then(data => { + if (!Array.isArray(data.files)) { + throw new TypeError('The return data is not an array'); + } + + const container = document.getElementById(tabId).querySelector('.grid-container'); + container.innerHTML = ''; + + data.files.forEach(workflowName => { + const title = workflowName.replace(/\.json$/, ''); + const thumbnail = generateThumbnailFromContent({ title }); + createGridItem(title, container, thumbnail, '', '', true); + }); + }) + .catch(error => { + console.error('Error fetching workflow list:', error); + alert('Fetch workflow list error.'); + }); +} + + +function generateThumbnailFromContent(content) { + const canvas = document.createElement('canvas'); + canvas.width = 150; + canvas.height = 150; + const ctx = canvas.getContext('2d'); + + ctx.fillStyle = '#f0f0f0'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.font = 'italic bold 14px "Helvetica Neue", sans-serif'; + ctx.textAlign = 'center'; + ctx.fillStyle = '#333'; + + ctx.fillText(content.title, canvas.width / 2, canvas.height / 2 + 20); + + return canvas.toDataURL(); +} diff --git a/src/agentscope/studio/static/js/index.js b/src/agentscope/studio/static/js/index.js index 1971a59ca..94eb88c97 100644 --- a/src/agentscope/studio/static/js/index.js +++ b/src/agentscope/studio/static/js/index.js @@ -102,7 +102,7 @@ function loadTabPage(pageUrl, javascriptUrl) { serverTabBtn.classList.remove("selected"); break; - case "static/html/market.html": + case "static/html/gallery.html": dashboardTabBtn.classList.remove("selected"); workstationTabBtn.classList.remove("selected"); marketTabBtn.classList.add("selected"); @@ -124,6 +124,7 @@ function loadTabPage(pageUrl, javascriptUrl) { }); } + loadTabPage("static/html/index-guide.html", null); document.addEventListener("DOMContentLoaded", function () { diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 34bf943a0..8dfbcd2f6 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -2266,6 +2266,7 @@ function fetchExample(index, processData) { function importExample(index) { + fetchExample(index, data => { const dataToImport = data.json; @@ -2764,4 +2765,275 @@ function setCookie(name, value, days) { expires = "; expires=" + date.toUTCString(); } document.cookie = name + "=" + (value || "") + expires + "; path=/"; -} \ No newline at end of file +} + +document.addEventListener('DOMContentLoaded', function() { + showTab('tab1'); +}); + + +function sendWorkflow(fileName) { + Swal.fire({ + text: 'Are you sure you want to import this workflow?', + icon: 'warning', + showCancelButton: true, + confirmButtonText: 'Yes, import it!', + cancelButtonText: 'Cancel' + }).then((result) => { + if (result.isConfirmed) { + const workstationUrl = '/workstation?filename=' + encodeURIComponent(fileName); + window.location.href = workstationUrl; + } + }); +} + +function deleteWorkflow(fileName) { + Swal.fire({ + title: 'Are you sure?', + text: "Workflow will be deleted!", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes, delete it!' + }).then((result) => { + if (result.isConfirmed) { + fetch('/delete-workflow', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + filename: fileName, + }) + }).then(response => response.json()) + .then(data => { + if (data.error) { + Swal.fire('Error', data.error, 'error'); + } else { + showLoadWorkflowList('tab2'); + Swal.fire('Deleted!', 'Your workflow has been deleted.', 'success'); + } + }) + .catch(error => { + console.error('Error:', error); + Swal.fire('Error', 'Delete workflow error.', 'error'); + }); + } + }); +} + + +function showTab(tabId) { + var tabs = document.getElementsByClassName("tab"); + for (var i = 0; i < tabs.length; i++) { + tabs[i].classList.remove("active"); + tabs[i].style.display = "none"; + } + var tab = document.getElementById(tabId); + if (tab) { + tab.classList.add("active"); + tab.style.display = "block"; + + var tabButtons = document.getElementsByClassName("tab-button"); + for (var j = 0; j < tabButtons.length; j++) { + tabButtons[j].classList.remove("active"); + } + var activeTabButton = document.querySelector(`.tab-button[onclick*="${tabId}"]`); + if (activeTabButton) { + activeTabButton.classList.add("active"); + } + + if (tabId === "tab2") { + showLoadWorkflowList(tabId); + } else if (tabId === "tab1") { + showGalleryWorkflowList(tabId); + } + } +} + + +function createGridItem(workflowName, container, thumbnail, author = '', time = '', showDeleteButton = false) { + var gridItem = document.createElement('div'); + gridItem.className = 'grid-item'; + gridItem.style.borderRadius = '15px'; + gridItem.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; + + var img = document.createElement('div'); + img.className = 'thumbnail'; + img.style.backgroundImage = `url('${thumbnail}')`; + img.style.backgroundSize = 'cover'; + img.style.backgroundPosition = 'center'; + gridItem.appendChild(img); + + var caption = document.createElement('div'); + caption.className = 'caption'; + caption.style.backgroundColor = 'white'; + + var h6 = document.createElement('h6'); + h6.textContent = workflowName; + h6.style.margin = '1px 0'; + + var pAuthor = document.createElement('p'); + pAuthor.textContent = `Author: ${author}`; + pAuthor.style.margin = '1px 0'; + pAuthor.style.fontSize = '10px'; + + var pTime = document.createElement('p'); + pTime.textContent = `Date: ${time}`; + pTime.style.margin = '1px 0'; + pTime.style.fontSize = '10px'; + + var button = document.createElement('button'); + button.textContent = ' Load '; + button.className = 'button'; + button.style.backgroundColor = '#007aff'; + button.style.color = 'white'; + button.style.padding = '2px 7px'; + button.style.border = 'none'; + button.style.borderRadius = '8px'; + button.style.fontSize = '12px'; + button.style.cursor = 'pointer'; + button.style.transition = 'background 0.3s'; + + button.addEventListener('mouseover', function () { + button.style.backgroundColor = '#005bb5'; + }); + + button.addEventListener('mouseout', function () { + button.style.backgroundColor = '#007aff'; + }); + button.onclick = function (e) { + e.preventDefault(); + sendWorkflow(workflowName); + }; + + caption.appendChild(h6); + if (author) caption.appendChild(pAuthor); + if (time) caption.appendChild(pTime); + caption.appendChild(button); + + if (showDeleteButton) { + var deleteButton = document.createElement('button'); + deleteButton.textContent = 'Delete'; + deleteButton.className = 'button'; + deleteButton.style.backgroundColor = '#007aff'; + deleteButton.style.color = 'white'; + deleteButton.style.padding = '2px 3px'; + deleteButton.style.border = 'none'; + deleteButton.style.borderRadius = '8px'; + deleteButton.style.fontSize = '12px'; + deleteButton.style.cursor = 'pointer'; + deleteButton.style.transition = 'background 0.3s'; + + deleteButton.addEventListener('mouseover', function () { + deleteButton.style.backgroundColor = '#005bb5'; + }); + deleteButton.addEventListener('mouseout', function () { + deleteButton.style.backgroundColor = '#007aff'; + }); + + deleteButton.onclick = function (e) { + e.preventDefault(); + deleteWorkflow(workflowName); + }; + + caption.appendChild(deleteButton); + } + + gridItem.appendChild(caption); + container.appendChild(gridItem); + console.log('Grid item appended:', gridItem); +} + + + +function showGalleryWorkflowList(tabId) { + const container = document.getElementById(tabId).querySelector('.grid-container'); + container.innerHTML = ''; + + fetch('/fetch-gallery', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}) + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + console.log('Fetched gallery data:', data); + + const workflows = data.json || []; + + if (!Array.isArray(workflows)) { + console.error('The server did not return an array as expected.', data); + workflows = [workflows]; + } + + workflows.forEach(workflow => { + const meta = workflow.meta; + const title = meta.title; + const author = meta.author; + const time = meta.time; + const thumbnail = meta.thumbnail || generateThumbnailFromContent(meta); + createGridItem(title, container, thumbnail, author, time, false); + }); + }) + .catch(error => { + console.error('Error fetching gallery workflows:', error); + + }); +} + +function showLoadWorkflowList(tabId) { + fetch('/list-workflows', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}) + }) + .then(response => response.json()) + .then(data => { + if (!Array.isArray(data.files)) { + throw new TypeError('The return data is not an array'); + } + + const container = document.getElementById(tabId).querySelector('.grid-container'); + container.innerHTML = ''; + + data.files.forEach(workflowName => { + const title = workflowName.replace(/\.json$/, ''); + const thumbnail = generateThumbnailFromContent({ title }); + createGridItem(title, container, thumbnail, '', '', true); + }); + }) + .catch(error => { + console.error('Error fetching workflow list:', error); + alert('Fetch workflow list error.'); + }); +} + + +function generateThumbnailFromContent(content) { + const canvas = document.createElement('canvas'); + canvas.width = 150; + canvas.height = 150; + const ctx = canvas.getContext('2d'); + + ctx.fillStyle = '#f0f0f0'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.font = 'italic bold 14px "Helvetica Neue", sans-serif'; + ctx.textAlign = 'center'; + ctx.fillStyle = '#333'; + + ctx.fillText(content.title, canvas.width / 2, canvas.height / 2 + 20); + + return canvas.toDataURL(); +} diff --git a/src/agentscope/studio/templates/index.html b/src/agentscope/studio/templates/index.html index 6420c58d9..d31049124 100644 --- a/src/agentscope/studio/templates/index.html +++ b/src/agentscope/studio/templates/index.html @@ -15,6 +15,8 @@ href="{{ url_for('static', filename='css/dashboard.css') }}"> + @@ -72,7 +74,7 @@
    -
    +
    +
    @@ -484,6 +516,19 @@

    "We want to hear from you"

    From 7c10044b7899c9d7a2703bb6e0ba18a08af6ecbc Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:59:29 +0800 Subject: [PATCH 20/47] Weirui patches1014 (#37) --- .../studio/static/css/workstation.css | 91 +++++++--- src/agentscope/studio/static/html/market.html | 1 - .../studio/static/js/workstation.js | 167 ++++++++---------- .../studio/templates/workstation.html | 91 ++++++---- 4 files changed, 196 insertions(+), 154 deletions(-) delete mode 100644 src/agentscope/studio/static/html/market.html diff --git a/src/agentscope/studio/static/css/workstation.css b/src/agentscope/studio/static/css/workstation.css index fec76c390..2a3d7e6c1 100644 --- a/src/agentscope/studio/static/css/workstation.css +++ b/src/agentscope/studio/static/css/workstation.css @@ -11,6 +11,8 @@ --workstation-sidebar-item-font-size: 18px; --workstation-sidebar-subitem-font-size: 15px; + + --logo-font-color: #000000; } html { @@ -212,7 +214,7 @@ body { padding: 5px 10px; } -.lock-btn{ +.lock-btn { display: flex; align-items: center; justify-content: center; @@ -877,6 +879,7 @@ pre[class*="language-"] { border-radius: 5px; background-color: #fff; } + .overlay { position: absolute; top: 0; @@ -891,7 +894,7 @@ pre[class*="language-"] { display: none; } -.guide-Example{ +.guide-Example { background: #fff; height: fit-content; border-radius: 4px; @@ -900,7 +903,7 @@ pre[class*="language-"] { z-index: 2; } -.guide-skip{ +.guide-skip { padding: 8px 12px; margin: 4px; border: none; @@ -911,6 +914,7 @@ pre[class*="language-"] { font-size: 16px; margin-top: 10px; } + .notification { position: fixed; flex-direction: column; @@ -954,20 +958,24 @@ pre[class*="language-"] { font-weight: bold; transition: transform 0.5s, color 0.5s; } + .notification-close:hover { color: #000; transform: rotate(180deg); } + .notification-close:mouseout { color: #ccc; transform: rotate(0deg); } -.notification-clickBotton-box{ + +.notification-clickBotton-box { align-self: end; display: flex; gap: 5px; } -.notification-btn{ + +.notification-btn { padding: 8px 12px; margin: 4px; border: none; @@ -975,17 +983,21 @@ pre[class*="language-"] { cursor: pointer; font-size: 16px; } -.notification-btn:hover{ + +.notification-btn:hover { transform: translateY(-2px); } -.notification-btn.confirmBotton{ + +.notification-btn.confirmBotton { background-color: var(--main-color); color: white; } -.notification-btn.cancelBotton{ - background-color:#ccc; + +.notification-btn.cancelBotton { + background-color: #ccc; } -.notification-progress{ + +.notification-progress { position: absolute; bottom: 0; left: 0; @@ -996,7 +1008,6 @@ pre[class*="language-"] { } - .two-column-layout { display: flex; width: 100%; @@ -1019,6 +1030,7 @@ pre[class*="language-"] { margin: 50px 0 20px 0; } + .tab-button { cursor: pointer; padding: 10px; @@ -1026,6 +1038,7 @@ pre[class*="language-"] { margin-right: 20px; border-radius: 15px; } + .tab-button.active { background-color: var(--main-color-light); } @@ -1052,37 +1065,58 @@ pre[class*="language-"] { .grid-item { background-color: #f0f0f0; - border-radius: 4px; + border-radius: 15px; overflow: hidden; width: 200px; height: 200px; position: relative; display: flex; flex-direction: column; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + transition: transform 0.2s ease-in-out; } -.grid-item :hover { +.grid-item:hover { transform: scale(1.05); } -.grid-item img { +.thumbnail { width: 100%; - height: 61.8%; - object-fit: cover; + flex-grow: 1; + background-size: cover; + background-position: center; } -.grid-item .caption { +.caption { width: 100%; - height: 38.2%; + height: auto; + text-align: center; + background-color: white; } -.thumbnail { - width: 100%; - flex-grow: 1; +.caption h6, .caption p { + margin: 1px 0; + font-size: 10px; } -.caption { - text-align: center; +.caption .button { + background-color: #007aff; + color: white; + padding: 2px 7px; + border: none; + border-radius: 8px; + font-size: 12px; + cursor: pointer; + transition: background 0.3s; +} + +.caption .load-button { + margin-top: 5px; +} + +.caption .delete-button { + margin-top: 5px; + padding: 2px 3px; } .tooltip { @@ -1106,3 +1140,14 @@ pre[class*="language-"] { visibility: visible; opacity: 1; } + +#navigation-bar-logo { + font-family: 'krypton', sans-serif; + font-size: 20px; + color: var(--logo-font-color); + white-space: nowrap; +} + +#navigation-bar-logo:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/src/agentscope/studio/static/html/market.html b/src/agentscope/studio/static/html/market.html deleted file mode 100644 index 37323ec5b..000000000 --- a/src/agentscope/studio/static/html/market.html +++ /dev/null @@ -1 +0,0 @@ -

    Coming soon ...

    \ No newline at end of file diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 8dfbcd2f6..49421fa0b 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -357,7 +357,7 @@ async function initializeWorkstationPage() { setTimeout(showSurveyModal, 30000); - if(!localStorage.getItem('firstGuide')){ + if (!localStorage.getItem('firstGuide')) { startGuide(); } reloadi18n(); @@ -812,7 +812,7 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { "sample_rate": "" } }, htmlSourceCode); - updateSampleRate(TextToAudioServiceID) + updateSampleRate(TextToAudioServiceID) break; case 'TextToImageService': editor.addNode('TextToImageService', 0, 0, @@ -821,7 +821,7 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { "model": "", "api_key": "", "n": 1, - "size":"" + "size": "" } }, htmlSourceCode); break; @@ -861,7 +861,7 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { "target_height": "", "fps": "", } - }, htmlSourceCode); + }, htmlSourceCode); break; case 'Post': @@ -891,6 +891,7 @@ function updateSampleRate(nodeId) { } const modelNameInput = newNode.querySelector('#model_name'); + function updateSampleRateValue() { const modelName = modelNameInput ? modelNameInput.value : ''; @@ -2292,7 +2293,7 @@ function importExample(index) { function importExample_step(index) { - if(!localStorage.getItem('firstGuide')){ + if (!localStorage.getItem('firstGuide')) { localStorage.setItem('firstGuide', 'true'); skipGuide(); } @@ -2459,7 +2460,7 @@ function hideSurveyModal() { document.getElementById("surveyModal").style.display = "none"; } -function reloadi18n(){ +function reloadi18n() { let currentLang = getCookie('locale') || 'en'; $("[i18n]").i18n({ defaultLang: currentLang, @@ -2467,18 +2468,18 @@ function reloadi18n(){ filePrefix: "i18n_", fileSuffix: "", forever: true, - callback: function() { + callback: function () { } }); } -window.addEventListener('storage', function(event) { +window.addEventListener('storage', function (event) { if (event.key === 'locale') { reloadi18n() } }, false); -function startGuide(){ +function startGuide() { const targetElement = document.querySelector('.guide-Example'); const element = document.querySelector('.tour-guide'); positionElementRightOf(element, targetElement); @@ -2488,20 +2489,20 @@ function getElementCoordinates(targetElement) { const style = window.getComputedStyle(targetElement); const rect = targetElement.getBoundingClientRect(); return { - left: rect.left + (parseFloat(style.left) || 0), - top: rect.top + (parseFloat(style.top) || 0), - right: rect.right + (parseFloat(style.top) || 0), - bottom: rect.bottom + (parseFloat(style.top) || 0), - width: rect.width, - height: rect.height, - x: rect.x, - y: rect.y, + left: rect.left + (parseFloat(style.left) || 0), + top: rect.top + (parseFloat(style.top) || 0), + right: rect.right + (parseFloat(style.top) || 0), + bottom: rect.bottom + (parseFloat(style.top) || 0), + width: rect.width, + height: rect.height, + x: rect.x, + y: rect.y, }; } function positionElementRightOf(element, targetElement) { const targetCoordinates = getElementCoordinates(targetElement); - const mask = document.querySelector(".overlay"); + const mask = document.querySelector(".overlay"); mask.style.display = "block"; element.style.position = 'absolute'; element.style.display = 'block'; @@ -2509,24 +2510,27 @@ function positionElementRightOf(element, targetElement) { element.style.top = `${targetCoordinates.y}px`; } -function skipGuide(){ +function skipGuide() { const element = document.querySelector(".tour-guide"); - const mask = document.querySelector(".overlay"); + const mask = document.querySelector(".overlay"); localStorage.setItem('firstGuide', 'true'); - if(element){ + if (element) { element.style.display = "none"; element.remove(); mask.style.display = "none"; mask.remove(); } } + class Notification { static count = 0; static instances = []; + static clearInstances() { Notification.count = 0; Notification.instances = []; } + constructor(props) { Notification.count += 1; Notification.instances.push(this); @@ -2544,47 +2548,51 @@ class Notification { this.reduceNumber = 0; this.init(props); } - init(props){ + + init(props) { this.setDefaultValues(props); this.element = document.createElement('div'); // init notification-box css this.element.className = 'notification'; // render title - this.title && this.renderTitle(getCookie("locale")=="zh" ? props.i18nTitle :this.title); + this.title && this.renderTitle(getCookie("locale") == "zh" ? props.i18nTitle : this.title); // render closeButtion this.closeBtn && this.renderCloseButton(); // render content - this.content && this.renderContent(getCookie("locale")=="zh" ? props.i18nContent :this.content); + this.content && this.renderContent(getCookie("locale") == "zh" ? props.i18nContent : this.content); // render confirmBtn (this.confirmBtn || this.cancelBtn) && this.renderClickButton(); this.progress && this.renderProgressBar(); // set position this.setPosition(this.position); document.body.appendChild(this.element); - setTimeout(()=>{ + setTimeout(() => { this.show(); - },10) + }, 10) } + // check if string is HTML - isHTMLString(string){ + isHTMLString(string) { const doc = new DOMParser().parseFromString(string, 'text/html'); return Array.from(doc.body.childNodes).some(node => node.nodeType === 1); } + // render closeButtion - renderCloseButton(){ + renderCloseButton() { this.closeBtn = document.createElement('span'); this.closeBtn.className = 'notification-close'; this.closeBtn.innerText = 'X'; this.closeBtn.onclick = this.destroyAll.bind(this); this.title.appendChild(this.closeBtn); } + // render title string or HTML - renderTitle(component){ - if(this.isHTMLString(component)){ + renderTitle(component) { + if (this.isHTMLString(component)) { this.title = document.createElement('div'); this.title.className = 'notification-title'; this.title.innerHTML = component; - }else{ + } else { this.title = document.createElement('div'); this.titleText = document.createElement('div'); this.title.className = 'notification-title'; @@ -2594,59 +2602,63 @@ class Notification { } this.element.appendChild(this.title); } + // render content string or HTML - renderContent(component){ - if(this.isHTMLString(component)){ + renderContent(component) { + if (this.isHTMLString(component)) { this.content = document.createElement('div'); this.content.className = 'notification-content'; this.content.innerHTML = component; - }else{ + } else { this.content = document.createElement('div'); this.content.className = 'notification-content'; this.content.innerText = component; } this.element.appendChild(this.content); } + // render clickbtn - renderClickButton(){ - if(this.confirmBtn || this.cancelBtn){ + renderClickButton() { + if (this.confirmBtn || this.cancelBtn) { this.clickBottonBox = document.createElement('div'); this.clickBottonBox.className = 'notification-clickBotton-box'; } - if(this.confirmBtn){ + if (this.confirmBtn) { this.confirmBotton = document.createElement('button'); this.confirmBotton.className = 'notification-btn confirmBotton'; - this.confirmBotton.innerText = getCookie("locale")=="zh" ? this.i18nConfirmBtn : this.confirmBtn; + this.confirmBotton.innerText = getCookie("locale") == "zh" ? this.i18nConfirmBtn : this.confirmBtn; this.confirmBotton.onclick = this.onConfirmCallback.bind(this); this.clickBottonBox.appendChild(this.confirmBotton); } - if(this.cancelBtn){ + if (this.cancelBtn) { this.cancelBotton = document.createElement('button'); this.cancelBotton.className = 'notification-btn cancelBotton'; - this.cancelBotton.innerText = getCookie("locale")=="zh" ? this.i18nCancelBtn : this.cancelBtn; + this.cancelBotton.innerText = getCookie("locale") == "zh" ? this.i18nCancelBtn : this.cancelBtn; this.cancelBotton.onclick = this.onCancelCallback.bind(this); this.clickBottonBox.appendChild(this.cancelBotton); } this.element.appendChild(this.clickBottonBox); } + // render progress bar - renderProgressBar(){ + renderProgressBar() { this.progressBar = document.createElement('div'); this.progressBar.className = 'notification-progress'; this.element.appendChild(this.progressBar); } + // stepProgressBar - stepProgressBar(callback){ + stepProgressBar(callback) { let startTime = performance.now(); const step = (timestamp) => { const progress = Math.min((timestamp + this.reduceNumber - startTime) / this.intervalTime, 1); - this.progressBar.style.width = ( 1- progress ) * 100 + '%'; + this.progressBar.style.width = (1 - progress) * 100 + '%'; if (progress < 1 && this.pause == false) { requestAnimationFrame(step) - }else{ - this.reduceNumber = timestamp + this.reduceNumber - startTime + } else { + this.reduceNumber = timestamp + this.reduceNumber - startTime } - if(progress == 1){ + if (progress == 1) { this.pause == true; this.reduceNumber = 0; callback(); @@ -2655,15 +2667,17 @@ class Notification { } requestAnimationFrame(step); } + setDefaultValues(props) { for (const key in props) { if (props[key] === undefined) { - return ; + return; } else { this[key] = props[key]; } } } + setPosition() { switch (this.position) { case 'top-left': @@ -2684,6 +2698,7 @@ class Notification { break; } } + show() { this.element.style.display = 'flex'; switch (this.position) { @@ -2705,6 +2720,7 @@ class Notification { break; } } + // hide() { // // this.element.style.display = 'none'; // } @@ -2714,6 +2730,7 @@ class Notification { } Notification.clearInstances(); } + removeChild() { let removeIndex; for (let i = 0; i < Notification.instances.length; i++) { @@ -2727,6 +2744,7 @@ class Notification { } this.element.remove(); } + addCloseListener() { this.closeBtn.addEventListener('click', () => { this.removeChild(); @@ -2743,10 +2761,10 @@ class Notification { onConfirmCallback() { if (typeof this.onConfirm === 'function') { this.pause = !this.pause - if(!this.pause){ + if (!this.pause) { this.stepProgressBar(this.onConfirm); - this.confirmBotton.innerText = getCookie("locale")=="zh" ? '暂停' : 'pause' - }else{ + this.confirmBotton.innerText = getCookie("locale") == "zh" ? '暂停' : 'pause' + } else { this.confirmBotton.innerText = this.confirmBtn } } @@ -2767,7 +2785,7 @@ function setCookie(name, value, days) { document.cookie = name + "=" + (value || "") + expires + "; path=/"; } -document.addEventListener('DOMContentLoaded', function() { +document.addEventListener('DOMContentLoaded', function () { showTab('tab1'); }); @@ -2856,53 +2874,28 @@ function showTab(tabId) { function createGridItem(workflowName, container, thumbnail, author = '', time = '', showDeleteButton = false) { var gridItem = document.createElement('div'); gridItem.className = 'grid-item'; - gridItem.style.borderRadius = '15px'; - gridItem.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; var img = document.createElement('div'); img.className = 'thumbnail'; img.style.backgroundImage = `url('${thumbnail}')`; - img.style.backgroundSize = 'cover'; - img.style.backgroundPosition = 'center'; gridItem.appendChild(img); var caption = document.createElement('div'); caption.className = 'caption'; - caption.style.backgroundColor = 'white'; var h6 = document.createElement('h6'); h6.textContent = workflowName; - h6.style.margin = '1px 0'; var pAuthor = document.createElement('p'); pAuthor.textContent = `Author: ${author}`; - pAuthor.style.margin = '1px 0'; - pAuthor.style.fontSize = '10px'; var pTime = document.createElement('p'); pTime.textContent = `Date: ${time}`; - pTime.style.margin = '1px 0'; - pTime.style.fontSize = '10px'; var button = document.createElement('button'); button.textContent = ' Load '; - button.className = 'button'; - button.style.backgroundColor = '#007aff'; - button.style.color = 'white'; - button.style.padding = '2px 7px'; - button.style.border = 'none'; - button.style.borderRadius = '8px'; - button.style.fontSize = '12px'; - button.style.cursor = 'pointer'; - button.style.transition = 'background 0.3s'; - - button.addEventListener('mouseover', function () { - button.style.backgroundColor = '#005bb5'; - }); + button.className = 'button load-button'; - button.addEventListener('mouseout', function () { - button.style.backgroundColor = '#007aff'; - }); button.onclick = function (e) { e.preventDefault(); sendWorkflow(workflowName); @@ -2916,22 +2909,7 @@ function createGridItem(workflowName, container, thumbnail, author = '', time = if (showDeleteButton) { var deleteButton = document.createElement('button'); deleteButton.textContent = 'Delete'; - deleteButton.className = 'button'; - deleteButton.style.backgroundColor = '#007aff'; - deleteButton.style.color = 'white'; - deleteButton.style.padding = '2px 3px'; - deleteButton.style.border = 'none'; - deleteButton.style.borderRadius = '8px'; - deleteButton.style.fontSize = '12px'; - deleteButton.style.cursor = 'pointer'; - deleteButton.style.transition = 'background 0.3s'; - - deleteButton.addEventListener('mouseover', function () { - deleteButton.style.backgroundColor = '#005bb5'; - }); - deleteButton.addEventListener('mouseout', function () { - deleteButton.style.backgroundColor = '#007aff'; - }); + deleteButton.className = 'button delete-button'; deleteButton.onclick = function (e) { e.preventDefault(); @@ -2947,7 +2925,6 @@ function createGridItem(workflowName, container, thumbnail, author = '', time = } - function showGalleryWorkflowList(tabId) { const container = document.getElementById(tabId).querySelector('.grid-container'); container.innerHTML = ''; @@ -3009,7 +2986,7 @@ function showLoadWorkflowList(tabId) { data.files.forEach(workflowName => { const title = workflowName.replace(/\.json$/, ''); - const thumbnail = generateThumbnailFromContent({ title }); + const thumbnail = generateThumbnailFromContent({title}); createGridItem(title, container, thumbnail, '', '', true); }); }) diff --git a/src/agentscope/studio/templates/workstation.html b/src/agentscope/studio/templates/workstation.html index be879247b..653a1a387 100644 --- a/src/agentscope/studio/templates/workstation.html +++ b/src/agentscope/studio/templates/workstation.html @@ -43,21 +43,23 @@
    - Workstation + {% if token_dict %} -
    -
  • -
    Example
    +
    Example +
    • + > Two Agents
      + > Pipeline
      + > Conversation
      + > Group Chat
      Use examples Learn more
      @@ -147,7 +158,7 @@
    • - Workflow + Workflow
      • @@ -175,7 +186,8 @@ ondragstart="drag(event)">Post API Dall-E
      • Wanx
      @@ -184,7 +196,7 @@
      + > Message
        @@ -198,7 +210,7 @@
        + > Agent
          @@ -233,7 +245,7 @@
          + > Pipeline
            @@ -278,7 +290,7 @@
            + > Service
              @@ -323,7 +335,7 @@
              + > Tool
                @@ -350,7 +362,9 @@
            -
            Gallery
            +
            Gallery +
  • @@ -489,8 +503,13 @@
      - - + +
    @@ -509,8 +528,10 @@
    × -

    "We want to hear from you"

    -
    From e37f00e52ebfa06ffa945dd8548b5e278f8c1f6f Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:18:46 +0800 Subject: [PATCH 21/47] fix translation (#38) --- src/agentscope/studio/static/i18n/i18n_zh.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/agentscope/studio/static/i18n/i18n_zh.json b/src/agentscope/studio/static/i18n/i18n_zh.json index 62f2a122b..07e99df42 100644 --- a/src/agentscope/studio/static/i18n/i18n_zh.json +++ b/src/agentscope/studio/static/i18n/i18n_zh.json @@ -49,7 +49,7 @@ "agent-reactagent-labelName": "名称", "agent-reactagent-labelName-Assistant": "助理", "agent-reactagent-SystemPrompt": "系统提示", - "agent-reactagent-SystemPrompt-textarea": "你是助理。", + "agent-reactagent-SystemPrompt-textarea": "你是一个助理。", "agent-reactagent-ModelConfigName": "模型配置名称", "agent-reactagent-Tools": "工具", "agent-reactagent-Tools-placeholder": "请将工具模块拖放到此区域。插入其他类型的模块可能会导致错误。", @@ -135,10 +135,10 @@ "service-write-text-readme": "将写入文本服务集成到 ReActAgent 中提升智能体能力", "Welcome-Welcome":"👏 欢迎!", "welcome-Easy-to-use": "易于使用的多智能体平台:", - "welcome-Shortkeys": "快捷:", + "welcome-Shortkeys": "要点:", "welcome-Easy-to-Use": "🤝 易于使用", "welcome-High-Robustness": "✅ 高鲁棒性", - "welcome-Actor-Based": "🌐 基于参与者的分配", + "welcome-Actor-Based": "🌐 基于Actor模型的分布式", "welcome-Documentations": "文档:", "welcome-Quick-Start": "🚀 快速入门", "welcome-Examples": "💡 例子", @@ -175,7 +175,7 @@ "en4-json-readme-sentence7": "- 应用程序中的对话智能体。", "en4-json-readme-moreDetails": "更多详情,请参阅", "en4-json-readme-moreDetailsHref": "此处", - "service-text-to-audio-reademe": "将文本到音频服务集成到 ReActAgent 中以增强智能体功能", + "service-text-to-audio-reademe": "将文本转音频服务集成到 ReActAgent 中以增强智能体功能", "service-text-to-audio-modelName": "模型名称", "service-text-to-audio-sampleRate": "采样率", "service-text-to-image-modelName": "模型名称", From 79effa675b3b8ec1f234d0d6e7a1c70a96214204 Mon Sep 17 00:00:00 2001 From: zhijianma Date: Fri, 18 Oct 2024 10:02:25 +0800 Subject: [PATCH 22/47] add input and output hover effects using css (#39) --- .../studio/static/css/workstation-drag-components.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/agentscope/studio/static/css/workstation-drag-components.css b/src/agentscope/studio/static/css/workstation-drag-components.css index 086a7bbc9..9d838523f 100644 --- a/src/agentscope/studio/static/css/workstation-drag-components.css +++ b/src/agentscope/studio/static/css/workstation-drag-components.css @@ -46,6 +46,14 @@ outline: 1px solid var(--main-color); } +.drawflow-node .input:hover { + background: #43b993; +} + +.drawflow-node .output:hover { + background: #43b993; +} + .drawflow > .drawflow-delete { border: 2px solid #43b993; background: white; From 1332b1ae3f8b4668cd9ea5d57049fd74641de6d9 Mon Sep 17 00:00:00 2001 From: myh-0521 <95087599+myh-0521@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:11:46 +0800 Subject: [PATCH 23/47] Gallery refine load return (#41) --- .../studio/static/css/workstation.css | 1 + .../studio/static/i18n/i18n_en.json | 9 + .../studio/static/i18n/i18n_zh.json | 9 + src/agentscope/studio/static/js/gallery.js | 97 +++++++---- src/agentscope/studio/static/js/index.js | 1 + .../studio/static/js/workstation.js | 158 ++++++++++++------ .../studio/templates/workstation.html | 42 +++-- 7 files changed, 214 insertions(+), 103 deletions(-) diff --git a/src/agentscope/studio/static/css/workstation.css b/src/agentscope/studio/static/css/workstation.css index 2a3d7e6c1..5af3fd2d8 100644 --- a/src/agentscope/studio/static/css/workstation.css +++ b/src/agentscope/studio/static/css/workstation.css @@ -114,6 +114,7 @@ body { margin: 0 5px; cursor: pointer; position: relative; + position: relative; } .menu-btn:hover { diff --git a/src/agentscope/studio/static/i18n/i18n_en.json b/src/agentscope/studio/static/i18n/i18n_en.json index 9ccaa476c..6d67ebacb 100644 --- a/src/agentscope/studio/static/i18n/i18n_en.json +++ b/src/agentscope/studio/static/i18n/i18n_en.json @@ -27,6 +27,15 @@ "workstation-sidebar-item-Tool": "Tool", "workstation-surveyContent": "We want to hear from you", "workstation-surveyContent-Button": "Participation in questionnaire surveys", + "workstation-menu-btn-home": "Home", + "workstation-menu-btn-download": "Download workflow configuration", + "workstation-menu-btn-load": "Load workflow configuration", + "workstation-menu-btn-check": "Check the workflow", + "workstation-menu-btn-clear": "Clear", + "workstation-menu-btn-export": "Export Python code", + "workstation-menu-btn-run": "Run the workflow", + "workstation-menu-btn-save": "Save workflow", + "workstation-menu-btn-workflow": "Load workflow", "agent-dialogagent-Copy": "Copy", "agent-dialogagent-readme": "An dialog agent that can interact with users or other agents.", "agent-dialogagent-labelName": "Name", diff --git a/src/agentscope/studio/static/i18n/i18n_zh.json b/src/agentscope/studio/static/i18n/i18n_zh.json index 07e99df42..02e81ab3a 100644 --- a/src/agentscope/studio/static/i18n/i18n_zh.json +++ b/src/agentscope/studio/static/i18n/i18n_zh.json @@ -27,6 +27,15 @@ "workstation-sidebar-item-Tool": "工具", "workstation-surveyContent": "我们希望听到您的意见", "workstation-surveyContent-Button": "参与问卷调查", + "workstation-menu-btn-home": "家", + "workstation-menu-btn-download": "下载工作流配置", + "workstation-menu-btn-load": "加载工作流配置", + "workstation-menu-btn-check": "检查工作流程", + "workstation-menu-btn-clear": "清除", + "workstation-menu-btn-export": "导出 Python 代码", + "workstation-menu-btn-run": "运行工作流", + "workstation-menu-btn-save": "保存工作流", + "workstation-menu-btn-workflow": "加载工作流", "agent-dialogagent-Copy": "复制", "agent-dialogagent-readme": "可与用户或其他智能体互动的对话智能体。", "agent-dialogagent-labelName": "名称", diff --git a/src/agentscope/studio/static/js/gallery.js b/src/agentscope/studio/static/js/gallery.js index 56cc28f26..57e0f1f43 100644 --- a/src/agentscope/studio/static/js/gallery.js +++ b/src/agentscope/studio/static/js/gallery.js @@ -1,5 +1,35 @@ - showTab('tab1'); + +function showTab(tabId) { + var tabs = document.getElementsByClassName("tab"); + for (var i = 0; i < tabs.length; i++) { + tabs[i].classList.remove("active"); + tabs[i].style.display = "none"; + } + var tab = document.getElementById(tabId); + if (tab) { + tab.classList.add("active"); + tab.style.display = "block"; + console.log(`Activated tab with ID: ${tab.id}`); + + var tabButtons = document.getElementsByClassName("tab-button"); + for (var j = 0; j < tabButtons.length; j++) { + tabButtons[j].classList.remove("active"); + } + var activeTabButton = document.querySelector(`.tab-button[onclick*="${tabId}"]`); + if (activeTabButton) { + activeTabButton.classList.add("active"); + } + + if (tabId === "tab2") { + showLoadWorkflowList(tabId); + } else if (tabId === "tab1") { + console.log('Loading Gallery Workflow List'); + showGalleryWorkflowList(tabId); + } + } +} + function sendWorkflow(fileName) { if (confirm('Are you sure you want to import this workflow?')) { const workstationUrl = '/workstation?filename=' + encodeURIComponent(fileName); @@ -7,6 +37,28 @@ function sendWorkflow(fileName) { } } +function importGalleryWorkflow(data) { + try { + const parsedData = JSON.parse(data); + addHtmlAndReplacePlaceHolderBeforeImport(parsedData) + .then(() => { + editor.clear(); + editor.import(parsedData); + importSetupNodes(parsedData); + + if (confirm('Imported!')) { + const workstationUrl = '/workstation'; + window.location.href = workstationUrl; + } + }) + .catch(error => { + alert(`Import error: ${error}`); + }); + } catch (error) { + alert(`Import error: ${error}`); + } +} + function deleteWorkflow(fileName) { if (confirm('Workflow has been deleted?')) { fetch('/delete-workflow', { @@ -32,36 +84,7 @@ function deleteWorkflow(fileName) { } } -function showTab(tabId) { - var tabs = document.getElementsByClassName("tab"); - for (var i = 0; i < tabs.length; i++) { - tabs[i].classList.remove("active"); - tabs[i].style.display = "none"; - } - var tab = document.getElementById(tabId); - if (tab) { - tab.classList.add("active"); - tab.style.display = "block"; - - var tabButtons = document.getElementsByClassName("tab-button"); - for (var j = 0; j < tabButtons.length; j++) { - tabButtons[j].classList.remove("active"); - } - var activeTabButton = document.querySelector(`.tab-button[onclick*="${tabId}"]`); - if (activeTabButton) { - activeTabButton.classList.add("active"); - } - - if (tabId === "tab2") { - showLoadWorkflowList(tabId); - } else if (tabId === "tab1") { - showGalleryWorkflowList(tabId); - } - } -} - - -function createGridItem(workflowName, container, thumbnail, author = '', time = '', showDeleteButton = false) { +function createGridItem(workflowName, container, thumbnail, author = '', time = '', showDeleteButton = false, index) { var gridItem = document.createElement('div'); gridItem.className = 'grid-item'; gridItem.style.borderRadius = '15px'; @@ -116,9 +139,15 @@ function createGridItem(workflowName, container, thumbnail, author = '', time = }); button.onclick = function (e) { e.preventDefault(); - sendWorkflow(workflowName); + if (showDeleteButton) { + sendWorkflow(workflowName); + } else { + const workflowData = galleryWorkflows[index]; + importGalleryWorkflow(JSON.stringify(workflowData)); + } }; + caption.appendChild(h6); if (author) caption.appendChild(pAuthor); if (time) caption.appendChild(pTime); @@ -158,6 +187,8 @@ function createGridItem(workflowName, container, thumbnail, author = '', time = console.log('Grid item appended:', gridItem); } +// let galleryWorkflows = []; + function showGalleryWorkflowList(tabId) { const container = document.getElementById(tabId).querySelector('.grid-container'); container.innerHTML = ''; @@ -196,7 +227,7 @@ function showGalleryWorkflowList(tabId) { }) .catch(error => { console.error('Error fetching gallery workflows:', error); - + alert('Failed to load gallery workflows.'); }); } diff --git a/src/agentscope/studio/static/js/index.js b/src/agentscope/studio/static/js/index.js index 94eb88c97..6202a7b8c 100644 --- a/src/agentscope/studio/static/js/index.js +++ b/src/agentscope/studio/static/js/index.js @@ -11,6 +11,7 @@ let activeExpanded = false; // Check if the script is already loaded function isScriptLoaded(src) { + if(src == 'static/js/gallery.js')return; let curURL = new URL(src, window.location.href).pathname; return Array.from(document.scripts).some((script) => { try { diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 49421fa0b..d160f1de6 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -2785,7 +2785,7 @@ function setCookie(name, value, days) { document.cookie = name + "=" + (value || "") + expires + "; path=/"; } -document.addEventListener('DOMContentLoaded', function () { +document.addEventListener('DOMContentLoaded', function() { showTab('tab1'); }); @@ -2805,6 +2805,35 @@ function sendWorkflow(fileName) { }); } + +function showEditorTab() { + document.getElementById('col-right').style.display = 'block'; + document.getElementById('col-right2').style.display = 'none'; + console.log('Show Editor'); +} +function importGalleryWorkflow(data) { + try { + const parsedData = JSON.parse(data); + addHtmlAndReplacePlaceHolderBeforeImport(parsedData) + .then(() => { + editor.clear(); + editor.import(parsedData); + importSetupNodes(parsedData); + Swal.fire({ + title: 'Imported!', + icon: 'success', + showConfirmButton: true + }).then((result) => { + if (result.isConfirmed) { + showEditorTab(); + } + }); + }); + } catch (error) { + Swal.showValidationMessage(`Import error: ${error}`); + } +} + function deleteWorkflow(fileName) { Swal.fire({ title: 'Are you sure?', @@ -2870,35 +2899,96 @@ function showTab(tabId) { } } +let galleryWorkflows = []; + +function showGalleryWorkflowList(tabId) { + const container = document.getElementById(tabId).querySelector('.grid-container'); + container.innerHTML = ''; + fetch('/fetch-gallery', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}) + }) + .then(response => response.json()) + .then(data => { + galleryWorkflows = data.json || []; // 存储获取到的工作流数据 + galleryWorkflows.forEach((workflow, index) => { + const meta = workflow.meta; + const title = meta.title; + const author = meta.author; + const time = meta.time; + const thumbnail = meta.thumbnail || generateThumbnailFromContent(meta); + createGridItem(title, container, thumbnail, author, time, false, index); // 将index传递给createGridItem + }); + }) + .catch(error => { + console.error('Error fetching gallery workflows:', error); + }); +} -function createGridItem(workflowName, container, thumbnail, author = '', time = '', showDeleteButton = false) { +function createGridItem(workflowName, container, thumbnail, author = '', time = '', showDeleteButton = false, index) { + var gridItem = document.createElement('div'); + gridItem.className = 'grid-item'; + gridItem.style.borderRadius = '15px'; var gridItem = document.createElement('div'); gridItem.className = 'grid-item'; + gridItem.style.borderRadius = '15px'; + gridItem.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; var img = document.createElement('div'); img.className = 'thumbnail'; img.style.backgroundImage = `url('${thumbnail}')`; + img.style.backgroundSize = 'cover'; + img.style.backgroundPosition = 'center'; gridItem.appendChild(img); var caption = document.createElement('div'); caption.className = 'caption'; + caption.style.backgroundColor = 'white'; var h6 = document.createElement('h6'); h6.textContent = workflowName; + h6.style.margin = '1px 0'; var pAuthor = document.createElement('p'); pAuthor.textContent = `Author: ${author}`; + pAuthor.style.margin = '1px 0'; + pAuthor.style.fontSize = '10px'; var pTime = document.createElement('p'); pTime.textContent = `Date: ${time}`; + pTime.style.margin = '1px 0'; + pTime.style.fontSize = '10px'; var button = document.createElement('button'); button.textContent = ' Load '; - button.className = 'button load-button'; + button.className = 'button'; + button.style.backgroundColor = '#007aff'; + button.style.color = 'white'; + button.style.padding = '2px 7px'; + button.style.border = 'none'; + button.style.borderRadius = '8px'; + button.style.fontSize = '12px'; + button.style.cursor = 'pointer'; + button.style.transition = 'background 0.3s'; + + button.addEventListener('mouseover', function () { + button.style.backgroundColor = '#005bb5'; + }); + button.addEventListener('mouseout', function () { + button.style.backgroundColor = '#007aff'; + }); button.onclick = function (e) { e.preventDefault(); - sendWorkflow(workflowName); + if (showDeleteButton) { + sendWorkflow(workflowName); + } else { + const workflowData = galleryWorkflows[index]; + importGalleryWorkflow(JSON.stringify(workflowData)); + } }; caption.appendChild(h6); @@ -2909,7 +2999,22 @@ function createGridItem(workflowName, container, thumbnail, author = '', time = if (showDeleteButton) { var deleteButton = document.createElement('button'); deleteButton.textContent = 'Delete'; - deleteButton.className = 'button delete-button'; + deleteButton.className = 'button'; + deleteButton.style.backgroundColor = '#007aff'; + deleteButton.style.color = 'white'; + deleteButton.style.padding = '2px 3px'; + deleteButton.style.border = 'none'; + deleteButton.style.borderRadius = '8px'; + deleteButton.style.fontSize = '12px'; + deleteButton.style.cursor = 'pointer'; + deleteButton.style.transition = 'background 0.3s'; + + deleteButton.addEventListener('mouseover', function () { + deleteButton.style.backgroundColor = '#005bb5'; + }); + deleteButton.addEventListener('mouseout', function () { + deleteButton.style.backgroundColor = '#007aff'; + }); deleteButton.onclick = function (e) { e.preventDefault(); @@ -2924,49 +3029,6 @@ function createGridItem(workflowName, container, thumbnail, author = '', time = console.log('Grid item appended:', gridItem); } - -function showGalleryWorkflowList(tabId) { - const container = document.getElementById(tabId).querySelector('.grid-container'); - container.innerHTML = ''; - - fetch('/fetch-gallery', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}) - }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - }) - .then(data => { - console.log('Fetched gallery data:', data); - - const workflows = data.json || []; - - if (!Array.isArray(workflows)) { - console.error('The server did not return an array as expected.', data); - workflows = [workflows]; - } - - workflows.forEach(workflow => { - const meta = workflow.meta; - const title = meta.title; - const author = meta.author; - const time = meta.time; - const thumbnail = meta.thumbnail || generateThumbnailFromContent(meta); - createGridItem(title, container, thumbnail, author, time, false); - }); - }) - .catch(error => { - console.error('Error fetching gallery workflows:', error); - - }); -} - function showLoadWorkflowList(tabId) { fetch('/list-workflows', { method: 'POST', diff --git a/src/agentscope/studio/templates/workstation.html b/src/agentscope/studio/templates/workstation.html index 653a1a387..b9fb957d3 100644 --- a/src/agentscope/studio/templates/workstation.html +++ b/src/agentscope/studio/templates/workstation.html @@ -43,9 +43,7 @@
    - + Workstation {% if token_dict %}
    @@ -362,9 +360,7 @@ -
    Gallery -
    +
    Gallery
    @@ -376,7 +372,7 @@ xmlns="http://www.w3.org/2000/svg"> - Home + Home
    {% set version = token_dict.get('version') if token_dict is defined else "local" %}
      - - + + +
    @@ -550,6 +542,12 @@

    "We want to hear from console.log('Show Gallery'); } + function showEditorTab() { + document.getElementById('col-right').style.display = 'block'; + document.getElementById('col-right2').style.display = 'none'; + console.log('Show Editor'); + } + console.log("Workstation page loaded"); initializeWorkstationPage(); From af3e3b2be79a8dbc3bcbd52c2f23dd9df6019903 Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:21:46 +0800 Subject: [PATCH 24/47] make guest button not seen (#42) --- src/agentscope/studio/static/css/login.css | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/agentscope/studio/static/css/login.css b/src/agentscope/studio/static/css/login.css index 43d2cd761..4e5317275 100644 --- a/src/agentscope/studio/static/css/login.css +++ b/src/agentscope/studio/static/css/login.css @@ -62,21 +62,17 @@ body { background: none; border: none; text-decoration: underline; + opacity: 0.001; } #loginGuestButton:hover { transform: scale(1.01); text-decoration: underline; -} - -#loginGuestButton:active { - transform: scale(1); - text-decoration: underline; + cursor: default; } #loginGuestButton:disabled { color: #d3d3d3; - cursor: not-allowed; text-decoration: none; transform: none; } From df3a5b6384af0e861f2c3d65b77508fd3546056c Mon Sep 17 00:00:00 2001 From: zhijianma Date: Tue, 22 Oct 2024 19:52:23 +0800 Subject: [PATCH 25/47] Feat/code (#28) --- setup.py | 1 + .../css/workstation-drag-components.css | 4 + .../studio/static/css/workstation.css | 8 + .../pipeline-forlooppipeline.html | 22 +- .../pipeline-ifelsepipeline.html | 22 +- .../html-drag-components/tool-code.html | 21 + .../html-drag-components/tool-if-else.html | 35 ++ .../studio/static/i18n/i18n_en.json | 7 + .../studio/static/i18n/i18n_zh.json | 7 + .../studio/static/js/workstation.js | 190 +++++- .../studio/templates/workstation.html | 11 + .../studio/tools/condition_operator.py | 31 + .../workstation/examples/1_conversation.json | 3 +- .../examples/3_condition_pipeline.json | 3 +- .../web/workstation/workflow_dag.py | 20 +- .../web/workstation/workflow_node.py | 582 ++++++------------ 16 files changed, 531 insertions(+), 436 deletions(-) create mode 100644 src/agentscope/studio/static/html-drag-components/tool-code.html create mode 100644 src/agentscope/studio/static/html-drag-components/tool-if-else.html create mode 100644 src/agentscope/studio/tools/condition_operator.py diff --git a/setup.py b/setup.py index 778fd79a9..e5908c3fc 100644 --- a/setup.py +++ b/setup.py @@ -88,6 +88,7 @@ "psutil", "scipy", "pillow", + "isort", ] distribute_requires = minimal_requires + rpc_requires diff --git a/src/agentscope/studio/static/css/workstation-drag-components.css b/src/agentscope/studio/static/css/workstation-drag-components.css index 9d838523f..128f39999 100644 --- a/src/agentscope/studio/static/css/workstation-drag-components.css +++ b/src/agentscope/studio/static/css/workstation-drag-components.css @@ -84,6 +84,10 @@ display: none; } +.hidden { + display: none; +} + .box input, .box select, .box textarea { display: flex; flex-direction: column; diff --git a/src/agentscope/studio/static/css/workstation.css b/src/agentscope/studio/static/css/workstation.css index 5af3fd2d8..ab1ca0cb7 100644 --- a/src/agentscope/studio/static/css/workstation.css +++ b/src/agentscope/studio/static/css/workstation.css @@ -1008,6 +1008,14 @@ pre[class*="language-"] { transition: width 0.1s linear; } +.code-content { + display: flex; + width: var(--code-content-width); + min-height: 200px; + height: 100%; + overflow: auto; + white-space: pre; +} .two-column-layout { display: flex; diff --git a/src/agentscope/studio/static/html-drag-components/pipeline-forlooppipeline.html b/src/agentscope/studio/static/html-drag-components/pipeline-forlooppipeline.html index 64f15e6a7..7cdd75175 100644 --- a/src/agentscope/studio/static/html-drag-components/pipeline-forlooppipeline.html +++ b/src/agentscope/studio/static/html-drag-components/pipeline-forlooppipeline.html @@ -17,9 +17,25 @@
    - - -
    + +
    + + +

    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/pipeline-ifelsepipeline.html b/src/agentscope/studio/static/html-drag-components/pipeline-ifelsepipeline.html index 96590eeec..92daf1b60 100644 --- a/src/agentscope/studio/static/html-drag-components/pipeline-ifelsepipeline.html +++ b/src/agentscope/studio/static/html-drag-components/pipeline-ifelsepipeline.html @@ -13,9 +13,25 @@ with if-else logic
    - - -
    + +
    + + +
    If condition
    diff --git a/src/agentscope/studio/static/html-drag-components/tool-code.html b/src/agentscope/studio/static/html-drag-components/tool-code.html new file mode 100644 index 000000000..518d04eb0 --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/tool-code.html @@ -0,0 +1,21 @@ +
    +
    + + + + + Code +
    + +
    +
    +
    + Execute Python Code +
    Note:
    +
    - Please do not modify the function name.
    +
    - Input and Output variables must be a Msg or dict object.
    +
    + +
    +
    diff --git a/src/agentscope/studio/static/html-drag-components/tool-if-else.html b/src/agentscope/studio/static/html-drag-components/tool-if-else.html new file mode 100644 index 000000000..740417004 --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/tool-if-else.html @@ -0,0 +1,35 @@ +
    +
    + + + + If/Else +
    + +
    +
    +
    A template for implementing control flow + with if-else logic. +
    + + +
    + + + +
    diff --git a/src/agentscope/studio/static/i18n/i18n_en.json b/src/agentscope/studio/static/i18n/i18n_en.json index 6d67ebacb..4cba00fd8 100644 --- a/src/agentscope/studio/static/i18n/i18n_en.json +++ b/src/agentscope/studio/static/i18n/i18n_en.json @@ -13,6 +13,8 @@ "dialogue-detail-dialogue-User":"User", "dialogue-detail-dialogue-textarea": "Input message here ...", "dialogue-detail-dialogue-send-btn": "Send", + "condition-Operator": "Condition Operator", + "condition-TargetValue": "Target Value", "workstation-Example":"Example", "workstation-sidebar-subitem-Agents": "Two Agents", "workstation-sidebar-subitem-Pipeline": "Pipeline", @@ -212,6 +214,11 @@ "tool-video-composition-targetVideoWidth": "Target Video Width", "tool-video-composition-targetVideoHeight": "Target Video Height", "tool-video-composition-targetVideoFps": "Target Video Fps", + "tool-code-readme": "Execute Python Code", + "tool-code-note": "Note:", + "tool-code-note-var": "- Input and Output variables must be a Msg or dict object.", + "tool-code-note-func": "- Please do not modify the function name.", + "tool-code-content": "Code", "workstarionjs-import-prev": "Previous", "workstarionjs-import-next": "Next", "workstarionjs-import-skip": "Skip", diff --git a/src/agentscope/studio/static/i18n/i18n_zh.json b/src/agentscope/studio/static/i18n/i18n_zh.json index 02e81ab3a..9790534fe 100644 --- a/src/agentscope/studio/static/i18n/i18n_zh.json +++ b/src/agentscope/studio/static/i18n/i18n_zh.json @@ -13,6 +13,8 @@ "dialogue-detail-dialogue-User":"用户", "dialogue-detail-dialogue-textarea": "在此输入消息...", "dialogue-detail-dialogue-send-btn": "发送", + "condition-Operator": "条件运算", + "condition-TargetValue": "目标值", "workstation-Example":"示例", "workstation-sidebar-subitem-Agents": "两个智能体", "workstation-sidebar-subitem-Pipeline": "流程", @@ -212,6 +214,11 @@ "tool-video-composition-targetVideoWidth": "目标视频宽度", "tool-video-composition-targetVideoHeight": "目标视频高度", "tool-video-composition-targetVideoFps": "目标视频帧率", + "tool-code-readme": "执行Python代码", + "tool-code-note": "注意:", + "tool-code-note-var": "- 输入和输出参数必须是Msg或者dict对象。", + "tool-code-note-func": "- 请勿修改函数名称。", + "tool-code-content": "代码", "workstarionjs-import-prev": "上一步", "workstarionjs-import-next": "下一步", "workstarionjs-import-skip": "跳过", diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index d160f1de6..7472b41c4 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -40,6 +40,8 @@ let nameToHtmlFile = { 'TextToAudioService': 'service-text-to-audio.html', 'TextToImageService': 'service-text-to-image.html', 'ImageComposition': 'tool-image-composition.html', + 'Code': 'tool-code.html', + // 'IF/ELSE': 'tool-if-else.html', 'ImageMotion': 'tool-image-motion.html', 'VideoComposition': 'tool-video-composition.html', } @@ -85,7 +87,6 @@ async function fetchHtml(fileName) { } } - async function initializeWorkstationPage() { console.log("Initialize Workstation Page") // Initialize the Drawflow editor @@ -726,7 +727,8 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { elements: [], "args": { "max_loop": 3, - "break_func": '' + "condition_op": "", + "target_value": "", } }, htmlSourceCode); break; @@ -745,7 +747,8 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { editor.addNode('IfElsePipeline', 1, 1, pos_x, pos_y, 'GROUP', { elements: [], args: { - "condition_func": '' + "condition_op": "", + "target_value": "", } }, htmlSourceCode); break; @@ -757,13 +760,6 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { "cases": [], } }, htmlSourceCode); - setupSwitchPipelineListeners(SwitchPipelineID); - const caseContainer = document.querySelector(`#node-${SwitchPipelineID} .case-container`); - if (caseContainer) { - addDefaultCase(caseContainer); - } else { - console.error(`Case container not found in node-${SwitchPipelineID}.`); - } break; // Workflow-Service @@ -812,7 +808,6 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { "sample_rate": "" } }, htmlSourceCode); - updateSampleRate(TextToAudioServiceID) break; case 'TextToImageService': editor.addNode('TextToImageService', 0, 0, @@ -840,6 +835,23 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { } }, htmlSourceCode); break; + case 'Code': + const CodeID = editor.addNode('Code', 1, 1, + pos_x, pos_y, 'Code', { + "args": { + "code": "def function(msg1: Msg) -> Msg:\n content1 = msg1.get(\"content\", \"\")\n return {\n \"role\": \"assistant\",\n \"content\": content1,\n \"name\": \"function\",\n }" + } + }, htmlSourceCode); + break; + // case 'IF/ELSE': + // const IfelseID = editor.addNode('IF/ELSE', 1, 2, + // pos_x, pos_y, 'IF/ELSE', { + // "args": { + // "condition_op": "", + // "target_value": "", + // } + // }, htmlSourceCode); + // break; case 'ImageMotion': editor.addNode('ImageMotion', 1, 1, @@ -883,6 +895,70 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { } } + +function initializeMonacoEditor(nodeId) { + require.config({ + paths: { + vs: "https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs", + }, + }); + + require(["vs/editor/editor.main"], function () { + const parentSelector = `#node-${nodeId}`; + const parentNode = document.querySelector(parentSelector); + + if (!parentNode) { + console.error(`Parent node with selector ${parentSelector} not found.`); + return; + } + + const codeContentElement = parentNode.querySelector(`.code-content`); + if (!codeContentElement) { + return; + } + + const node = editor.getNodeFromId(nodeId); + if (!node) { + console.error(`Node with ID ${nodeId} not found.`); + return; + } + + const editorInstance = monaco.editor.create(codeContentElement, { + value: node.data.args.code, + language: "python", + theme: "vs-light", + minimap: { + enabled: false, + }, + wordWrap: "on", + lineNumbersMinChars: 1, + scrollBeyondLastLine: false, + readOnly: false, + }); + + editorInstance.onDidChangeModelContent(function () { + const updatedNode = editor.getNodeFromId(nodeId); + if (updatedNode) { + updatedNode.data.args.code = editorInstance.getValue().trim(); + editor.updateNodeDataFromId(nodeId, updatedNode.data); + } + }); + + const resizeObserver = new ResizeObserver(() => { + editorInstance.layout(); + }); + resizeObserver.observe(parentNode); + parentNode.addEventListener('DOMNodeRemoved', function () { + resizeObserver.disconnect(); + }); + + }, function (error) { + console.error("Error encountered while loading monaco editor: ", error); + }); +} + + + function updateSampleRate(nodeId) { const newNode = document.getElementById(`node-${nodeId}`); if (!newNode) { @@ -929,7 +1005,7 @@ function setupTextInputListeners(nodeId) { }; newNode.addEventListener('mousedown', function (event) { const target = event.target; - if (target.tagName === 'TEXTAREA' || target.tagName === 'INPUT') { + if (target.tagName === 'TEXTAREA' || target.tagName === 'INPUT' || target.closest('.code-content')) { stopPropagation(event); } }, false); @@ -1141,10 +1217,39 @@ function hideShowGroupNodes(groupId, show) { } } +function setupConditionListeners(nodeId) { + const newNode = document.getElementById(`node-${nodeId}`); + if (newNode) { + const conditionOp = newNode.querySelector('#condition_op'); + const targetContainer = newNode.querySelector('#target-container'); + console.log(conditionOp, targetContainer); + + function updateTargetVisibility() { + const condition_op = conditionOp ? conditionOp.value : ''; + const hideConditions = ['', 'is empty', 'is null', 'is not empty', 'is not null']; + if (hideConditions.includes(condition_op)) { + targetContainer.style.display = 'none'; + } else { + targetContainer.style.display = 'block'; + } + } + + if (conditionOp) { + conditionOp.addEventListener('input', updateTargetVisibility); + updateTargetVisibility(); + } + } +} + function setupNodeListeners(nodeId) { const newNode = document.getElementById(`node-${nodeId}`); if (newNode) { + initializeMonacoEditor(nodeId); + setupConditionListeners(nodeId); + updateSampleRate(nodeId); + setupSwitchPipelineListeners(nodeId); + const titleBox = newNode.querySelector('.title-box'); const contentBox = newNode.querySelector('.box') || newNode.querySelector('.box-highlight'); @@ -1223,7 +1328,6 @@ function setupSwitchPipelineListeners(nodeId) { } const addCaseButton = newNode.querySelector('.add-case'); if (!addCaseButton) { - console.error(`Add Case button not found in node-${nodeId}.`); return; } addCaseButton.addEventListener('click', function () { @@ -1269,7 +1373,6 @@ function setupSwitchPipelineListeners(nodeId) { const removeCaseButton = newNode.querySelector('.remove-case'); if (!removeCaseButton) { - console.error(`Remove Case button not found in node-${nodeId}.`); return; } removeCaseButton.addEventListener('click', function () { @@ -1283,6 +1386,47 @@ function setupSwitchPipelineListeners(nodeId) { } editor.updateConnectionNodes('node-' + nodeId); }); + + var caseContainer = newNode.querySelector('.case-container'); + if (!caseContainer) { + console.error(`Case container not found in node-${nodeId}.`); + return; + } + + var defaultCaseElement = caseContainer.querySelector('.default-case'); + if (defaultCaseElement) { + caseContainer.removeChild(defaultCaseElement); + } + + var cases = editor.getNodeFromId(nodeId).data.args.cases; + for (var caseCount = 0; caseCount < cases.length; caseCount++) { + + var caseElement = document.createElement('div'); + caseElement.classList.add('case-placeholder'); + + var caseText = document.createTextNode(`Case ${caseCount + 1}: `); + caseElement.appendChild(caseText); + + var inputElement = document.createElement('input'); + inputElement.type = 'text'; + inputElement.placeholder = `Case Pattern`; + inputElement.value = cases[caseCount]; + + inputElement.dataset.caseIndex = caseCount; + + caseElement.appendChild(inputElement); + caseContainer.appendChild(caseElement); + + inputElement.addEventListener('input', function (e) { + var nodeData = editor.getNodeFromId(nodeId).data; + console.log("nodeData", nodeData); + var index = e.target.dataset.caseIndex; + console.log("index", index); + nodeData.args.cases[index] = e.target.value; + editor.updateNodeDataFromId(nodeId, nodeData); + }); + } + addDefaultCase(caseContainer); } function addDefaultCase(caseContainer) { @@ -1533,6 +1677,20 @@ function checkConditions() { return false; } } + if (node.name === 'Code') { + const code = node.data.args.code; + const pattern = /\bdef\s+function\s*\(/; + + if (!pattern.test(code)) { + Swal.fire({ + title: 'Invalid Code Function Name', + text: `${node.name} only support "function" as the function name.`, + icon: 'error', + confirmButtonText: 'Ok' + }); + return false; + } + } } let unmatchedConfigNames = [...agentModelConfigNames].filter(name => !modelConfigNames.has(name)); @@ -1877,6 +2035,10 @@ function showExportHTMLPopup() { // Remove the html attribute from the nodes to avoid inconsistencies in html removeHtmlFromUsers(rawData); + const hasError = sortElementsByPosition(rawData); + if (hasError) { + return; + } const exportData = JSON.stringify(rawData, null, 4); diff --git a/src/agentscope/studio/templates/workstation.html b/src/agentscope/studio/templates/workstation.html index b9fb957d3..d97f43cad 100644 --- a/src/agentscope/studio/templates/workstation.html +++ b/src/agentscope/studio/templates/workstation.html @@ -37,6 +37,7 @@ + @@ -357,6 +358,16 @@ draggable="true" ondragstart="drag(event)"> Post +
  • + Code +
  • + diff --git a/src/agentscope/studio/tools/condition_operator.py b/src/agentscope/studio/tools/condition_operator.py new file mode 100644 index 000000000..193be9b25 --- /dev/null +++ b/src/agentscope/studio/tools/condition_operator.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +"""Condition operator""" +from agentscope.message import Msg + + +def eval_condition_operator( + actual_value: str, + operator: str, + target_value: str = None, +) -> bool: + """Eval condition operator only for Msg content or string""" + if isinstance(actual_value, Msg): + actual_value = actual_value.get("content", "") + + operator_funcs = { + "contains": lambda: target_value in actual_value, + "not contains": lambda: target_value not in actual_value, + "start with": lambda: actual_value.startswith(target_value), + "end with": lambda: actual_value.endswith(target_value), + "equals": lambda: actual_value == target_value, + "not equals": lambda: actual_value != target_value, + "is empty": lambda: not bool(actual_value), + "is not empty": lambda: bool(actual_value), + "is null": lambda: actual_value is None, + "is not null": lambda: actual_value is not None, + } + + if operator in operator_funcs: + return operator_funcs[operator]() + else: + raise ValueError(f"Invalid condition operator: {operator}") diff --git a/src/agentscope/web/workstation/examples/1_conversation.json b/src/agentscope/web/workstation/examples/1_conversation.json index a92be236d..6a2afdd6e 100644 --- a/src/agentscope/web/workstation/examples/1_conversation.json +++ b/src/agentscope/web/workstation/examples/1_conversation.json @@ -23,7 +23,8 @@ ], "args": { "max_loop": 3, - "break_func": "" + "condition_op": "is null", + "target_value": "" } }, "inputs": { diff --git a/src/agentscope/web/workstation/examples/3_condition_pipeline.json b/src/agentscope/web/workstation/examples/3_condition_pipeline.json index e1b6c3878..07098f14b 100644 --- a/src/agentscope/web/workstation/examples/3_condition_pipeline.json +++ b/src/agentscope/web/workstation/examples/3_condition_pipeline.json @@ -7,7 +7,8 @@ "5" ], "args": { - "condition_func": "lambda x: \"user\" in x.get(\"content\", \"\")" + "condition_op": "contains", + "target_value": "user" } }, "inputs": { diff --git a/src/agentscope/web/workstation/workflow_dag.py b/src/agentscope/web/workstation/workflow_dag.py index 87ccdb227..9caec1c03 100644 --- a/src/agentscope/web/workstation/workflow_dag.py +++ b/src/agentscope/web/workstation/workflow_dag.py @@ -27,18 +27,6 @@ nx = None -def remove_duplicates_from_end(lst: list) -> list: - """remove duplicates element from end on a list""" - seen = set() - result = [] - for item in reversed(lst): - if item not in seen: - seen.add(item) - result.append(item) - result.reverse() - return result - - class ASDiGraph(nx.DiGraph): """ A class that represents a directed graph, extending the functionality of @@ -128,9 +116,10 @@ def compile( # type: ignore[no-untyped-def] def format_python_code(code: str) -> str: try: from black import FileMode, format_str + import isort - logger.debug("Formatting Code with black...") - return format_str(code, mode=FileMode()) + logger.debug("Formatting Code with black and isort...") + return isort.code(format_str(code, mode=FileMode())) except Exception: return code @@ -153,9 +142,6 @@ def format_python_code(code: str) -> str: header = "\n".join(self.imports) - # Remove duplicate import - new_imports = remove_duplicates_from_end(header.split("\n")) - header = "\n".join(new_imports) body = "\n ".join(self.inits + self.execs) main_body = f"def main():\n {body}" diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 2b23e8647..dd6f37e23 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -5,7 +5,9 @@ from enum import IntEnum from functools import partial from typing import List, Optional, Any - +import json +import re +from textwrap import dedent from agentscope import msghub from agentscope.agents import ( DialogAgent, @@ -40,10 +42,12 @@ dashscope_text_to_audio, dashscope_text_to_image, ServiceToolkit, + ServiceExecStatus, ) from agentscope.studio.tools.image_composition import stitch_images_with_grid from agentscope.studio.tools.image_motion import create_video_or_gif_from_image from agentscope.studio.tools.video_composition import merge_videos +from agentscope.studio.tools.condition_operator import eval_condition_operator from agentscope.studio.tools.web_post import web_post @@ -93,9 +97,13 @@ def __init__( self.dep_opts = dep_opts self.dep_vars = [opt.var_name for opt in self.dep_opts] self.var_name = f"{self.node_type.name.lower()}_{self.node_id}" + self.source_kwargs.pop("condition_op", "") + self.source_kwargs.pop("target_value", "") + self._post_init() + def _post_init(self) -> None: # Warning: Might cause error when args is still string - if not only_compile: + if not self.only_compile: for key, value in self.opt_kwargs.items(): if is_callable_expression(value): self.opt_kwargs[key] = convert_str_to_callable(value) @@ -134,21 +142,8 @@ class ModelNode(WorkflowNode): node_type = WorkflowNodeType.MODEL - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() ModelManager.get_instance().load_model_configs([self.opt_kwargs]) def compile(self) -> dict: @@ -170,21 +165,8 @@ class MsgNode(WorkflowNode): node_type = WorkflowNodeType.MESSAGE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.msg = Msg(**self.opt_kwargs) def __call__(self, x: dict = None) -> dict: @@ -206,21 +188,8 @@ class DialogAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.pipeline = DialogAgent(**self.opt_kwargs) def __call__(self, x: dict = None) -> dict: @@ -243,21 +212,8 @@ class UserAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.pipeline = UserAgent(**self.opt_kwargs) def __call__(self, x: dict = None) -> dict: @@ -280,21 +236,8 @@ class TextToImageAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.pipeline = TextToImageAgent(**self.opt_kwargs) def __call__(self, x: dict = None) -> dict: @@ -317,21 +260,8 @@ class DictDialogAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.pipeline = DictDialogAgent(**self.opt_kwargs) def __call__(self, x: dict = None) -> dict: @@ -354,24 +284,11 @@ class ReActAgentNode(WorkflowNode): node_type = WorkflowNodeType.AGENT - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() # Build tools self.service_toolkit = ServiceToolkit() - for tool in dep_opts: + for tool in self.dep_opts: if not hasattr(tool, "service_func"): raise TypeError(f"{tool} must be tool!") self.service_toolkit.add(tool.service_func) @@ -411,21 +328,8 @@ class MsgHubNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.announcement = Msg( name=self.opt_kwargs["announcement"].get("name", "Host"), content=self.opt_kwargs["announcement"].get("content", "Welcome!"), @@ -478,21 +382,8 @@ class PlaceHolderNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.pipeline = placeholder def __call__(self, x: dict = None) -> dict: @@ -518,21 +409,8 @@ class SequentialPipelineNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.pipeline = SequentialPipeline(operators=self.dep_opts) def __call__(self, x: dict = None) -> dict: @@ -558,21 +436,16 @@ class ForLoopPipelineNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, + def _post_init(self) -> None: + # Not call super post init to avoid converting callable + self.condition_op = self.opt_kwargs.pop("condition_op", "") + self.target_value = self.opt_kwargs.pop("target_value", "") + self.opt_kwargs["break_func"] = partial( + eval_condition_operator, + operator=self.condition_op, + target_value=self.target_value, ) + assert ( len(self.dep_opts) == 1 ), "ForLoopPipelineNode can only contain one PipelineNode." @@ -586,11 +459,16 @@ def __call__(self, x: dict = None) -> dict: def compile(self) -> dict: return { - "imports": "from agentscope.pipelines import ForLoopPipeline", + "imports": "from agentscope.pipelines import ForLoopPipeline\n" + "from functools import partial\n" + "from agentscope.studio.tools.condition_operator import " + "eval_condition_operator", "inits": f"{self.var_name} = ForLoopPipeline(" f"loop_body_operators=" f"{deps_converter(self.dep_vars)}," - f" {kwarg_converter(self.source_kwargs)})", + f" {kwarg_converter(self.source_kwargs)}," + f" break_func=partial(eval_condition_operator, operator" + f"='{self.condition_op}', target_value='{self.target_value}'))", "execs": f"{DEFAULT_FLOW_VAR} = {self.var_name}" f"({DEFAULT_FLOW_VAR})", } @@ -606,21 +484,8 @@ class WhileLoopPipelineNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() assert ( len(self.dep_opts) == 1 ), "WhileLoopPipelineNode can only contain one PipelineNode." @@ -654,21 +519,16 @@ class IfElsePipelineNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, + def _post_init(self) -> None: + # Not call super post init to avoid converting callable + self.condition_op = self.opt_kwargs.pop("condition_op", "") + self.target_value = self.opt_kwargs.pop("target_value", "") + self.opt_kwargs["condition_func"] = partial( + eval_condition_operator, + operator=self.condition_op, + target_value=self.target_value, ) + assert ( 0 < len(self.dep_opts) <= 2 ), "IfElsePipelineNode must contain one or two PipelineNode." @@ -688,13 +548,21 @@ def __call__(self, x: dict = None) -> dict: return self.pipeline(x) def compile(self) -> dict: - imports = "from agentscope.pipelines import IfElsePipeline" + imports = ( + "from agentscope.pipelines import IfElsePipeline\n" + "from functools import partial\n" + "from agentscope.studio.tools.condition_operator " + "import eval_condition_operator" + ) execs = f"{DEFAULT_FLOW_VAR} = {self.var_name}({DEFAULT_FLOW_VAR})" if len(self.dep_vars) == 1: return { "imports": imports, "inits": f"{self.var_name} = IfElsePipeline(" - f"if_body_operators={self.dep_vars[0]})", + f"if_body_operators={self.dep_vars[0]}, " + f"condition_func=partial(eval_condition_operator, " + f"operator='{self.condition_op}', " + f"target_value='{self.target_value}'))", "execs": execs, } elif len(self.dep_vars) == 2: @@ -702,7 +570,10 @@ def compile(self) -> dict: "imports": imports, "inits": f"{self.var_name} = IfElsePipeline(" f"if_body_operators={self.dep_vars[0]}, " - f"else_body_operators={self.dep_vars[1]})", + f"else_body_operators={self.dep_vars[1]}," + f"condition_func=partial(eval_condition_operator, " + f"operator='{self.condition_op}', " + f"target_value='{self.target_value}'))", "execs": execs, } raise ValueError @@ -718,21 +589,8 @@ class SwitchPipelineNode(WorkflowNode): node_type = WorkflowNodeType.PIPELINE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() assert 0 < len(self.dep_opts), ( "SwitchPipelineNode must contain at least " "one PipelineNode." ) @@ -798,21 +656,8 @@ class CopyNode(WorkflowNode): node_type = WorkflowNodeType.COPY - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() assert len(self.dep_opts) == 1, "CopyNode can only have one parent!" self.pipeline = self.dep_opts[0] self.var_name = self.pipeline.var_name @@ -836,21 +681,8 @@ class BingSearchServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.service_func = partial(bing_search, **self.opt_kwargs) def compile(self) -> dict: @@ -871,21 +703,8 @@ class GoogleSearchServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.service_func = partial(google_search, **self.opt_kwargs) def compile(self) -> dict: @@ -906,21 +725,8 @@ class PythonServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.service_func = execute_python_code def compile(self) -> dict: @@ -939,21 +745,8 @@ class ReadTextServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.service_func = read_text_file def compile(self) -> dict: @@ -972,21 +765,8 @@ class WriteTextServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.service_func = write_text_file def compile(self) -> dict: @@ -1003,22 +783,8 @@ class PostNode(WorkflowNode): node_type = WorkflowNodeType.TOOL - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) - + def _post_init(self) -> None: + super()._post_init() if "kwargs" in self.opt_kwargs: kwargs = ast.literal_eval(self.opt_kwargs["kwargs"].strip()) del self.opt_kwargs["kwargs"] @@ -1048,21 +814,8 @@ class TextToAudioServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.service_func = partial(dashscope_text_to_audio, **self.opt_kwargs) def compile(self) -> dict: @@ -1083,21 +836,8 @@ class TextToImageServiceNode(WorkflowNode): node_type = WorkflowNodeType.SERVICE - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.service_func = partial(dashscope_text_to_image, **self.opt_kwargs) def compile(self) -> dict: @@ -1118,21 +858,8 @@ class ImageCompositionNode(WorkflowNode): node_type = WorkflowNodeType.TOOL - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.pipeline = partial(stitch_images_with_grid, **self.opt_kwargs) def __call__(self, x: list = None) -> dict: @@ -1159,21 +886,8 @@ class ImageMotionNode(WorkflowNode): node_type = WorkflowNodeType.TOOL - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.pipeline = partial( create_video_or_gif_from_image, **self.opt_kwargs, @@ -1202,21 +916,8 @@ class VideoCompositionNode(WorkflowNode): node_type = WorkflowNodeType.TOOL - def __init__( - self, - node_id: str, - opt_kwargs: dict, - source_kwargs: dict, - dep_opts: list, - only_compile: bool = True, - ) -> None: - super().__init__( - node_id, - opt_kwargs, - source_kwargs, - dep_opts, - only_compile, - ) + def _post_init(self) -> None: + super()._post_init() self.pipeline = partial(merge_videos, **self.opt_kwargs) def __call__(self, x: dict = None) -> dict: @@ -1234,6 +935,92 @@ def compile(self) -> dict: } +class CodeNode(WorkflowNode): + """ + Python Code Node + """ + + node_type = WorkflowNodeType.TOOL + + def _post_init(self) -> None: + super()._post_init() + self.pipeline = execute_python_code + self.code_tags = "{{code}}" + self.input_tags = "{{inputs}}" + self.output_tags = "<>" + + def template(self) -> str: + """ + Code template + """ + template_str = dedent( + f""" + from agentscope.message import Msg + {self.code_tags} + import json + + if isinstance({self.input_tags}, str): + inputs_obj = json.loads({self.input_tags}) + else: + inputs_obj = {self.input_tags} + + output_obj = function(*inputs_obj) + + output_json = json.dumps(output_obj, indent=4) + result = f'''{self.output_tags}{{output_json}}{self.output_tags}''' + print(result) + """, + ) + return template_str + + def extract_result(self, content: str) -> Any: + """ + Extract result from content + """ + result = re.search( + rf"{self.output_tags}(.*){self.output_tags}", + content, + re.DOTALL, + ) + if not result: + raise ValueError("Failed to parse result") + result = result.group(1) + return result + + def __call__(self, x: list = None) -> dict: + if isinstance(x, dict): + x = [x] + + code = self.template().replace( + self.code_tags, + self.opt_kwargs.get("code", ""), + ) + inputs = json.dumps(x, ensure_ascii=True).replace("null", "None") + code = code.replace(self.input_tags, inputs) + try: + out = self.pipeline(code) + if out.status == ServiceExecStatus.SUCCESS: + content = self.extract_result(out.content) + return Msg(**json.loads(content)) + return out + except Exception as e: + raise RuntimeError( + f"Code id: {self.node_id},error executing :{e}", + ) from e + + def compile(self) -> dict: + code = self.opt_kwargs.get("code", "").replace( + "def function", + f"def function_{self.node_id}", + ) + return { + "imports": f"from agentscope.message import Msg\n{code}", + "inits": "", + "execs": f"{DEFAULT_FLOW_VAR} = function_{self.node_id}" + f"(*[{DEFAULT_FLOW_VAR}])", + } + + NODE_NAME_MAPPING = { "dashscope_chat": ModelNode, "openai_chat": ModelNode, @@ -1263,6 +1050,7 @@ def compile(self) -> dict: "TextToAudioService": TextToAudioServiceNode, "TextToImageService": TextToImageServiceNode, "ImageComposition": ImageCompositionNode, + "Code": CodeNode, "ImageMotion": ImageMotionNode, "VideoComposition": VideoCompositionNode, } From 37b2414f5bc101c0f97c05f1ec16ff8c4a78a350 Mon Sep 17 00:00:00 2001 From: rabZhang <515184034@qq.com> Date: Fri, 25 Oct 2024 15:51:16 +0800 Subject: [PATCH 26/47] =?UTF-8?q?fixBug=EF=BC=9A=E9=80=9A=E8=BF=87config?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=B7=A5=E4=BD=9C=E6=B5=81=E4=B9=8B=E5=90=8E?= =?UTF-8?q?=EF=BC=8Cinput=E8=BE=93=E5=85=A5=E6=A1=86=E4=B8=8D=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=94=BE=E5=A4=A7=E7=BC=A9=E5=B0=8F=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E6=8B=96=E6=8B=BDgroup=E8=8A=82=E7=82=B9=E7=9A=84?= =?UTF-8?q?=E6=97=B6=E5=80=99=EF=BC=8C=E5=86=85=E9=83=A8=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E4=BC=9A=E4=B8=80=E7=9B=B4=E4=B8=8A=E5=8D=87=20(#43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: rabzhang <> --- .../studio/static/js/workstation.js | 22 ++++++++++--------- .../static/js_third_party/drawflow.min.js | 8 +++---- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 7472b41c4..4995b63d0 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -214,11 +214,6 @@ async function initializeWorkstationPage() { // console.log('Position mouse x:' + position.x + ' y:' + position.y); }) - editor.on('nodeMoved', function (id) { - console.log("Node moved " + id); - disableButtons(); - }) - editor.on('zoom', function (zoom) { console.log('Zoom level ' + zoom); }) @@ -277,8 +272,8 @@ async function initializeWorkstationPage() { if (editor.node_selected && editor.drag) { const selectedNodeId = editor.node_selected.id.slice(5); - var dx = (last_x - x) * editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom); - var dy = (last_y - y) * editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom); + var dx = Math.ceil((last_x - x) * editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)); + var dy = Math.ceil((last_y - y) * editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)); if (editor.node_selected.classList.contains("GROUP")) { moveGroupNodes(selectedNodeId, -dx, -dy); @@ -322,6 +317,7 @@ async function initializeWorkstationPage() { togglePortsDisplay(dragNode, ''); removeOfGroupNode(dragNode); } + disableButtons(); }) editor.on("nodeRemoved", (id) => { @@ -491,8 +487,8 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { if (editor.editor_mode === 'fixed') { return false; } - pos_x = pos_x * (editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)) - (editor.precanvas.getBoundingClientRect().x * (editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom))); - pos_y = pos_y * (editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)) - (editor.precanvas.getBoundingClientRect().y * (editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom))); + pos_x = Math.ceil(pos_x * (editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)) - (editor.precanvas.getBoundingClientRect().x * (editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)))); + pos_y = Math.ceil(pos_y * (editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)) - (editor.precanvas.getBoundingClientRect().y * (editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)))); var htmlSourceCode = await fetchHtmlSourceCodeByName(name); @@ -2373,8 +2369,14 @@ async function addHtmlAndReplacePlaceHolderBeforeImport(data) { function importSetupNodes(dataToImport) { Object.keys(dataToImport.drawflow.Home.data).forEach((nodeId) => { + // import the node use addNode function + disableButtons(); + makeNodeTop(nodeId); setupNodeListeners(nodeId); - + setupNodeCopyListens(nodeId); + addEventListenersToNumberInputs(nodeId); + setupTextInputListeners(nodeId); + reloadi18n(); const nodeElement = document.getElementById(`node-${nodeId}`); if (nodeElement) { const copyButton = nodeElement.querySelector('.button.copy-button'); diff --git a/src/agentscope/studio/static/js_third_party/drawflow.min.js b/src/agentscope/studio/static/js_third_party/drawflow.min.js index 570713204..8db242f31 100644 --- a/src/agentscope/studio/static/js_third_party/drawflow.min.js +++ b/src/agentscope/studio/static/js_third_party/drawflow.min.js @@ -140,8 +140,8 @@ y: s }), this.precanvas.style.transform = "translate(" + i + "px, " + s + "px) scale(" + this.zoom + ")"), this.drag) { e.preventDefault(); - var i = (this.pos_x - t) * this.precanvas.clientWidth / (this.precanvas.clientWidth * this.zoom), - s = (this.pos_y - n) * this.precanvas.clientHeight / (this.precanvas.clientHeight * this.zoom); + var i = Math.ceil((this.pos_x - t) * this.precanvas.clientWidth / (this.precanvas.clientWidth * this.zoom)), + s = Math.ceil((this.pos_y - n) * this.precanvas.clientHeight / (this.precanvas.clientHeight * this.zoom)); this.pos_x = t, this.pos_y = n, this.ele_selected.style.top = this.ele_selected.offsetTop - s + "px", this.ele_selected.style.left = this.ele_selected.offsetLeft - i + "px", this.drawflow.drawflow[this.module].data[this.ele_selected.id.slice(5)].pos_x = this.ele_selected.offsetLeft - i, this.drawflow.drawflow[this.module].data[this.ele_selected.id.slice(5)].pos_y = this.ele_selected.offsetTop - s, this.updateConnectionNodes(this.ele_selected.id) } if (this.drag_point) { @@ -235,11 +235,11 @@ } zoom_in() { - this.zoom < this.zoom_max && (this.zoom += this.zoom_value, this.zoom_refresh()) + this.zoom < this.zoom_max && (this.zoom = (Number(this.zoom) + Number(this.zoom_value)).toFixed(1), this.zoom_refresh()) } zoom_out() { - this.zoom > this.zoom_min && (this.zoom -= this.zoom_value, this.zoom_refresh()) + this.zoom > this.zoom_min && (this.zoom = (Number(this.zoom) - Number(this.zoom_value)).toFixed(1), this.zoom_refresh()) } zoom_reset() { From 0bb6ce33beafeeae9e2f146563ec56786477da34 Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:06:30 +0800 Subject: [PATCH 27/47] linter (#44) --- .eslintrc | 17 + .pre-commit-config.yaml | 14 + .stylelintrc | 6 + docs/sphinx_doc/en/source/_static/custom.css | 4 +- .../zh_CN/source/_static/custom.css | 4 +- src/agentscope/studio/static/css/base.css | 128 +- .../static/css/dashboard-detail-code.css | 138 +- .../static/css/dashboard-detail-dialogue.css | 620 +- .../css/dashboard-detail-invocation.css | 60 +- .../studio/static/css/dashboard-detail.css | 100 +- .../studio/static/css/dashboard-runs.css | 126 +- .../studio/static/css/dashboard.css | 46 +- src/agentscope/studio/static/css/font.css | 8 +- src/agentscope/studio/static/css/gallery.css | 88 +- src/agentscope/studio/static/css/index.css | 398 +- src/agentscope/studio/static/css/login.css | 198 +- src/agentscope/studio/static/css/server.css | 154 +- .../css/workstation-drag-components.css | 186 +- .../studio/static/css/workstation.css | 1432 ++--- .../studio/static/js/dashboard-detail-code.js | 114 +- .../static/js/dashboard-detail-dialogue.js | 1008 ++-- .../static/js/dashboard-detail-invocation.js | 134 +- .../studio/static/js/dashboard-detail.js | 144 +- .../studio/static/js/dashboard-runs.js | 194 +- src/agentscope/studio/static/js/dashboard.js | 194 +- src/agentscope/studio/static/js/gallery.js | 482 +- src/agentscope/studio/static/js/index.js | 512 +- src/agentscope/studio/static/js/server.js | 808 +-- .../studio/static/js/workstation.js | 5349 ++++++++--------- .../studio/static/js/workstation_iframe.js | 4 +- 30 files changed, 6360 insertions(+), 6310 deletions(-) create mode 100644 .eslintrc create mode 100644 .stylelintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..dcd27a36f --- /dev/null +++ b/.eslintrc @@ -0,0 +1,17 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "rules": { + "semi": ["error", "always"], + "quotes": ["error", "double"], + "indent": ["error", 2], + "linebreak-style": ["error", "unix"], + "brace-style": ["error", "1tbs"], + "curly": ["error", "all"], + "no-eval": ["error"], + "prefer-const": ["error"], + "arrow-spacing": ["error", { "before": true, "after": true }] + } +} \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fbd2fb365..3bc9b261f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -99,6 +99,20 @@ repos: --disable=W0201, --disable=C0302, ] + - repo: https://github.com/pre-commit/mirrors-eslint + rev: v7.32.0 + hooks: + - id: eslint + files: \.(js|jsx)$ + exclude: '.*js_third_party.*' + args: ['--fix'] + - repo: https://github.com/thibaudcolas/pre-commit-stylelint + rev: v14.4.0 + hooks: + - id: stylelint + files: \.(css|scss|sass|less)$ + exclude: '.*css_third_party.*' + args: ['--fix'] - repo: https://github.com/regebro/pyroma rev: "4.0" hooks: diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 000000000..52759563b --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,6 @@ +{ + "rules": { + "indentation": 2, + "string-quotes": "single" + } +} \ No newline at end of file diff --git a/docs/sphinx_doc/en/source/_static/custom.css b/docs/sphinx_doc/en/source/_static/custom.css index 68f11ceed..02b7f4cbe 100644 --- a/docs/sphinx_doc/en/source/_static/custom.css +++ b/docs/sphinx_doc/en/source/_static/custom.css @@ -1,4 +1,4 @@ .language-selector a { - color: white; - width: 20px; + color: white; + width: 20px; } \ No newline at end of file diff --git a/docs/sphinx_doc/zh_CN/source/_static/custom.css b/docs/sphinx_doc/zh_CN/source/_static/custom.css index 68f11ceed..02b7f4cbe 100644 --- a/docs/sphinx_doc/zh_CN/source/_static/custom.css +++ b/docs/sphinx_doc/zh_CN/source/_static/custom.css @@ -1,4 +1,4 @@ .language-selector a { - color: white; - width: 20px; + color: white; + width: 20px; } \ No newline at end of file diff --git a/src/agentscope/studio/static/css/base.css b/src/agentscope/studio/static/css/base.css index c6ea9a188..cc1340378 100644 --- a/src/agentscope/studio/static/css/base.css +++ b/src/agentscope/studio/static/css/base.css @@ -1,31 +1,31 @@ :root { - --tab-btn-icon-length: 20px; + --tab-btn-icon-length: 20px; - --body-bg: #ffffff; + --body-bg: #ffffff; - --main-color: #59AC80; - --main-color-light: #A0D9C4; - --main-color-light-light: #D1E9DC; - --main-color-very-light: #F3F9F1; - --main-color-dark: #3D7D5A; - --base-color: #F3F9F1; + --main-color: #59AC80; + --main-color-light: #A0D9C4; + --main-color-light-light: #D1E9DC; + --main-color-very-light: #F3F9F1; + --main-color-dark: #3D7D5A; + --base-color: #F3F9F1; - --border-color: #EBEBF0; + --border-color: #EBEBF0; - /*Titlebar for each page*/ - --page-titlebar-height: 60px; - --page-content-height: calc(100% - var(--page-titlebar-height)); + /*Titlebar for each page*/ + --page-titlebar-height: 60px; + --page-content-height: calc(100% - var(--page-titlebar-height)); - /*Navigation bar*/ - --page-sidebar-width: 250px; + /*Navigation bar*/ + --page-sidebar-width: 250px; } /*Text cannot be selected*/ .unselectable-text { - -webkit-user-select: none; /* Safari */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* IE10+/Edge */ - user-select: none; /* Standard */ + -webkit-user-select: none; /* Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+/Edge */ + user-select: none; /* Standard */ } a { @@ -33,78 +33,78 @@ a { } .page-titlebar { - display: flex; - justify-content: space-between; - width: 100%; - height: var(--page-titlebar-height); - flex-direction: row; - align-items: center; - border-bottom: 1px solid var(--border-color); - box-sizing: border-box; - padding: 0 20px; + display: flex; + justify-content: space-between; + width: 100%; + height: var(--page-titlebar-height); + flex-direction: row; + align-items: center; + border-bottom: 1px solid var(--border-color); + box-sizing: border-box; + padding: 0 20px; } .github-user-content{ - display: flex; - align-items: center; + display: flex; + align-items: center; } .user-detail{ - display: flex; - position: relative; - cursor: pointer; + display: flex; + position: relative; + cursor: pointer; } .user-option{ - position: relative; - display: flex;; - margin-right: 5px; + position: relative; + display: flex;; + margin-right: 5px; } .user-option a{ - color: black; - text-decoration: none; - font-weight: normal; + color: black; + text-decoration: none; + font-weight: normal; } .triangle-down { - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 5px solid gray; - position: relative; - transform: translateY(10px); - margin-left: 5px; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid gray; + position: relative; + transform: translateY(10px); + margin-left: 5px; } .user-detail:hover .dropdown { - display: block; + display: block; } .dropdown { - display: none; - position: absolute; - width: 100%; - top:100%; - left: 0; - background-color: #f9f9f9; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 1; - white-space: nowrap; - border-radius: 0 0px 5px 5px; - overflow: hidden; + display: none; + position: absolute; + width: 100%; + top:100%; + left: 0; + background-color: #f9f9f9; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; + white-space: nowrap; + border-radius: 0 0px 5px 5px; + overflow: hidden; } .dropdown a { - color: black; - padding: 4px 8px; - text-decoration: none; - display: block; + color: black; + padding: 4px 8px; + text-decoration: none; + display: block; } .dropdown a:hover { - background-color: #f1f1f1; + background-color: #f1f1f1; } .triangle-down:hover { - border-top: 5px solid #000; + border-top: 5px solid #000; } diff --git a/src/agentscope/studio/static/css/dashboard-detail-code.css b/src/agentscope/studio/static/css/dashboard-detail-code.css index fb1e7c748..cc2fdcb72 100644 --- a/src/agentscope/studio/static/css/dashboard-detail-code.css +++ b/src/agentscope/studio/static/css/dashboard-detail-code.css @@ -1,101 +1,101 @@ :root { - --code-list-width: 350px; - --code-content-width: calc(100% - var(--code-list-width)); + --code-list-width: 350px; + --code-content-width: calc(100% - var(--code-list-width)); } #code-body { - display: flex; - flex-direction: row; - height: 100%; - width: 100%; + display: flex; + flex-direction: row; + height: 100%; + width: 100%; } #code-list { - display: flex; - flex-direction: column; - height: 100%; - width: var(--code-list-width); - box-sizing: border-box; - border: 0; + display: flex; + flex-direction: column; + height: 100%; + width: var(--code-list-width); + box-sizing: border-box; + border: 0; } #code-list ul { - display: block; - flex-direction: column; - align-items: flex-start; - justify-content: center; - width: 100%; - height: 35px; - box-sizing: border-box; - border-bottom: 1px solid var(--border-color); - padding: 5px; - margin: 0 0 3px 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + display: block; + flex-direction: column; + align-items: flex-start; + justify-content: center; + width: 100%; + height: 35px; + box-sizing: border-box; + border-bottom: 1px solid var(--border-color); + padding: 5px; + margin: 0 0 3px 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } #code-filename { - display: block; - flex-direction: column; - align-items: flex-start; - justify-content: center; - width: 100%; - height: 35px; - box-sizing: border-box; - border-bottom: 1px solid var(--border-color); - padding: 5px; - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + display: block; + flex-direction: column; + align-items: flex-start; + justify-content: center; + width: 100%; + height: 35px; + box-sizing: border-box; + border-bottom: 1px solid var(--border-color); + padding: 5px; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } #code-list li { - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: center; - width: 100%; - height: 35px; - padding: 3px 15px; - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + width: 100%; + height: 35px; + padding: 3px 15px; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + box-sizing: border-box; } #code-list li:hover { - background-color: var(--main-color-light-light); - cursor: pointer; + background-color: var(--main-color-light-light); + cursor: pointer; } #code-list li.selected { - background-color: var(--main-color-light); + background-color: var(--main-color-light); } #code-list ul:hover { - background-color: var(--main-color-light); - overflow: visible; - width: fit-content; - border-radius: 5px; - z-index: 10; + background-color: var(--main-color-light); + overflow: visible; + width: fit-content; + border-radius: 5px; + z-index: 10; } #code-content { - display: flex; - flex-direction: column; - width: var(--code-content-width); - height: 100%; - box-sizing: border-box; - border: 1px solid var(--border-color); + display: flex; + flex-direction: column; + width: var(--code-content-width); + height: 100%; + box-sizing: border-box; + border: 1px solid var(--border-color); } #code-editor { - display: flex; - width: 100%; - height: 100%; - overflow: auto; - white-space: pre; + display: flex; + width: 100%; + height: 100%; + overflow: auto; + white-space: pre; } diff --git a/src/agentscope/studio/static/css/dashboard-detail-dialogue.css b/src/agentscope/studio/static/css/dashboard-detail-dialogue.css index e7912f5b7..255fc6fc0 100644 --- a/src/agentscope/studio/static/css/dashboard-detail-dialogue.css +++ b/src/agentscope/studio/static/css/dashboard-detail-dialogue.css @@ -1,484 +1,484 @@ :root { - --chat-bubble-border-radius: 5px; + --chat-bubble-border-radius: 5px; - --chat-icon-width: 50px; - --chat-icon-height: 50px; - --chat-icon-horizontal-margin: 10px; - /*TODO: not used here*/ - --chat-icon-total-width: calc(var(--chat-icon-width) + var(--chat-icon-horizontal-margin) * 2); + --chat-icon-width: 50px; + --chat-icon-height: 50px; + --chat-icon-horizontal-margin: 10px; + /*TODO: not used here*/ + --chat-icon-total-width: calc(var(--chat-icon-width) + var(--chat-icon-horizontal-margin) * 2); - --chat-detail-width: 350px; + --chat-detail-width: 350px; - /*Switch bar*/ - --chat-detail-switch-width: 50px; - --chat-detail-swtich-btn-length: 40px; - --chat-detail-switch-svg-length: 20px; + /*Switch bar*/ + --chat-detail-switch-width: 50px; + --chat-detail-swtich-btn-length: 40px; + --chat-detail-switch-svg-length: 20px; - --chat-panel-width: calc(100% - var(--chat-detail-width) - var(--chat-detail-switch-width)); + --chat-panel-width: calc(100% - var(--chat-detail-width) - var(--chat-detail-switch-width)); - /*Input area*/ - --chat-input-panel-height: 100px; - --chat-control-panel-height: 56px; - --chat-control-btn-margin-top: 6px; + /*Input area*/ + --chat-input-panel-height: 100px; + --chat-control-panel-height: 56px; + --chat-control-btn-margin-top: 6px; - /*Delete button*/ - --chat-control-file-delete-length: 16px; - --chat-control-file-delete-position: -5px; + /*Delete button*/ + --chat-control-file-delete-length: 16px; + --chat-control-file-delete-position: -5px; - /*List item*/ - --chat-control-file-item-length: calc(var(--chat-control-panel-height) - var(--chat-control-btn-margin-top)); + /*List item*/ + --chat-control-file-item-length: calc(var(--chat-control-panel-height) - var(--chat-control-btn-margin-top)); - /*Should be smaller than --chat-control-file-item-length--*/ - --chat-control-file-svg-length: 35px; - --chat-control-file-svg-margin: calc((var(--chat-control-file-item-length) - var(--chat-control-file-svg-length)) / 2); + /*Should be smaller than --chat-control-file-item-length--*/ + --chat-control-file-svg-length: 35px; + --chat-control-file-svg-margin: calc((var(--chat-control-file-item-length) - var(--chat-control-file-svg-length)) / 2); - --chat-input-panel-font-size: 15px; + --chat-input-panel-font-size: 15px; } #chat-body { - display: flex; - flex-direction: row; - width: 100%; - height: 100%; + display: flex; + flex-direction: row; + width: 100%; + height: 100%; } #chat-panel { - display: flex; - flex-direction: column; - width: var(--chat-panel-width); - height: 100%; - padding: 50px 50px; - box-sizing: border-box; + display: flex; + flex-direction: column; + width: var(--chat-panel-width); + height: 100%; + padding: 50px 50px; + box-sizing: border-box; } #chat-detail { - display: flex; - flex-direction: column; - height: 100%; - max-height: 100%; - width: var(--chat-detail-width); - box-sizing: border-box; - border-left: 1px solid var(--border-color); - padding: 50px 20px; + display: flex; + flex-direction: column; + height: 100%; + max-height: 100%; + width: var(--chat-detail-width); + box-sizing: border-box; + border-left: 1px solid var(--border-color); + padding: 50px 20px; } #chat-detail-switch { - display: flex; - flex-direction: column; - align-items: center; - height: 100%; - width: var(--chat-detail-switch-width); - box-sizing: border-box; - border-left: 1px solid var(--border-color); + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + width: var(--chat-detail-switch-width); + box-sizing: border-box; + border-left: 1px solid var(--border-color); } .chat-detail-switch-btn { - display: flex; - flex-direction: column; - width: var(--chat-detail-swtich-btn-length); - height: var(--chat-detail-swtich-btn-length); - align-items: center; - justify-content: center; - cursor: pointer; - box-sizing: border-box; - border-radius: 10px; - margin-top: 10px; - fill: #000000; + display: flex; + flex-direction: column; + width: var(--chat-detail-swtich-btn-length); + height: var(--chat-detail-swtich-btn-length); + align-items: center; + justify-content: center; + cursor: pointer; + box-sizing: border-box; + border-radius: 10px; + margin-top: 10px; + fill: #000000; } .chat-detail-switch-btn svg { - display: flex; - width: var(--chat-detail-switch-svg-length); - height: var(--chat-detail-switch-svg-length); - align-items: center; - justify-content: center; + display: flex; + width: var(--chat-detail-switch-svg-length); + height: var(--chat-detail-switch-svg-length); + align-items: center; + justify-content: center; } .chat-detail-switch-btn:hover { - background-color: var(--main-color-light); - fill: var(--base-color); + background-color: var(--main-color-light); + fill: var(--base-color); } .chat-detail-switch-btn:active { - background-color: var(--main-color-dark); - fill: var(--base-color); + background-color: var(--main-color-dark); + fill: var(--base-color); } .chat-detail-switch-btn.selected { - background-color: var(--main-color); - fill: var(--base-color); + background-color: var(--main-color); + fill: var(--base-color); } #dialogue-detail-content { - display: flex; - height: 100%; - width: 100%; - flex-direction: column; - max-height: 100%; - max-width: 100%; - box-sizing: border-box; - white-space: normal; - word-wrap: break-word; - overflow-wrap: break-word; + display: flex; + height: 100%; + width: 100%; + flex-direction: column; + max-height: 100%; + max-width: 100%; + box-sizing: border-box; + white-space: normal; + word-wrap: break-word; + overflow-wrap: break-word; } #dialogue-info-title { - display: flex; - flex-direction: column; - justify-content: center; - font-weight: bold; - width: 100%; - height: 50px; - font-size: 18px; + display: flex; + flex-direction: column; + justify-content: center; + font-weight: bold; + width: 100%; + height: 50px; + font-size: 18px; } .dialogue-info-key { - display: flex; - width: 100%; - height: 30px; - align-items: center; - font-size: 15px; - font-weight: bold; + display: flex; + width: 100%; + height: 30px; + align-items: center; + font-size: 15px; + font-weight: bold; } .dialogue-info-value { - display: flex; - background-color: var(--border-color); - height: fit-content; - width: 100%; - max-width: 100%; - align-items: center; - padding: 5px 10px; - border-radius: 2px; - min-height: 33px; - box-sizing: border-box; - word-break: break-word; - overflow-wrap: break-word; - white-space: pre-wrap; + display: flex; + background-color: var(--border-color); + height: fit-content; + width: 100%; + max-width: 100%; + align-items: center; + padding: 5px 10px; + border-radius: 2px; + min-height: 33px; + box-sizing: border-box; + word-break: break-word; + overflow-wrap: break-word; + white-space: pre-wrap; } .dialogue-info-row { - display: flex; - flex-direction: column; - width: 100%; - height: fit-content; - margin: 5px 0; + display: flex; + flex-direction: column; + width: 100%; + height: fit-content; + margin: 5px 0; } #chat-box { - display: flex; - box-sizing: border-box; - flex-direction: column; - width: 100%; - flex-grow: 1; - margin-bottom: 50px; - max-height: 100%; - border: 1px solid var(--border-color); - border-radius: 8px; + display: flex; + box-sizing: border-box; + flex-direction: column; + width: 100%; + flex-grow: 1; + margin-bottom: 50px; + max-height: 100%; + border: 1px solid var(--border-color); + border-radius: 8px; } #chat-box-content { - display: flex; - width: 100%; - height: 100%; - flex-direction: column; - max-height: 100%; + display: flex; + width: 100%; + height: 100%; + flex-direction: column; + max-height: 100%; } .chat-row { - display: flex; - flex-direction: row; - box-sizing: border-box; - width: 100%; - height: fit-content; - padding: 10px 0; + display: flex; + flex-direction: row; + box-sizing: border-box; + width: 100%; + height: fit-content; + padding: 10px 0; } .chat-row:hover { - background-color: var(--main-color-very-light); + background-color: var(--main-color-very-light); } .user.chat-row { - justify-content: flex-end; + justify-content: flex-end; } .text-right-align { - text-align: right; + text-align: right; } .chat-icon { - display: flex; - justify-content: center; - box-sizing: border-box; - width: var(--chat-icon-width); - height: var(--chat-icon-height); - border-radius: 5px; - margin: 0 var(--chat-icon-horizontal-margin); - padding: 10px; - background-color: var(--main-color); - fill: #ffffff; + display: flex; + justify-content: center; + box-sizing: border-box; + width: var(--chat-icon-width); + height: var(--chat-icon-height); + border-radius: 5px; + margin: 0 var(--chat-icon-horizontal-margin); + padding: 10px; + background-color: var(--main-color); + fill: #ffffff; } .chat-content { - display: flex; - flex-direction: column; - width: calc(100% - 140px); - /*TODO: for test*/ - box-sizing: border-box; + display: flex; + flex-direction: column; + width: calc(100% - 140px); + /*TODO: for test*/ + box-sizing: border-box; } .user.chat-content { - align-items: flex-end; + align-items: flex-end; } .chat-name { - display: flex; - margin-bottom: 5px; + display: flex; + margin-bottom: 5px; } .chat-bubble { - display: flex; - flex-direction: column; - border: 1px solid var(--main-color-light); - background-color: #ffffff; - flex-grow: 1; - box-sizing: border-box; - padding: 10px; - width: fit-content; - max-width: 100%; - min-width: 100px; - word-wrap: break-word; - overflow-wrap: break-word; + display: flex; + flex-direction: column; + border: 1px solid var(--main-color-light); + background-color: #ffffff; + flex-grow: 1; + box-sizing: border-box; + padding: 10px; + width: fit-content; + max-width: 100%; + min-width: 100px; + word-wrap: break-word; + overflow-wrap: break-word; } .user.chat-bubble { - border-radius: var(--chat-bubble-border-radius) 0 var(--chat-bubble-border-radius) var(--chat-bubble-border-radius); - white-space: pre-wrap; + border-radius: var(--chat-bubble-border-radius) 0 var(--chat-bubble-border-radius) var(--chat-bubble-border-radius); + white-space: pre-wrap; } .other.chat-bubble { - border-radius: 0 var(--chat-bubble-border-radius) var(--chat-bubble-border-radius) var(--chat-bubble-border-radius); + border-radius: 0 var(--chat-bubble-border-radius) var(--chat-bubble-border-radius) var(--chat-bubble-border-radius); } .chat-bubble p { - display: inline; - flex-direction: row; - align-items: center; - margin: 0; + display: inline; + flex-direction: row; + align-items: center; + margin: 0; } .chat-bubble-multimodal-item { - display: flex; - max-height: 200px; - max-width: 100%; - width: auto; - height: auto; - min-height: 50px; - min-width: 50px; - margin-top: 5px; + display: flex; + max-height: 200px; + max-width: 100%; + width: auto; + height: auto; + min-height: 50px; + min-width: 50px; + margin-top: 5px; } #chat-input { - display: flex; - height: fit-content; - width: 100%; - box-sizing: border-box; - border: 1px solid var(--border-color); - border-radius: 8px; - flex-direction: column; - padding: 10px; + display: flex; + height: fit-content; + width: 100%; + box-sizing: border-box; + border: 1px solid var(--border-color); + border-radius: 8px; + flex-direction: column; + padding: 10px; } #chat-input:focus { - border-color: var(--main-color); + border-color: var(--main-color); } #chat-input-name { - display: flex; - flex-direction: column; - width: fit-content; - max-width: 100px; - height: fit-content; - margin-right: 5px; - align-items: center; - justify-content: center; - box-sizing: border-box; - padding: 2px; - font-size: var(--chat-input-panel-font-size); + display: flex; + flex-direction: column; + width: fit-content; + max-width: 100px; + height: fit-content; + margin-right: 5px; + align-items: center; + justify-content: center; + box-sizing: border-box; + padding: 2px; + font-size: var(--chat-input-panel-font-size); } #chat-input-textarea { - display: flex; - flex-grow: 1; - height: 100%; - resize: none; - box-sizing: border-box; - padding: 2px; - font-size: var(--chat-input-panel-font-size); - font-family: source-sans, sans-serif; + display: flex; + flex-grow: 1; + height: 100%; + resize: none; + box-sizing: border-box; + padding: 2px; + font-size: var(--chat-input-panel-font-size); + font-family: source-sans, sans-serif; - /*remove border*/ - border-color: transparent; - border-width: 0; - outline: none; + /*remove border*/ + border-color: transparent; + border-width: 0; + outline: none; } #chat-control-panel { - display: flex; - flex-direction: row; - justify-content: flex-end; - align-items: center; - height: var(--chat-control-panel-height); - width: 100%; - box-sizing: border-box; + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + height: var(--chat-control-panel-height); + width: 100%; + box-sizing: border-box; } #chat-control-url-btn svg { - display: flex; - height: 20px; - width: 20px; - align-items: center; - justify-content: center; + display: flex; + height: 20px; + width: 20px; + align-items: center; + justify-content: center; } #chat-input-panel { - display: flex; - width: 100%; - height: var(--chat-input-panel-height); - flex-direction: row; + display: flex; + width: 100%; + height: var(--chat-input-panel-height); + flex-direction: row; } .chat-control-btn { - display: flex; - flex-direction: row; - align-items: center; - height: calc(100% - var(--chat-control-btn-margin-top)); - border-radius: 5px; - padding: 0 15px; - border: none; - font-size: 15px; - cursor: pointer; - background-color: var(--main-color-light-light); - color: var(--main-color-dark); - fill: var(--main-color-dark); - margin-top: var(--chat-control-btn-margin-top); + display: flex; + flex-direction: row; + align-items: center; + height: calc(100% - var(--chat-control-btn-margin-top)); + border-radius: 5px; + padding: 0 15px; + border: none; + font-size: 15px; + cursor: pointer; + background-color: var(--main-color-light-light); + color: var(--main-color-dark); + fill: var(--main-color-dark); + margin-top: var(--chat-control-btn-margin-top); } .chat-control-btn:disabled, .chat-control-btn:disabled:hover, .chat-control-btn:disabled:active { - background-color: var(--border-color); - color: var(--base-color); - fill: var(--base-color); - cursor: not-allowed; + background-color: var(--border-color); + color: var(--base-color); + fill: var(--base-color); + cursor: not-allowed; } #chat-control-url-btn { - width: fit-content; - margin-left: 10px; + width: fit-content; + margin-left: 10px; } #chat-control-send-btn { - width: fit-content; - margin-left: 10px; + width: fit-content; + margin-left: 10px; } #chat-control-file-list { - display: flex; - flex-direction: row; - align-items: flex-end; - height: 100%; - flex-grow: 1; - padding: 0 15px; - border: none; - font-size: 15px; - overflow-x: auto; + display: flex; + flex-direction: row; + align-items: flex-end; + height: 100%; + flex-grow: 1; + padding: 0 15px; + border: none; + font-size: 15px; + overflow-x: auto; } .chat-control-file-item { - position: relative; - display: inline-block; - border-radius: 5px; - align-items: center; - justify-content: center; - height: var(--chat-control-file-item-length); - width: var(--chat-control-file-item-length); - margin: 0 5px; - vertical-align: center; - text-align: center; + position: relative; + display: inline-block; + border-radius: 5px; + align-items: center; + justify-content: center; + height: var(--chat-control-file-item-length); + width: var(--chat-control-file-item-length); + margin: 0 5px; + vertical-align: center; + text-align: center; } .chat-control-file-item:hover { - background-color: var(--main-color-light); + background-color: var(--main-color-light); } .chat-control-file-delete { - position: absolute; - top: var(--chat-control-file-delete-position); - right: var(--chat-control-file-delete-position); - height: var(--chat-control-file-delete-length); - width: var(--chat-control-file-delete-length); - background-color: white; - fill: var(--main-color-dark); - border: 2px solid var(--main-color-dark); - border-radius: 50%; - cursor: pointer; - box-sizing: border-box; - display: none; + position: absolute; + top: var(--chat-control-file-delete-position); + right: var(--chat-control-file-delete-position); + height: var(--chat-control-file-delete-length); + width: var(--chat-control-file-delete-length); + background-color: white; + fill: var(--main-color-dark); + border: 2px solid var(--main-color-dark); + border-radius: 50%; + cursor: pointer; + box-sizing: border-box; + display: none; } .chat-control-file-delete > svg { - display: flex; - width: calc(var(--chat-control-file-delete-length) - 4px); - height: calc(var(--chat-control-file-delete-length) - 4px); + display: flex; + width: calc(var(--chat-control-file-delete-length) - 4px); + height: calc(var(--chat-control-file-delete-length) - 4px); } .chat-control-file-item:hover .chat-control-file-delete { - display: block; + display: block; } .chat-control-file-item > svg { - display: block; - margin: var(--chat-control-file-svg-margin); - height: var(--chat-control-file-svg-length); - width: var(--chat-control-file-svg-length); + display: block; + margin: var(--chat-control-file-svg-margin); + height: var(--chat-control-file-svg-length); + width: var(--chat-control-file-svg-length); } .chat-control-btn:hover { - background-color: var(--main-color); - color: var(--base-color); - fill: var(--base-color); + background-color: var(--main-color); + color: var(--base-color); + fill: var(--base-color); } .chat-control-btn:active { - background-color: var(--main-color-dark); - color: var(--base-color); - fill: var(--base-color); + background-color: var(--main-color-dark); + color: var(--base-color); + fill: var(--base-color); } .chat-control-send-btn-svg { - display: flex; - width: 20px; - height: 25px; - margin-left: 5px; + display: flex; + width: 20px; + height: 25px; + margin-left: 5px; } #hidden-file-input { - display: none; + display: none; } #agentSwitchBtn { - display: none; + display: none; } .chat-bubble pre code { - white-space: pre-wrap; - word-wrap: break-word; + white-space: pre-wrap; + word-wrap: break-word; } #chat-box-content .clusterize-no-data { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - color: var(--main-color-light); + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + color: var(--main-color-light); } \ No newline at end of file diff --git a/src/agentscope/studio/static/css/dashboard-detail-invocation.css b/src/agentscope/studio/static/css/dashboard-detail-invocation.css index 6a7f1782e..29ec10ccb 100644 --- a/src/agentscope/studio/static/css/dashboard-detail-invocation.css +++ b/src/agentscope/studio/static/css/dashboard-detail-invocation.css @@ -1,44 +1,44 @@ #invocation-body { - display: flex; - flex-direction: row; - align-items: flex-end; - height: 100%; - width: 100%; + display: flex; + flex-direction: row; + align-items: flex-end; + height: 100%; + width: 100%; } #invocation-list { - display: flex; - flex-direction: column; - height: 100%; - width: 350px; - box-sizing: border-box; - border: 0; - background-color: #ffffff; + display: flex; + flex-direction: column; + height: 100%; + width: 350px; + box-sizing: border-box; + border: 0; + background-color: #ffffff; } #invocation-content { - display: flex; - flex-direction: column; - width: calc(100% - 350px); - height: 100%; - box-sizing: border-box; - border-left: 1px solid var(--border-color); + display: flex; + flex-direction: column; + width: calc(100% - 350px); + height: 100%; + box-sizing: border-box; + border-left: 1px solid var(--border-color); } #invocation-content .monaco-editor { - display: flex; - height: 100%; - width: 100%; - flex-grow: 1; - min-width: 100%; + display: flex; + height: 100%; + width: 100%; + flex-grow: 1; + min-width: 100%; } .content-placeholder { - display: flex; - color: var(--main-color-light-light); - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - font-size: 18px; + display: flex; + color: var(--main-color-light-light); + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + font-size: 18px; } \ No newline at end of file diff --git a/src/agentscope/studio/static/css/dashboard-detail.css b/src/agentscope/studio/static/css/dashboard-detail.css index 7681cfd14..aa8851c30 100644 --- a/src/agentscope/studio/static/css/dashboard-detail.css +++ b/src/agentscope/studio/static/css/dashboard-detail.css @@ -1,82 +1,82 @@ :root { - --detail-sidebar-padding-vertical: 5px; - --detail-content-width: calc(100% - var(--page-sidebar-width)); + --detail-sidebar-padding-vertical: 5px; + --detail-content-width: calc(100% - var(--page-sidebar-width)); } #detail-body { - display: flex; - flex-direction: row; - height: 100%; - width: 100%; + display: flex; + flex-direction: row; + height: 100%; + width: 100%; } #detail-sidebar { - display: flex; - box-sizing: border-box; - height: 100%; - flex-direction: column; - width: var(--page-sidebar-width); - background-color: #ffffff; + display: flex; + box-sizing: border-box; + height: 100%; + flex-direction: column; + width: var(--page-sidebar-width); + background-color: #ffffff; - border-right-color: var(--border-color); - border-right-width: 1px; - border-right-style: solid; - padding: var(--detail-sidebar-padding-vertical) 0; + border-right-color: var(--border-color); + border-right-width: 1px; + border-right-style: solid; + padding: var(--detail-sidebar-padding-vertical) 0; } .detail-sidebar-tabs { - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - padding: 45px 10px; - box-sizing: border-box; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + padding: 45px 10px; + box-sizing: border-box; } .detail-sidebar-tab { - display: flex; - flex-direction: row; - height: var(--navigation-bar-item-height); - width: 100%; - box-sizing: border-box; - align-items: center; - border-radius: 10px; - padding-left: 20px; - margin: 5px 0; - cursor: pointer; + display: flex; + flex-direction: row; + height: var(--navigation-bar-item-height); + width: 100%; + box-sizing: border-box; + align-items: center; + border-radius: 10px; + padding-left: 20px; + margin: 5px 0; + cursor: pointer; } .detail-sidebar-tab:hover { - background-color: var(--main-color-light); - fill: var(--base-color); - color: var(--base-color); + background-color: var(--main-color-light); + fill: var(--base-color); + color: var(--base-color); } .detail-sidebar-tab:active { - background-color: var(--main-color-dark); - fill: var(--base-color); - color: var(--base-color); + background-color: var(--main-color-dark); + fill: var(--base-color); + color: var(--base-color); } .detail-sidebar-tab.selected { - background-color: var(--main-color); - fill: var(--base-color); - color: var(--base-color); + background-color: var(--main-color); + fill: var(--base-color); + color: var(--base-color); } .detail-sidebar-tab-svg { - display: flex; - width: var(--tab-btn-icon-length); - height: var(--tab-btn-icon-length); - margin-right: 10px; + display: flex; + width: var(--tab-btn-icon-length); + height: var(--tab-btn-icon-length); + margin-right: 10px; } #detail-content { - display: flex; - background-color: #ffffff; - flex-direction: row; - height: 100%; - width: var(--detail-content-width); + display: flex; + background-color: #ffffff; + flex-direction: row; + height: 100%; + width: var(--detail-content-width); } diff --git a/src/agentscope/studio/static/css/dashboard-runs.css b/src/agentscope/studio/static/css/dashboard-runs.css index 73e7712dd..2cc2cd5a7 100644 --- a/src/agentscope/studio/static/css/dashboard-runs.css +++ b/src/agentscope/studio/static/css/dashboard-runs.css @@ -1,72 +1,72 @@ :root { - --runs-content-padding-vertical: 50px; - --runs-content-padding-horizontal: 80px; - --runs-content-control-panel-height: 55px; - --runs-search-input-height: 35px; - --runs-table-height: calc(100% - var(--runs-content-control-panel-height)); + --runs-content-padding-vertical: 50px; + --runs-content-padding-horizontal: 80px; + --runs-content-control-panel-height: 55px; + --runs-search-input-height: 35px; + --runs-table-height: calc(100% - var(--runs-content-control-panel-height)); } #runs-content { - display: flex; - flex-direction: column; - height: 100%; - width: 100%; - padding: var(--runs-content-padding-vertical) var(--runs-content-padding-horizontal); - box-sizing: border-box; + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + padding: var(--runs-content-padding-vertical) var(--runs-content-padding-horizontal); + box-sizing: border-box; } #runs-control-panel { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - height: var(--runs-content-control-panel-height); - width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: var(--runs-content-control-panel-height); + width: 100%; } #runs-table { - flex-grow: 1; - overflow-y: auto; - width: 100%; - height: var(--runs-table-height); - max-height: var(--runs-table-height); - background-color: #ffffff; - border: 1px solid var(--border-color); + flex-grow: 1; + overflow-y: auto; + width: 100%; + height: var(--runs-table-height); + max-height: var(--runs-table-height); + background-color: #ffffff; + border: 1px solid var(--border-color); } #runs-search-input { - display: flex; - height: var(--runs-search-input-height); - flex-grow: 1; - align-items: center; - border: 1px solid var(--border-color); - padding: 0 10px; + display: flex; + height: var(--runs-search-input-height); + flex-grow: 1; + align-items: center; + border: 1px solid var(--border-color); + padding: 0 10px; } #runs-search-input:focus { - border-color: var(--main-color); - outline: none; /* 移除默认的焦点轮廓样式 */ + border-color: var(--main-color); + outline: none; /* 移除默认的焦点轮廓样式 */ } /* Remove border from tabulator */ .tabulator .tabulator-cell, .tabulator .tabulator-col, .tabulator .tabulator-header .tabulator-col { - border: none !important; + border: none !important; } .tabulator .tabulator-cell { - height: 45px; + height: 45px; } /* Remove the bottom border from table header */ .tabulator .tabulator-header { - border-bottom: none !important; + border-bottom: none !important; } .tabulator-col-sorter-element.tabulator-sortable.tabulator-col { - background-color: var(--main-color-light); - color: var(--main-color-dark); + background-color: var(--main-color-light); + color: var(--main-color-dark); } /* Set the same color for all rows */ @@ -80,45 +80,45 @@ /*}*/ .runs-table-status-tag { - display: flex; - flex-direction: row; - align-items: center; - height: 26px; - width: fit-content; - border-radius: 13px; - color: var(--base-color); - box-sizing: border-box; - margin: 0 5px; - padding: 0 14px; + display: flex; + flex-direction: row; + align-items: center; + height: 26px; + width: fit-content; + border-radius: 13px; + color: var(--base-color); + box-sizing: border-box; + margin: 0 5px; + padding: 0 14px; } .running.runs-table-status-tag { - background-color: #d1e7fa; - color: #58c1ef; - fill: #58c1ef; + background-color: #d1e7fa; + color: #58c1ef; + fill: #58c1ef; } .finished.runs-table-status-tag { - background-color: var(--main-color-light); - color: var(--main-color-dark); - fill: var(--main-color-dark); + background-color: var(--main-color-light); + color: var(--main-color-dark); + fill: var(--main-color-dark); } .waiting.runs-table-status-tag { - background-color: #f8efba; - color: #f6b93b; - fill: #f6b93b; + background-color: #f8efba; + color: #f6b93b; + fill: #f6b93b; } .unknown.runs-table-status-tag { - background-color: #e1e1e1; - color: #3d4047; - fill: #3d4047; + background-color: #e1e1e1; + color: #3d4047; + fill: #3d4047; } .runs-table-status-svg { - display: flex; - width: 15px; - height: 15px; - margin-right: 7px; + display: flex; + width: 15px; + height: 15px; + margin-right: 7px; } diff --git a/src/agentscope/studio/static/css/dashboard.css b/src/agentscope/studio/static/css/dashboard.css index d5e71c3c7..4078883c1 100644 --- a/src/agentscope/studio/static/css/dashboard.css +++ b/src/agentscope/studio/static/css/dashboard.css @@ -1,37 +1,37 @@ #dashboard-panel { - display: flex; - height: 100%; - width: 100%; - flex-direction: column; + display: flex; + height: 100%; + width: 100%; + flex-direction: column; } .dashboard-titlebar-span { - display: flex; - height: 50px; - width: fit-content; - align-items: center; - cursor: pointer; + display: flex; + height: 50px; + width: fit-content; + align-items: center; + cursor: pointer; } .dashboard-subtitlebar-span { - display: flex; - height: 50px; - width: fit-content; - align-items: center; + display: flex; + height: 50px; + width: fit-content; + align-items: center; } .dashboard-titlebar-svg { - display: flex; - width: 15px; - height: 15px; - align-items: center; - justify-content: center; - margin: 0 10px; + display: flex; + width: 15px; + height: 15px; + align-items: center; + justify-content: center; + margin: 0 10px; } #dashboard-content { - display: flex; - height: var(--page-content-height); - width: 100%; - flex-direction: row; + display: flex; + height: var(--page-content-height); + width: 100%; + flex-direction: row; } \ No newline at end of file diff --git a/src/agentscope/studio/static/css/font.css b/src/agentscope/studio/static/css/font.css index 5b151527f..5d87f16ea 100644 --- a/src/agentscope/studio/static/css/font.css +++ b/src/agentscope/studio/static/css/font.css @@ -1,9 +1,9 @@ @font-face { - font-family: 'krypton'; - src: url('/static/fonts/KRYPTON.ttf') format('truetype'); + font-family: 'krypton'; + src: url('/static/fonts/KRYPTON.ttf') format('truetype'); } @font-face { - font-family: 'source-sans'; - src: url('/static/fonts/SourceSans3-Regular.ttf') format('truetype'); + font-family: 'source-sans'; + src: url('/static/fonts/SourceSans3-Regular.ttf') format('truetype'); } \ No newline at end of file diff --git a/src/agentscope/studio/static/css/gallery.css b/src/agentscope/studio/static/css/gallery.css index 5024161e4..8bdc6ccc8 100644 --- a/src/agentscope/studio/static/css/gallery.css +++ b/src/agentscope/studio/static/css/gallery.css @@ -1,88 +1,88 @@ .two-column-layout { - display: flex; - width: 100%; - height: var(--page-content-height); + display: flex; + width: 100%; + height: var(--page-content-height); } .sidebar { - flex-basis: var(--page-sidebar-width); - box-sizing: border-box; - padding: 50px 10px; - border-right: 1px solid var(--border-color); - overflow-y: auto; - width: 100%; + flex-basis: var(--page-sidebar-width); + box-sizing: border-box; + padding: 50px 10px; + border-right: 1px solid var(--border-color); + overflow-y: auto; + width: 100%; } .tabs { - display: block; - padding: 0; - margin: 50px 0 20px 0; + display: block; + padding: 0; + margin: 50px 0 20px 0; } .tab-button { - cursor: pointer; - padding: 10px; - border: none; - margin-right: 20px; - border-radius: 15px; + cursor: pointer; + padding: 10px; + border: none; + margin-right: 20px; + border-radius: 15px; } .tab-button.active { - background-color: var(--main-color-light); + background-color: var(--main-color-light); } .tab { - display: none; + display: none; } .tab.active { - display: block; + display: block; } .grid-container { - grid-template-columns: repeat(6, 1fr); - gap: 1rem; - padding: 1rem; - display: flex; - flex-wrap: wrap; - gap: 1rem; - padding: 1rem; + grid-template-columns: repeat(6, 1fr); + gap: 1rem; + padding: 1rem; + display: flex; + flex-wrap: wrap; + gap: 1rem; + padding: 1rem; } .grid-item { - background-color: #f0f0f0; - border-radius: 4px; - overflow: hidden; - width: 200px; - height: 200px; - position: relative; - display: flex; - flex-direction: column; + background-color: #f0f0f0; + border-radius: 4px; + overflow: hidden; + width: 200px; + height: 200px; + position: relative; + display: flex; + flex-direction: column; } .grid-item :hover { - transform: scale(1.05); + transform: scale(1.05); } .grid-item img { - width: 100%; - height: 61.8%; - object-fit: cover; + width: 100%; + height: 61.8%; + object-fit: cover; } .grid-item .caption { - width: 100%; - height: 38.2%; + width: 100%; + height: 38.2%; } .thumbnail { - width: 100%; - flex-grow: 1; + width: 100%; + flex-grow: 1; } .caption { - text-align: center; + text-align: center; } diff --git a/src/agentscope/studio/static/css/index.css b/src/agentscope/studio/static/css/index.css index 99d227392..0ab164015 100644 --- a/src/agentscope/studio/static/css/index.css +++ b/src/agentscope/studio/static/css/index.css @@ -1,315 +1,315 @@ :root { - --navigation-bar-background-color: #ffffff; - --logo-font-color: #000000; - --tab-font-color: #000000; + --navigation-bar-background-color: #ffffff; + --logo-font-color: #000000; + --tab-font-color: #000000; - --navigation-bar-width: 240px; - --content-width: calc(100% - var(--navigation-bar-width)); + --navigation-bar-width: 240px; + --content-width: calc(100% - var(--navigation-bar-width)); - /*To ensure the collapsed navigation bar fit the icon*/ - --navigation-bar-item-icon-length: var(--tab-btn-icon-length); - --navigation-bar-item-icon-margin: 10px; - --navigation-bar-item-padding: 10px; - --navigation-bar-width-collapsed: calc(var(--navigation-bar-item-icon-length) + 2 * var(--navigation-bar-item-icon-margin) + 2 * var(--navigation-bar-item-padding)); - --navigation-bar-item-height: calc(var(--navigation-bar-item-icon-length) + 2 * var(--navigation-bar-item-icon-margin)) + /*To ensure the collapsed navigation bar fit the icon*/ + --navigation-bar-item-icon-length: var(--tab-btn-icon-length); + --navigation-bar-item-icon-margin: 10px; + --navigation-bar-item-padding: 10px; + --navigation-bar-width-collapsed: calc(var(--navigation-bar-item-icon-length) + 2 * var(--navigation-bar-item-icon-margin) + 2 * var(--navigation-bar-item-padding)); + --navigation-bar-item-height: calc(var(--navigation-bar-item-icon-length) + 2 * var(--navigation-bar-item-icon-margin)) } html { - height: 100%; /* Fill the entire screen height */ - width: 100%; /* Fill the entire screen width */ - margin: 0; /* Eliminate default html margin */ - padding: 0; + height: 100%; /* Fill the entire screen height */ + width: 100%; /* Fill the entire screen width */ + margin: 0; /* Eliminate default html margin */ + padding: 0; } body { - display: flex; /* Allows body to be a flex container */ - height: 100%; /* Fill the entire screen height */ - width: 100%; /* Fill the entire screen width */ - margin: 0; /* Eliminate default body margin */ - flex-direction: row; - padding: 0; - background: var(--body-bg); - font-family: source-sans, sans-serif; - box-sizing: border-box; + display: flex; /* Allows body to be a flex container */ + height: 100%; /* Fill the entire screen height */ + width: 100%; /* Fill the entire screen width */ + margin: 0; /* Eliminate default body margin */ + flex-direction: row; + padding: 0; + background: var(--body-bg); + font-family: source-sans, sans-serif; + box-sizing: border-box; } #workstation-iframe { - display: flex; - width: 100%; - height: 100%; - border: 0; + display: flex; + width: 100%; + height: 100%; + border: 0; } #navigation-bar { - display: flex; - position: relative; - flex-direction: column; - width: var(--navigation-bar-width); - height: 100%; - justify-content: space-between; - align-items: center; - padding: 50px 0 50px 0; - box-sizing: border-box; - background-color: var(--navigation-bar-background-color); - border-right: 1px solid var(--border-color); + display: flex; + position: relative; + flex-direction: column; + width: var(--navigation-bar-width); + height: 100%; + justify-content: space-between; + align-items: center; + padding: 50px 0 50px 0; + box-sizing: border-box; + background-color: var(--navigation-bar-background-color); + border-right: 1px solid var(--border-color); - /*Animation*/ - transition: width 0.5s; + /*Animation*/ + transition: width 0.5s; } #navigation-bar.collapsed { - width: var(--navigation-bar-width-collapsed); + width: var(--navigation-bar-width-collapsed); } .navigation-bar-item-label { - display: flex; - white-space: nowrap; + display: flex; + white-space: nowrap; } #navigation-bar.collapsed .navigation-bar-item-label { - display: none; + display: none; } #content { - display: flex; - box-sizing: border-box; - width: var(--content-width); - height: 100%; - flex-grow: 1; + display: flex; + box-sizing: border-box; + width: var(--content-width); + height: 100%; + flex-grow: 1; } #guide { - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - box-sizing: border-box; - padding: 50px + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + box-sizing: border-box; + padding: 50px } .guide-block { - display: flex; - flex-direction: column; - height: 150px; - width: 500px; - min-width: 200px; - background-color: var(--main-color); - border-radius: 10px; - justify-content: flex-end; - padding: 25px; - color: #ffffff; - cursor: pointer; + display: flex; + flex-direction: column; + height: 150px; + width: 500px; + min-width: 200px; + background-color: var(--main-color); + border-radius: 10px; + justify-content: flex-end; + padding: 25px; + color: #ffffff; + cursor: pointer; } /*when guide-block and right are all in class*/ .guide-block.left { - margin-right: 10px; + margin-right: 10px; } .guide-block.right { - margin-left: 10px; + margin-left: 10px; } .guide-entry { - display: flex; - flex-direction: row; - justify-content: space-around; - width: 100%; - height: fit-content; - margin: 25px 0; + display: flex; + flex-direction: row; + justify-content: space-around; + width: 100%; + height: fit-content; + margin: 25px 0; } .empty.guide-block { - background-color: #ffffff; + background-color: #ffffff; } .guide-block-title { - display: flex; - height: fit-content; - width: 100%; - justify-content: flex-start; - align-items: center; - font-size: 25px; - font-weight: bold; + display: flex; + height: fit-content; + width: 100%; + justify-content: flex-start; + align-items: center; + font-size: 25px; + font-weight: bold; } .guide-block-detail { - display: flex; - height: fit-content; - width: 100%; - justify-content: flex-start; - align-items: center; - font-size: 15px; + display: flex; + height: fit-content; + width: 100%; + justify-content: flex-start; + align-items: center; + font-size: 15px; } #navigation-bar-logo { - font-family: 'krypton', sans-serif; - font-size: 20px; - color: var(--logo-font-color); - white-space: nowrap; + font-family: 'krypton', sans-serif; + font-size: 20px; + color: var(--logo-font-color); + white-space: nowrap; } #navigation-bar.collapsed .navigation-bar-logo-text { - display: none; + display: none; } .navigation-bar-items { - display: flex; - justify-content: center; - flex-grow: 1; /* 使得 .navigation-bar-items 占据可用空间 */ - width: 100%; - flex-direction: column; + display: flex; + justify-content: center; + flex-grow: 1; /* 使得 .navigation-bar-items 占据可用空间 */ + width: 100%; + flex-direction: column; - padding: var(--navigation-bar-item-padding); - box-sizing: border-box; + padding: var(--navigation-bar-item-padding); + box-sizing: border-box; } .navigation-bar-item { - display: flex; - flex-direction: row; - align-items: center; - background-color: var(--navigation-bar-background-color); - width: 100%; - height: var(--navigation-bar-item-height); - border-radius: 10px; - color: var(--tab-font-color); - box-sizing: border-box; - margin: 5px 0; - cursor: pointer; + display: flex; + flex-direction: row; + align-items: center; + background-color: var(--navigation-bar-background-color); + width: 100%; + height: var(--navigation-bar-item-height); + border-radius: 10px; + color: var(--tab-font-color); + box-sizing: border-box; + margin: 5px 0; + cursor: pointer; } .navigation-bar-item:hover { - background-color: var(--main-color-light); - fill: var(--base-color); - color: var(--base-color); + background-color: var(--main-color-light); + fill: var(--base-color); + color: var(--base-color); } .navigation-bar-item:active { - background-color: var(--main-color-dark); - fill: var(--base-color); - color: var(--base-color); + background-color: var(--main-color-dark); + fill: var(--base-color); + color: var(--base-color); } .navigation-bar-item.selected { - background-color: var(--main-color); - fill: var(--base-color); - color: var(--base-color); + background-color: var(--main-color); + fill: var(--base-color); + color: var(--base-color); } .navigation-bar-item-icon { - display: flex; - width: var(--navigation-bar-item-icon-length); - height: var(--navigation-bar-item-icon-length); - margin: 0 var(--navigation-bar-item-icon-margin); - min-height: var(--navigation-bar-item-icon-length); - min-width: var(--navigation-bar-item-icon-length); + display: flex; + width: var(--navigation-bar-item-icon-length); + height: var(--navigation-bar-item-icon-length); + margin: 0 var(--navigation-bar-item-icon-margin); + min-height: var(--navigation-bar-item-icon-length); + min-width: var(--navigation-bar-item-icon-length); } .navigation-bar-hr { - display: flex; - height: 1px; - background-color: var(--border-color); - border: none; - margin: 10px 0; + display: flex; + height: 1px; + background-color: var(--border-color); + border: none; + margin: 10px 0; } .coming-soon-hint { - display: flex; - height: 100%; - width: 100%; - justify-content: center; - align-items: center; - color: var(--main-color-light); + display: flex; + height: 100%; + width: 100%; + justify-content: center; + align-items: center; + color: var(--main-color-light); } .navbar,.navbar ul{ - display: flex; - justify-content: center; - align-items: center; - margin: 0; - padding: 0; + display: flex; + justify-content: center; + align-items: center; + margin: 0; + padding: 0; } .navbar{ - position: absolute; - /* padding: 10px; */ - background-color: #fff; - border-radius: 50px; - background-color: var(--main-color); - top: 90%; - left: 2%; + position: absolute; + /* padding: 10px; */ + background-color: #fff; + border-radius: 50px; + background-color: var(--main-color); + top: 90%; + left: 2%; } .navbar input{ - width: 40px; - height: 40px; - opacity: 0; - cursor: pointer; + width: 40px; + height: 40px; + opacity: 0; + cursor: pointer; } .navbar span{ - position: absolute; - left:11px; - width: 25px; - height: 4px; - top: calc(50% - 10px); - border-radius: 15px; - background-color: #fff; - pointer-events: none; - transition: transform 0.3s ease-in-out,top 0.3s ease-in-out 0.3s; + position: absolute; + left:11px; + width: 25px; + height: 4px; + top: calc(50% - 10px); + border-radius: 15px; + background-color: #fff; + pointer-events: none; + transition: transform 0.3s ease-in-out,top 0.3s ease-in-out 0.3s; } .navbar span:nth-child(3){ - top: calc(50% + 6px); + top: calc(50% + 6px); } .navbar ul{ - width: 0; - overflow: hidden; - transition: all 0.5s; - white-space: nowrap; + width: 0; + overflow: hidden; + transition: all 0.5s; + white-space: nowrap; } .navbar ul li{ - list-style: none; - margin: 0px 15px; + list-style: none; + margin: 0px 15px; } .navbar ul li a{ - text-decoration: none; - font-size: 20px; - font-weight: 700; - color:#fff; + text-decoration: none; + font-size: 20px; + font-weight: 700; + color:#fff; } .navbar ul li a:hover{ - color:#000; + color:#000; } .navbar input:checked ~ ul{ - width: 100px; + width: 100px; } .navbar input:checked ~ span:nth-child(2){ - top: calc(50% - 2px); - transform: rotate(-45deg); - background-color: #fff; - transition: top 0.3s ease-in-out,transform 0.3s ease-in-out 0.3s; + top: calc(50% - 2px); + transform: rotate(-45deg); + background-color: #fff; + transition: top 0.3s ease-in-out,transform 0.3s ease-in-out 0.3s; } .navbar input:checked ~ span:nth-child(3){ - top: calc(50% - 2px); - transform: rotate(45deg); - background-color: #fff; - transition: top 0.3s ease-in-out,transform 0.3s ease-in-out 0.3s; + top: calc(50% - 2px); + transform: rotate(45deg); + background-color: #fff; + transition: top 0.3s ease-in-out,transform 0.3s ease-in-out 0.3s; } .close{ - position: absolute; - display: none; - top: -20%; - left: 70%; - width: 20px; - height: 20px; - border: 1px solid #999; - border-radius: 50%; - justify-content: center; - align-items: end; - background: #999; - color: #fff; - cursor: pointer; + position: absolute; + display: none; + top: -20%; + left: 70%; + width: 20px; + height: 20px; + border: 1px solid #999; + border-radius: 50%; + justify-content: center; + align-items: end; + background: #999; + color: #fff; + cursor: pointer; } .navbar:hover .close{ - display: flex; + display: flex; } .navbar input:checked ~ .close{ - display: none; + display: none; } \ No newline at end of file diff --git a/src/agentscope/studio/static/css/login.css b/src/agentscope/studio/static/css/login.css index 4e5317275..93988b23d 100644 --- a/src/agentscope/studio/static/css/login.css +++ b/src/agentscope/studio/static/css/login.css @@ -1,158 +1,158 @@ body { - font-family: 'Arial', sans-serif; - background-color: #f0f0f0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: 100vh; - margin: 0; + font-family: 'Arial', sans-serif; + background-color: #f0f0f0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; } .login-container { - padding: 2rem; - background: #fff; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - border-radius: 8px; - text-align: center; - width: 100%; - max-width: 80%; + padding: 2rem; + background: #fff; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + border-radius: 8px; + text-align: center; + width: 100%; + max-width: 80%; } #loginButton { - background-color: #2ea44f; - color: white; - font-size: 18px; - padding: 15px 24px; - border: none; - border-radius: 5px; - cursor: pointer; - box-shadow: 0px 4px 14px -3px rgba(0, 0, 0, 0.4); - transition: background-color 0.3s, transform 0.2s; - margin-top: 1rem; - display: inline-block; - width: 100%; + background-color: #2ea44f; + color: white; + font-size: 18px; + padding: 15px 24px; + border: none; + border-radius: 5px; + cursor: pointer; + box-shadow: 0px 4px 14px -3px rgba(0, 0, 0, 0.4); + transition: background-color 0.3s, transform 0.2s; + margin-top: 1rem; + display: inline-block; + width: 100%; } #loginButton:hover { - background-color: #2c974b; - transform: scale(1.05); + background-color: #2c974b; + transform: scale(1.05); } #loginButton:active { - background-color: #258741; - transform: scale(1); + background-color: #258741; + transform: scale(1); } #loginButton:disabled { - background-color: #94d3a2; - cursor: not-allowed; + background-color: #94d3a2; + cursor: not-allowed; } #loginGuestButton { - color: #a0a0a0; - font-size: 12px; - padding: 5px 8px; - cursor: pointer; - box-shadow: none; - transition: transform 0.2s; - margin-top: 0.5rem; - display: inline-block; - width: auto; - background: none; - border: none; - text-decoration: underline; - opacity: 0.001; + color: #a0a0a0; + font-size: 12px; + padding: 5px 8px; + cursor: pointer; + box-shadow: none; + transition: transform 0.2s; + margin-top: 0.5rem; + display: inline-block; + width: auto; + background: none; + border: none; + text-decoration: underline; + opacity: 0.001; } #loginGuestButton:hover { - transform: scale(1.01); - text-decoration: underline; - cursor: default; + transform: scale(1.01); + text-decoration: underline; + cursor: default; } #loginGuestButton:disabled { - color: #d3d3d3; - text-decoration: none; - transform: none; + color: #d3d3d3; + text-decoration: none; + transform: none; } .terms { - background: #fff; - padding: 20px; - margin: 1rem auto; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.05); - border-radius: 8px; - max-width: 600px; + background: #fff; + padding: 20px; + margin: 1rem auto; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.05); + border-radius: 8px; + max-width: 600px; } .terms ul { - margin-left: 20px; + margin-left: 20px; } .terms li { - margin-bottom: 10px; + margin-bottom: 10px; } .checkbox { - margin-bottom: 1rem; + margin-bottom: 1rem; } .brand-gif { - background: #fff; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); - width: 50%; - height: auto; - border-radius: 8px; + background: #fff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + width: 50%; + height: auto; + border-radius: 8px; } .link-like { - color: #707070; - text-decoration: underline; - cursor: pointer; - opacity: 0.15; + color: #707070; + text-decoration: underline; + cursor: pointer; + opacity: 0.15; } .link-like:hover { - opacity: 1.0; + opacity: 1.0; } .waiting { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; - background-color: rgba(255, 255, 255, 0.8); - border-radius: 10px; - padding: 20px 40px; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); - flex-direction: column; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 10px; + padding: 20px 40px; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); + flex-direction: column; } .css-spinner { - border: 4px solid rgba(0, 0, 0, .1); - border-radius: 50%; - border-top: 4px solid #3498db; - width: 40px; - height: 40px; - animation: spin 2s linear infinite; + border: 4px solid rgba(0, 0, 0, .1); + border-radius: 50%; + border-top: 4px solid #3498db; + width: 40px; + height: 40px; + animation: spin 2s linear infinite; } @keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } .waiting b { - color: #555; - font-weight: normal; - font-size: 1.5em; + color: #555; + font-weight: normal; + font-size: 1.5em; } diff --git a/src/agentscope/studio/static/css/server.css b/src/agentscope/studio/static/css/server.css index 285e151f2..a916730ad 100644 --- a/src/agentscope/studio/static/css/server.css +++ b/src/agentscope/studio/static/css/server.css @@ -1,132 +1,132 @@ :root { - --server-content-padding-vertical: 20px; - --server-content-padding-horizontal: 30px; - --server-table-height: calc(100% - var(--runs-content-control-panel-height)); + --server-content-padding-vertical: 20px; + --server-content-padding-horizontal: 30px; + --server-table-height: calc(100% - var(--runs-content-control-panel-height)); } #server-panel { - display: flex; - height: 100%; - width: 100%; - flex-direction: column; + display: flex; + height: 100%; + width: 100%; + flex-direction: column; } #server-content { - display: flex; - flex-direction: column; - width: 100%; - padding: var(--server-content-padding-vertical) var(--server-content-padding-horizontal); - box-sizing: border-box; + display: flex; + flex-direction: column; + width: 100%; + padding: var(--server-content-padding-vertical) var(--server-content-padding-horizontal); + box-sizing: border-box; } .collapsed { - display: none; + display: none; } #server-table { - flex-grow: 1; - overflow-y: auto; - max-height: 30vh; - width: 100%; - background-color: #ffffff; - border: 1px solid var(--border-color); + flex-grow: 1; + overflow-y: auto; + max-height: 30vh; + width: 100%; + background-color: #ffffff; + border: 1px solid var(--border-color); } #agent-memory-content { - display: flex; - flex-direction: row; - height: 200px; - width: 100%; - border: 1px solid var(--border-color); + display: flex; + flex-direction: row; + height: 200px; + width: 100%; + border: 1px solid var(--border-color); } #agent-memory-table { - display: flex; - flex-direction: column; - width: 350px; - box-sizing: border-box; - border: 0; - background-color: #ffffff; + display: flex; + flex-direction: column; + width: 350px; + box-sizing: border-box; + border: 0; + background-color: #ffffff; } #agent-memory-raw { - display: flex; - flex-direction: column; - width: calc(100% - 350px); - height: 100%; - box-sizing: border-box; - border-left: 1px solid var(--border-color); + display: flex; + flex-direction: column; + width: calc(100% - 350px); + height: 100%; + box-sizing: border-box; + border-left: 1px solid var(--border-color); } #agent-memory-raw .monaco-editor { - display: flex; - height: 100%; - width: 100%; - min-width: 100%; + display: flex; + height: 100%; + width: 100%; + min-width: 100%; } .server-section-title-bar { - display: flex; - justify-content: flex-start; - font-size: 20px; - padding: 6px 0px; + display: flex; + justify-content: flex-start; + font-size: 20px; + padding: 6px 0px; } .align-right-btn { - margin-left: auto; + margin-left: auto; } #server-content .icon { - display: flex; - height: 20px; - padding: 4px 15px; - cursor: pointer; + display: flex; + height: 20px; + padding: 4px 15px; + cursor: pointer; } #agent-table { - flex-grow: 1; - overflow-y: auto; - max-height: 30vh; - width: 100%; - background-color: #ffffff; - border: 1px solid var(--border-color); + flex-grow: 1; + overflow-y: auto; + max-height: 30vh; + width: 100%; + background-color: #ffffff; + border: 1px solid var(--border-color); } .status-tag { - display: flex; - flex-direction: row; - align-items: center; - height: 26px; - width: fit-content; - border-radius: 13px; - color: var(--base-color); - box-sizing: border-box; - margin: 0 5px; - padding: 0 14px; + display: flex; + flex-direction: row; + align-items: center; + height: 26px; + width: fit-content; + border-radius: 13px; + color: var(--base-color); + box-sizing: border-box; + margin: 0 5px; + padding: 0 14px; } .running.status-tag { - background-color: #ccf4dd; - color: #27ae60; - fill: #27ae60; + background-color: #ccf4dd; + color: #27ae60; + fill: #27ae60; } .dead.status-tag { - background-color: #f5d5d1; - color: #c0392b; - fill: #c0392b; + background-color: #f5d5d1; + color: #c0392b; + fill: #c0392b; } .loading.status-tag { - background-color: #e1e1e1; - color: #3d4047; - fill: #3d4047; + background-color: #e1e1e1; + color: #3d4047; + fill: #3d4047; } .unknown.status-tag { - background-color: #e1e1e1; - color: #3d4047; - fill: #3d4047; + background-color: #e1e1e1; + color: #3d4047; + fill: #3d4047; } \ No newline at end of file diff --git a/src/agentscope/studio/static/css/workstation-drag-components.css b/src/agentscope/studio/static/css/workstation-drag-components.css index 128f39999..2a67c7cf6 100644 --- a/src/agentscope/studio/static/css/workstation-drag-components.css +++ b/src/agentscope/studio/static/css/workstation-drag-components.css @@ -1,165 +1,165 @@ .title-box { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - overflow: hidden; - text-overflow: ellipsis; - width: 100%; - height: 50px; - box-sizing: border-box; - border-radius: 5px; - background: var(--background-box-title); - padding: 0 10px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + height: 50px; + box-sizing: border-box; + border-radius: 5px; + background: var(--background-box-title); + padding: 0 10px; } .drawflow-node.selected .title-box { - background-color: #ffffff; + background-color: #ffffff; } .title-box-left-items { - display: flex; - flex-direction: row; - align-items: center; - overflow-x: auto; - height: 100%; + display: flex; + flex-direction: row; + align-items: center; + overflow-x: auto; + height: 100%; } .title-box-svg { - display: flex; - align-items: center; - justify-content: center; - height: 25px; - width: 25px; - background-color: var(--main-color); - border-radius: 5px; - fill: #ffffff; - margin-right: 8px; + display: flex; + align-items: center; + justify-content: center; + height: 25px; + width: 25px; + background-color: var(--main-color); + border-radius: 5px; + fill: #ffffff; + margin-right: 8px; } /*TODO: 没有生效*/ .drawflow-node.selected input, .drawflow-node.selected textarea { - border: 1px solid var(--main-color-light); + border: 1px solid var(--main-color-light); } .drawflow-node.selected input:focus, .drawflow-node.selected textarea:focus { - outline: 1px solid var(--main-color); + outline: 1px solid var(--main-color); } .drawflow-node .input:hover { - background: #43b993; + background: #43b993; } .drawflow-node .output:hover { - background: #43b993; + background: #43b993; } .drawflow > .drawflow-delete { - border: 2px solid #43b993; - background: white; - color: #43b993; - -webkit-box-shadow: 0 2px 20px 2px #43b993; - box-shadow: 0 2px 20px 2px #43b993; + border: 2px solid #43b993; + background: white; + color: #43b993; + -webkit-box-shadow: 0 2px 20px 2px #43b993; + box-shadow: 0 2px 20px 2px #43b993; } .toggle-arrow { - display: flex; - height: 10px; - width: 10px; - margin-right: 5px; - transform: translateY(-50%); - cursor: pointer; - box-sizing: border-box; + display: flex; + height: 10px; + width: 10px; + margin-right: 5px; + transform: translateY(-50%); + cursor: pointer; + box-sizing: border-box; } .box { - display: flex; - flex-direction: column; - font-size: 14px; - margin-top: 10px; - height: fit-content; + display: flex; + flex-direction: column; + font-size: 14px; + margin-top: 10px; + height: fit-content; } .box.hidden { - display: none; + display: none; } .hidden { - display: none; + display: none; } .box input, .box select, .box textarea { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - box-sizing: border-box; - width: 100%; - height: 40px; - min-height: 40px; - padding: 0 10px; - border: 1px solid var(--border-color); - border-radius: 5px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + box-sizing: border-box; + width: 100%; + height: 40px; + min-height: 40px; + padding: 0 10px; + border: 1px solid var(--border-color); + border-radius: 5px; } .box label { - display: flex; - flex-direction: row; - align-items: center; - margin-top: 5px; - margin-bottom: 5px; + display: flex; + flex-direction: row; + align-items: center; + margin-top: 5px; + margin-bottom: 5px; } .box a { - target: "_blank"; + target: '_blank'; } .readme { - display: flex; - flex-direction: column; - background-color: #ADD8E6; - font-size: 14px; - border: 1px solid #ddd; - border-radius: 5px; - width: 100%; - height: fit-content; - box-sizing: border-box; - padding: 10px; + display: flex; + flex-direction: column; + background-color: #ADD8E6; + font-size: 14px; + border: 1px solid #ddd; + border-radius: 5px; + width: 100%; + height: fit-content; + box-sizing: border-box; + padding: 10px; } .drawflow-node .box-highlight { - padding: 10px 20px 20px 20px; - color: #555555; - font-size: 16px; - background-color: rgba(173, 216, 230, 0.5); - overflow: auto; + padding: 10px 20px 20px 20px; + color: #555555; + font-size: 16px; + background-color: rgba(173, 216, 230, 0.5); + overflow: auto; } .drawflow-node.welcome { - width: 250px; - min-width: 250px; + width: 250px; + min-width: 250px; } .drawflow .connection .point { - stroke: var(--border-color); - stroke-width: 2; - fill: white; + stroke: var(--border-color); + stroke-width: 2; + fill: white; } .drawflow .connection .point.selected, .drawflow .connection .point:hover { - fill: #4ea9ff; + fill: #4ea9ff; } .drawflow .drawflow-node.GROUP { - width: 500px; - z-index: 0; + width: 500px; + z-index: 0; } .placeholder { - height: 300px; - z-index: 0; + height: 300px; + z-index: 0; } .drawflow .drawflow-node.GROUP.hover-drop { - background: hsl(225, 100%, 96%); + background: hsl(225, 100%, 96%); } \ No newline at end of file diff --git a/src/agentscope/studio/static/css/workstation.css b/src/agentscope/studio/static/css/workstation.css index ab1ca0cb7..32cc47c2f 100644 --- a/src/agentscope/studio/static/css/workstation.css +++ b/src/agentscope/studio/static/css/workstation.css @@ -1,1162 +1,1162 @@ :root { - --background-color: #ffffff; - --background-box-title: #f7f7f7; - --scale-factor: 2; + --background-color: #ffffff; + --background-box-title: #f7f7f7; + --scale-factor: 2; - --menu-height: 48px; - --menu-btn-height: 35px; - --menu-btn-width: 50px; + --menu-height: 48px; + --menu-btn-height: 35px; + --menu-btn-width: 50px; - --drawflow-height: calc(100% - var(--menu-height)); + --drawflow-height: calc(100% - var(--menu-height)); - --workstation-sidebar-item-font-size: 18px; - --workstation-sidebar-subitem-font-size: 15px; + --workstation-sidebar-item-font-size: 18px; + --workstation-sidebar-subitem-font-size: 15px; - --logo-font-color: #000000; + --logo-font-color: #000000; } html { - height: 100%; /* Fill the entire screen height */ - width: 100%; /* Fill the entire screen width */ - margin: 0; /* Eliminate default html margin */ - padding: 0; + height: 100%; /* Fill the entire screen height */ + width: 100%; /* Fill the entire screen width */ + margin: 0; /* Eliminate default html margin */ + padding: 0; } body { - display: flex; /* Allows body to be a flex container */ - height: 100%; /* Fill the entire screen height */ - width: 100%; /* Fill the entire screen width */ - margin: 0; /* Eliminate default body margin */ - flex-direction: row; - padding: 0; - background: var(--body-bg); - font-family: sans-serif; - box-sizing: border-box; + display: flex; /* Allows body to be a flex container */ + height: 100%; /* Fill the entire screen height */ + width: 100%; /* Fill the entire screen width */ + margin: 0; /* Eliminate default body margin */ + flex-direction: row; + padding: 0; + background: var(--body-bg); + font-family: sans-serif; + box-sizing: border-box; } #workstation-panel { - display: flex; - height: 100%; - width: 100%; - flex-direction: column; + display: flex; + height: 100%; + width: 100%; + flex-direction: column; } .them-edit-link { - position: absolute; - top: 10px; - right: 100px; - color: black; - font-size: 40px; + position: absolute; + top: 10px; + right: 100px; + color: black; + font-size: 40px; } .them-edit-link a { - text-decoration: none; + text-decoration: none; } .wrapper { - display: flex; - flex-direction: row; - width: 100%; - box-sizing: border-box; - height: var(--page-content-height); + display: flex; + flex-direction: row; + width: 100%; + box-sizing: border-box; + height: var(--page-content-height); } .col { - display: flex; - flex-direction: column; - width: var(--page-sidebar-width); - min-width: var(--page-sidebar-width); - max-width: var(--page-sidebar-width); - box-sizing: border-box; - height: 100%; - border-right: 1px solid var(--border-color); - padding: 50px 10px; - overflow-y: auto; + display: flex; + flex-direction: column; + width: var(--page-sidebar-width); + min-width: var(--page-sidebar-width); + max-width: var(--page-sidebar-width); + box-sizing: border-box; + height: 100%; + border-right: 1px solid var(--border-color); + padding: 50px 10px; + overflow-y: auto; } .col ul { - margin: 0; + margin: 0; } .col ul { - padding-left: 0; + padding-left: 0; } .col ul, li { - list-style-type: none; + list-style-type: none; } .col-right { - display: flex; - flex-direction: column; - width: calc(100% - var(--page-sidebar-width)); - height: 100%; + display: flex; + flex-direction: column; + width: calc(100% - var(--page-sidebar-width)); + height: 100%; } .menu { - display: flex; - flex-direction: row; - height: var(--menu-height); - width: 100%; - box-sizing: border-box; - align-items: center; + display: flex; + flex-direction: row; + height: var(--menu-height); + width: 100%; + box-sizing: border-box; + align-items: center; } .menu-btn { - display: flex; - height: var(--menu-btn-height); - width: var(--menu-btn-width); - white-space: nowrap; - align-items: center; - justify-content: center; - background-color: var(--main-color-light); - border-radius: 10px; - margin: 0 5px; - cursor: pointer; - position: relative; - position: relative; + display: flex; + height: var(--menu-btn-height); + width: var(--menu-btn-width); + white-space: nowrap; + align-items: center; + justify-content: center; + background-color: var(--main-color-light); + border-radius: 10px; + margin: 0 5px; + cursor: pointer; + position: relative; + position: relative; } .menu-btn:hover { - background-color: var(--main-color); - fill: #ffffff; + background-color: var(--main-color); + fill: #ffffff; } .menu-btn:active { - background-color: var(--main-color-dark); - fill: #ffffff; + background-color: var(--main-color-dark); + fill: #ffffff; } .menu-btn-svg { - display: flex; - width: 20px; - height: 20px; + display: flex; + width: 20px; + height: 20px; } .menu-btn:hover .tooltip { - display: flex; - flex-direction: column; - align-items: center; - height: fit-content; - width: fit-content; - padding: 10px; - background-color: var(--main-color-light); + display: flex; + flex-direction: column; + align-items: center; + height: fit-content; + width: fit-content; + padding: 10px; + background-color: var(--main-color-light); } .btn-cover { - position: relative; - cursor: not-allowed; - background: transparent; - z-index: 15000; - margin-left: 10px; + position: relative; + cursor: not-allowed; + background: transparent; + z-index: 15000; + margin-left: 10px; } .btn-cover::after { - content: attr(data-title); - pointer-events: none; - visibility: hidden; - opacity: 0; - transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out; - - position: absolute; - left: 50%; - top: 100%; - transform: translateX(-50%); - white-space: normal; - max-width: 200px; - text-align: center; - word-wrap: break-word; - background-color: #000; - color: #fff; - padding: 1px 1px; - border-radius: 3px; - font-size: 0.75rem; - z-index: 15000; - - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - margin-top: 8px; - min-width: 100px; + content: attr(data-title); + pointer-events: none; + visibility: hidden; + opacity: 0; + transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out; + + position: absolute; + left: 50%; + top: 100%; + transform: translateX(-50%); + white-space: normal; + max-width: 200px; + text-align: center; + word-wrap: break-word; + background-color: #000; + color: #fff; + padding: 1px 1px; + border-radius: 3px; + font-size: 0.75rem; + z-index: 15000; + + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + margin-top: 8px; + min-width: 100px; } .btn-cover[data-title]:hover::after { - visibility: visible; - opacity: 1; - min-width: 100px; + visibility: visible; + opacity: 1; + min-width: 100px; } .btn-cover:hover + .btn-disabled:hover::after { - visibility: visible; - opacity: 1; + visibility: visible; + opacity: 1; } .btn-disabled { - pointer-events: none; - opacity: 0.6; - background-color: #ccc; - color: #666; - border: 1px solid #999; + pointer-events: none; + opacity: 0.6; + background-color: #ccc; + color: #666; + border: 1px solid #999; } .btn-enabled + .btn-cover { - display: block; + display: block; } .swal-wide { - width: 80% !important; + width: 80% !important; } .control-bar { - display: flex; - flex-direction: row; - float: right; - position: absolute; - bottom: 10px; - right: 10px; - padding: 5px 10px; + display: flex; + flex-direction: row; + float: right; + position: absolute; + bottom: 10px; + right: 10px; + padding: 5px 10px; } .lock-btn { - display: flex; - align-items: center; - justify-content: center; - padding: 5px; - background: #555555; - border-radius: 4px; - z-index: 10000; - margin-right: 10px; - height: fit-content; - width: fit-content; + display: flex; + align-items: center; + justify-content: center; + padding: 5px; + background: #555555; + border-radius: 4px; + z-index: 10000; + margin-right: 10px; + height: fit-content; + width: fit-content; } .zoom-bar { - display: flex; - float: right; - bottom: 10px; - right: 10px; - padding: 5px 10px; - background: #555555; - border-radius: 4px; - z-index: 10000; + display: flex; + float: right; + bottom: 10px; + right: 10px; + padding: 5px 10px; + background: #555555; + border-radius: 4px; + z-index: 10000; } .control-bar svg { - display: flex; - height: 25px; - width: 25px; - justify-content: center; - align-items: center; - fill: white; - margin: 5px; + display: flex; + height: 25px; + width: 25px; + justify-content: center; + align-items: center; + fill: white; + margin: 5px; } .zoom-bar svg:nth-child(1) { - padding-left: 0px; + padding-left: 0px; } #drawflow { - display: flex; - width: 100%; - height: var(--drawflow-height); - top: 40px; - background: var(--background-color); - background-size: 25px 25px; - background-image: linear-gradient(to right, #f1f1f1 1px, transparent 1px), + display: flex; + width: 100%; + height: var(--drawflow-height); + top: 40px; + background: var(--background-color); + background-size: 25px 25px; + background-image: linear-gradient(to right, #f1f1f1 1px, transparent 1px), linear-gradient(to bottom, #f1f1f1 1px, transparent 1px); } @media only screen and (max-width: 768px) { - .col { - width: 50px; - } + .col { + width: 50px; + } - .col .workstation-sidebar-dragitem span { - display: none; - } + .col .workstation-sidebar-dragitem span { + display: none; + } - #drawflow { - width: calc(100vw - 51px); - } + #drawflow { + width: calc(100vw - 51px); + } } /* Modal */ .modal { - display: none; - position: fixed; - z-index: 10000; - left: 0; - top: 0; - width: 100vw; - height: 100vh; - overflow: auto; - background-color: rgb(0, 0, 0); - background-color: rgba(0, 0, 0, 0.7); + display: none; + position: fixed; + z-index: 10000; + left: 0; + top: 0; + width: 100vw; + height: 100vh; + overflow: auto; + background-color: rgb(0, 0, 0); + background-color: rgba(0, 0, 0, 0.7); } .modal-content { - position: relative; - background-color: #fefefe; - margin: 15% auto; - padding: 20px; - border: 1px solid #888; - width: 400px; + position: relative; + background-color: #fefefe; + margin: 15% auto; + padding: 20px; + border: 1px solid #888; + width: 400px; } .modal .close { - color: #aaa; - float: right; - font-size: 28px; - font-weight: bold; - cursor: pointer; + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + cursor: pointer; } @media only screen and (max-width: 768px) { - .modal-content { - width: 80%; - } + .modal-content { + width: 80%; + } } .workstation-sidebar-item { - display: flex; - flex-direction: column; - justify-content: center; - cursor: pointer; - font-size: var(--workstation-sidebar-item-font-size); - font-weight: bold; - box-sizing: border-box; - width: 100%; - height: 45px; - border-radius: 10px; - padding: 0 10px; + display: flex; + flex-direction: column; + justify-content: center; + cursor: pointer; + font-size: var(--workstation-sidebar-item-font-size); + font-weight: bold; + box-sizing: border-box; + width: 100%; + height: 45px; + border-radius: 10px; + padding: 0 10px; } .workstation-sidebar-subitem { - display: flex; - flex-direction: row; - align-items: center; - cursor: pointer; - font-size: var(--workstation-sidebar-subitem-font-size); - box-sizing: border-box; - width: 100%; - height: 40px; - border-radius: 10px; - padding: 0 10px; - font-weight: bold; - color: #808080; + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; + font-size: var(--workstation-sidebar-subitem-font-size); + box-sizing: border-box; + width: 100%; + height: 40px; + border-radius: 10px; + padding: 0 10px; + font-weight: bold; + color: #808080; } .workstation-sidebar-subitem:hover { - background-color: var(--main-color-light); - color: #ffffff; + background-color: var(--main-color-light); + color: #ffffff; } .workstation-sidebar-placeholder { - display: flex; - height: 20px; - width: 100%; + display: flex; + height: 20px; + width: 100%; } .workstation-sidebar-sub-content { - display: none; + display: none; } .active + .workstation-sidebar-sub-content { - display: block; + display: block; } .workstation-sidebar-dragitem { - display: flex; - flex-direction: column; - justify-content: center; - font-size: var(--workstation-sidebar-subitem-font-size); - box-sizing: border-box; - width: calc(100% - 10px); - cursor: move; - height: 40px; - border-radius: 10px; - padding: 0 10px; - margin-left: 10px; + display: flex; + flex-direction: column; + justify-content: center; + font-size: var(--workstation-sidebar-subitem-font-size); + box-sizing: border-box; + width: calc(100% - 10px); + cursor: move; + height: 40px; + border-radius: 10px; + padding: 0 10px; + margin-left: 10px; } .workstation-sidebar-dragitem:hover { - background-color: var(--main-color-light); - color: #ffffff; + background-color: var(--main-color-light); + color: #ffffff; } .workstation-sidebar-subitem-arrow { - display: flex; - height: 10px; - width: 10px; - align-items: center; - justify-content: center; - fill: #000; - margin-right: 5px; + display: flex; + height: 10px; + width: 10px; + align-items: center; + justify-content: center; + fill: #000; + margin-right: 5px; } .highlighted-tab { - cursor: pointer; - color: #ffffff; - background-color: rgba(255, 167, 38, 0.8); - user-select: none; - transition: background-color 0.3s ease; - text-align: center; + cursor: pointer; + color: #ffffff; + background-color: rgba(255, 167, 38, 0.8); + user-select: none; + transition: background-color 0.3s ease; + text-align: center; } .highlighted-tab:hover { - background-color: rgba(251, 140, 0, 0.9); + background-color: rgba(251, 140, 0, 0.9); } .toggle-sub-title { - cursor: pointer; - background-color: var(--main-color-light); - padding: 10px; - margin-top: 0; - margin-bottom: 0; - user-select: none; - transition: background-color 0.3s ease; - text-align: center; - border-radius: 10px; - flex-grow: 1; + cursor: pointer; + background-color: var(--main-color-light); + padding: 10px; + margin-top: 0; + margin-bottom: 0; + user-select: none; + transition: background-color 0.3s ease; + text-align: center; + border-radius: 10px; + flex-grow: 1; } .toggle-sub-title:hover { - background-color: #e2e2e2; + background-color: #e2e2e2; } .draggable-content { - display: flex; - flex-direction: column; - max-height: 0; - opacity: 0; - overflow: auto; - transition: max-height 0.3s ease, opacity 0.3s ease, padding 0.3s ease, margin 0.3s ease; + display: flex; + flex-direction: column; + max-height: 0; + opacity: 0; + overflow: auto; + transition: max-height 0.3s ease, opacity 0.3s ease, padding 0.3s ease, margin 0.3s ease; } .draggable-content.visible { - margin-top: 5px; - padding: 10px; - height: fit-content; - opacity: 1; + margin-top: 5px; + padding: 10px; + height: fit-content; + opacity: 1; } -pre[class*="language-"] { - font-size: 0.5em; - max-height: 60vh; - overflow: auto; - margin: 1rem 0; +pre[class*='language-'] { + font-size: 0.5em; + max-height: 60vh; + overflow: auto; + margin: 1rem 0; } .swal2-title { - font-size: 20px; + font-size: 20px; } .resize-handle-se { - position: absolute; - width: 18px; - height: 18px; - bottom: 0; - fill: var(--border-color); - right: 0; - cursor: nwse-resize; - z-index: 10; + position: absolute; + width: 18px; + height: 18px; + bottom: 0; + fill: var(--border-color); + right: 0; + cursor: nwse-resize; + z-index: 10; } .drawflow .drawflow-node.selected .resize-handle-se { - fill: var(--main-color-light); + fill: var(--main-color-light); } .if-placeholder, .else-placeholder { - border: 1px dashed #ccc; - height: 30%; - margin: 5px; + border: 1px dashed #ccc; + height: 30%; + margin: 5px; } .cases-container { - margin-top: 10px; - min-height: 50px; + margin-top: 10px; + min-height: 50px; } .case-placeholder { - border: 1px dashed #ccc; - height: 100px; - padding: 5px; - margin-bottom: 5px; + border: 1px dashed #ccc; + height: 100px; + padding: 5px; + margin-bottom: 5px; } .case-placeholder:last-child { - height: 100px; - border-bottom: 1px dashed #ccc; + height: 100px; + border-bottom: 1px dashed #ccc; } .button { - display: inline-block; - padding: 6px 12px; - margin: 5px; - font-size: 10px; - font-weight: bold; - color: #ffffff; - background-image: linear-gradient(to right, #6e48aa, #9d50bb); - border: 2px solid transparent; - border-radius: 30px; - cursor: pointer; - text-align: center; - text-decoration: none; - transition: all 0.3s ease; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + display: inline-block; + padding: 6px 12px; + margin: 5px; + font-size: 10px; + font-weight: bold; + color: #ffffff; + background-image: linear-gradient(to right, #6e48aa, #9d50bb); + border: 2px solid transparent; + border-radius: 30px; + cursor: pointer; + text-align: center; + text-decoration: none; + transition: all 0.3s ease; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .button:hover { - background-image: linear-gradient(to left, #6e48aa, #9d50bb); - box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2); - transform: translateY(-2px); + background-image: linear-gradient(to left, #6e48aa, #9d50bb); + box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2); + transform: translateY(-2px); } .add-case { - background-image: linear-gradient(to right, #56ab2f, #a8e063); + background-image: linear-gradient(to right, #56ab2f, #a8e063); } .remove-case { - background-image: linear-gradient(to right, #f85032, #e73827); + background-image: linear-gradient(to right, #f85032, #e73827); } .button:disabled, .button:disabled:hover, .button:disabled:active { - background-color: #cccccc; - color: #666666; - cursor: not-allowed; - box-shadow: none; - border: 2px solid #cccccc; + background-color: #cccccc; + color: #666666; + cursor: not-allowed; + box-shadow: none; + border: 2px solid #cccccc; } .code-snippet { - display: inline; - margin-bottom: 4px; - line-height: 1.4; - padding: 2px 4px; - background-color: #f0f0f0; - font-family: 'Courier New', monospace; - border-radius: 3px; - border: 1px solid #eee; - color: #d63384; + display: inline; + margin-bottom: 4px; + line-height: 1.4; + padding: 2px 4px; + background-color: #f0f0f0; + font-family: 'Courier New', monospace; + border-radius: 3px; + border: 1px solid #eee; + color: #d63384; } .iframe-container { - display: none; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 20000; - background: rgba(0, 0, 0, 0.5); - justify-content: center; - align-items: center; + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 20000; + background: rgba(0, 0, 0, 0.5); + justify-content: center; + align-items: center; } .copilot-iframe { - width: 100%; - height: 100%; - border: 1px solid #ddd; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - transition: all 0.3s; - background: #ffffff; - border-radius: 10px; + width: 100%; + height: 100%; + border: 1px solid #ddd; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + transition: all 0.3s; + background: #ffffff; + border-radius: 10px; } #close-iframe { - position: absolute; - top: 10px; - right: 10px; - padding: 5px 10px; - border: none; - background: rgba(0, 123, 255, 0.4); - color: white; - cursor: pointer; - border-radius: 0 0 0 5px; - font-size: 16px; - z-index: 10000; + position: absolute; + top: 10px; + right: 10px; + padding: 5px 10px; + border: none; + background: rgba(0, 123, 255, 0.4); + color: white; + cursor: pointer; + border-radius: 0 0 0 5px; + font-size: 16px; + z-index: 10000; } #iframe-wrapper { - position: relative; - width: 90%; - height: 90%; + position: relative; + width: 90%; + height: 90%; } #close-iframe .fas { - pointer-events: none; + pointer-events: none; } .tools-placeholder { - border: 2px dashed #ccc; - padding: 30px; - text-align: center; - margin-top: 10px; - color: #888; + border: 2px dashed #ccc; + padding: 30px; + text-align: center; + margin-top: 10px; + color: #888; } .swal2-container { - z-index: 20000 !important; + z-index: 20000 !important; } .modules-info { - background-color: rgba(128, 128, 128, 0.2); - padding: 2px; - margin-bottom: 10px; + background-color: rgba(128, 128, 128, 0.2); + padding: 2px; + margin-bottom: 10px; } .modules-info h4 { - margin: 0 0 10px 0; - font-size: 18px; - color: #444; + margin: 0 0 10px 0; + font-size: 18px; + color: #444; } .modules-info ul { - margin: 0; - padding: 0 0 0 20px; + margin: 0; + padding: 0 0 0 20px; } .modules-info li { - font-size: 14px; - color: #555; - margin-bottom: 10px; + font-size: 14px; + color: #555; + margin-bottom: 10px; } .highlight-module { - font-weight: bold; - color: #2a6496; - cursor: pointer; + font-weight: bold; + color: #2a6496; + cursor: pointer; } .highlight-module:hover { - text-decoration: underline; - color: #1d5b8f; + text-decoration: underline; + color: #1d5b8f; } .advanced-title-box { - cursor: pointer; - color: #333; - margin-top: 20px; + cursor: pointer; + color: #333; + margin-top: 20px; } .advanced-box { - margin: 10px 0; + margin: 10px 0; } .swal2-textarea { - height: 400px; - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; /* 设置等宽字体 */ + height: 400px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; /* 设置等宽字体 */ } .swal2-textarea.code { - padding: 15px; - background-color: #f5f5f5; - border: 1px solid #eee; - box-shadow: inset 0 1px 8px rgba(0, 0, 0, .1); - border-radius: 4px; + padding: 15px; + background-color: #f5f5f5; + border: 1px solid #eee; + box-shadow: inset 0 1px 8px rgba(0, 0, 0, .1); + border-radius: 4px; } .example-wrapper { - display: flex; - align-items: center; - justify-content: space-between; + display: flex; + align-items: center; + justify-content: space-between; } .new-action { - cursor: pointer; - padding: 8px; - margin-left: auto; - border: none; - border-radius: 20px; - background-color: var(--main-color); - color: white; - transition: all 0.3s ease; - font-size: 14px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - max-width: 30px; - overflow: hidden; - white-space: nowrap; - display: flex; - justify-content: center; - align-items: center; + cursor: pointer; + padding: 8px; + margin-left: auto; + border: none; + border-radius: 20px; + background-color: var(--main-color); + color: white; + transition: all 0.3s ease; + font-size: 14px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + max-width: 30px; + overflow: hidden; + white-space: nowrap; + display: flex; + justify-content: center; + align-items: center; } .new-action:hover, .new-action:focus { - width: auto; - padding: 8px 16px; - background-color: var(--main-color); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); + width: auto; + padding: 8px 16px; + background-color: var(--main-color); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); } .new-action:active { - background-color: var(--main-color); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + background-color: var(--main-color); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } #import-buttons { - display: flex; - flex-direction: column; - align-items: center; - position: absolute; - top: 30%; - right: 50px; - z-index: 10; - padding: 5px; - border-radius: 10px; - background-color: #ffffff; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - font-family: 'Poppins', sans-serif; - transition: all 0.3s ease; + display: flex; + flex-direction: column; + align-items: center; + position: absolute; + top: 30%; + right: 50px; + z-index: 10; + padding: 5px; + border-radius: 10px; + background-color: #ffffff; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + font-family: 'Poppins', sans-serif; + transition: all 0.3s ease; } #import-buttons:hover { - box-shadow: 5px 5px 20px rgba(0, 0, 0, 0.25); - transform: translateY(-2px); + box-shadow: 5px 5px 20px rgba(0, 0, 0, 0.25); + transform: translateY(-2px); } #step-info { - margin-bottom: 10px; - padding: 10px; - border-radius: 4px; - text-align: center; - width: 150px; - font-family: 'Poppins', sans-serif; - font-size: 14px; - font-weight: 500; - letter-spacing: 0.05em; - line-height: 1.6; - color: #333; - background-color: rgba(173, 216, 230, 0.5); - box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.1); - cursor: pointer; + margin-bottom: 10px; + padding: 10px; + border-radius: 4px; + text-align: center; + width: 150px; + font-family: 'Poppins', sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.05em; + line-height: 1.6; + color: #333; + background-color: rgba(173, 216, 230, 0.5); + box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.1); + cursor: pointer; } #step-warning { - position: absolute; - top: 8%; - right: 25.5%; - padding: 10px; - text-align: center; - /*font-family: 'Poppins', sans-serif;*/ - font-size: 14px; - letter-spacing: 0.05em; - line-height: 1.6; - color: #333; - margin: 10px; - border-radius: 5px; - border: 1px solid #ffcc00; - background-color: rgba(255, 255, 0, 0.15); - box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.1); - cursor: pointer; + position: absolute; + top: 8%; + right: 25.5%; + padding: 10px; + text-align: center; + /*font-family: 'Poppins', sans-serif;*/ + font-size: 14px; + letter-spacing: 0.05em; + line-height: 1.6; + color: #333; + margin: 10px; + border-radius: 5px; + border: 1px solid #ffcc00; + background-color: rgba(255, 255, 0, 0.15); + box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.1); + cursor: pointer; } #step-info:hover, #step-warning:hover { - box-shadow: 5px 5px 20px rgba(0, 0, 0, 0.25); - transform: translateY(-2px); + box-shadow: 5px 5px 20px rgba(0, 0, 0, 0.25); + transform: translateY(-2px); } #import-prev, #import-next, #import-skip, #import-quit { - padding: 8px 12px; - margin: 4px; - border: none; - border-radius: 4px; - background-color: var(--main-color); - color: white; - cursor: pointer; - font-size: 16px; + padding: 8px 12px; + margin: 4px; + border: none; + border-radius: 4px; + background-color: var(--main-color); + color: white; + cursor: pointer; + font-size: 16px; } #import-prev:hover, #import-next:hover, #import-skip:hover, #import-quit:hover { - background-color: var(--main-color); - transform: translateY(-2px); + background-color: var(--main-color); + transform: translateY(-2px); } #import-prev i, #import-next i, #import-skip i, #import-quit i { - margin-right: 5px; + margin-right: 5px; } #import-next:disabled, #import-prev:disabled, #import-skip:disabled { - background-color: #ccc; - cursor: not-allowed; + background-color: #ccc; + cursor: not-allowed; } #surveyModal { - display: none; - position: fixed; - bottom: 60px; - right: 20px; - background-color: white; - box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); - z-index: 1000; - border-radius: 10px; - overflow: hidden; - animation: fadeIn 0.3s; + display: none; + position: fixed; + bottom: 60px; + right: 20px; + background-color: white; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 1000; + border-radius: 10px; + overflow: hidden; + animation: fadeIn 0.3s; } #surveyContent { - position: relative; - padding: 20px; - text-align: center; + position: relative; + padding: 20px; + text-align: center; } #surveyButton { - padding: 10px 20px; - background-color: #2ea44f; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; - transition: background-color 0.3s, transform 0.2s; + padding: 10px 20px; + background-color: #2ea44f; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s, transform 0.2s; } #surveyButton:hover { - background-color: #258741; - transform: scale(1.05); + background-color: #258741; + transform: scale(1.05); } #surveyClose { - position: absolute; - top: 10px; - right: 15px; - color: #aaaaaa; - font-size: 25px; - cursor: pointer; + position: absolute; + top: 10px; + right: 15px; + color: #aaaaaa; + font-size: 25px; + cursor: pointer; } #surveyClose:hover { - color: #777; + color: #777; } @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } + from { + opacity: 0; + } + to { + opacity: 1; + } } .text-input { - font-family: Arial, sans-serif; - font-size: 16px; - line-height: 1.2; - resize: vertical; + font-family: Arial, sans-serif; + font-size: 16px; + line-height: 1.2; + resize: vertical; } .tour-guide { - display: none; - z-index: 3; + display: none; + z-index: 3; } .tour-step { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - padding: 10px; - border: 1px solid #ccc; - border-radius: 5px; - background-color: #fff; + position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: 10px; + border: 1px solid #ccc; + border-radius: 5px; + background-color: #fff; } .tour-arrow { - position: absolute; - top: 50%; - left: -10px; - width: 0; - height: 0; - border-style: solid; - border-width: 10px 10px 10px 0; - border-color: transparent #ccc transparent transparent; - transform: translateY(-50%); + position: absolute; + top: 50%; + left: -10px; + width: 0; + height: 0; + border-style: solid; + border-width: 10px 10px 10px 0; + border-color: transparent #ccc transparent transparent; + transform: translateY(-50%); } .tour-content { - display: flex; - flex-direction: column; - align-items: center; - border-radius: 5px; - background-color: #fff; + display: flex; + flex-direction: column; + align-items: center; + border-radius: 5px; + background-color: #fff; } .overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; - z-index: 1; - display: none; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1; + display: none; } .guide-Example { - background: #fff; - height: fit-content; - border-radius: 4px; - border: 1px solid #fff; - color: #000; - z-index: 2; + background: #fff; + height: fit-content; + border-radius: 4px; + border: 1px solid #fff; + color: #000; + z-index: 2; } .guide-skip { - padding: 8px 12px; - margin: 4px; - border: none; - border-radius: 4px; - background-color: var(--main-color); - color: white; - cursor: pointer; - font-size: 16px; - margin-top: 10px; + padding: 8px 12px; + margin: 4px; + border: none; + border-radius: 4px; + background-color: var(--main-color); + color: white; + cursor: pointer; + font-size: 16px; + margin-top: 10px; } .notification { - position: fixed; - flex-direction: column; - padding: 10px 30px; - border: 1px solid #ccc; - border-radius: 10px; - background-color: #fff; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - font-size: 14px; - line-height: 1.5; - z-index: 1000; - overflow: hidden; - box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); - transition: right 0.5s ease-out, left 0.5s ease-out; + position: fixed; + flex-direction: column; + padding: 10px 30px; + border: 1px solid #ccc; + border-radius: 10px; + background-color: #fff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + font-size: 14px; + line-height: 1.5; + z-index: 1000; + overflow: hidden; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + transition: right 0.5s ease-out, left 0.5s ease-out; } .notification-arrow { - position: absolute; - width: 10px; - height: 10px; - background-color: #ccc; - transform: rotate(45deg); + position: absolute; + width: 10px; + height: 10px; + background-color: #ccc; + transform: rotate(45deg); } .notification-title { - display: flex; - justify-content: space-between; - align-items: center; - font-weight: bold; - margin-bottom: 5px; + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + margin-bottom: 5px; } .notification-content { - margin-bottom: 5px; + margin-bottom: 5px; } .notification-close { - cursor: pointer; - color: #ccc; - font-size: 12px; - font-weight: bold; - transition: transform 0.5s, color 0.5s; + cursor: pointer; + color: #ccc; + font-size: 12px; + font-weight: bold; + transition: transform 0.5s, color 0.5s; } .notification-close:hover { - color: #000; - transform: rotate(180deg); + color: #000; + transform: rotate(180deg); } .notification-close:mouseout { - color: #ccc; - transform: rotate(0deg); + color: #ccc; + transform: rotate(0deg); } .notification-clickBotton-box { - align-self: end; - display: flex; - gap: 5px; + align-self: end; + display: flex; + gap: 5px; } .notification-btn { - padding: 8px 12px; - margin: 4px; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 16px; + padding: 8px 12px; + margin: 4px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; } .notification-btn:hover { - transform: translateY(-2px); + transform: translateY(-2px); } .notification-btn.confirmBotton { - background-color: var(--main-color); - color: white; + background-color: var(--main-color); + color: white; } .notification-btn.cancelBotton { - background-color: #ccc; + background-color: #ccc; } .notification-progress { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 4px; - background-color: #4CAF50; - transition: width 0.1s linear; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 4px; + background-color: #4CAF50; + transition: width 0.1s linear; } .code-content { - display: flex; - width: var(--code-content-width); - min-height: 200px; - height: 100%; - overflow: auto; - white-space: pre; + display: flex; + width: var(--code-content-width); + min-height: 200px; + height: 100%; + overflow: auto; + white-space: pre; } .two-column-layout { - display: flex; - width: 100%; - height: var(--page-content-height); + display: flex; + width: 100%; + height: var(--page-content-height); } .sidebar { - flex-basis: var(--page-sidebar-width); - box-sizing: border-box; - padding: 50px 10px; - border-right: 1px solid var(--border-color); - overflow-y: auto; - width: 100%; + flex-basis: var(--page-sidebar-width); + box-sizing: border-box; + padding: 50px 10px; + border-right: 1px solid var(--border-color); + overflow-y: auto; + width: 100%; } .tabs { - display: block; - padding: 0; - margin: 50px 0 20px 0; + display: block; + padding: 0; + margin: 50px 0 20px 0; } .tab-button { - cursor: pointer; - padding: 10px; - border: none; - margin-right: 20px; - border-radius: 15px; + cursor: pointer; + padding: 10px; + border: none; + margin-right: 20px; + border-radius: 15px; } .tab-button.active { - background-color: var(--main-color-light); + background-color: var(--main-color-light); } .tab { - display: none; + display: none; } .tab.active { - display: block; + display: block; } .grid-container { - grid-template-columns: repeat(6, 1fr); - gap: 1rem; - padding: 1rem; - display: flex; - flex-wrap: wrap; - gap: 1rem; - padding: 1rem; + grid-template-columns: repeat(6, 1fr); + gap: 1rem; + padding: 1rem; + display: flex; + flex-wrap: wrap; + gap: 1rem; + padding: 1rem; } .grid-item { - background-color: #f0f0f0; - border-radius: 15px; - overflow: hidden; - width: 200px; - height: 200px; - position: relative; - display: flex; - flex-direction: column; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - transition: transform 0.2s ease-in-out; + background-color: #f0f0f0; + border-radius: 15px; + overflow: hidden; + width: 200px; + height: 200px; + position: relative; + display: flex; + flex-direction: column; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + transition: transform 0.2s ease-in-out; } .grid-item:hover { - transform: scale(1.05); + transform: scale(1.05); } .thumbnail { - width: 100%; - flex-grow: 1; - background-size: cover; - background-position: center; + width: 100%; + flex-grow: 1; + background-size: cover; + background-position: center; } .caption { - width: 100%; - height: auto; - text-align: center; - background-color: white; + width: 100%; + height: auto; + text-align: center; + background-color: white; } .caption h6, .caption p { - margin: 1px 0; - font-size: 10px; + margin: 1px 0; + font-size: 10px; } .caption .button { - background-color: #007aff; - color: white; - padding: 2px 7px; - border: none; - border-radius: 8px; - font-size: 12px; - cursor: pointer; - transition: background 0.3s; + background-color: #007aff; + color: white; + padding: 2px 7px; + border: none; + border-radius: 8px; + font-size: 12px; + cursor: pointer; + transition: background 0.3s; } .caption .load-button { - margin-top: 5px; + margin-top: 5px; } .caption .delete-button { - margin-top: 5px; - padding: 2px 3px; + margin-top: 5px; + padding: 2px 3px; } .tooltip { - visibility: hidden; - width: 120px; - background-color: black; - color: rgb(0, 0, 0); - text-align: center; - border-radius: 5px; - padding: 5px; - position: absolute; - z-index: 1; - top: 100%; - left: 50%; - margin-left: -30px; - opacity: 0; - transition: opacity 0.3s; + visibility: hidden; + width: 120px; + background-color: black; + color: rgb(0, 0, 0); + text-align: center; + border-radius: 5px; + padding: 5px; + position: absolute; + z-index: 1; + top: 100%; + left: 50%; + margin-left: -30px; + opacity: 0; + transition: opacity 0.3s; } .menu-btn:hover .tooltip { - visibility: visible; - opacity: 1; + visibility: visible; + opacity: 1; } #navigation-bar-logo { - font-family: 'krypton', sans-serif; - font-size: 20px; - color: var(--logo-font-color); - white-space: nowrap; + font-family: 'krypton', sans-serif; + font-size: 20px; + color: var(--logo-font-color); + white-space: nowrap; } #navigation-bar-logo:hover { - cursor: pointer; + cursor: pointer; } \ No newline at end of file diff --git a/src/agentscope/studio/static/js/dashboard-detail-code.js b/src/agentscope/studio/static/js/dashboard-detail-code.js index e43c779cf..747fc69c7 100644 --- a/src/agentscope/studio/static/js/dashboard-detail-code.js +++ b/src/agentscope/studio/static/js/dashboard-detail-code.js @@ -2,76 +2,76 @@ let currentCode = null; let editorInstance = null; function initializeDashboardDetailCodePage(codeUrl) { - initializeMonacoEditor(); + initializeMonacoEditor(); - fetch("/api/code?run_dir=" + codeUrl) - .then((response) => { - if (!response.ok) { - throw new Error("Connection error, cannot load the web page."); - } - return response.json(); - }) - .then((data) => { - console.log("Get ", data); - currentCode = data; - constructCodeFileList(codeUrl, data); - }) - .catch((error) => { - console.error("Error encountered while loading page: ", error); - }); + fetch("/api/code?run_dir=" + codeUrl) + .then((response) => { + if (!response.ok) { + throw new Error("Connection error, cannot load the web page."); + } + return response.json(); + }) + .then((data) => { + console.log("Get ", data); + currentCode = data; + constructCodeFileList(codeUrl, data); + }) + .catch((error) => { + console.error("Error encountered while loading page: ", error); + }); } function constructCodeFileList(codeUrl, code) { - let codeFileRows = [`
      ${codeUrl}
    `]; + let codeFileRows = [`
      ${codeUrl}
    `]; - if (code !== null && code !== undefined) { - if (Object.keys(code).length === 0) { - codeFileRows = [ - '
    No code available
    ', - ]; - } else { - Object.keys(code).forEach((key) => - codeFileRows.push( - `
  • ${key}
  • ` - ) - ); - } + if (code !== null && code !== undefined) { + if (Object.keys(code).length === 0) { + codeFileRows = [ + "
    No code available
    ", + ]; + } else { + Object.keys(code).forEach((key) => + codeFileRows.push( + `
  • ${key}
  • ` + ) + ); } + } - document.getElementById("code-list").innerHTML = codeFileRows.join("\n"); + document.getElementById("code-list").innerHTML = codeFileRows.join("\n"); } function initializeMonacoEditor() { - require.config({ - paths: { - vs: "https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs", - }, - }); - require(["vs/editor/editor.main"], function () { - editorInstance = monaco.editor.create( - document.getElementById("code-editor"), - { - language: "python", - theme: "vs-light", - scrollBeyondLastLine: false, - readOnly: true, - } - ); - }, function (error) { - console.error("Error encountered while loading monaco editor: ", error); - }); + require.config({ + paths: { + vs: "https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs", + }, + }); + require(["vs/editor/editor.main"], function () { + editorInstance = monaco.editor.create( + document.getElementById("code-editor"), + { + language: "python", + theme: "vs-light", + scrollBeyondLastLine: false, + readOnly: true, + } + ); + }, function (error) { + console.error("Error encountered while loading monaco editor: ", error); + }); } function displayCode(codeFileName) { - document.getElementById("code-filename").innerHTML = codeFileName; + document.getElementById("code-filename").innerHTML = codeFileName; - if (editorInstance) { - editorInstance.setValue(currentCode[codeFileName]); - } else { - console.log( - "Monaco editor instance is not available, set text content" - ); - document.getElementById("code-editor").textContent = + if (editorInstance) { + editorInstance.setValue(currentCode[codeFileName]); + } else { + console.log( + "Monaco editor instance is not available, set text content" + ); + document.getElementById("code-editor").textContent = currentCode[codeFileName]; - } + } } diff --git a/src/agentscope/studio/static/js/dashboard-detail-dialogue.js b/src/agentscope/studio/static/js/dashboard-detail-dialogue.js index a13520ceb..5fa87622c 100644 --- a/src/agentscope/studio/static/js/dashboard-detail-dialogue.js +++ b/src/agentscope/studio/static/js/dashboard-detail-dialogue.js @@ -1,7 +1,7 @@ let chatRowOtherTemplate, - chatRowUserTemplate, - chatRowSystemTemplate, - infoRowTemplate; + chatRowUserTemplate, + chatRowSystemTemplate, + infoRowTemplate; let currentRuntimeInfo, currentMsgInfo, currentAgentInfo; @@ -9,18 +9,18 @@ let randomNumberGenerator; let infoClusterize; -let inputFileList = document.getElementById("chat-control-file-list"); +const inputFileList = document.getElementById("chat-control-file-list"); let waitForUserInput = false; let userInputRequest = null; const agentIcons = [ - '', - '', - '', - '', - '', - '', + "", + "", + "", + "", + "", + "", ]; let nameToIconAndColor = {}; @@ -28,625 +28,627 @@ let nameToIconAndColor = {}; hljs.highlightAll(); const marked_options = { - gfm: true, - breaks: true, - pedantic: false, + gfm: true, + breaks: true, + pedantic: false, }; marked.use({ - renderer: { - code(code, infostring, escaped) { - const language = infostring - ? hljs.getLanguage(infostring) - ? infostring - : "plaintext" - : "plaintext"; - // Use Highlight.js to highlight code blocks - return `
    ${
    -                hljs.highlight(code, {language}).value
    -            }
    `; - }, + renderer: { + code(code, infostring, escaped) { + const language = infostring + ? hljs.getLanguage(infostring) + ? infostring + : "plaintext" + : "plaintext"; + // Use Highlight.js to highlight code blocks + return `
    ${
    +        hljs.highlight(code, {language}).value
    +      }
    `; }, + }, }); function randomSelectAgentIcon() { - return agentIcons[Math.floor(randomNumberGenerator.nextFloat() * agentIcons.length)]; + return agentIcons[Math.floor(randomNumberGenerator.nextFloat() * agentIcons.length)]; } function randomIntFromInterval(min, max) { - return Math.floor(randomNumberGenerator.nextFloat() * (max - min + 1) + min); + return Math.floor(randomNumberGenerator.nextFloat() * (max - min + 1) + min); } function hslToHex(h, s, l) { - l /= 100; - const a = (s * Math.min(l, 1 - l)) / 100; - const f = (n) => { - const k = (n + h / 30) % 12; - const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); - return Math.round(255 * color) - .toString(16) - .padStart(2, "0"); // 转换为16进制并补零 - }; - return `#${f(0)}${f(8)}${f(4)}`; + l /= 100; + const a = (s * Math.min(l, 1 - l)) / 100; + const f = (n) => { + const k = (n + h / 30) % 12; + const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); + return Math.round(255 * color) + .toString(16) + .padStart(2, "0"); // 转换为16进制并补零 + }; + return `#${f(0)}${f(8)}${f(4)}`; } function randomSelectColor() { - const h = randomIntFromInterval(0, 360); // 色相:0°到360° - const s = randomIntFromInterval(50, 100); // 饱和度:50%到100% - const l = randomIntFromInterval(25, 75); // 亮度:25%到75% - return hslToHex(h, s, l); + const h = randomIntFromInterval(0, 360); // 色相:0°到360° + const s = randomIntFromInterval(50, 100); // 饱和度:50%到100% + const l = randomIntFromInterval(25, 75); // 亮度:25%到75% + return hslToHex(h, s, l); } async function loadChatTemplate() { - const response = await fetch("static/html/template.html"); - const htmlText = await response.text(); - const parser = new DOMParser(); - const doc = parser.parseFromString(htmlText, "text/html"); - - // save - chatRowOtherTemplate = doc.querySelector( - "#chat-row-other-template" - ).content; - chatRowUserTemplate = doc.querySelector("#chat-row-user-template").content; - chatRowSystemTemplate = doc.querySelector( - "#chat-row-system-template" - ).content; - infoRowTemplate = doc.querySelector("#dialogue-info-row-template").content; + const response = await fetch("static/html/template.html"); + const htmlText = await response.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlText, "text/html"); + + // save + chatRowOtherTemplate = doc.querySelector( + "#chat-row-other-template" + ).content; + chatRowUserTemplate = doc.querySelector("#chat-row-user-template").content; + chatRowSystemTemplate = doc.querySelector( + "#chat-row-system-template" + ).content; + infoRowTemplate = doc.querySelector("#dialogue-info-row-template").content; } // Add a chat row according to the role field in the message function addChatRow(index, pMsg) { - switch (pMsg.role.toLowerCase()) { - case "user": - return _addUserChatRow(index, pMsg); - case "system": - return _addSystemChatRow(index, pMsg); - case "assistant": - return _addAssistantChatRow(index, pMsg); - default: - console.error("Unknown role: " + pMsg.role); - return _addAssistantChatRow(index, pMsg); - } + switch (pMsg.role.toLowerCase()) { + case "user": + return _addUserChatRow(index, pMsg); + case "system": + return _addSystemChatRow(index, pMsg); + case "assistant": + return _addAssistantChatRow(index, pMsg); + default: + console.error("Unknown role: " + pMsg.role); + return _addAssistantChatRow(index, pMsg); + } } function _determineFileType(url) { - // Image - let img_suffix = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp"]; - // Video - let video_suffix = [ - ".mp4", - ".webm", - ".avi", - ".mov", - ".wmv", - ".flv", - ".f4v", - ".m4v", - ".rmvb", - ".rm", - ".3gp", - ".dat", - ".ts", - ".mts", - ".vob", - ]; + // Image + const img_suffix = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp"]; + // Video + const video_suffix = [ + ".mp4", + ".webm", + ".avi", + ".mov", + ".wmv", + ".flv", + ".f4v", + ".m4v", + ".rmvb", + ".rm", + ".3gp", + ".dat", + ".ts", + ".mts", + ".vob", + ]; // Audio - let audio_suffix = [".mp3", ".wav", ".wma", ".ogg", ".aac", ".flac"]; + const audio_suffix = [".mp3", ".wav", ".wma", ".ogg", ".aac", ".flac"]; - const parsed_url = new URL(url); - const path = parsed_url.pathname; + const parsed_url = new URL(url); + const path = parsed_url.pathname; - const extension = path.split('.').pop().toLowerCase(); + const extension = path.split(".").pop().toLowerCase(); - if (img_suffix.includes('.' + extension)) { - return "image"; - } else if (video_suffix.includes('.' + extension)) { - return "video"; - } else if (audio_suffix.includes('.' + extension)) { - return "audio"; - } - return "file"; + if (img_suffix.includes("." + extension)) { + return "image"; + } else if (video_suffix.includes("." + extension)) { + return "video"; + } else if (audio_suffix.includes("." + extension)) { + return "audio"; + } + return "file"; } function _getMultiModalComponent(url) { - // Determine the type of the file - let urlType = _determineFileType(url); - - // If we need to fetch the url from the backend - let src = null; - if (url.startsWith("http://") || url.startsWith("https://")) { - // Obtain the url from the backend - src = url; - } else { - src = "/api/file?path=" + url; - } - - switch (urlType) { - case "image": - return `Image`; - case "audio": - return ``; - case "video": - return ``; - default: - return `${url}`; - } + // Determine the type of the file + const urlType = _determineFileType(url); + + // If we need to fetch the url from the backend + let src = null; + if (url.startsWith("http://") || url.startsWith("https://")) { + // Obtain the url from the backend + src = url; + } else { + src = "/api/file?path=" + url; + } + + switch (urlType) { + case "image": + return `Image`; + case "audio": + return ``; + case "video": + return ``; + default: + return `${url}`; + } } // Render multiple urls in a chat bubble function _renderMultiModalUrls(urls) { - if (urls == null || urls === "") { - return "" - } - - if (typeof urls === "string") { - urls = [urls] - } - - if (Array.isArray(urls) && urls.length > 0) { - let innerHtml = ""; - for (let i = 0; i < urls.length; i++) { - innerHtml += _getMultiModalComponent(urls[i]); - } - return innerHtml; - } else { - return "" + if (urls == null || urls === "") { + return ""; + } + + if (typeof urls === "string") { + urls = [urls]; + } + + if (Array.isArray(urls) && urls.length > 0) { + let innerHtml = ""; + for (let i = 0; i < urls.length; i++) { + innerHtml += _getMultiModalComponent(urls[i]); } + return innerHtml; + } else { + return ""; + } } function _addUserChatRow(index, pMsg) { - const template = chatRowUserTemplate.cloneNode(true); - // template.querySelector('.chat-icon'). - template.querySelector(".chat-name").textContent = pMsg.name; - let chatBubble = template.querySelector(".chat-bubble"); - chatBubble.textContent += pMsg.content; - chatBubble.innerHTML += _renderMultiModalUrls(pMsg.url); - template.querySelector(".chat-row").setAttribute("data-index", index); - template - .querySelector(".chat-row") - .setAttribute("data-msg", JSON.stringify(pMsg)); - return template.firstElementChild; + const template = chatRowUserTemplate.cloneNode(true); + // template.querySelector('.chat-icon'). + template.querySelector(".chat-name").textContent = pMsg.name; + const chatBubble = template.querySelector(".chat-bubble"); + chatBubble.textContent += pMsg.content; + chatBubble.innerHTML += _renderMultiModalUrls(pMsg.url); + template.querySelector(".chat-row").setAttribute("data-index", index); + template + .querySelector(".chat-row") + .setAttribute("data-msg", JSON.stringify(pMsg)); + return template.firstElementChild; } function _addAssistantChatRow(index, pMsg) { - const template = chatRowOtherTemplate.cloneNode(true); - - // If not record - let svg_html, color; - if (pMsg.name in nameToIconAndColor) { - svg_html = nameToIconAndColor[pMsg.name][0]; - color = nameToIconAndColor[pMsg.name][1]; - } else { - // First choose a svg icon - svg_html = randomSelectAgentIcon(); - color = randomSelectColor(); - // Record the color and icon - nameToIconAndColor[pMsg.name] = [svg_html, color]; - } - template.querySelector(".chat-icon").innerHTML = svg_html; - // change the background color randomly - template.querySelector(".chat-icon").style.backgroundColor = color; - - template.querySelector(".chat-name").textContent = pMsg.name; - let chatBubble = template.querySelector(".chat-bubble"); - chatBubble.innerHTML += marked.parse(pMsg.content, marked_options); - chatBubble.innerHTML += _renderMultiModalUrls(pMsg.url); - template.querySelector(".chat-row").setAttribute("data-index", index); - template - .querySelector(".chat-row") - .setAttribute("data-msg", JSON.stringify(pMsg)); - return template.firstElementChild; + const template = chatRowOtherTemplate.cloneNode(true); + + // If not record + let svg_html, color; + if (pMsg.name in nameToIconAndColor) { + svg_html = nameToIconAndColor[pMsg.name][0]; + color = nameToIconAndColor[pMsg.name][1]; + } else { + // First choose a svg icon + svg_html = randomSelectAgentIcon(); + color = randomSelectColor(); + // Record the color and icon + nameToIconAndColor[pMsg.name] = [svg_html, color]; + } + template.querySelector(".chat-icon").innerHTML = svg_html; + // change the background color randomly + template.querySelector(".chat-icon").style.backgroundColor = color; + + template.querySelector(".chat-name").textContent = pMsg.name; + const chatBubble = template.querySelector(".chat-bubble"); + chatBubble.innerHTML += marked.parse(pMsg.content, marked_options); + chatBubble.innerHTML += _renderMultiModalUrls(pMsg.url); + template.querySelector(".chat-row").setAttribute("data-index", index); + template + .querySelector(".chat-row") + .setAttribute("data-msg", JSON.stringify(pMsg)); + return template.firstElementChild; } function _addSystemChatRow(index, pMsg) { - const template = chatRowSystemTemplate.cloneNode(true); - template.querySelector(".chat-name").textContent = pMsg.name; - let chatBubble = template.querySelector(".chat-bubble"); - chatBubble.innerHTML += marked.parse(pMsg.content, marked_options); - chatBubble.innerHTML += _renderMultiModalUrls(pMsg.url); - template.querySelector(".chat-row").setAttribute("data-index", index); - template - .querySelector(".chat-row") - .setAttribute("data-msg", JSON.stringify(pMsg)); - - return template.firstElementChild; + const template = chatRowSystemTemplate.cloneNode(true); + template.querySelector(".chat-name").textContent = pMsg.name; + const chatBubble = template.querySelector(".chat-bubble"); + chatBubble.innerHTML += marked.parse(pMsg.content, marked_options); + chatBubble.innerHTML += _renderMultiModalUrls(pMsg.url); + template.querySelector(".chat-row").setAttribute("data-index", index); + template + .querySelector(".chat-row") + .setAttribute("data-msg", JSON.stringify(pMsg)); + + return template.firstElementChild; } function _addKeyValueInfoRow(pKey, pValue) { - const template = infoRowTemplate.cloneNode(true); - template.querySelector(".dialogue-info-key").textContent = + const template = infoRowTemplate.cloneNode(true); + template.querySelector(".dialogue-info-key").textContent = pKey.toUpperCase(); - // Not null - if (pValue !== null) { - let infoValue = template.querySelector(".dialogue-info-value"); - if (pKey.toLowerCase() === "url") { - infoValue.style.wordBreak = "break-all"; - } - - if (typeof pValue === "object") { - infoValue.textContent = JSON.stringify(pValue); - } else { - infoValue.textContent = pValue; - } + // Not null + if (pValue !== null) { + const infoValue = template.querySelector(".dialogue-info-value"); + if (pKey.toLowerCase() === "url") { + infoValue.style.wordBreak = "break-all"; + } + + if (typeof pValue === "object") { + infoValue.textContent = JSON.stringify(pValue); + } else { + infoValue.textContent = pValue; } - return template.firstElementChild.outerHTML; + } + return template.firstElementChild.outerHTML; } function disableInput() { - document.getElementById("chat-control-url-btn").disabled = true; - document.getElementById("chat-control-send-btn").disabled = true; + document.getElementById("chat-control-url-btn").disabled = true; + document.getElementById("chat-control-send-btn").disabled = true; } function activateInput() { - document.getElementById("chat-control-url-btn").disabled = false; - document.getElementById("chat-control-send-btn").disabled = false; + document.getElementById("chat-control-url-btn").disabled = false; + document.getElementById("chat-control-send-btn").disabled = false; } function _showInfoInDialogueDetailContent(data) { - if (data === null) { - infoClusterize.clear(); - return; - } - - let priorityKeys = [ - "run_id", - "id", - "project", - "name", - "timestamp", - "role", - "url", - "metadata", - "content", - ]; + if (data === null) { + infoClusterize.clear(); + return; + } + + const priorityKeys = [ + "run_id", + "id", + "project", + "name", + "timestamp", + "role", + "url", + "metadata", + "content", + ]; // Deal with the priority keys first - let infoRows = priorityKeys - .filter((key) => key in data) - .map((key) => _addKeyValueInfoRow(key, data[key])); + const infoRows = priorityKeys + .filter((key) => key in data) + .map((key) => _addKeyValueInfoRow(key, data[key])); // Handle the rest of the keys - Object.keys(data) - .filter((key) => !priorityKeys.includes(key)) // Skip the priority keys - .forEach((key) => infoRows.push(_addKeyValueInfoRow(key, data[key]))); + Object.keys(data) + .filter((key) => !priorityKeys.includes(key)) // Skip the priority keys + .forEach((key) => infoRows.push(_addKeyValueInfoRow(key, data[key]))); - // Create table - infoClusterize.update(infoRows); + // Create table + infoClusterize.update(infoRows); } function _obtainAllUrlFromFileList() { - let urls = []; - for (let i = 0; i < inputFileList.children.length; i++) { - // obtain from the title attribute - urls.push(inputFileList.children[i].title); - } - return urls; + const urls = []; + for (let i = 0; i < inputFileList.children.length; i++) { + // obtain from the title attribute + urls.push(inputFileList.children[i].title); + } + return urls; } function addFileListItem(url) { - let svg; - switch (_determineFileType(url)) { - case "image": - svg = ` + let svg; + switch (_determineFileType(url)) { + case "image": + svg = ` `; - break; - case "video": - svg = ` + break; + case "video": + svg = ` `; - break; - case "audio": - svg = ``; - break; - default: - svg = ``; - break; - } - - let newItem = document.createElement("div"); - newItem.classList.add("chat-control-file-item"); - newItem.innerHTML = svg; - newItem.title = url; - - // Delete btn - const deleteBtn = document.createElement("div"); - deleteBtn.classList.add("chat-control-file-delete"); - // deleteBtn.innerHTML = ``; - deleteBtn.innerHTML = ``; - deleteBtn.onclick = function () { - inputFileList.removeChild(newItem); - }; - newItem.appendChild(deleteBtn); - - inputFileList.appendChild(newItem); - inputFileList.scrollLeft = + break; + case "audio": + svg = ""; + break; + default: + svg = ""; + break; + } + + const newItem = document.createElement("div"); + newItem.classList.add("chat-control-file-item"); + newItem.innerHTML = svg; + newItem.title = url; + + // Delete btn + const deleteBtn = document.createElement("div"); + deleteBtn.classList.add("chat-control-file-delete"); + // deleteBtn.innerHTML = ``; + deleteBtn.innerHTML = ""; + deleteBtn.onclick = function () { + inputFileList.removeChild(newItem); + }; + newItem.appendChild(deleteBtn); + + inputFileList.appendChild(newItem); + inputFileList.scrollLeft = inputFileList.scrollWidth - inputFileList.clientWidth; } // Turn a list of dom elements to a list of html strings function _turnDom2HTML(domList) { - return Array.from(domList).map((dom) => dom.outerHTML); + return Array.from(domList).map((dom) => dom.outerHTML); } function _showUrlPrompt() { - const userInput = prompt("Please enter a local or web URL:", ""); + const userInput = prompt("Please enter a local or web URL:", ""); - if (userInput !== null && userInput !== "") { - addFileListItem(userInput); - } + if (userInput !== null && userInput !== "") { + addFileListItem(userInput); + } } function _isMacOS() { - return navigator.platform.toUpperCase().indexOf("MAC") >= 0; + return navigator.platform.toUpperCase().indexOf("MAC") >= 0; } function initializeDashboardDetailDialoguePage(pRuntimeInfo) { - console.log("Initialize with runtime id: " + pRuntimeInfo.run_id); + console.log("Initialize with runtime id: " + pRuntimeInfo.run_id); - // Initialize the random seed generator by run_id - randomNumberGenerator = new SeededRand(_hashStringToSeed(pRuntimeInfo.run_id)); + // Initialize the random seed generator by run_id + randomNumberGenerator = new SeededRand(_hashStringToSeed(pRuntimeInfo.run_id)); - // Reset the flag - waitForUserInput = false; + // Reset the flag + waitForUserInput = false; - // Empty the record dictionary - nameToIconAndColor = {}; + // Empty the record dictionary + nameToIconAndColor = {}; - let sendBtn = document.getElementById( - "chat-control-send-btn" - ); + const sendBtn = document.getElementById( + "chat-control-send-btn" + ); - let inputTextArea = document.getElementById( - "chat-input-textarea" - ) + const inputTextArea = document.getElementById( + "chat-input-textarea" + ); - inputTextArea.addEventListener("keydown", function (e) { - if (e.key === "Enter" && !e.ctrlKey && !e.metaKey) { - e.preventDefault(); - - if (sendBtn.disabled === false) { - sendBtn.click(); - } - } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { - e.preventDefault(); + inputTextArea.addEventListener("keydown", function (e) { + if (e.key === "Enter" && !e.ctrlKey && !e.metaKey) { + e.preventDefault(); - let cursorPosition = inputTextArea.selectionStart; - let textBeforeCursor = inputTextArea.value.substring(0, cursorPosition); - let textAfterCursor = inputTextArea.value.substring(cursorPosition); + if (sendBtn.disabled === false) { + sendBtn.click(); + } + } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { + e.preventDefault(); - inputTextArea.value = textBeforeCursor + "\n" + textAfterCursor; + const cursorPosition = inputTextArea.selectionStart; + const textBeforeCursor = inputTextArea.value.substring(0, cursorPosition); + const textAfterCursor = inputTextArea.value.substring(cursorPosition); - // Update the cursor position - inputTextArea.selectionStart = inputTextArea.selectionEnd = cursorPosition + 1; - } - }) + inputTextArea.value = textBeforeCursor + "\n" + textAfterCursor; - // Set the placeholder according to the platform - if (_isMacOS()) { - inputTextArea.placeholder = "Input message here, ⌘ + Enter for new line"; - } else { - inputTextArea.placeholder = "Input message here, Ctrl + Enter for new line"; + // Update the cursor position + inputTextArea.selectionStart = inputTextArea.selectionEnd = cursorPosition + 1; } - - // Load the chat template - loadChatTemplate() - .then(() => { - // Initialize the detail objects - currentRuntimeInfo = pRuntimeInfo; - currentMsgInfo = null; - currentAgentInfo = null; - - infoClusterize = new Clusterize({ - scrollId: "chat-detail", - contentId: "dialogue-detail-content", - }); - - disableInput(); - - // Fetch the chat history from backend - fetch( - "/api/messages/run/" + + }); + + // Set the placeholder according to the platform + if (_isMacOS()) { + inputTextArea.placeholder = "Input message here, ⌘ + Enter for new line"; + } else { + inputTextArea.placeholder = "Input message here, Ctrl + Enter for new line"; + } + + // Load the chat template + loadChatTemplate() + .then(() => { + // Initialize the detail objects + currentRuntimeInfo = pRuntimeInfo; + currentMsgInfo = null; + currentAgentInfo = null; + + infoClusterize = new Clusterize({ + scrollId: "chat-detail", + contentId: "dialogue-detail-content", + }); + + disableInput(); + + // Fetch the chat history from backend + fetch( + "/api/messages/run/" + pRuntimeInfo.run_id + "?run_dir=" + pRuntimeInfo.run_dir - ) - .then((response) => { - if (!response.ok) { - throw new Error("Failed to fetch messages data"); - } - return response.json(); - }) - .then((data) => { - // Load the chat history - let chatRows = data.map((msg, index) => - addChatRow(index, msg) - ); - var clusterize = new Clusterize({ - rows: _turnDom2HTML(chatRows), - scrollId: "chat-box", - contentId: "chat-box-content", - }); - document.getElementById("chat-box-content"); - addEventListener("click", function (event) { - let target = event.target; - - while ( - target && + ) + .then((response) => { + if (!response.ok) { + throw new Error("Failed to fetch messages data"); + } + return response.json(); + }) + .then((data) => { + // Load the chat history + const chatRows = data.map((msg, index) => + addChatRow(index, msg) + ); + const clusterize = new Clusterize({ + rows: _turnDom2HTML(chatRows), + scrollId: "chat-box", + contentId: "chat-box-content", + }); + document.getElementById("chat-box-content"); + addEventListener("click", function (event) { + let target = event.target; + + while ( + target && target !== this && target instanceof Element - ) { - if (target.matches(".chat-row")) { - // Record the current message - currentMsgInfo = JSON.parse( - target.getAttribute("data-msg") - ); - - // Update web ui - showInDetail("Message"); - break; - } - target = target.parentNode; - } - }); - // Load the detail content in the right panel - // traverse all the keys in pRuntimeInfo and create a key-value row - currentRuntimeInfo = pRuntimeInfo; - showInDetail("Runtime"); - - var socket = io(); - socket.on("connect", () => { - // Tell flask server the web ui is ready - socket.emit("join", {run_id: pRuntimeInfo.run_id}); - - sendBtn.onclick = () => { - var message = document.getElementById( - "chat-input-textarea" - ).value; - - if (message === "") { - alert("Please input a message!"); - return; - } - - // Send the message to the flask server according to the current request - let url = _obtainAllUrlFromFileList(); - socket.emit("user_input_ready", { - run_id: userInputRequest.run_id, - agent_id: userInputRequest.agent_id, - name: userInputRequest.name, - url: url, - content: message, - }); - // Finish a user input - while (inputFileList.firstChild) { - inputFileList.removeChild(inputFileList.firstChild); - } - - waitForUserInput = false; - - console.log("Studio: send user_input_ready"); - - document.getElementById( - "chat-input-textarea" - ).value = ""; - disableInput(); - }; - }); - socket.on("display_message", (data) => { - if (data.run_id === pRuntimeInfo.run_id) { - console.log("Studio: receive display_message"); - - // Check the chatRows list in the reverse order to - // save time - let found = false; - for (let index = chatRows.length - 1; index >= 0; index--) { - const row = chatRows[index]; - let rowDataMsg = JSON.parse(row.getAttribute("data-msg")); - if (rowDataMsg.id === data.id) { - // Update the row - chatRows[index] = addChatRow(index, data); - // Update the list - clusterize.update(_turnDom2HTML(chatRows)); - found = true; - // Break the loop - break; - } - } - - if (!found) { - // Create a new row - let newRow = addChatRow( - clusterize.getRowsAmount(), - data - ); - chatRows.push(newRow); - clusterize.append(_turnDom2HTML([newRow])); - clusterize.refresh(); - } - - var scrollElem = + ) { + if (target.matches(".chat-row")) { + // Record the current message + currentMsgInfo = JSON.parse( + target.getAttribute("data-msg") + ); + + // Update web ui + showInDetail("Message"); + break; + } + target = target.parentNode; + } + }); + // Load the detail content in the right panel + // traverse all the keys in pRuntimeInfo and create a key-value row + currentRuntimeInfo = pRuntimeInfo; + showInDetail("Runtime"); + + const socket = io(); + socket.on("connect", () => { + // Tell flask server the web ui is ready + socket.emit("join", {run_id: pRuntimeInfo.run_id}); + + sendBtn.onclick = () => { + const message = document.getElementById( + "chat-input-textarea" + ).value; + + if (message === "") { + alert("Please input a message!"); + return; + } + + // Send the message to the flask server according to the current request + const url = _obtainAllUrlFromFileList(); + socket.emit("user_input_ready", { + run_id: userInputRequest.run_id, + agent_id: userInputRequest.agent_id, + name: userInputRequest.name, + url: url, + content: message, + }); + // Finish a user input + while (inputFileList.firstChild) { + inputFileList.removeChild(inputFileList.firstChild); + } + + waitForUserInput = false; + + console.log("Studio: send user_input_ready"); + + document.getElementById( + "chat-input-textarea" + ).value = ""; + disableInput(); + }; + }); + socket.on("display_message", (data) => { + if (data.run_id === pRuntimeInfo.run_id) { + console.log("Studio: receive display_message"); + + // Check the chatRows list in the reverse order to + // save time + let found = false; + for (let index = chatRows.length - 1; index >= 0; index--) { + const row = chatRows[index]; + const rowDataMsg = JSON.parse(row.getAttribute("data-msg")); + if (rowDataMsg.id === data.id) { + // Update the row + chatRows[index] = addChatRow(index, data); + // Update the list + clusterize.update(_turnDom2HTML(chatRows)); + found = true; + // Break the loop + break; + } + } + + if (!found) { + // Create a new row + const newRow = addChatRow( + clusterize.getRowsAmount(), + data + ); + chatRows.push(newRow); + clusterize.append(_turnDom2HTML([newRow])); + clusterize.refresh(); + } + + const scrollElem = document.getElementById("chat-box"); - scrollElem.scrollTop = scrollElem.scrollHeight; - } - }); - socket.on("enable_user_input", (data) => { - // Require user input in web ui - console.log("Studio: receive enable_user_input"); - - // If already waiting for user input, just abort the request - if (!waitForUserInput) { - // If not waiting for user input, enable the send button - waitForUserInput = true; - // Record the current request - userInputRequest = data; - activateInput(); - document.getElementById( - "chat-input-name" - ).textContent = data.name; - } - }); - }) - .catch((error) => { - console.error("Failed to fetch messages data:", error); - }); + scrollElem.scrollTop = scrollElem.scrollHeight; + } + }); + socket.on("enable_user_input", (data) => { + // Require user input in web ui + console.log("Studio: receive enable_user_input"); + + // If already waiting for user input, just abort the request + if (!waitForUserInput) { + // If not waiting for user input, enable the send button + waitForUserInput = true; + // Record the current request + userInputRequest = data; + activateInput(); + document.getElementById( + "chat-input-name" + ).textContent = data.name; + } + }); }) .catch((error) => { - console.error("Failed to load chat template:", error); + console.error("Failed to fetch messages data:", error); }); + }) + .catch((error) => { + console.error("Failed to load chat template:", error); + }); } function showInDetail(detailType) { - document.getElementById("dialogue-info-title").innerHTML = + document.getElementById("dialogue-info-title").innerHTML = detailType.toUpperCase(); - document.getElementById("runtimeSwitchBtn").classList.remove("selected"); - document.getElementById("msgSwitchBtn").classList.remove("selected"); - document.getElementById("agentSwitchBtn").classList.remove("selected"); - - switch (detailType.toLowerCase()) { - case "runtime": - document - .getElementById("runtimeSwitchBtn") - .classList.add("selected"); - _showInfoInDialogueDetailContent(currentRuntimeInfo); - break; - case "message": - document.getElementById("msgSwitchBtn").classList.add("selected"); - _showInfoInDialogueDetailContent(currentMsgInfo); - break; - case "agent": - document.getElementById("agentSwitchBtn").classList.add("selected"); - _showInfoInDialogueDetailContent(currentAgentInfo); - break; - } + document.getElementById("runtimeSwitchBtn").classList.remove("selected"); + document.getElementById("msgSwitchBtn").classList.remove("selected"); + document.getElementById("agentSwitchBtn").classList.remove("selected"); + + switch (detailType.toLowerCase()) { + case "runtime": + document + .getElementById("runtimeSwitchBtn") + .classList.add("selected"); + _showInfoInDialogueDetailContent(currentRuntimeInfo); + break; + case "message": + document.getElementById("msgSwitchBtn").classList.add("selected"); + _showInfoInDialogueDetailContent(currentMsgInfo); + break; + case "agent": + document.getElementById("agentSwitchBtn").classList.add("selected"); + _showInfoInDialogueDetailContent(currentAgentInfo); + break; + } } function _hashStringToSeed(str) { - let hash = 0; - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; // Turn it into a 32bit integer - } - return hash; + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Turn it into a 32bit integer + } + return hash; } // Linear congruential generator class SeededRand { - constructor(seed) { - this.modulus = 2147483648; // 2**31 - this.multiplier = 48271; // 常数 - this.increment = 0; // 常数 - this.seed = seed % this.modulus; - if (this.seed <= 0) this.seed += this.modulus - 1; + constructor(seed) { + this.modulus = 2147483648; // 2**31 + this.multiplier = 48271; // 常数 + this.increment = 0; // 常数 + this.seed = seed % this.modulus; + if (this.seed <= 0) { + this.seed += this.modulus - 1; } + } - nextFloat() { - this.seed = (this.seed * this.multiplier + this.increment) % this.modulus; - return this.seed / this.modulus; - } + nextFloat() { + this.seed = (this.seed * this.multiplier + this.increment) % this.modulus; + return this.seed / this.modulus; + } } \ No newline at end of file diff --git a/src/agentscope/studio/static/js/dashboard-detail-invocation.js b/src/agentscope/studio/static/js/dashboard-detail-invocation.js index 34daf1ea7..213f2ae8b 100644 --- a/src/agentscope/studio/static/js/dashboard-detail-invocation.js +++ b/src/agentscope/studio/static/js/dashboard-detail-invocation.js @@ -1,75 +1,75 @@ let invocationEditor = null; function initializeDashboardDetailInvocationPage(runDir) { - // load monaco editor - require.config({ - paths: { - vs: "https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs", + // load monaco editor + require.config({ + paths: { + vs: "https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs", + }, + }); + require(["vs/editor/editor.main"], function () { + invocationEditor = monaco.editor.create( + document.getElementById("invocation-content"), + { + language: "json", + theme: "vs-light", + minimap: { + enabled: false, }, - }); - require(["vs/editor/editor.main"], function () { - invocationEditor = monaco.editor.create( - document.getElementById("invocation-content"), - { - language: "json", - theme: "vs-light", - minimap: { - enabled: false, - }, - scrollBeyondLastLine: false, - readOnly: true, - } - ); - }); + scrollBeyondLastLine: false, + readOnly: true, + } + ); + }); - // fetch data from server - fetch("/api/invocation?run_dir=" + runDir) - .then((response) => { - if (response.ok) { - return response.json(); - } else { - throw new Error("Failed to fetch invocation detail"); - } - }) - .then((data) => { - console.log(data); - var invocationTable = new Tabulator("#invocation-list", { - data: data, - columns: [ - { - title: "Model Wrapper", - field: "model_class", - editor: false, - vertAlign: "middle", - }, - { - title: "Timestamp", - field: "timestamp", - editor: false, - vertAlign: "middle", - }, - ], - layout: "fitColumns", - placeholder: + // fetch data from server + fetch("/api/invocation?run_dir=" + runDir) + .then((response) => { + if (response.ok) { + return response.json(); + } else { + throw new Error("Failed to fetch invocation detail"); + } + }) + .then((data) => { + console.log(data); + const invocationTable = new Tabulator("#invocation-list", { + data: data, + columns: [ + { + title: "Model Wrapper", + field: "model_class", + editor: false, + vertAlign: "middle", + }, + { + title: "Timestamp", + field: "timestamp", + editor: false, + vertAlign: "middle", + }, + ], + layout: "fitColumns", + placeholder: "
    No invocation records available.
    ", - initialSort: [ - { - column: "timestamp", - dir: "asc", - }, - ], - }); + initialSort: [ + { + column: "timestamp", + dir: "asc", + }, + ], + }); - // Set up row click event - invocationTable.on("rowClick", function (e, row) { - // Jump to the run detail page - console.log(row.getData()); - invocationEditor.setValue( - JSON.stringify(row.getData(), null, 2) - ); - }); - }) - .catch((error) => { - console.error(error); - }); + // Set up row click event + invocationTable.on("rowClick", function (e, row) { + // Jump to the run detail page + console.log(row.getData()); + invocationEditor.setValue( + JSON.stringify(row.getData(), null, 2) + ); + }); + }) + .catch((error) => { + console.error(error); + }); } diff --git a/src/agentscope/studio/static/js/dashboard-detail.js b/src/agentscope/studio/static/js/dashboard-detail.js index 806b15131..0847ac3a6 100644 --- a/src/agentscope/studio/static/js/dashboard-detail.js +++ b/src/agentscope/studio/static/js/dashboard-detail.js @@ -2,17 +2,17 @@ let runtimeInfo = null; let currentContent = null; function initializeDashboardDetailPageByUrl(pageUrl) { - switch (pageUrl) { - case "static/html/dashboard-detail-dialogue.html": - initializeDashboardDetailDialoguePage(runtimeInfo); - break; - case "static/html/dashboard-detail-code.html": - initializeDashboardDetailCodePage(runtimeInfo["run_dir"]); - break; - case "static/html/dashboard-detail-invocation.html": - initializeDashboardDetailInvocationPage(runtimeInfo["run_dir"]); - break; - } + switch (pageUrl) { + case "static/html/dashboard-detail-dialogue.html": + initializeDashboardDetailDialoguePage(runtimeInfo); + break; + case "static/html/dashboard-detail-code.html": + initializeDashboardDetailCodePage(runtimeInfo["run_dir"]); + break; + case "static/html/dashboard-detail-invocation.html": + initializeDashboardDetailInvocationPage(runtimeInfo["run_dir"]); + break; + } } // The dashboard detail page supports three tabs: @@ -20,73 +20,73 @@ function initializeDashboardDetailPageByUrl(pageUrl) { // 2. code tab: the code files // 3. invocation tab: the model invocation records function loadDashboardDetailContent(pageUrl, javascriptUrl) { - const dialogueTabBtn = document.getElementById("dialogue-tab-btn"); - const codeTabBtn = document.getElementById("code-tab-btn"); - const invocationTabBtn = document.getElementById("invocation-tab-btn"); - if (currentContent === pageUrl) { - return; - } else { - currentContent = pageUrl; - } - // switch selected status - switch (pageUrl) { - case "static/html/dashboard-detail-dialogue.html": - dialogueTabBtn.classList.add("selected"); - codeTabBtn.classList.remove("selected"); - invocationTabBtn.classList.remove("selected"); - break; - case "static/html/dashboard-detail-code.html": - dialogueTabBtn.classList.remove("selected"); - codeTabBtn.classList.add("selected"); - invocationTabBtn.classList.remove("selected"); - break; - case "static/html/dashboard-detail-invocation.html": - dialogueTabBtn.classList.remove("selected"); - codeTabBtn.classList.remove("selected"); - invocationTabBtn.classList.add("selected"); - break; - } + const dialogueTabBtn = document.getElementById("dialogue-tab-btn"); + const codeTabBtn = document.getElementById("code-tab-btn"); + const invocationTabBtn = document.getElementById("invocation-tab-btn"); + if (currentContent === pageUrl) { + return; + } else { + currentContent = pageUrl; + } + // switch selected status + switch (pageUrl) { + case "static/html/dashboard-detail-dialogue.html": + dialogueTabBtn.classList.add("selected"); + codeTabBtn.classList.remove("selected"); + invocationTabBtn.classList.remove("selected"); + break; + case "static/html/dashboard-detail-code.html": + dialogueTabBtn.classList.remove("selected"); + codeTabBtn.classList.add("selected"); + invocationTabBtn.classList.remove("selected"); + break; + case "static/html/dashboard-detail-invocation.html": + dialogueTabBtn.classList.remove("selected"); + codeTabBtn.classList.remove("selected"); + invocationTabBtn.classList.add("selected"); + break; + } - fetch(pageUrl, { cache: "no-store" }) - .then((response) => { - if (!response.ok) { - throw new Error("Connection error, cannot load the web page."); - } - return response.text(); - }) - .then((html) => { - // Load the page content - document.getElementById("detail-content").innerHTML = html; + fetch(pageUrl, { cache: "no-store" }) + .then((response) => { + if (!response.ok) { + throw new Error("Connection error, cannot load the web page."); + } + return response.text(); + }) + .then((html) => { + // Load the page content + document.getElementById("detail-content").innerHTML = html; - if (!isScriptLoaded(javascriptUrl)) { - let script = document.createElement("script"); - script.src = javascriptUrl; - script.onload = function () { - initializeDashboardDetailPageByUrl(pageUrl); - }; - document.head.appendChild(script); - } else { - initializeDashboardDetailPageByUrl(pageUrl); - } - }) - .catch((error) => { - console.error("Error encountered while loading page: ", error); - document.getElementById("content").innerHTML = + if (!isScriptLoaded(javascriptUrl)) { + const script = document.createElement("script"); + script.src = javascriptUrl; + script.onload = function () { + initializeDashboardDetailPageByUrl(pageUrl); + }; + document.head.appendChild(script); + } else { + initializeDashboardDetailPageByUrl(pageUrl); + } + }) + .catch((error) => { + console.error("Error encountered while loading page: ", error); + document.getElementById("content").innerHTML = "

    Loading failed.

    " + error; - }); + }); } // Initialize the dashboard detail page, this function is the entry point of the dashboard detail page function initializeDashboardDetailPage(pRuntimeInfo) { - // The default content of the dashboard detail page - console.log( - "Initialize dashboard detail page with runtime id: " + + // The default content of the dashboard detail page + console.log( + "Initialize dashboard detail page with runtime id: " + pRuntimeInfo.run_id - ); - runtimeInfo = pRuntimeInfo; - currentContent = null; - loadDashboardDetailContent( - "static/html/dashboard-detail-dialogue.html", - "static/js/dashboard-detail-dialogue.js" - ); + ); + runtimeInfo = pRuntimeInfo; + currentContent = null; + loadDashboardDetailContent( + "static/html/dashboard-detail-dialogue.html", + "static/js/dashboard-detail-dialogue.js" + ); } diff --git a/src/agentscope/studio/static/js/dashboard-runs.js b/src/agentscope/studio/static/js/dashboard-runs.js index 51be4960c..e3375ebc4 100644 --- a/src/agentscope/studio/static/js/dashboard-runs.js +++ b/src/agentscope/studio/static/js/dashboard-runs.js @@ -1,115 +1,115 @@ // Search functionality for runs table function allColumnFilter(data) { - let searchValue = document - .getElementById("runs-search-input") - .value.toLowerCase(); + const searchValue = document + .getElementById("runs-search-input") + .value.toLowerCase(); - for (var field in data) { - if (data.hasOwnProperty(field)) { - var value = data[field]; + for (const field in data) { + if (data.hasOwnProperty(field)) { + let value = data[field]; - if (value != null) { - value = value.toString().toLowerCase(); - if (value.includes(searchValue)) { - return true; - } - } + if (value != null) { + value = value.toString().toLowerCase(); + if (value.includes(searchValue)) { + return true; } + } } - return false; + } + return false; } function renderIconInRunsTable(cell, formatterParams, onRendered) { - let value = cell.getValue(); - switch (value) { - case "running": - return '
    running
    '; + const value = cell.getValue(); + switch (value) { + case "running": + return "
    running
    "; - case "waiting": - return '
    waiting
    '; + case "waiting": + return "
    waiting
    "; - case "finished": - return '
    finished
    '; + case "finished": + return "
    finished
    "; - default: - return '
    unknown
    '; - } + default: + return "
    unknown
    "; + } } function initializeDashboardRunsPage() { - //TODO: fetch runs data from server - fetch("/api/runs/all") - .then((response) => { - if (!response.ok) { - throw new Error("Failed to fetch runs data"); - } - return response.json(); - }) - .then((data) => { - var runsTable = new Tabulator("#runs-table", { - data: data, - columns: [ - { - title: "Status", - field: "status", - editor: false, - vertAlign: "middle", - formatter: renderIconInRunsTable, - }, - { - title: "ID", - field: "run_id", - editor: false, - vertAlign: "middle", - }, - { - title: "Project", - field: "project", - editor: false, - vertAlign: "middle", - }, - { - title: "Name", - field: "name", - editor: false, - vertAlign: "middle", - }, - { - title: "Timestamp", - field: "timestamp", - editor: false, - vertAlign: "middle", - }, - ], - layout: "fitColumns", - initialSort: [{ column: "timestamp", dir: "desc" }], - }); - - // Search logic - document - .getElementById("runs-search-input") - .addEventListener("input", function (e) { - let searchValue = e.target.value; - if (searchValue) { - // Filter the table - runsTable.setFilter(allColumnFilter); - } else { - //Clear the filter - runsTable.clearFilter(); - } - }); + //TODO: fetch runs data from server + fetch("/api/runs/all") + .then((response) => { + if (!response.ok) { + throw new Error("Failed to fetch runs data"); + } + return response.json(); + }) + .then((data) => { + const runsTable = new Tabulator("#runs-table", { + data: data, + columns: [ + { + title: "Status", + field: "status", + editor: false, + vertAlign: "middle", + formatter: renderIconInRunsTable, + }, + { + title: "ID", + field: "run_id", + editor: false, + vertAlign: "middle", + }, + { + title: "Project", + field: "project", + editor: false, + vertAlign: "middle", + }, + { + title: "Name", + field: "name", + editor: false, + vertAlign: "middle", + }, + { + title: "Timestamp", + field: "timestamp", + editor: false, + vertAlign: "middle", + }, + ], + layout: "fitColumns", + initialSort: [{ column: "timestamp", dir: "desc" }], + }); - // Set up row click event - runsTable.on("rowClick", function (e, row) { - // Jump to the run detail page - loadDetailPageInDashboardContent( - "static/html/dashboard-detail.html", - "static/js/dashboard-detail.js", - row.getData() - ); - }); - }) - .catch((error) => { - console.error(error); + // Search logic + document + .getElementById("runs-search-input") + .addEventListener("input", function (e) { + const searchValue = e.target.value; + if (searchValue) { + // Filter the table + runsTable.setFilter(allColumnFilter); + } else { + //Clear the filter + runsTable.clearFilter(); + } }); + + // Set up row click event + runsTable.on("rowClick", function (e, row) { + // Jump to the run detail page + loadDetailPageInDashboardContent( + "static/html/dashboard-detail.html", + "static/js/dashboard-detail.js", + row.getData() + ); + }); + }) + .catch((error) => { + console.error(error); + }); } diff --git a/src/agentscope/studio/static/js/dashboard.js b/src/agentscope/studio/static/js/dashboard.js index ffaeeb43d..9b066623f 100644 --- a/src/agentscope/studio/static/js/dashboard.js +++ b/src/agentscope/studio/static/js/dashboard.js @@ -1,117 +1,117 @@ function loadRunsPageInDashboardContent(pageUrl, javascriptUrl) { - fetch(pageUrl) - .then((response) => { - if (!response.ok) { - throw new Error("Connection error, cannot load the web page."); - } - return response.text(); - }) - .then((html) => { - // Load the page content - document.getElementById("dashboard-content").innerHTML = html; + fetch(pageUrl) + .then((response) => { + if (!response.ok) { + throw new Error("Connection error, cannot load the web page."); + } + return response.text(); + }) + .then((html) => { + // Load the page content + document.getElementById("dashboard-content").innerHTML = html; - // Load the javascript file - if (!isScriptLoaded(javascriptUrl)) { - console.log("Loading script from " + javascriptUrl + "..."); - let script = document.createElement("script"); - script.src = javascriptUrl; - script.onload = function () { - // Initialize the runs tables - initializeDashboardRunsPage(); - }; - document.head.appendChild(script); - } else { - // Initialize the runs tables - initializeDashboardRunsPage(); - } + // Load the javascript file + if (!isScriptLoaded(javascriptUrl)) { + console.log("Loading script from " + javascriptUrl + "..."); + const script = document.createElement("script"); + script.src = javascriptUrl; + script.onload = function () { + // Initialize the runs tables + initializeDashboardRunsPage(); + }; + document.head.appendChild(script); + } else { + // Initialize the runs tables + initializeDashboardRunsPage(); + } - // Jump to specific run if url contains run_id - const urlParams = new URLSearchParams(window.location.search); - if (urlParams.has("run_id")) { - var loc = window.location; - var baseUrl = loc.protocol + "//" + loc.host; - const run_id = urlParams.get("run_id"); - history.replaceState(null, null, baseUrl); - fetch("/api/runs/get/" + run_id) - .then((response) => { - console.log("fetch /api/runs/get/"); + // Jump to specific run if url contains run_id + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.has("run_id")) { + const loc = window.location; + const baseUrl = loc.protocol + "//" + loc.host; + const run_id = urlParams.get("run_id"); + history.replaceState(null, null, baseUrl); + fetch("/api/runs/get/" + run_id) + .then((response) => { + console.log("fetch /api/runs/get/"); - if (!response.ok) { - throw new Error( - "Runtime id " + run_id + " not found." - ); - } - return response.json(); - }) - .then((data) => { - console.log("get runs" + data); - loadDetailPageInDashboardContent( - "static/html/dashboard-detail.html", - "static/js/dashboard-detail.js", - data - ); - }); + if (!response.ok) { + throw new Error( + "Runtime id " + run_id + " not found." + ); } - }) - .catch((error) => { - console.error("Error encountered while loading page: ", error); - document.getElementById("content").innerHTML = + return response.json(); + }) + .then((data) => { + console.log("get runs" + data); + loadDetailPageInDashboardContent( + "static/html/dashboard-detail.html", + "static/js/dashboard-detail.js", + data + ); + }); + } + }) + .catch((error) => { + console.error("Error encountered while loading page: ", error); + document.getElementById("content").innerHTML = "

    Loading failed.

    " + error; - }); + }); } function loadDetailPageInDashboardContent(pageUrl, javascriptUrl, runtimeInfo) { - fetch(pageUrl) - .then((response) => { - if (!response.ok) { - throw new Error("Connection error, cannot load the web page."); - } - return response.text(); - }) - .then((html) => { - // Load the page content - document.getElementById("dashboard-content").innerHTML = html; + fetch(pageUrl) + .then((response) => { + if (!response.ok) { + throw new Error("Connection error, cannot load the web page."); + } + return response.text(); + }) + .then((html) => { + // Load the page content + document.getElementById("dashboard-content").innerHTML = html; - if (!isScriptLoaded(javascriptUrl)) { - console.log("Loading script from " + javascriptUrl + "..."); - let script = document.createElement("script"); - script.src = javascriptUrl; - script.onload = function () { - initializeDashboardDetailPage(runtimeInfo); - }; - document.head.appendChild(script); - } else { - initializeDashboardDetailPage(runtimeInfo); - } + if (!isScriptLoaded(javascriptUrl)) { + console.log("Loading script from " + javascriptUrl + "..."); + const script = document.createElement("script"); + script.src = javascriptUrl; + script.onload = function () { + initializeDashboardDetailPage(runtimeInfo); + }; + document.head.appendChild(script); + } else { + initializeDashboardDetailPage(runtimeInfo); + } - // Update the title bar of the dashboard page - let titleBar = document.getElementById("dashboard-titlebar"); - // TODO: Add link to the dashboard page - titleBar.innerHTML += - '' + + // Update the title bar of the dashboard page + const titleBar = document.getElementById("dashboard-titlebar"); + // TODO: Add link to the dashboard page + titleBar.innerHTML += + "" + "Runtime ID: " + runtimeInfo.run_id + ""; - let mainTitle = document.getElementById("dashboard-main-title"); - mainTitle.onclick = () => { - loadTabPage( - "static/html/dashboard.html", - "static/js/dashboard.js" - ); - }; - }) - .catch((error) => { - console.error("Error encountered while loading page: ", error); - document.getElementById("content").innerHTML = + const mainTitle = document.getElementById("dashboard-main-title"); + mainTitle.onclick = () => { + loadTabPage( + "static/html/dashboard.html", + "static/js/dashboard.js" + ); + }; + }) + .catch((error) => { + console.error("Error encountered while loading page: ", error); + document.getElementById("content").innerHTML = "

    Loading failed.

    " + error; - }); + }); } // Initialize the dashboard page with a table of runtime instances function initializeDashboardPage() { - // The default content of the dashboard page - loadRunsPageInDashboardContent( - "static/html/dashboard-runs.html", - "static/js/dashboard-runs.js" - ); + // The default content of the dashboard page + loadRunsPageInDashboardContent( + "static/html/dashboard-runs.html", + "static/js/dashboard-runs.js" + ); } diff --git a/src/agentscope/studio/static/js/gallery.js b/src/agentscope/studio/static/js/gallery.js index 57e0f1f43..67695e748 100644 --- a/src/agentscope/studio/static/js/gallery.js +++ b/src/agentscope/studio/static/js/gallery.js @@ -1,280 +1,284 @@ -showTab('tab1'); +showTab("tab1"); function showTab(tabId) { - var tabs = document.getElementsByClassName("tab"); - for (var i = 0; i < tabs.length; i++) { - tabs[i].classList.remove("active"); - tabs[i].style.display = "none"; + const tabs = document.getElementsByClassName("tab"); + for (let i = 0; i < tabs.length; i++) { + tabs[i].classList.remove("active"); + tabs[i].style.display = "none"; + } + const tab = document.getElementById(tabId); + if (tab) { + tab.classList.add("active"); + tab.style.display = "block"; + console.log(`Activated tab with ID: ${tab.id}`); + + const tabButtons = document.getElementsByClassName("tab-button"); + for (let j = 0; j < tabButtons.length; j++) { + tabButtons[j].classList.remove("active"); + } + const activeTabButton = document.querySelector(`.tab-button[onclick*="${tabId}"]`); + if (activeTabButton) { + activeTabButton.classList.add("active"); } - var tab = document.getElementById(tabId); - if (tab) { - tab.classList.add("active"); - tab.style.display = "block"; - console.log(`Activated tab with ID: ${tab.id}`); - - var tabButtons = document.getElementsByClassName("tab-button"); - for (var j = 0; j < tabButtons.length; j++) { - tabButtons[j].classList.remove("active"); - } - var activeTabButton = document.querySelector(`.tab-button[onclick*="${tabId}"]`); - if (activeTabButton) { - activeTabButton.classList.add("active"); - } - if (tabId === "tab2") { - showLoadWorkflowList(tabId); - } else if (tabId === "tab1") { - console.log('Loading Gallery Workflow List'); - showGalleryWorkflowList(tabId); - } + if (tabId === "tab2") { + showLoadWorkflowList(tabId); + } else if (tabId === "tab1") { + console.log("Loading Gallery Workflow List"); + showGalleryWorkflowList(tabId); } + } } function sendWorkflow(fileName) { - if (confirm('Are you sure you want to import this workflow?')) { - const workstationUrl = '/workstation?filename=' + encodeURIComponent(fileName); - window.location.href = workstationUrl; - } + if (confirm("Are you sure you want to import this workflow?")) { + const workstationUrl = "/workstation?filename=" + encodeURIComponent(fileName); + window.location.href = workstationUrl; + } } function importGalleryWorkflow(data) { - try { - const parsedData = JSON.parse(data); - addHtmlAndReplacePlaceHolderBeforeImport(parsedData) - .then(() => { - editor.clear(); - editor.import(parsedData); - importSetupNodes(parsedData); - - if (confirm('Imported!')) { - const workstationUrl = '/workstation'; - window.location.href = workstationUrl; - } - }) - .catch(error => { - alert(`Import error: ${error}`); - }); - } catch (error) { + try { + const parsedData = JSON.parse(data); + addHtmlAndReplacePlaceHolderBeforeImport(parsedData) + .then(() => { + editor.clear(); + editor.import(parsedData); + importSetupNodes(parsedData); + + if (confirm("Imported!")) { + const workstationUrl = "/workstation"; + window.location.href = workstationUrl; + } + }) + .catch(error => { alert(`Import error: ${error}`); - } + }); + } catch (error) { + alert(`Import error: ${error}`); + } } function deleteWorkflow(fileName) { - if (confirm('Workflow has been deleted?')) { - fetch('/delete-workflow', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - filename: fileName, - }) - }).then(response => response.json()) - .then(data => { - if (data.error) { - alert(data.error); - } else { - showLoadWorkflowList('tab2'); - } - }) - .catch(error => { - console.error('Error:', error); - alert('delete workflow error.'); - }); - } + if (confirm("Workflow has been deleted?")) { + fetch("/delete-workflow", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + filename: fileName, + }) + }).then(response => response.json()) + .then(data => { + if (data.error) { + alert(data.error); + } else { + showLoadWorkflowList("tab2"); + } + }) + .catch(error => { + console.error("Error:", error); + alert("delete workflow error."); + }); + } } -function createGridItem(workflowName, container, thumbnail, author = '', time = '', showDeleteButton = false, index) { - var gridItem = document.createElement('div'); - gridItem.className = 'grid-item'; - gridItem.style.borderRadius = '15px'; - gridItem.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; - - - var img = document.createElement('div'); - img.className = 'thumbnail'; - img.style.backgroundImage = `url('${thumbnail}')`; - img.style.backgroundSize = 'cover'; - img.style.backgroundPosition = 'center'; - gridItem.appendChild(img); - - var caption = document.createElement('div'); - caption.className = 'caption'; - caption.style.backgroundColor = 'white'; - - var h6 = document.createElement('h6'); - h6.textContent = workflowName; - h6.style.margin = '1px 0'; - - var pAuthor = document.createElement('p'); - pAuthor.textContent = `Author: ${author}`; - pAuthor.style.margin = '1px 0'; - pAuthor.style.fontSize = '10px'; - - var pTime = document.createElement('p'); - pTime.textContent = `Date: ${time}`; - pTime.style.margin = '1px 0'; - pTime.style.fontSize = '10px'; - - var button = document.createElement('button'); - button.textContent = 'Load'; - button.className = 'button'; - button.style.marginRight = '5px'; - button.style.backgroundColor = '#007aff'; - button.style.backgroundImage = 'linear-gradient(to right, #6e48aa, #9d50bb)'; - button.style.color = 'white'; - button.style.padding = '2px 7px'; - button.style.border = 'none'; - button.style.borderRadius = '8px'; - button.style.fontSize = '12px'; - button.style.cursor = 'pointer'; - button.style.transition = 'background 0.3s'; - - button.addEventListener('mouseover', function () { - button.style.backgroundColor = '#005bb5'; +function createGridItem(workflowName, container, thumbnail, author = "", time = "", showDeleteButton = false, index) { + const gridItem = document.createElement("div"); + gridItem.className = "grid-item"; + gridItem.style.borderRadius = "15px"; + gridItem.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.2)"; + + + const img = document.createElement("div"); + img.className = "thumbnail"; + img.style.backgroundImage = `url('${thumbnail}')`; + img.style.backgroundSize = "cover"; + img.style.backgroundPosition = "center"; + gridItem.appendChild(img); + + const caption = document.createElement("div"); + caption.className = "caption"; + caption.style.backgroundColor = "white"; + + const h6 = document.createElement("h6"); + h6.textContent = workflowName; + h6.style.margin = "1px 0"; + + const pAuthor = document.createElement("p"); + pAuthor.textContent = `Author: ${author}`; + pAuthor.style.margin = "1px 0"; + pAuthor.style.fontSize = "10px"; + + const pTime = document.createElement("p"); + pTime.textContent = `Date: ${time}`; + pTime.style.margin = "1px 0"; + pTime.style.fontSize = "10px"; + + const button = document.createElement("button"); + button.textContent = "Load"; + button.className = "button"; + button.style.marginRight = "5px"; + button.style.backgroundColor = "#007aff"; + button.style.backgroundImage = "linear-gradient(to right, #6e48aa, #9d50bb)"; + button.style.color = "white"; + button.style.padding = "2px 7px"; + button.style.border = "none"; + button.style.borderRadius = "8px"; + button.style.fontSize = "12px"; + button.style.cursor = "pointer"; + button.style.transition = "background 0.3s"; + + button.addEventListener("mouseover", function () { + button.style.backgroundColor = "#005bb5"; + }); + + button.addEventListener("mouseout", function () { + button.style.backgroundColor = "#007aff"; + }); + button.onclick = function (e) { + e.preventDefault(); + if (showDeleteButton) { + sendWorkflow(workflowName); + } else { + const workflowData = galleryWorkflows[index]; + importGalleryWorkflow(JSON.stringify(workflowData)); + } + }; + + + caption.appendChild(h6); + if (author) { + caption.appendChild(pAuthor); + } + if (time) { + caption.appendChild(pTime); + } + caption.appendChild(button); + + if (showDeleteButton) { + const deleteButton = document.createElement("button"); + deleteButton.textContent = "Delete"; + deleteButton.className = "button"; + deleteButton.style.backgroundColor = "#007aff"; + deleteButton.style.backgroundImage = "linear-gradient(to right, #6e48aa, #9d50bb)"; + deleteButton.style.color = "white"; + deleteButton.style.padding = "2px 3px"; + deleteButton.style.border = "none"; + deleteButton.style.borderRadius = "8px"; + deleteButton.style.fontSize = "12px"; + deleteButton.style.cursor = "pointer"; + deleteButton.style.transition = "background 0.3s"; + + deleteButton.addEventListener("mouseover", function () { + deleteButton.style.backgroundColor = "#005bb5"; }); - - button.addEventListener('mouseout', function () { - button.style.backgroundColor = '#007aff'; + deleteButton.addEventListener("mouseout", function () { + deleteButton.style.backgroundColor = "#007aff"; }); - button.onclick = function (e) { - e.preventDefault(); - if (showDeleteButton) { - sendWorkflow(workflowName); - } else { - const workflowData = galleryWorkflows[index]; - importGalleryWorkflow(JSON.stringify(workflowData)); - } - }; + deleteButton.onclick = function (e) { + e.preventDefault(); + deleteWorkflow(workflowName); + }; - caption.appendChild(h6); - if (author) caption.appendChild(pAuthor); - if (time) caption.appendChild(pTime); - caption.appendChild(button); + caption.appendChild(deleteButton); + } - if (showDeleteButton) { - var deleteButton = document.createElement('button'); - deleteButton.textContent = 'Delete'; - deleteButton.className = 'button'; - deleteButton.style.backgroundColor = '#007aff'; - deleteButton.style.backgroundImage = 'linear-gradient(to right, #6e48aa, #9d50bb)'; - deleteButton.style.color = 'white'; - deleteButton.style.padding = '2px 3px'; - deleteButton.style.border = 'none'; - deleteButton.style.borderRadius = '8px'; - deleteButton.style.fontSize = '12px'; - deleteButton.style.cursor = 'pointer'; - deleteButton.style.transition = 'background 0.3s'; - - deleteButton.addEventListener('mouseover', function () { - deleteButton.style.backgroundColor = '#005bb5'; - }); - deleteButton.addEventListener('mouseout', function () { - deleteButton.style.backgroundColor = '#007aff'; - }); - - deleteButton.onclick = function (e) { - e.preventDefault(); - deleteWorkflow(workflowName); - }; - - caption.appendChild(deleteButton); - } - - gridItem.appendChild(caption); - container.appendChild(gridItem); - console.log('Grid item appended:', gridItem); + gridItem.appendChild(caption); + container.appendChild(gridItem); + console.log("Grid item appended:", gridItem); } // let galleryWorkflows = []; function showGalleryWorkflowList(tabId) { - const container = document.getElementById(tabId).querySelector('.grid-container'); - container.innerHTML = ''; - - fetch('/fetch-gallery', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}) + const container = document.getElementById(tabId).querySelector(".grid-container"); + container.innerHTML = ""; + + fetch("/fetch-gallery", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({}) + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + console.log("Fetched gallery data:", data); + + const workflows = data.json || []; + + if (!Array.isArray(workflows)) { + console.error("The server did not return an array as expected.", data); + workflows = [workflows]; + } + + workflows.forEach(workflow => { + const meta = workflow.meta; + const title = meta.title; + const author = meta.author; + const time = meta.time; + const thumbnail = meta.thumbnail || generateThumbnailFromContent(meta); + createGridItem(title, container, thumbnail, author, time, false); + }); }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - }) - .then(data => { - console.log('Fetched gallery data:', data); - - const workflows = data.json || []; - - if (!Array.isArray(workflows)) { - console.error('The server did not return an array as expected.', data); - workflows = [workflows]; - } - - workflows.forEach(workflow => { - const meta = workflow.meta; - const title = meta.title; - const author = meta.author; - const time = meta.time; - const thumbnail = meta.thumbnail || generateThumbnailFromContent(meta); - createGridItem(title, container, thumbnail, author, time, false); - }); - }) - .catch(error => { - console.error('Error fetching gallery workflows:', error); - alert('Failed to load gallery workflows.'); - }); + .catch(error => { + console.error("Error fetching gallery workflows:", error); + alert("Failed to load gallery workflows."); + }); } function showLoadWorkflowList(tabId) { - fetch('/list-workflows', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}) + fetch("/list-workflows", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({}) + }) + .then(response => response.json()) + .then(data => { + if (!Array.isArray(data.files)) { + throw new TypeError("The return data is not an array"); + } + + const container = document.getElementById(tabId).querySelector(".grid-container"); + container.innerHTML = ""; + + data.files.forEach(workflowName => { + const title = workflowName.replace(/\.json$/, ""); + const thumbnail = generateThumbnailFromContent({ title }); + createGridItem(title, container, thumbnail, "", "", true); + }); }) - .then(response => response.json()) - .then(data => { - if (!Array.isArray(data.files)) { - throw new TypeError('The return data is not an array'); - } - - const container = document.getElementById(tabId).querySelector('.grid-container'); - container.innerHTML = ''; - - data.files.forEach(workflowName => { - const title = workflowName.replace(/\.json$/, ''); - const thumbnail = generateThumbnailFromContent({ title }); - createGridItem(title, container, thumbnail, '', '', true); - }); - }) - .catch(error => { - console.error('Error fetching workflow list:', error); - alert('Fetch workflow list error.'); - }); + .catch(error => { + console.error("Error fetching workflow list:", error); + alert("Fetch workflow list error."); + }); } function generateThumbnailFromContent(content) { - const canvas = document.createElement('canvas'); - canvas.width = 150; - canvas.height = 150; - const ctx = canvas.getContext('2d'); + const canvas = document.createElement("canvas"); + canvas.width = 150; + canvas.height = 150; + const ctx = canvas.getContext("2d"); - ctx.fillStyle = '#f0f0f0'; - ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "#f0f0f0"; + ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.font = 'italic bold 14px "Helvetica Neue", sans-serif'; - ctx.textAlign = 'center'; - ctx.fillStyle = '#333'; + ctx.font = "italic bold 14px \"Helvetica Neue\", sans-serif"; + ctx.textAlign = "center"; + ctx.fillStyle = "#333"; - ctx.fillText(content.title, canvas.width / 2, canvas.height / 2 + 20); + ctx.fillText(content.title, canvas.width / 2, canvas.height / 2 + 20); - return canvas.toDataURL(); + return canvas.toDataURL(); } diff --git a/src/agentscope/studio/static/js/index.js b/src/agentscope/studio/static/js/index.js index 6202a7b8c..b318f79f8 100644 --- a/src/agentscope/studio/static/js/index.js +++ b/src/agentscope/studio/static/js/index.js @@ -3,7 +3,7 @@ const workstationTabBtn = document.getElementById("workstation-tab-btn"); const marketTabBtn = document.getElementById("market-tab-btn"); const serverTabBtn = document.getElementById("server-tab-btn"); const navigationBar = document.getElementById("navigation-bar"); -const suspendedBallDom = document.querySelector('.navbar') +const suspendedBallDom = document.querySelector(".navbar"); let currentPageUrl = null; let inGuidePage = true; // When navigation bar collapsed, only when the mouse leaves the navigation bar, then navigation bar will be able to be expanded @@ -11,312 +11,320 @@ let activeExpanded = false; // Check if the script is already loaded function isScriptLoaded(src) { - if(src == 'static/js/gallery.js')return; - let curURL = new URL(src, window.location.href).pathname; - return Array.from(document.scripts).some((script) => { - try { - let existURL = new URL(script.src).pathname; - return existURL === curURL; - } catch (error) { - console.warn( - "Error occurred when checking if the script is loaded: ", - error - ); - return false; - } - }); + if (src == "static/js/gallery.js") { + return; + } + const curURL = new URL(src, window.location.href).pathname; + return Array.from(document.scripts).some((script) => { + try { + const existURL = new URL(script.src).pathname; + return existURL === curURL; + } catch (error) { + console.warn( + "Error occurred when checking if the script is loaded: ", + error + ); + return false; + } + }); } // After loading different pages, we need to call the initialization function of this page function initializeTabPageByUrl(pageUrl) { - switch (pageUrl) { - case "static/html/dashboard.html": - initializeDashboardPage(); - break; - case "static/html/workstation_iframe.html": - let script = document.createElement("script"); - script.src = "static/js/workstation_iframe.js"; - document.head.appendChild(script); - break; - case "static/html/server.html": - initializeServerPage(); - break; - } + switch (pageUrl) { + case "static/html/dashboard.html": + initializeDashboardPage(); + break; + case "static/html/workstation_iframe.html": + const script = document.createElement("script"); + script.src = "static/js/workstation_iframe.js"; + document.head.appendChild(script); + break; + case "static/html/server.html": + initializeServerPage(); + break; + } } // Loading different pages in index.html function loadTabPage(pageUrl, javascriptUrl) { - fetch(pageUrl) - .then((response) => { - if (!response.ok) { - throw new Error("Connection error, cannot load the web page."); - } - return response.text(); - }) - .then((html) => { - currentPageUrl = pageUrl; - - // Hide the sidebar for other pages except the guide page - if (pageUrl === "static/html/index-guide.html") { - navigationBar.classList.remove("collapsed"); - inGuidePage = true; - } else { - navigationBar.classList.add("collapsed"); - inGuidePage = false; - activeExpanded = false; - } - - // Load the page content - document.getElementById("content").innerHTML = html; - - if(!localStorage.getItem('currentLanguage')){ - localStorage.setItem('currentLanguage', 'en'); - } - // Load the javascript file - if (javascriptUrl && !isScriptLoaded(javascriptUrl)) { - let script = document.createElement("script"); - script.src = javascriptUrl; - script.onload = function () { - // The first time we must initialize the page within the onload function to ensure the script is loaded - initializeTabPageByUrl(pageUrl); - }; - document.head.appendChild(script); - } else { - console.log("Script already loaded for " + javascriptUrl); - // If is not the first time, we can directly call the initialization function - initializeTabPageByUrl(pageUrl); - } - - // switch selected status of the tab buttons - switch (pageUrl) { - case "static/html/dashboard.html": - dashboardTabBtn.classList.add("selected"); - workstationTabBtn.classList.remove("selected"); - marketTabBtn.classList.remove("selected"); - serverTabBtn.classList.remove("selected"); - break; - - case "static/html/workstation_iframe.html": - dashboardTabBtn.classList.remove("selected"); - workstationTabBtn.classList.add("selected"); - marketTabBtn.classList.remove("selected"); - serverTabBtn.classList.remove("selected"); - break; - - case "static/html/gallery.html": - dashboardTabBtn.classList.remove("selected"); - workstationTabBtn.classList.remove("selected"); - marketTabBtn.classList.add("selected"); - serverTabBtn.classList.remove("selected"); - break; - - case "static/html/server.html": - dashboardTabBtn.classList.remove("selected"); - workstationTabBtn.classList.remove("selected"); - marketTabBtn.classList.remove("selected"); - serverTabBtn.classList.add("selected"); - break; - } - }) - .catch((error) => { - console.error("Error encountered while loading page: ", error); - document.getElementById("content").innerHTML = + fetch(pageUrl) + .then((response) => { + if (!response.ok) { + throw new Error("Connection error, cannot load the web page."); + } + return response.text(); + }) + .then((html) => { + currentPageUrl = pageUrl; + + // Hide the sidebar for other pages except the guide page + if (pageUrl === "static/html/index-guide.html") { + navigationBar.classList.remove("collapsed"); + inGuidePage = true; + } else { + navigationBar.classList.add("collapsed"); + inGuidePage = false; + activeExpanded = false; + } + + // Load the page content + document.getElementById("content").innerHTML = html; + + if (!localStorage.getItem("currentLanguage")) { + localStorage.setItem("currentLanguage", "en"); + } + // Load the javascript file + if (javascriptUrl && !isScriptLoaded(javascriptUrl)) { + const script = document.createElement("script"); + script.src = javascriptUrl; + script.onload = function () { + // The first time we must initialize the page within the onload function to ensure the script is loaded + initializeTabPageByUrl(pageUrl); + }; + document.head.appendChild(script); + } else { + console.log("Script already loaded for " + javascriptUrl); + // If is not the first time, we can directly call the initialization function + initializeTabPageByUrl(pageUrl); + } + + // switch selected status of the tab buttons + switch (pageUrl) { + case "static/html/dashboard.html": + dashboardTabBtn.classList.add("selected"); + workstationTabBtn.classList.remove("selected"); + marketTabBtn.classList.remove("selected"); + serverTabBtn.classList.remove("selected"); + break; + + case "static/html/workstation_iframe.html": + dashboardTabBtn.classList.remove("selected"); + workstationTabBtn.classList.add("selected"); + marketTabBtn.classList.remove("selected"); + serverTabBtn.classList.remove("selected"); + break; + + case "static/html/gallery.html": + dashboardTabBtn.classList.remove("selected"); + workstationTabBtn.classList.remove("selected"); + marketTabBtn.classList.add("selected"); + serverTabBtn.classList.remove("selected"); + break; + + case "static/html/server.html": + dashboardTabBtn.classList.remove("selected"); + workstationTabBtn.classList.remove("selected"); + marketTabBtn.classList.remove("selected"); + serverTabBtn.classList.add("selected"); + break; + } + }) + .catch((error) => { + console.error("Error encountered while loading page: ", error); + document.getElementById("content").innerHTML = "

    Loading failed.

    " + error; - }); + }); } loadTabPage("static/html/index-guide.html", null); document.addEventListener("DOMContentLoaded", function () { - const urlParams = new URLSearchParams(window.location.search); - if (urlParams.has("run_id")) { - loadTabPage("static/html/dashboard.html", "static/js/dashboard.js"); - } + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.has("run_id")) { + loadTabPage("static/html/dashboard.html", "static/js/dashboard.js"); + } }); navigationBar.addEventListener("mouseenter", function () { - if (activeExpanded) { - navigationBar.classList.remove("collapsed"); - } + if (activeExpanded) { + navigationBar.classList.remove("collapsed"); + } }); navigationBar.addEventListener("mouseleave", function () { - // In guide page, the navigation bar will not be collapsed - if (!inGuidePage) { - // Collapse the navigation bar when the mouse leaves the navigation bar - navigationBar.classList.add("collapsed"); - // Allow the navigation bar to be expanded when the mouse leaves the navigation bar to avoid expanding right after collapsing (when not finished collapsing yet) - activeExpanded = true; - } + // In guide page, the navigation bar will not be collapsed + if (!inGuidePage) { + // Collapse the navigation bar when the mouse leaves the navigation bar + navigationBar.classList.add("collapsed"); + // Allow the navigation bar to be expanded when the mouse leaves the navigation bar to avoid expanding right after collapsing (when not finished collapsing yet) + activeExpanded = true; + } }); class SuspensionBall { - constructor(dom,callback = null) { - this.callback = callback; - this.startEvt = ''; - this.moveEvt = ''; - this.endEvt = ''; - this.drag = dom; - this.isClick = true; - this.disX = 0; - this.disY = 0; - this.left = 0; - this.top = 0; - this.starX = 0; - this.starY = 0; - } + constructor(dom, callback = null) { + this.callback = callback; + this.startEvt = ""; + this.moveEvt = ""; + this.endEvt = ""; + this.drag = dom; + this.isClick = true; + this.disX = 0; + this.disY = 0; + this.left = 0; + this.top = 0; + this.starX = 0; + this.starY = 0; - init() { - this.initEvent(); - } + // 绑定 this + this.startFun = this.startFun.bind(this); + this.moveFun = this.moveFun.bind(this); + this.endFun = this.endFun.bind(this); + } - initEvent() { - if ('ontouchstart' in window) { - this.startEvt = 'touchstart'; - this.moveEvt = 'touchmove'; - this.endEvt = 'touchend'; - } else { - this.startEvt = 'mousedown'; - this.moveEvt = 'mousemove'; - this.endEvt = 'mouseup'; - } - this.drag.addEventListener(this.startEvt, this.startFun); - } + init() { + this.initEvent(); + } - startFun = (e) => { - e.preventDefault(); - e = e || window.event; - this.isClick = true; - this.starX = e.touches ? e.touches[0].clientX : e.clientX; - this.starY = e.touches ? e.touches[0].clientY : e.clientY; - this.disX = this.starX - this.drag?.offsetLeft; - this.disY = this.starY - this.drag?.offsetTop; - document.addEventListener(this.moveEvt, this.moveFun); - document.addEventListener(this.endEvt, this.endFun); + initEvent() { + if ("ontouchstart" in window) { + this.startEvt = "touchstart"; + this.moveEvt = "touchmove"; + this.endEvt = "touchend"; + } else { + this.startEvt = "mousedown"; + this.moveEvt = "mousemove"; + this.endEvt = "mouseup"; } + this.drag.addEventListener(this.startEvt, this.startFun); + } - moveFun = (e) => { - e.preventDefault(); - e = e || window.event; - if ( - Math.abs(this.starX - (e.touches ? e.touches[0].clientX : e.clientX)) > 20 || - Math.abs(this.starY - (e.touches ? e.touches[0].clientY : e.clientY)) > 20 - ) { - this.isClick = false; - } - this.left = (e.touches ? e.touches[0].clientX : e.clientX) - this.disX; - this.top = (e.touches ? e.touches[0].clientY : e.clientY) - this.disY; - if (this.left < 0) { - this.left = 0; - } else if (this.left > document.documentElement.clientWidth - this.drag.offsetWidth) { - this.left = document.documentElement.clientWidth - this.drag.offsetWidth; - } - if (this.top < 0) { - this.top = 0; - } else if (this.top > document.documentElement.clientHeight - this.drag.offsetHeight) { - this.top = document.documentElement.clientHeight - this.drag.offsetHeight; - } - this.drag.style.left = this.left + 'px'; - this.drag.style.top = this.top + 'px'; - } + startFun(e) { + e.preventDefault(); + e = e || window.event; + this.isClick = true; + this.starX = e.touches ? e.touches[0].clientX : e.clientX; + this.starY = e.touches ? e.touches[0].clientY : e.clientY; + this.disX = this.starX - this.drag?.offsetLeft; + this.disY = this.starY - this.drag?.offsetTop; + document.addEventListener(this.moveEvt, this.moveFun); + document.addEventListener(this.endEvt, this.endFun); + } - endFun = (e)=> { - document.removeEventListener(this.moveEvt, this.moveFun); - document.removeEventListener(this.endEvt, this.endFun); - if (this.isClick && this.callback) { // 点击 - this.callback() - } + moveFun(e) { + e.preventDefault(); + e = e || window.event; + if ( + Math.abs(this.starX - (e.touches ? e.touches[0].clientX : e.clientX)) > 20 || + Math.abs(this.starY - (e.touches ? e.touches[0].clientY : e.clientY)) > 20 + ) { + this.isClick = false; + } + this.left = (e.touches ? e.touches[0].clientX : e.clientX) - this.disX; + this.top = (e.touches ? e.touches[0].clientY : e.clientY) - this.disY; + if (this.left < 0) { + this.left = 0; + } else if (this.left > document.documentElement.clientWidth - this.drag.offsetWidth) { + this.left = document.documentElement.clientWidth - this.drag.offsetWidth; + } + if (this.top < 0) { + this.top = 0; + } else if (this.top > document.documentElement.clientHeight - this.drag.offsetHeight) { + this.top = document.documentElement.clientHeight - this.drag.offsetHeight; } - removeDrag = () => { - this.drag.remove(); + this.drag.style.left = this.left + "px"; + this.drag.style.top = this.top + "px"; + } + + endFun(e) { + document.removeEventListener(this.moveEvt, this.moveFun); + document.removeEventListener(this.endEvt, this.endFun); + if (this.isClick && this.callback) { // 点击 + this.callback(); } } + removeDrag() { + this.drag.remove(); + } +} + const suspensionBall = new SuspensionBall(suspendedBallDom); suspensionBall.init(); const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'childList') { - debounceFun(); - } - }); + mutations.forEach((mutation) => { + if (mutation.type === "childList") { + debounceFun(); + } + }); }); observer.observe(document.body, { - childList: true, - subtree: true, + childList: true, + subtree: true, }); const debounceFun = debounce(function refreshI18n() { - let currentLang = getCookie('locale') || 'en'; - observer.disconnect(); - $("[i18n]").i18n({ - defaultLang: currentLang, - filePath: "../static/i18n/", - filePrefix: "i18n_", - fileSuffix: "", - forever: true, - callback: function() { - observer.observe(document.body, { - childList: true, - subtree: true, - }); - } - }); -},100) - -document.addEventListener('DOMContentLoaded', function() { - let currentLang = getCookie('locale') || 'en'; - switch (currentLang){ - case 'en': - document.getElementById('translate').innerText = 'en' - break; - case 'zh': - document.getElementById('translate').innerText = '中' - break; + const currentLang = getCookie("locale") || "en"; + observer.disconnect(); + $("[i18n]").i18n({ + defaultLang: currentLang, + filePath: "../static/i18n/", + filePrefix: "i18n_", + fileSuffix: "", + forever: true, + callback: function () { + observer.observe(document.body, { + childList: true, + subtree: true, + }); } + }); +}, 100); + +document.addEventListener("DOMContentLoaded", function () { + const currentLang = getCookie("locale") || "en"; + switch (currentLang) { + case "en": + document.getElementById("translate").innerText = "en"; + break; + case "zh": + document.getElementById("translate").innerText = "中"; + break; + } }); -function changeLanguage(){ - let currentLang = getCookie('locale') || 'en'; - switch (currentLang){ - case 'en': - document.getElementById('translate').innerText = '中' - localStorage.setItem('locale', 'zh'); - setCookie('locale', 'zh'); - break; - case 'zh': - document.getElementById('translate').innerText = 'en' - localStorage.setItem('locale', 'en'); - setCookie('locale', 'en'); - break; - } +function changeLanguage() { + const currentLang = getCookie("locale") || "en"; + switch (currentLang) { + case "en": + document.getElementById("translate").innerText = "中"; + localStorage.setItem("locale", "zh"); + setCookie("locale", "zh"); + break; + case "zh": + document.getElementById("translate").innerText = "en"; + localStorage.setItem("locale", "en"); + setCookie("locale", "en"); + break; + } } function debounce(func, delay) { - let timeout; + let timeout; - return function(...args) { - clearTimeout(timeout); - timeout = setTimeout(() => func(...args), delay); - }; + return function (...args) { + clearTimeout(timeout); + timeout = setTimeout(() => func(...args), delay); + }; } function getCookie(name) { - var matches = document.cookie.match(new RegExp( - "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)" - )); - return matches ? decodeURIComponent(matches[1]) : undefined; + const matches = document.cookie.match(new RegExp( + "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") + "=([^;]*)" + )); + return matches ? decodeURIComponent(matches[1]) : undefined; } function setCookie(name, value, days) { - var expires = ""; - if (days) { - var date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - expires = "; expires=" + date.toUTCString(); - } - document.cookie = name + "=" + (value || "") + expires + "; path=/"; + let expires = ""; + if (days) { + const date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toUTCString(); + } + document.cookie = name + "=" + (value || "") + expires + "; path=/"; } \ No newline at end of file diff --git a/src/agentscope/studio/static/js/server.js b/src/agentscope/studio/static/js/server.js index bdf1ede12..16e3288fd 100644 --- a/src/agentscope/studio/static/js/server.js +++ b/src/agentscope/studio/static/js/server.js @@ -8,485 +8,485 @@ var messageEditor; // Sever table functions function deleteServer(row) { - fetch("/api/servers/delete", { - method: "POST", - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - body: JSON.stringify({ - server_id: row.getData().id, - stop: row.getData().status == "running", - }), + fetch("/api/servers/delete", { + method: "POST", + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ + server_id: row.getData().id, + stop: row.getData().status == "running", + }), + }) + .then((response) => { + if (!response.ok) { + throw new Error("Failed to delete server"); + } + return response.json(); }) - .then((response) => { - if (!response.ok) { - throw new Error("Failed to delete server"); - } - return response.json(); - }) - .then((data) => { - row.delete(); - }); + .then((data) => { + row.delete(); + }); } function deleteServerBtn(e, cell) { - const serverId = cell.getData().id; - if (confirm(`Are you sure to stop server ${serverId} ?`)) { - deleteServer(cell.getRow()); - } + const serverId = cell.getData().id; + if (confirm(`Are you sure to stop server ${serverId} ?`)) { + deleteServer(cell.getRow()); + } } function newServer() { - console.log("new Server"); + console.log("new Server"); } function flushServerTable(data) { - if (serversTable) { - serversTable.setData(data); - console.log("Flush Server Table"); - } else { - console.error("Server Table is not initialized."); - } + if (serversTable) { + serversTable.setData(data); + console.log("Flush Server Table"); + } else { + console.error("Server Table is not initialized."); + } } function deleteDeadServer() { - let deadServerIds = []; - if (serversTable) { - let rows = serversTable.getRows(); - for (let i = 0; i < rows.length; i++) { - let row = rows[i]; - if (row.getData().status == "dead") { - deadServerIds.push(row.getData().id); - deleteServer(row); - } - } - } else { - console.error("Server Table is not initialized."); + const deadServerIds = []; + if (serversTable) { + const rows = serversTable.getRows(); + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + if (row.getData().status == "dead") { + deadServerIds.push(row.getData().id); + deleteServer(row); + } } - console.log("Delete dead servers: ", deadServerIds); - return deadServerIds; + } else { + console.error("Server Table is not initialized."); + } + console.log("Delete dead servers: ", deadServerIds); + return deadServerIds; } function deleteIcon(cell, formatterParams, onRendered) { - return ''; + return ""; } function getServerStatus(cell, formatterParams, onRendered) { - cell.getElement().innerHTML = - '
    loading
    '; + cell.getElement().innerHTML = + "
    loading
    "; - const serverId = cell.getRow().getData().id; + const serverId = cell.getRow().getData().id; - fetch(`/api/servers/status/${serverId}`) - .then((response) => { - if (!response.ok) { - throw new Error("Failed to get server status"); - } - return response.json(); - }) - .then((data) => { - let row = cell.getRow(); - if (data.status == "running") { - cell.getElement().innerHTML = - '
    running
    '; - row.update({ - status: data.status, - cpu: data.cpu, - mem: data.mem, - size: data.size, - }); - } else { - cell.getElement().innerHTML = - '
    dead
    '; - row.update({ - status: "dead", - }); - } - }) - .catch((error) => { - console.error("Error fetching server status:", error); - cell.getElement().innerHTML = - '
    unknown
    '; + fetch(`/api/servers/status/${serverId}`) + .then((response) => { + if (!response.ok) { + throw new Error("Failed to get server status"); + } + return response.json(); + }) + .then((data) => { + const row = cell.getRow(); + if (data.status == "running") { + cell.getElement().innerHTML = + "
    running
    "; + row.update({ + status: data.status, + cpu: data.cpu, + mem: data.mem, + size: data.size, + }); + } else { + cell.getElement().innerHTML = + "
    dead
    "; + row.update({ + status: "dead", }); + } + }) + .catch((error) => { + console.error("Error fetching server status:", error); + cell.getElement().innerHTML = + "
    unknown
    "; + }); } function cpuUsage(cell, formatterParams, onRendered) { - const cpu = cell.getData().cpu; - if (!cpu && cpu !== 0) { - return '
    unknown
    '; - } else { - return `
    ${cell.getData().cpu} %
    `; - } + const cpu = cell.getData().cpu; + if (!cpu && cpu !== 0) { + return "
    unknown
    "; + } else { + return `
    ${cell.getData().cpu} %
    `; + } } function memoryUsage(cell, formatterParams, onRendered) { - if (cell.getData().mem) { - return `
    ${cell - .getData() - .mem.toFixed(2)} MB
    `; - } else { - return '
    unknown
    '; - } + if (cell.getData().mem) { + return `
    ${cell + .getData() + .mem.toFixed(2)} MB
    `; + } else { + return "
    unknown
    "; + } } function getServerTableData(callback) { - fetch("/api/servers/all") - .then((response) => { - if (!response.ok) { - throw new Error("Failed to fetch servers data"); - } - return response.json(); - }) - .then((data) => { - callback(data); - }); + fetch("/api/servers/all") + .then((response) => { + if (!response.ok) { + throw new Error("Failed to fetch servers data"); + } + return response.json(); + }) + .then((data) => { + callback(data); + }); } function initServerTable(data) { - serversTable = new Tabulator("#server-table", { - data: data, - columns: [ - { - title: "", - field: "status", - vertAlign: "middle", - visible: false, - }, - { - title: "ID", - field: "id", - vertAlign: "middle", - }, - { - title: "Host", - field: "host", - vertAlign: "middle", - }, - { - title: "Port", - field: "port", - vertAlign: "middle", - }, - { - title: "Created Time", - field: "create_time", - vertAlign: "middle", - }, - { - title: "Status", - vertAlign: "middle", - formatter: getServerStatus, - }, - { - title: "Agent Number", - field: "size", - vertAlign: "middle", - }, - { - title: "CPU Usage", - field: "cpu", - vertAlign: "middle", - formatter: cpuUsage, - }, - { - title: "Memory Usage", - field: "mem", - vertAlign: "middle", - formatter: memoryUsage, - }, - { - title: "Delete", - formatter: deleteIcon, - width: 75, - vertAlign: "middle", - cellClick: deleteServerBtn, - }, - ], - layout: "fitColumns", - }); - serversTable.on("rowClick", function (e, row) { - if (row.getData().status != "running") { - return; - } - if (e.target.classList.contains("cell-btn")) { - return; - } - loadAgentDetails(row.getData()); - }); + serversTable = new Tabulator("#server-table", { + data: data, + columns: [ + { + title: "", + field: "status", + vertAlign: "middle", + visible: false, + }, + { + title: "ID", + field: "id", + vertAlign: "middle", + }, + { + title: "Host", + field: "host", + vertAlign: "middle", + }, + { + title: "Port", + field: "port", + vertAlign: "middle", + }, + { + title: "Created Time", + field: "create_time", + vertAlign: "middle", + }, + { + title: "Status", + vertAlign: "middle", + formatter: getServerStatus, + }, + { + title: "Agent Number", + field: "size", + vertAlign: "middle", + }, + { + title: "CPU Usage", + field: "cpu", + vertAlign: "middle", + formatter: cpuUsage, + }, + { + title: "Memory Usage", + field: "mem", + vertAlign: "middle", + formatter: memoryUsage, + }, + { + title: "Delete", + formatter: deleteIcon, + width: 75, + vertAlign: "middle", + cellClick: deleteServerBtn, + }, + ], + layout: "fitColumns", + }); + serversTable.on("rowClick", function (e, row) { + if (row.getData().status != "running") { + return; + } + if (e.target.classList.contains("cell-btn")) { + return; + } + loadAgentDetails(row.getData()); + }); } // Agent table functions function getAgentTableData(serverId, callback) { - fetch(`/api/servers/agent_info/${serverId}`) - .then((response) => { - if (!response.ok) { - throw new Error("Failed to fetch agents data"); - } - return response.json(); - }) - .then((data) => { - callback(serverId, data); - }); + fetch(`/api/servers/agent_info/${serverId}`) + .then((response) => { + if (!response.ok) { + throw new Error("Failed to fetch agents data"); + } + return response.json(); + }) + .then((data) => { + callback(serverId, data); + }); } function flushAgentTable(serverId, data) { - if (agentsTable) { - agentsTable.setData(data); - console.log("Flush Agent Table"); - } else { - console.error("Agent Table is not initialized."); - } + if (agentsTable) { + agentsTable.setData(data); + console.log("Flush Agent Table"); + } else { + console.error("Agent Table is not initialized."); + } } function deleteAllAgent() { - let serverId = curServerId; - if (agentsTable) { - if (confirm(`Are you sure to delete all agent on ${serverId} ?`)) { - fetch(`/api/servers/agents/delete`, { - method: "POST", - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - body: JSON.stringify({ - server_id: serverId, - }), - }) - .then((response) => { - return response.json(); - }) - .then((data) => { - agentsTable.clearData(); - }) - .catch((error) => { - console.error("Error when deleting all agent:", error); - }); - } - } else { - console.error("Agent Table is not initialized."); + const serverId = curServerId; + if (agentsTable) { + if (confirm(`Are you sure to delete all agent on ${serverId} ?`)) { + fetch("/api/servers/agents/delete", { + method: "POST", + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ + server_id: serverId, + }), + }) + .then((response) => { + return response.json(); + }) + .then((data) => { + agentsTable.clearData(); + }) + .catch((error) => { + console.error("Error when deleting all agent:", error); + }); } + } else { + console.error("Agent Table is not initialized."); + } } function initAgentTable(serverId, data) { - curServerId = serverId; - agentsTable = new Tabulator("#agent-table", { - data: data, - columns: [ - { - title: "ID", - field: "agent_id", - vertAlign: "middle", - }, - { - title: "Name", - field: "name", - vertAlign: "middle", - }, - { - title: "Class", - field: "type", - vertAlign: "middle", - }, - { - title: "System prompt", - field: "sys_prompt", - vertAlign: "middle", - }, - { - title: "Model", - field: "model", - vertAlign: "middle", - formatter: function (cell, formatterParams, onRendered) { - if (cell.getData().model == null) { - return `
    None
    `; - } - return `
    [${ - cell.getData().model.model_type - }]: ${cell.getData().model.config_name}
    `; - }, - }, - { - title: "Delete", - formatter: deleteIcon, - width: 75, - hozAlign: "center", - vertAlign: "middle", - cellClick: function (e, cell) { - if ( - confirm( - `Are you sure to delete agent ${ - cell.getData().id - } ?` - ) - ) { - fetch(`/api/servers/agents/delete`, { - method: "POST", - headers: { - "Content-Type": + curServerId = serverId; + agentsTable = new Tabulator("#agent-table", { + data: data, + columns: [ + { + title: "ID", + field: "agent_id", + vertAlign: "middle", + }, + { + title: "Name", + field: "name", + vertAlign: "middle", + }, + { + title: "Class", + field: "type", + vertAlign: "middle", + }, + { + title: "System prompt", + field: "sys_prompt", + vertAlign: "middle", + }, + { + title: "Model", + field: "model", + vertAlign: "middle", + formatter: function (cell, formatterParams, onRendered) { + if (cell.getData().model == null) { + return "
    None
    "; + } + return `
    [${ + cell.getData().model.model_type + }]: ${cell.getData().model.config_name}
    `; + }, + }, + { + title: "Delete", + formatter: deleteIcon, + width: 75, + hozAlign: "center", + vertAlign: "middle", + cellClick: function (e, cell) { + if ( + confirm( + `Are you sure to delete agent ${ + cell.getData().id + } ?` + ) + ) { + fetch("/api/servers/agents/delete", { + method: "POST", + headers: { + "Content-Type": "application/json; charset=utf-8", - }, - body: JSON.stringify({ - agent_id: cell.getData().agent_id, - server_id: serverId, - }), - }) - .then((response) => { - return response.json(); - }) - .then((data) => { - cell.getRow().delete(); - }) - .catch((error) => { - console.error( - "Error when deleting agent:", - error - ); - }); - } - }, - }, - ], - layout: "fitColumns", - }); - agentsTable.on("rowClick", function (e, row) { - if (e.target.classList.contains("cell-btn")) { - return; - } - loadAgentMemory(serverId, row.getData().agent_id, row.getData().name); - }); + }, + body: JSON.stringify({ + agent_id: cell.getData().agent_id, + server_id: serverId, + }), + }) + .then((response) => { + return response.json(); + }) + .then((data) => { + cell.getRow().delete(); + }) + .catch((error) => { + console.error( + "Error when deleting agent:", + error + ); + }); + } + }, + }, + ], + layout: "fitColumns", + }); + agentsTable.on("rowClick", function (e, row) { + if (e.target.classList.contains("cell-btn")) { + return; + } + loadAgentMemory(serverId, row.getData().agent_id, row.getData().name); + }); } function loadAgentDetails(serverData) { - var serverDetail = document.getElementById("server-detail"); - var serverDetailTitle = serverDetail.querySelector(".server-section-title"); - serverDetailTitle.textContent = `Agents on (${serverData.host}:${serverData.port})[${serverData.id}]`; - serverDetail.classList.remove("collapsed"); - var agentMemory = document.getElementById("agent-memory"); - if (!agentMemory.classList.contains("collapsed")) { - agentMemory.classList.add("collapsed"); - } - getAgentTableData(serverData.id, initAgentTable); + const serverDetail = document.getElementById("server-detail"); + const serverDetailTitle = serverDetail.querySelector(".server-section-title"); + serverDetailTitle.textContent = `Agents on (${serverData.host}:${serverData.port})[${serverData.id}]`; + serverDetail.classList.remove("collapsed"); + const agentMemory = document.getElementById("agent-memory"); + if (!agentMemory.classList.contains("collapsed")) { + agentMemory.classList.add("collapsed"); + } + getAgentTableData(serverData.id, initAgentTable); } // agent memory functions function showMessage(message) { - if (messageEditor) { - messageEditor.setValue(JSON.stringify(message, null, 2)); - } else { - console.error("Message Editor is not initialized."); - } + if (messageEditor) { + messageEditor.setValue(JSON.stringify(message, null, 2)); + } else { + console.error("Message Editor is not initialized."); + } } function loadAgentMemory(serverId, agentId, agentName) { - var agentMemory = document.getElementById("agent-memory"); - var agentMemoryTitle = agentMemory.querySelector(".server-section-title"); - agentMemoryTitle.textContent = `Memory of (${agentName})[${agentId}]`; - agentMemory.classList.remove("collapsed"); - getAgentMemoryData(serverId, agentId, initAgentMemoryTable); + const agentMemory = document.getElementById("agent-memory"); + const agentMemoryTitle = agentMemory.querySelector(".server-section-title"); + agentMemoryTitle.textContent = `Memory of (${agentName})[${agentId}]`; + agentMemory.classList.remove("collapsed"); + getAgentMemoryData(serverId, agentId, initAgentMemoryTable); } function getAgentMemoryData(serverId, agentId, callback) { - fetch(`/api/servers/agents/memory`, { - method: "POST", - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - body: JSON.stringify({ - server_id: serverId, - agent_id: agentId, - }), + fetch("/api/servers/agents/memory", { + method: "POST", + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ + server_id: serverId, + agent_id: agentId, + }), + }) + .then((response) => { + if (!response.ok) { + throw new Error("Failed to fetch agent memory data"); + } + return response.json(); }) - .then((response) => { - if (!response.ok) { - throw new Error("Failed to fetch agent memory data"); - } - return response.json(); - }) - .then((data) => { - // Update the agent memory table with the fetched data - callback(agentId, data); - }); + .then((data) => { + // Update the agent memory table with the fetched data + callback(agentId, data); + }); } function initAgentMemoryTable(agentId, memoryData) { - agentMemoryTable = new Tabulator("#agent-memory-table", { - data: memoryData, - columns: [ - { - title: "Name", - field: "name", - vertAlign: "middle", - }, - { - title: "Role", - field: "role", - vertAlign: "middle", - }, - ], - layout: "fitColumns", - }); - agentMemoryTable.on("rowClick", function (e, row) { - showMessage(row.getData()); - }); - require.config({ - paths: { - vs: "https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs", + agentMemoryTable = new Tabulator("#agent-memory-table", { + data: memoryData, + columns: [ + { + title: "Name", + field: "name", + vertAlign: "middle", + }, + { + title: "Role", + field: "role", + vertAlign: "middle", + }, + ], + layout: "fitColumns", + }); + agentMemoryTable.on("rowClick", function (e, row) { + showMessage(row.getData()); + }); + require.config({ + paths: { + vs: "https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs", + }, + }); + require(["vs/editor/editor.main"], function () { + if (messageEditor) { + messageEditor.dispose(); + messageEditor = null; + } + messageEditor = monaco.editor.create( + document.getElementById("agent-memory-raw"), + { + language: "json", + theme: "vs-light", + minimap: { + enabled: false, }, - }); - require(["vs/editor/editor.main"], function () { - if (messageEditor) { - messageEditor.dispose(); - messageEditor = null; - } - messageEditor = monaco.editor.create( - document.getElementById("agent-memory-raw"), - { - language: "json", - theme: "vs-light", - minimap: { - enabled: false, - }, - wordWrap: "on", - scrollBeyondLastLine: false, - readOnly: true, - } - ); - }); + wordWrap: "on", + scrollBeyondLastLine: false, + readOnly: true, + } + ); + }); } function flushAgentMemoryTable(agentId, data) { - if (agentMemoryTable) { - agentMemoryTable.setData(data); - console.log("Flush Agent Memory Table"); - } else { - console.error("Agent Memory Table is not initialized."); - } + if (agentMemoryTable) { + agentMemoryTable.setData(data); + console.log("Flush Agent Memory Table"); + } else { + console.error("Agent Memory Table is not initialized."); + } } // Initialize the server page with a table of servers function initializeServerPage() { - // init servers - console.log("init server manager script"); - getServerTableData(initServerTable); - let serverflushBtn = document.getElementById("flush-server-btn"); - serverflushBtn.onclick = function () { - getServerTableData(flushServerTable); - }; - let deleteDeadServerBtn = document.getElementById("delete-dead-server-btn"); - deleteDeadServerBtn.onclick = deleteDeadServer; - let agentflushBtn = document.getElementById("flush-agent-btn"); - agentflushBtn.onclick = function () { - let serverId = curServerId; - getAgentTableData(serverId, flushAgentTable); - }; - let deleteAllAgentBtn = document.getElementById("delete-all-agent-btn"); - deleteAllAgentBtn.onclick = deleteAllAgent; - let memoryflushBtn = document.getElementById("flush-memory-btn"); - memoryflushBtn.onclick = flushAgentMemoryTable; - window.addEventListener("resize", () => { - if (messageEditor) { - messageEditor.layout(); - } - }); + // init servers + console.log("init server manager script"); + getServerTableData(initServerTable); + const serverflushBtn = document.getElementById("flush-server-btn"); + serverflushBtn.onclick = function () { + getServerTableData(flushServerTable); + }; + const deleteDeadServerBtn = document.getElementById("delete-dead-server-btn"); + deleteDeadServerBtn.onclick = deleteDeadServer; + const agentflushBtn = document.getElementById("flush-agent-btn"); + agentflushBtn.onclick = function () { + const serverId = curServerId; + getAgentTableData(serverId, flushAgentTable); + }; + const deleteAllAgentBtn = document.getElementById("delete-all-agent-btn"); + deleteAllAgentBtn.onclick = deleteAllAgent; + const memoryflushBtn = document.getElementById("flush-memory-btn"); + memoryflushBtn.onclick = flushAgentMemoryTable; + window.addEventListener("resize", () => { + if (messageEditor) { + messageEditor.layout(); + } + }); } diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 4995b63d0..32e6381db 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -11,3232 +11,3231 @@ let accumulatedImportData; let descriptionStep; -let nameToHtmlFile = { - 'welcome': 'welcome.html', - 'dashscope_chat': 'model-dashscope-chat.html', - 'openai_chat': 'model-openai-chat.html', - 'post_api_chat': 'model-post-api-chat.html', - 'post_api_dall_e': 'model-post-api-dall-e.html', - 'dashscope_image_synthesis': 'model-wanx.html', - 'Message': 'message-msg.html', - 'DialogAgent': 'agent-dialogagent.html', - 'UserAgent': 'agent-useragent.html', - 'TextToImageAgent': 'agent-texttoimageagent.html', - 'DictDialogAgent': 'agent-dictdialogagent.html', - 'ReActAgent': 'agent-reactagent.html', - 'Placeholder': 'pipeline-placeholder.html', - 'MsgHub': 'pipeline-msghub.html', - 'SequentialPipeline': 'pipeline-sequentialpipeline.html', - 'ForLoopPipeline': 'pipeline-forlooppipeline.html', - 'WhileLoopPipeline': 'pipeline-whilelooppipeline.html', - 'IfElsePipeline': 'pipeline-ifelsepipeline.html', - 'SwitchPipeline': 'pipeline-switchpipeline.html', - 'BingSearchService': 'service-bing-search.html', - 'GoogleSearchService': 'service-google-search.html', - 'PythonService': 'service-execute-python.html', - 'ReadTextService': 'service-read-text.html', - 'WriteTextService': 'service-write-text.html', - 'Post': 'tool-post.html', - 'TextToAudioService': 'service-text-to-audio.html', - 'TextToImageService': 'service-text-to-image.html', - 'ImageComposition': 'tool-image-composition.html', - 'Code': 'tool-code.html', - // 'IF/ELSE': 'tool-if-else.html', - 'ImageMotion': 'tool-image-motion.html', - 'VideoComposition': 'tool-video-composition.html', -} +const nameToHtmlFile = { + "welcome": "welcome.html", + "dashscope_chat": "model-dashscope-chat.html", + "openai_chat": "model-openai-chat.html", + "post_api_chat": "model-post-api-chat.html", + "post_api_dall_e": "model-post-api-dall-e.html", + "dashscope_image_synthesis": "model-wanx.html", + "Message": "message-msg.html", + "DialogAgent": "agent-dialogagent.html", + "UserAgent": "agent-useragent.html", + "TextToImageAgent": "agent-texttoimageagent.html", + "DictDialogAgent": "agent-dictdialogagent.html", + "ReActAgent": "agent-reactagent.html", + "Placeholder": "pipeline-placeholder.html", + "MsgHub": "pipeline-msghub.html", + "SequentialPipeline": "pipeline-sequentialpipeline.html", + "ForLoopPipeline": "pipeline-forlooppipeline.html", + "WhileLoopPipeline": "pipeline-whilelooppipeline.html", + "IfElsePipeline": "pipeline-ifelsepipeline.html", + "SwitchPipeline": "pipeline-switchpipeline.html", + "BingSearchService": "service-bing-search.html", + "GoogleSearchService": "service-google-search.html", + "PythonService": "service-execute-python.html", + "ReadTextService": "service-read-text.html", + "WriteTextService": "service-write-text.html", + "Post": "tool-post.html", + "TextToAudioService": "service-text-to-audio.html", + "TextToImageService": "service-text-to-image.html", + "ImageComposition": "tool-image-composition.html", + "Code": "tool-code.html", + // 'IF/ELSE': 'tool-if-else.html', + "ImageMotion": "tool-image-motion.html", + "VideoComposition": "tool-video-composition.html", +}; const ModelNames48k = [ - 'sambert-zhinan-v1', - "sambert-zhiqi-v1", - "sambert-zhichu-v1", - "sambert-zhide-v1", - "sambert-zhijia-v1", - "sambert-zhiru-v1", - "sambert-zhiqian-v1", - "sambert-zhixiang-v1", - "sambert-zhiwei-v1", -] + "sambert-zhinan-v1", + "sambert-zhiqi-v1", + "sambert-zhichu-v1", + "sambert-zhide-v1", + "sambert-zhijia-v1", + "sambert-zhiru-v1", + "sambert-zhiqian-v1", + "sambert-zhixiang-v1", + "sambert-zhiwei-v1", +]; // Cache the loaded html files -let htmlCache = {}; +const htmlCache = {}; // When clicking the sidebar item, it will expand/collapse the next content function onClickSidebarSubItem(element) { - element.classList.toggle("active"); - let content = element.nextElementSibling; - if (content.style.display === "block") { - content.style.display = "none"; - } else { - content.style.display = "block"; - } + element.classList.toggle("active"); + const content = element.nextElementSibling; + if (content.style.display === "block") { + content.style.display = "none"; + } else { + content.style.display = "block"; + } } // Load html source code dynamically async function fetchHtml(fileName) { - try { - let filePath = 'static/html-drag-components/' + fileName; - const response = await fetch(filePath); - if (!response.ok) { - throw new Error('Fail to load ' + filePath); - } - return await response.text(); - } catch (error) { - return error; + try { + const filePath = "static/html-drag-components/" + fileName; + const response = await fetch(filePath); + if (!response.ok) { + throw new Error("Fail to load " + filePath); } + return await response.text(); + } catch (error) { + return error; + } } async function initializeWorkstationPage() { - console.log("Initialize Workstation Page") - // Initialize the Drawflow editor - let id = document.getElementById("drawflow"); - editor = new Drawflow(id); - editor.reroute = true; - editor.createCurvature = function createCurvature(start_pos_x, start_pos_y, end_pos_x, end_pos_y, curvature_value, type) { - var line_x = start_pos_x; - var line_y = start_pos_y; - var x = end_pos_x; - var y = end_pos_y; - var curvature = curvature_value; - //type openclose open close other - switch (type) { - case 'open': - if (start_pos_x >= end_pos_x) { - var hx1 = line_x + Math.abs(x - line_x) * curvature; - var hx2 = x - Math.abs(x - line_x) * (curvature * -1); - } else { - var hx1 = line_x + Math.abs(x - line_x) * curvature; - var hx2 = x - Math.abs(x - line_x) * curvature; - } - return ' M ' + line_x + ' ' + line_y + ' C ' + hx1 + ' ' + line_y + ' ' + hx2 + ' ' + y + ' ' + x + ' ' + y; - - case 'close': - if (start_pos_x >= end_pos_x) { - var hx1 = line_x + Math.abs(x - line_x) * (curvature * -1); - var hx2 = x - Math.abs(x - line_x) * curvature; - } else { - var hx1 = line_x + Math.abs(x - line_x) * curvature; - var hx2 = x - Math.abs(x - line_x) * curvature; - } //M0 75H10L5 80L0 75Z - return ' M ' + line_x + ' ' + line_y + ' C ' + hx1 + ' ' + line_y + ' ' + hx2 + ' ' + y + ' ' + x + ' ' + y + ' M ' + (x - 11) + ' ' + y + ' L' + (x - 20) + ' ' + (y - 5) + ' L' + (x - 20) + ' ' + (y + 5) + 'Z'; - - case 'other': - if (start_pos_x >= end_pos_x) { - var hx1 = line_x + Math.abs(x - line_x) * (curvature * -1); - var hx2 = x - Math.abs(x - line_x) * (curvature * -1); - } else { - var hx1 = line_x + Math.abs(x - line_x) * curvature; - var hx2 = x - Math.abs(x - line_x) * curvature; - } - return ' M ' + line_x + ' ' + line_y + ' C ' + hx1 + ' ' + line_y + ' ' + hx2 + ' ' + y + ' ' + x + ' ' + y; - - default: - var hx1 = line_x + Math.abs(x - line_x) * curvature; - var hx2 = x - Math.abs(x - line_x) * curvature; - - return ' M ' + line_x + ' ' + line_y + ' C ' + hx1 + ' ' + line_y + ' ' + hx2 + ' ' + y + ' ' + x + ' ' + y + ' M ' + (x - 11) + ' ' + y + ' L' + (x - 20) + ' ' + (y - 5) + ' L' + (x - 20) + ' ' + (y + 5) + 'Z'; - } - } - editor.start(); - editor.zoom_out(); - - let welcome = await fetchHtml('welcome.html'); - const welcomeID = editor.addNode('welcome', 0, 0, 50, 50, 'welcome', {}, welcome); - setupNodeListeners(welcomeID); - - editor.on('nodeCreated', function (id) { - console.log("Node created " + id); - disableButtons(); - makeNodeTop(id); - setupNodeListeners(id); - setupNodeCopyListens(id); - addEventListenersToNumberInputs(id); - setupTextInputListeners(id); - reloadi18n(); - }) - - editor.on('nodeRemoved', function (id) { - console.log("Node removed " + id); - disableButtons(); - Object.keys(editor.drawflow.drawflow[editor.module].data).forEach(nodeKey => { - var node = editor.drawflow.drawflow[editor.module].data[nodeKey]; - var nodeData = + console.log("Initialize Workstation Page"); + // Initialize the Drawflow editor + const id = document.getElementById("drawflow"); + editor = new Drawflow(id); + editor.reroute = true; + editor.createCurvature = function createCurvature(start_pos_x, start_pos_y, end_pos_x, end_pos_y, curvature_value, type) { + const line_x = start_pos_x; + const line_y = start_pos_y; + const x = end_pos_x; + const y = end_pos_y; + const curvature = curvature_value; + //type openclose open close other + switch (type) { + case "open": + if (start_pos_x >= end_pos_x) { + var hx1 = line_x + Math.abs(x - line_x) * curvature; + var hx2 = x - Math.abs(x - line_x) * (curvature * -1); + } else { + var hx1 = line_x + Math.abs(x - line_x) * curvature; + var hx2 = x - Math.abs(x - line_x) * curvature; + } + return " M " + line_x + " " + line_y + " C " + hx1 + " " + line_y + " " + hx2 + " " + y + " " + x + " " + y; + + case "close": + if (start_pos_x >= end_pos_x) { + var hx1 = line_x + Math.abs(x - line_x) * (curvature * -1); + var hx2 = x - Math.abs(x - line_x) * curvature; + } else { + var hx1 = line_x + Math.abs(x - line_x) * curvature; + var hx2 = x - Math.abs(x - line_x) * curvature; + } //M0 75H10L5 80L0 75Z + return " M " + line_x + " " + line_y + " C " + hx1 + " " + line_y + " " + hx2 + " " + y + " " + x + " " + y + " M " + (x - 11) + " " + y + " L" + (x - 20) + " " + (y - 5) + " L" + (x - 20) + " " + (y + 5) + "Z"; + + case "other": + if (start_pos_x >= end_pos_x) { + var hx1 = line_x + Math.abs(x - line_x) * (curvature * -1); + var hx2 = x - Math.abs(x - line_x) * (curvature * -1); + } else { + var hx1 = line_x + Math.abs(x - line_x) * curvature; + var hx2 = x - Math.abs(x - line_x) * curvature; + } + return " M " + line_x + " " + line_y + " C " + hx1 + " " + line_y + " " + hx2 + " " + y + " " + x + " " + y; + + default: + var hx1 = line_x + Math.abs(x - line_x) * curvature; + var hx2 = x - Math.abs(x - line_x) * curvature; + + return " M " + line_x + " " + line_y + " C " + hx1 + " " + line_y + " " + hx2 + " " + y + " " + x + " " + y + " M " + (x - 11) + " " + y + " L" + (x - 20) + " " + (y - 5) + " L" + (x - 20) + " " + (y + 5) + "Z"; + } + }; + editor.start(); + editor.zoom_out(); + + const welcome = await fetchHtml("welcome.html"); + const welcomeID = editor.addNode("welcome", 0, 0, 50, 50, "welcome", {}, welcome); + setupNodeListeners(welcomeID); + + editor.on("nodeCreated", function (id) { + console.log("Node created " + id); + disableButtons(); + makeNodeTop(id); + setupNodeListeners(id); + setupNodeCopyListens(id); + addEventListenersToNumberInputs(id); + setupTextInputListeners(id); + reloadi18n(); + }); + + editor.on("nodeRemoved", function (id) { + console.log("Node removed " + id); + disableButtons(); + Object.keys(editor.drawflow.drawflow[editor.module].data).forEach(nodeKey => { + const node = editor.drawflow.drawflow[editor.module].data[nodeKey]; + const nodeData = editor.drawflow.drawflow[editor.module].data[nodeKey].data; - console.log("nodeKey", nodeKey); - console.log("node", node); - console.log("nodeData", nodeData); - console.log("id", id); - - if (nodeData && nodeData.copies) { - console.log("Array.isArray(nodeData.copies)", Array.isArray(nodeData.copies)) - if (nodeData.copies.includes(id)) { - console.log("nodeData.copies", nodeData.copies); - console.log("nodeData.copies.includes(id)", - nodeData.copies.includes(id)); - var index = nodeData.copies.indexOf(id); - console.log("index", index); - if (index > -1) { - nodeData.copies.splice(index, 1); - editor.updateNodeDataFromId(nodeKey, nodeData); - } - } - } - }) - }) - - editor.on('nodeSelected', function (id) { - console.log("Node selected " + id); - makeNodeTop(id); - }) - - editor.on('moduleCreated', function (name) { - console.log("Module Created " + name); - }) - - editor.on('moduleChanged', function (name) { - console.log("Module Changed " + name); - }) - - editor.on('connectionCreated', function (connection) { - console.log('Connection created'); - console.log(connection); - disableButtons(); - }) - - editor.on('connectionRemoved', function (connection) { - console.log('Connection removed'); - console.log(connection); - disableButtons(); - }) - - editor.on('mouseMove', function (position) { - // console.log('Position mouse x:' + position.x + ' y:' + position.y); - }) - - editor.on('zoom', function (zoom) { - console.log('Zoom level ' + zoom); - }) - - editor.on('translate', function (position) { - console.log('Translate x:' + position.x + ' y:' + position.y); - }) - - editor.on('addReroute', function (id) { - console.log("Reroute added " + id); - }) - - editor.on('removeReroute', function (id) { - console.log("Reroute removed " + id); - }) - - editor.selectNode = function (id) { - if (this.node_selected != null) { - this.node_selected.classList.remove("selected"); - if (this.node_selected !== this.ele_selected) { - this.dispatch('nodeUnselected', true); - } - } - const element = document.querySelector(`#node-${id}`); - this.ele_selected = element; - this.node_selected = element; - this.node_selected.classList.add("selected"); - if (this.node_selected !== this.ele_selected) { - this.node_selected = element; - this.node_selected.classList.add('selected'); - this.dispatch('nodeSelected', this.ele_selected.id.slice(5)); - } - console.log(id) - } - - let last_x = 0; - let last_y = 0; - let dragElementHover = null; - - editor.on("mouseMove", ({x, y}) => { - const hoverEles = document.elementsFromPoint(x, y); - const nextGroup = hoverEles.find(ele => ele.classList.contains('GROUP') && (!editor.node_selected || ele.id !== editor.node_selected.id)); - - if (nextGroup) { - if (dragElementHover !== nextGroup) { - if (dragElementHover) { - dragElementHover.classList.remove("hover-drop"); - } - dragElementHover = nextGroup; - dragElementHover.classList.add("hover-drop"); - } - } else if (dragElementHover) { - dragElementHover.classList.remove("hover-drop"); - dragElementHover = null; - } - - if (editor.node_selected && editor.drag) { - const selectedNodeId = editor.node_selected.id.slice(5); - var dx = Math.ceil((last_x - x) * editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)); - var dy = Math.ceil((last_y - y) * editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)); - - if (editor.node_selected.classList.contains("GROUP")) { - moveGroupNodes(selectedNodeId, -dx, -dy); - } - } else { - if (dragElementHover) { - dragElementHover.classList.remove("hover-drop"); - dragElementHover = null; - } - } - - last_x = x; - last_y = y; + console.log("nodeKey", nodeKey); + console.log("node", node); + console.log("nodeData", nodeData); + console.log("id", id); + + if (nodeData && nodeData.copies) { + console.log("Array.isArray(nodeData.copies)", Array.isArray(nodeData.copies)); + if (nodeData.copies.includes(id)) { + console.log("nodeData.copies", nodeData.copies); + console.log("nodeData.copies.includes(id)", + nodeData.copies.includes(id)); + const index = nodeData.copies.indexOf(id); + console.log("index", index); + if (index > -1) { + nodeData.copies.splice(index, 1); + editor.updateNodeDataFromId(nodeKey, nodeData); + } + } + } }); + }); + + editor.on("nodeSelected", function (id) { + console.log("Node selected " + id); + makeNodeTop(id); + }); + + editor.on("moduleCreated", function (name) { + console.log("Module Created " + name); + }); + + editor.on("moduleChanged", function (name) { + console.log("Module Changed " + name); + }); + + editor.on("connectionCreated", function (connection) { + console.log("Connection created"); + console.log(connection); + disableButtons(); + }); + + editor.on("connectionRemoved", function (connection) { + console.log("Connection removed"); + console.log(connection); + disableButtons(); + }); + + editor.on("mouseMove", function (position) { + // console.log('Position mouse x:' + position.x + ' y:' + position.y); + }); + + editor.on("zoom", function (zoom) { + console.log("Zoom level " + zoom); + }); + + editor.on("translate", function (position) { + console.log("Translate x:" + position.x + " y:" + position.y); + }); + + editor.on("addReroute", function (id) { + console.log("Reroute added " + id); + }); + + editor.on("removeReroute", function (id) { + console.log("Reroute removed " + id); + }); + + editor.selectNode = function (id) { + if (this.node_selected != null) { + this.node_selected.classList.remove("selected"); + if (this.node_selected !== this.ele_selected) { + this.dispatch("nodeUnselected", true); + } + } + const element = document.querySelector(`#node-${id}`); + this.ele_selected = element; + this.node_selected = element; + this.node_selected.classList.add("selected"); + if (this.node_selected !== this.ele_selected) { + this.node_selected = element; + this.node_selected.classList.add("selected"); + this.dispatch("nodeSelected", this.ele_selected.id.slice(5)); + } + console.log(id); + }; + + let last_x = 0; + let last_y = 0; + let dragElementHover = null; + + editor.on("mouseMove", ({x, y}) => { + const hoverEles = document.elementsFromPoint(x, y); + const nextGroup = hoverEles.find(ele => ele.classList.contains("GROUP") && (!editor.node_selected || ele.id !== editor.node_selected.id)); + + if (nextGroup) { + if (dragElementHover !== nextGroup) { + if (dragElementHover) { + dragElementHover.classList.remove("hover-drop"); + } + dragElementHover = nextGroup; + dragElementHover.classList.add("hover-drop"); + } + } else if (dragElementHover) { + dragElementHover.classList.remove("hover-drop"); + dragElementHover = null; + } + + if (editor.node_selected && editor.drag) { + const selectedNodeId = editor.node_selected.id.slice(5); + const dx = Math.ceil((last_x - x) * editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)); + const dy = Math.ceil((last_y - y) * editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)); + + if (editor.node_selected.classList.contains("GROUP")) { + moveGroupNodes(selectedNodeId, -dx, -dy); + } + } else { + if (dragElementHover) { + dragElementHover.classList.remove("hover-drop"); + dragElementHover = null; + } + } + + last_x = x; + last_y = y; + }); + + editor.on("nodeMoved", (id) => { + const dragNode = id; + if (dragElementHover !== null) { + const dropNode = dragElementHover.id.slice(5); + if (dragNode !== dropNode) { + removeOfGroupNode(dragNode); + dragElementHover.classList.remove("hover-drop"); + const dropNodeInfo = editor.getNodeFromId(dropNode); + const dropNodeInfoData = dropNodeInfo.data; + if (dropNodeInfoData.elements.indexOf(dragNode) === -1) { + dropNodeInfoData.elements.push(dragNode); + editor.updateNodeDataFromId(dropNode, dropNodeInfoData); + // remove connections + editor.removeConnectionNodeId("node-" + id); + // Hide the ports when node is inside the group + togglePortsDisplay(dragNode, "none"); + const dragNodeData = editor.getNodeFromId(dragNode); + if (dragNodeData.class !== "GROUP") { + collapseNode(dragNode); + } + } + } + dragElementHover = null; + } else { + // If the node is moved outside of any group, show the ports + togglePortsDisplay(dragNode, ""); + removeOfGroupNode(dragNode); + } + disableButtons(); + }); - editor.on("nodeMoved", (id) => { - const dragNode = id; - if (dragElementHover !== null) { - const dropNode = dragElementHover.id.slice(5); - if (dragNode !== dropNode) { - removeOfGroupNode(dragNode); - dragElementHover.classList.remove("hover-drop"); - const dropNodeInfo = editor.getNodeFromId(dropNode); - const dropNodeInfoData = dropNodeInfo.data; - if (dropNodeInfoData.elements.indexOf(dragNode) === -1) { - dropNodeInfoData.elements.push(dragNode); - editor.updateNodeDataFromId(dropNode, dropNodeInfoData); - // remove connections - editor.removeConnectionNodeId('node-' + id); - // Hide the ports when node is inside the group - togglePortsDisplay(dragNode, 'none'); - const dragNodeData = editor.getNodeFromId(dragNode); - if (dragNodeData.class !== "GROUP") { - collapseNode(dragNode); - } - } - } - dragElementHover = null; - } else { - // If the node is moved outside of any group, show the ports - togglePortsDisplay(dragNode, ''); - removeOfGroupNode(dragNode); - } - disableButtons(); - }) - - editor.on("nodeRemoved", (id) => { - removeOfGroupNode(id); - }); + editor.on("nodeRemoved", (id) => { + removeOfGroupNode(id); + }); - /* DRAG EVENT */ + /* DRAG EVENT */ - /* Mouse and Touch Actions */ + /* Mouse and Touch Actions */ - var elements = document.getElementsByClassName('workstation-sidebar-dragitem'); - for (var i = 0; i < elements.length; i++) { - elements[i].addEventListener('touchend', drop, false); - elements[i].addEventListener('touchmove', positionMobile, false); - elements[i].addEventListener('touchstart', drag, false); - } + const elements = document.getElementsByClassName("workstation-sidebar-dragitem"); + for (let i = 0; i < elements.length; i++) { + elements[i].addEventListener("touchend", drop, false); + elements[i].addEventListener("touchmove", positionMobile, false); + elements[i].addEventListener("touchstart", drag, false); + } - mobile_item_selec = ''; - mobile_last_move = null; + mobile_item_selec = ""; + mobile_last_move = null; - importQueue = []; - currentImportIndex = 0; - accumulatedImportData = {}; - descriptionStep = []; - console.log("importQueue", importQueue) + importQueue = []; + currentImportIndex = 0; + accumulatedImportData = {}; + descriptionStep = []; + console.log("importQueue", importQueue); - document.getElementById('surveyButton').addEventListener('click', function () { - window.open('https://survey.aliyun.com/apps/zhiliao/vgpTppn22', '_blank'); - }); + document.getElementById("surveyButton").addEventListener("click", function () { + window.open("https://survey.aliyun.com/apps/zhiliao/vgpTppn22", "_blank"); + }); - document.getElementById('surveyClose').addEventListener('click', function () { - hideSurveyModal(); - }); + document.getElementById("surveyClose").addEventListener("click", function () { + hideSurveyModal(); + }); - setTimeout(showSurveyModal, 30000); + setTimeout(showSurveyModal, 30000); - if (!localStorage.getItem('firstGuide')) { - startGuide(); - } - reloadi18n(); + if (!localStorage.getItem("firstGuide")) { + startGuide(); + } + reloadi18n(); } function makeNodeTop(id) { - const node = document.getElementById(`node-${id}`); - const nodeInfo = editor.getNodeFromId(id); + const node = document.getElementById(`node-${id}`); + const nodeInfo = editor.getNodeFromId(id); - if (nodeInfo) { - console.log("currentZIndex: " + currentZIndex); - currentZIndex += 1; - node.style.zIndex = currentZIndex; + if (nodeInfo) { + console.log("currentZIndex: " + currentZIndex); + currentZIndex += 1; + node.style.zIndex = currentZIndex; - if (nodeInfo.class === 'GROUP') { - nodeInfo.data.elements.forEach((elementId) => { - makeNodeTop(elementId); - }); - } + if (nodeInfo.class === "GROUP") { + nodeInfo.data.elements.forEach((elementId) => { + makeNodeTop(elementId); + }); } + } } function moveGroupNodes(groupId, dx, dy) { - const groupInfo = editor.getNodeFromId(groupId); - const elements = groupInfo.data.elements || []; - elements.forEach(eleId => { - const eleNode = document.getElementById(`node-${eleId}`); - const eleNodeInfo = editor.getNodeFromId(eleId); + const groupInfo = editor.getNodeFromId(groupId); + const elements = groupInfo.data.elements || []; + elements.forEach(eleId => { + const eleNode = document.getElementById(`node-${eleId}`); + const eleNodeInfo = editor.getNodeFromId(eleId); - if (eleNode) { - const nodeData = editor.drawflow.drawflow[editor.module].data[eleId]; - const newPosX = nodeData.pos_x + dx; - const newPosY = nodeData.pos_y + dy; + if (eleNode) { + const nodeData = editor.drawflow.drawflow[editor.module].data[eleId]; + const newPosX = nodeData.pos_x + dx; + const newPosY = nodeData.pos_y + dy; - eleNode.style.left = newPosX + "px"; - eleNode.style.top = newPosY + "px"; + eleNode.style.left = newPosX + "px"; + eleNode.style.top = newPosY + "px"; - if (editor.drawflow.drawflow[editor.module] && + if (editor.drawflow.drawflow[editor.module] && editor.drawflow.drawflow[editor.module].data && editor.drawflow.drawflow[editor.module].data[eleId]) { - editor.drawflow.drawflow[editor.module].data[eleId].pos_x = newPosX; - editor.drawflow.drawflow[editor.module].data[eleId].pos_y = newPosY; - } + editor.drawflow.drawflow[editor.module].data[eleId].pos_x = newPosX; + editor.drawflow.drawflow[editor.module].data[eleId].pos_y = newPosY; + } - editor.updateConnectionNodes(`node-${eleId}`); - } + editor.updateConnectionNodes(`node-${eleId}`); + } - if (eleNodeInfo.class && eleNodeInfo.class === "GROUP") { - moveGroupNodes(eleId, dx, dy); - } - }); + if (eleNodeInfo.class && eleNodeInfo.class === "GROUP") { + moveGroupNodes(eleId, dx, dy); + } + }); } function collapseNode(nodeId) { - const nodeElement = document.getElementById(`node-${nodeId}`); - const contentBox = nodeElement.querySelector('.box'); - const toggleArrow = nodeElement.querySelector('.toggle-arrow'); + const nodeElement = document.getElementById(`node-${nodeId}`); + const contentBox = nodeElement.querySelector(".box"); + const toggleArrow = nodeElement.querySelector(".toggle-arrow"); - contentBox.classList.add('hidden'); - toggleArrow.textContent = "\u25BC"; + contentBox.classList.add("hidden"); + toggleArrow.textContent = "\u25BC"; } function togglePortsDisplay(nodeId, displayStyle) { - const nodeElement = document.querySelector(`#node-${nodeId}`); - if (nodeElement) { - const inputs = nodeElement.querySelectorAll('.inputs .input'); - const outputs = nodeElement.querySelectorAll('.outputs .output'); - inputs.forEach(input => { - input.style.display = displayStyle; - }); - outputs.forEach(output => { - output.style.display = displayStyle; - }); - } + const nodeElement = document.querySelector(`#node-${nodeId}`); + if (nodeElement) { + const inputs = nodeElement.querySelectorAll(".inputs .input"); + const outputs = nodeElement.querySelectorAll(".outputs .output"); + inputs.forEach(input => { + input.style.display = displayStyle; + }); + outputs.forEach(output => { + output.style.display = displayStyle; + }); + } } function removeOfGroupNode(id) { - Object.keys(editor.drawflow.drawflow[editor.module].data).forEach(ele => { - if (editor.drawflow.drawflow[editor.module].data[ele].class === "GROUP") { - const findIndex = editor.drawflow.drawflow[editor.module].data[ele].data.elements.indexOf(id); - if (findIndex !== -1) { - editor.drawflow.drawflow[editor.module].data[ele].data.elements.splice(findIndex, 1); - } - } - }) + Object.keys(editor.drawflow.drawflow[editor.module].data).forEach(ele => { + if (editor.drawflow.drawflow[editor.module].data[ele].class === "GROUP") { + const findIndex = editor.drawflow.drawflow[editor.module].data[ele].data.elements.indexOf(id); + if (findIndex !== -1) { + editor.drawflow.drawflow[editor.module].data[ele].data.elements.splice(findIndex, 1); + } + } + }); } function positionMobile(ev) { - mobile_last_move = ev; + mobile_last_move = ev; } function allowDrop(ev) { - ev.preventDefault(); + ev.preventDefault(); } function drag(ev) { - if (ev.type === "touchstart") { - mobile_item_selec = ev.target.closest(".workstation-sidebar-dragitem").getAttribute('data-node'); - } else { - ev.dataTransfer.setData("node", ev.target.getAttribute('data-node')); - } + if (ev.type === "touchstart") { + mobile_item_selec = ev.target.closest(".workstation-sidebar-dragitem").getAttribute("data-node"); + } else { + ev.dataTransfer.setData("node", ev.target.getAttribute("data-node")); + } } function drop(ev) { - if (ev.type === "touchend") { - var parentdrawflow = document.elementFromPoint(mobile_last_move.touches[0].clientX, mobile_last_move.touches[0].clientY).closest("#drawflow"); - if (parentdrawflow != null) { - addNodeToDrawFlow(mobile_item_selec, mobile_last_move.touches[0].clientX, mobile_last_move.touches[0].clientY); - } - mobile_item_selec = ''; - } else { - ev.preventDefault(); - var data = ev.dataTransfer.getData("node"); - addNodeToDrawFlow(data, ev.clientX, ev.clientY); + if (ev.type === "touchend") { + const parentdrawflow = document.elementFromPoint(mobile_last_move.touches[0].clientX, mobile_last_move.touches[0].clientY).closest("#drawflow"); + if (parentdrawflow != null) { + addNodeToDrawFlow(mobile_item_selec, mobile_last_move.touches[0].clientX, mobile_last_move.touches[0].clientY); } + mobile_item_selec = ""; + } else { + ev.preventDefault(); + const data = ev.dataTransfer.getData("node"); + addNodeToDrawFlow(data, ev.clientX, ev.clientY); + } } async function addNodeToDrawFlow(name, pos_x, pos_y) { - if (editor.editor_mode === 'fixed') { - return false; - } - pos_x = Math.ceil(pos_x * (editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)) - (editor.precanvas.getBoundingClientRect().x * (editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)))); - pos_y = Math.ceil(pos_y * (editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)) - (editor.precanvas.getBoundingClientRect().y * (editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)))); - - var htmlSourceCode = await fetchHtmlSourceCodeByName(name); - - switch (name) { - // Workflow-Model - case 'dashscope_chat': - editor.addNode('dashscope_chat', 0, 0, pos_x, - pos_y, - 'dashscope_chat', { - "args": + if (editor.editor_mode === "fixed") { + return false; + } + pos_x = Math.ceil(pos_x * (editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)) - (editor.precanvas.getBoundingClientRect().x * (editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)))); + pos_y = Math.ceil(pos_y * (editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)) - (editor.precanvas.getBoundingClientRect().y * (editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)))); + + const htmlSourceCode = await fetchHtmlSourceCodeByName(name); + + switch (name) { + // Workflow-Model + case "dashscope_chat": + editor.addNode("dashscope_chat", 0, 0, pos_x, + pos_y, + "dashscope_chat", { + "args": { - "config_name": '', - "model_name": '', - "api_key": '', - "temperature": 0.0, - "seed": 0, - "model_type": 'dashscope_chat' + "config_name": "", + "model_name": "", + "api_key": "", + "temperature": 0.0, + "seed": 0, + "model_type": "dashscope_chat" } - }, - htmlSourceCode); - break; - - case 'openai_chat': - editor.addNode('openai_chat', 0, 0, pos_x, - pos_y, - 'openai_chat', { - "args": + }, + htmlSourceCode); + break; + + case "openai_chat": + editor.addNode("openai_chat", 0, 0, pos_x, + pos_y, + "openai_chat", { + "args": { - "config_name": '', - "model_name": '', - "api_key": '', - "temperature": 0.0, - "seed": 0, - "model_type": 'openai_chat' + "config_name": "", + "model_name": "", + "api_key": "", + "temperature": 0.0, + "seed": 0, + "model_type": "openai_chat" } - }, - htmlSourceCode); - break; - - case 'post_api_chat': - editor.addNode('post_api_chat', 0, 0, pos_x, pos_y, - 'post_api_chat', { - "args": + }, + htmlSourceCode); + break; + + case "post_api_chat": + editor.addNode("post_api_chat", 0, 0, pos_x, pos_y, + "post_api_chat", { + "args": { - "config_name": '', - "api_url": '', - "headers": { - "content_type": 'application/json', - "authorization": '', - }, - "json_args": { - "model": '', - "temperature": 0.0, - "seed": 0, - }, - "model_type": 'post_api_chat', - "messages_key": 'messages' + "config_name": "", + "api_url": "", + "headers": { + "content_type": "application/json", + "authorization": "", + }, + "json_args": { + "model": "", + "temperature": 0.0, + "seed": 0, + }, + "model_type": "post_api_chat", + "messages_key": "messages" } - }, - htmlSourceCode); - break; - - case 'post_api_dall_e': - editor.addNode('post_api_dall_e', 0, - 0, - pos_x, pos_y, - 'post_api_dall_e', { - "args": + }, + htmlSourceCode); + break; + + case "post_api_dall_e": + editor.addNode("post_api_dall_e", 0, + 0, + pos_x, pos_y, + "post_api_dall_e", { + "args": { - "config_name": '', - "api_url": '', - "headers": { - "content_type": 'application/json', - "authorization": '', - }, - "json_args": { - "model": '', - "n": 1, - "size": "", - "temperature": 0.0, - "seed": 0, - }, - "model_type": 'post_api_dall_e', - "messages_key": 'prompt' + "config_name": "", + "api_url": "", + "headers": { + "content_type": "application/json", + "authorization": "", + }, + "json_args": { + "model": "", + "n": 1, + "size": "", + "temperature": 0.0, + "seed": 0, + }, + "model_type": "post_api_dall_e", + "messages_key": "prompt" } - }, - htmlSourceCode); - break; - - case 'dashscope_image_synthesis': - editor.addNode('dashscope_image_synthesis', 0, - 0, - pos_x, pos_y, - 'dashscope_image_synthesis', { - "args": + }, + htmlSourceCode); + break; + + case "dashscope_image_synthesis": + editor.addNode("dashscope_image_synthesis", 0, + 0, + pos_x, pos_y, + "dashscope_image_synthesis", { + "args": { - "config_name": '', - "model_name": '', - "generate_args": { - "n": 1, - "size": "", - "temperature": 0.0, - "seed": 0, - }, - "model_type": 'dashscope_image_synthesis' + "config_name": "", + "model_name": "", + "generate_args": { + "n": 1, + "size": "", + "temperature": 0.0, + "seed": 0, + }, + "model_type": "dashscope_image_synthesis" } - }, htmlSourceCode); - break; - - // Message - case 'Message': - editor.addNode('Message', 1, 1, pos_x, - pos_y, 'Message', { - "args": + }, htmlSourceCode); + break; + + // Message + case "Message": + editor.addNode("Message", 1, 1, pos_x, + pos_y, "Message", { + "args": { - "name": '', - "content": '', - "url": '' - } - }, htmlSourceCode); - break; - - // Workflow-Agent - case 'DialogAgent': - const DialogAgentID = editor.addNode('DialogAgent', 1, 1, - pos_x, - pos_y, - 'DialogAgent', { - "args": { - "name": '', - "sys_prompt": '', - "model_config_name": '' - } - }, htmlSourceCode); - var nodeElement = document.querySelector(`#node-${DialogAgentID} .node-id`); - if (nodeElement) { - nodeElement.textContent = DialogAgentID; - } - break; - - case 'UserAgent': - const UserAgentID = editor.addNode('UserAgent', 1, 1, pos_x, - pos_y, 'UserAgent', { - "args": {"name": 'User'} - }, htmlSourceCode); - var nodeElement = document.querySelector(`#node-${UserAgentID} .node-id`); - if (nodeElement) { - nodeElement.textContent = UserAgentID; - } - break; - - case 'TextToImageAgent': - const TextToImageAgentID = - editor.addNode('TextToImageAgent', 1, - 1, pos_x, pos_y, - 'TextToImageAgent', { - "args": { - "name": '', - "model_config_name": '' - } - }, htmlSourceCode); - var nodeElement = document.querySelector(`#node-${TextToImageAgentID} .node-id`); - if (nodeElement) { - nodeElement.textContent = TextToImageAgentID; - } - break; - - case 'DictDialogAgent': - const DictDialogAgentID = editor.addNode('DictDialogAgent', 1, - 1, pos_x, pos_y, - 'DictDialogAgent', { - "args": { - "name": '', - "sys_prompt": '', - "model_config_name": '', - "parse_func": '', - "fault_handler": '', - "max_retries": 3, - } - }, htmlSourceCode); - var nodeElement = document.querySelector(`#node-${DictDialogAgentID} .node-id`); - if (nodeElement) { - nodeElement.textContent = DictDialogAgentID; - } - break; - - case 'ReActAgent': - const ReActAgentID = editor.addNode('ReActAgent', 1, 1, pos_x, pos_y, - 'GROUP', { - elements: [], - "args": { - "name": '', - "sys_prompt": '', - "model_config_name": '', - "max_iters": 10, - "verbose": '', - } - }, htmlSourceCode); - var nodeElement = document.querySelector(`#node-${ReActAgentID} .node-id`); - if (nodeElement) { - nodeElement.textContent = ReActAgentID; - } - break; - - // Workflow-Pipeline - case 'Placeholder': - editor.addNode('Placeholder', 1, 1, - pos_x, pos_y, 'Placeholder', {}, htmlSourceCode); - break; - - case 'MsgHub': - editor.addNode('MsgHub', 1, 1, pos_x, pos_y, - 'GROUP', { - elements: [], - "args": { - "announcement": { - "name": '', - "content": '' + "name": "", + "content": "", + "url": "" } - } - }, htmlSourceCode); - break; - - case 'SequentialPipeline': - editor.addNode('SequentialPipeline', 1, 1, pos_x, pos_y, - 'GROUP', {elements: []}, htmlSourceCode); - break; - - case 'ForLoopPipeline': - editor.addNode('ForLoopPipeline', 1, 1, pos_x, pos_y, - 'GROUP', { - elements: [], - "args": { - "max_loop": 3, - "condition_op": "", - "target_value": "", - } - }, htmlSourceCode); - break; - - case 'WhileLoopPipeline': - editor.addNode('WhileLoopPipeline', 1, 1, pos_x, pos_y, - 'GROUP', { - elements: [], - "args": { - "condition_func": '' - } - }, htmlSourceCode); - break; - - case 'IfElsePipeline': - editor.addNode('IfElsePipeline', 1, - 1, pos_x, pos_y, 'GROUP', { - elements: [], args: { - "condition_op": "", - "target_value": "", - } - }, htmlSourceCode); - break; - - case 'SwitchPipeline': - const SwitchPipelineID = editor.addNode('SwitchPipeline', 1, 1, pos_x, pos_y, 'GROUP', { - elements: [], args: { - "condition_func": '', - "cases": [], - } - }, htmlSourceCode); - break; - - // Workflow-Service - case 'BingSearchService': - editor.addNode('BingSearchService', 0, 0, - pos_x, pos_y, 'BingSearchService', { - "args": { - "api_key": "", - "num_results": 3, - } - }, htmlSourceCode); - break; - - case 'GoogleSearchService': - editor.addNode('GoogleSearchService', 0, 0, - pos_x, pos_y, 'GoogleSearchService', { - "args": { - "api_key": "", - "cse_id": "", - "num_results": 3, - } - }, htmlSourceCode); - break; - - case 'PythonService': - editor.addNode('PythonService', 0, 0, - pos_x, pos_y, 'PythonService', {}, htmlSourceCode); - break; - - case 'ReadTextService': - editor.addNode('ReadTextService', 0, 0, - pos_x, pos_y, 'ReadTextService', {}, htmlSourceCode); - break; - - case 'WriteTextService': - editor.addNode('WriteTextService', 0, 0, - pos_x, pos_y, 'WriteTextService', {}, htmlSourceCode); - break; - - case 'TextToAudioService': - const TextToAudioServiceID = editor.addNode('TextToAudioService', 0, 0, - pos_x, pos_y, 'TextToAudioService', { - "args": { - "model": "", - "api_key": "", - "sample_rate": "" - } - }, htmlSourceCode); - break; - case 'TextToImageService': - editor.addNode('TextToImageService', 0, 0, - pos_x, pos_y, 'TextToImageService', { - "args": { - "model": "", - "api_key": "", - "n": 1, - "size": "" - } - }, htmlSourceCode); - break; - - case 'ImageComposition': - editor.addNode('ImageComposition', 1, 1, - pos_x, pos_y, 'ImageComposition', { - "args": { - "titles": "", - "output_path": "", - "row": 1, - "column": 1, - "spacing": 10, - "title_height": 100, - "font_name": "PingFang", - } - }, htmlSourceCode); - break; - case 'Code': - const CodeID = editor.addNode('Code', 1, 1, - pos_x, pos_y, 'Code', { - "args": { - "code": "def function(msg1: Msg) -> Msg:\n content1 = msg1.get(\"content\", \"\")\n return {\n \"role\": \"assistant\",\n \"content\": content1,\n \"name\": \"function\",\n }" - } - }, htmlSourceCode); - break; - // case 'IF/ELSE': - // const IfelseID = editor.addNode('IF/ELSE', 1, 2, - // pos_x, pos_y, 'IF/ELSE', { - // "args": { - // "condition_op": "", - // "target_value": "", - // } - // }, htmlSourceCode); - // break; - - case 'ImageMotion': - editor.addNode('ImageMotion', 1, 1, - pos_x, pos_y, 'ImageMotion', { - "args": { - "output_path": "", - "output_format": "", - "duration": "", - } - }, htmlSourceCode); - break; + }, htmlSourceCode); + break; + + // Workflow-Agent + case "DialogAgent": + const DialogAgentID = editor.addNode("DialogAgent", 1, 1, + pos_x, + pos_y, + "DialogAgent", { + "args": { + "name": "", + "sys_prompt": "", + "model_config_name": "" + } + }, htmlSourceCode); + var nodeElement = document.querySelector(`#node-${DialogAgentID} .node-id`); + if (nodeElement) { + nodeElement.textContent = DialogAgentID; + } + break; - case 'VideoComposition': - editor.addNode('VideoComposition', 1, 1, - pos_x, pos_y, 'VideoComposition', { - "args": { - "output_path": "", - "target_width": "", - "target_height": "", - "fps": "", - } - }, htmlSourceCode); - break; + case "UserAgent": + const UserAgentID = editor.addNode("UserAgent", 1, 1, pos_x, + pos_y, "UserAgent", { + "args": {"name": "User"} + }, htmlSourceCode); + var nodeElement = document.querySelector(`#node-${UserAgentID} .node-id`); + if (nodeElement) { + nodeElement.textContent = UserAgentID; + } + break; - case 'Post': - editor.addNode('Post', 1, 1, - pos_x, pos_y, 'Post', { + case "TextToImageAgent": + const TextToImageAgentID = + editor.addNode("TextToImageAgent", 1, + 1, pos_x, pos_y, + "TextToImageAgent", { "args": { - "url": "", - "headers": '', - "data": '', - "json": '', - "kwargs": '', - "output_path": "", - "output_type": "", + "name": "", + "model_config_name": "" } - }, htmlSourceCode); - break; - - default: - } + }, htmlSourceCode); + var nodeElement = document.querySelector(`#node-${TextToImageAgentID} .node-id`); + if (nodeElement) { + nodeElement.textContent = TextToImageAgentID; + } + break; + + case "DictDialogAgent": + const DictDialogAgentID = editor.addNode("DictDialogAgent", 1, + 1, pos_x, pos_y, + "DictDialogAgent", { + "args": { + "name": "", + "sys_prompt": "", + "model_config_name": "", + "parse_func": "", + "fault_handler": "", + "max_retries": 3, + } + }, htmlSourceCode); + var nodeElement = document.querySelector(`#node-${DictDialogAgentID} .node-id`); + if (nodeElement) { + nodeElement.textContent = DictDialogAgentID; + } + break; + + case "ReActAgent": + const ReActAgentID = editor.addNode("ReActAgent", 1, 1, pos_x, pos_y, + "GROUP", { + elements: [], + "args": { + "name": "", + "sys_prompt": "", + "model_config_name": "", + "max_iters": 10, + "verbose": "", + } + }, htmlSourceCode); + var nodeElement = document.querySelector(`#node-${ReActAgentID} .node-id`); + if (nodeElement) { + nodeElement.textContent = ReActAgentID; + } + break; + + // Workflow-Pipeline + case "Placeholder": + editor.addNode("Placeholder", 1, 1, + pos_x, pos_y, "Placeholder", {}, htmlSourceCode); + break; + + case "MsgHub": + editor.addNode("MsgHub", 1, 1, pos_x, pos_y, + "GROUP", { + elements: [], + "args": { + "announcement": { + "name": "", + "content": "" + } + } + }, htmlSourceCode); + break; + + case "SequentialPipeline": + editor.addNode("SequentialPipeline", 1, 1, pos_x, pos_y, + "GROUP", {elements: []}, htmlSourceCode); + break; + + case "ForLoopPipeline": + editor.addNode("ForLoopPipeline", 1, 1, pos_x, pos_y, + "GROUP", { + elements: [], + "args": { + "max_loop": 3, + "condition_op": "", + "target_value": "", + } + }, htmlSourceCode); + break; + + case "WhileLoopPipeline": + editor.addNode("WhileLoopPipeline", 1, 1, pos_x, pos_y, + "GROUP", { + elements: [], + "args": { + "condition_func": "" + } + }, htmlSourceCode); + break; + + case "IfElsePipeline": + editor.addNode("IfElsePipeline", 1, + 1, pos_x, pos_y, "GROUP", { + elements: [], args: { + "condition_op": "", + "target_value": "", + } + }, htmlSourceCode); + break; + + case "SwitchPipeline": + const SwitchPipelineID = editor.addNode("SwitchPipeline", 1, 1, pos_x, pos_y, "GROUP", { + elements: [], args: { + "condition_func": "", + "cases": [], + } + }, htmlSourceCode); + break; + + // Workflow-Service + case "BingSearchService": + editor.addNode("BingSearchService", 0, 0, + pos_x, pos_y, "BingSearchService", { + "args": { + "api_key": "", + "num_results": 3, + } + }, htmlSourceCode); + break; + + case "GoogleSearchService": + editor.addNode("GoogleSearchService", 0, 0, + pos_x, pos_y, "GoogleSearchService", { + "args": { + "api_key": "", + "cse_id": "", + "num_results": 3, + } + }, htmlSourceCode); + break; + + case "PythonService": + editor.addNode("PythonService", 0, 0, + pos_x, pos_y, "PythonService", {}, htmlSourceCode); + break; + + case "ReadTextService": + editor.addNode("ReadTextService", 0, 0, + pos_x, pos_y, "ReadTextService", {}, htmlSourceCode); + break; + + case "WriteTextService": + editor.addNode("WriteTextService", 0, 0, + pos_x, pos_y, "WriteTextService", {}, htmlSourceCode); + break; + + case "TextToAudioService": + const TextToAudioServiceID = editor.addNode("TextToAudioService", 0, 0, + pos_x, pos_y, "TextToAudioService", { + "args": { + "model": "", + "api_key": "", + "sample_rate": "" + } + }, htmlSourceCode); + break; + case "TextToImageService": + editor.addNode("TextToImageService", 0, 0, + pos_x, pos_y, "TextToImageService", { + "args": { + "model": "", + "api_key": "", + "n": 1, + "size": "" + } + }, htmlSourceCode); + break; + + case "ImageComposition": + editor.addNode("ImageComposition", 1, 1, + pos_x, pos_y, "ImageComposition", { + "args": { + "titles": "", + "output_path": "", + "row": 1, + "column": 1, + "spacing": 10, + "title_height": 100, + "font_name": "PingFang", + } + }, htmlSourceCode); + break; + case "Code": + const CodeID = editor.addNode("Code", 1, 1, + pos_x, pos_y, "Code", { + "args": { + "code": "def function(msg1: Msg) -> Msg:\n content1 = msg1.get(\"content\", \"\")\n return {\n \"role\": \"assistant\",\n \"content\": content1,\n \"name\": \"function\",\n }" + } + }, htmlSourceCode); + break; + // case 'IF/ELSE': + // const IfelseID = editor.addNode('IF/ELSE', 1, 2, + // pos_x, pos_y, 'IF/ELSE', { + // "args": { + // "condition_op": "", + // "target_value": "", + // } + // }, htmlSourceCode); + // break; + + case "ImageMotion": + editor.addNode("ImageMotion", 1, 1, + pos_x, pos_y, "ImageMotion", { + "args": { + "output_path": "", + "output_format": "", + "duration": "", + } + }, htmlSourceCode); + break; + + case "VideoComposition": + editor.addNode("VideoComposition", 1, 1, + pos_x, pos_y, "VideoComposition", { + "args": { + "output_path": "", + "target_width": "", + "target_height": "", + "fps": "", + } + }, htmlSourceCode); + break; + + case "Post": + editor.addNode("Post", 1, 1, + pos_x, pos_y, "Post", { + "args": { + "url": "", + "headers": "", + "data": "", + "json": "", + "kwargs": "", + "output_path": "", + "output_type": "", + } + }, htmlSourceCode); + break; + + default: + } } function initializeMonacoEditor(nodeId) { - require.config({ - paths: { - vs: "https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs", - }, + require.config({ + paths: { + vs: "https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs", + }, + }); + + require(["vs/editor/editor.main"], function () { + const parentSelector = `#node-${nodeId}`; + const parentNode = document.querySelector(parentSelector); + + if (!parentNode) { + console.error(`Parent node with selector ${parentSelector} not found.`); + return; + } + + const codeContentElement = parentNode.querySelector(".code-content"); + if (!codeContentElement) { + return; + } + + const node = editor.getNodeFromId(nodeId); + if (!node) { + console.error(`Node with ID ${nodeId} not found.`); + return; + } + + const editorInstance = monaco.editor.create(codeContentElement, { + value: node.data.args.code, + language: "python", + theme: "vs-light", + minimap: { + enabled: false, + }, + wordWrap: "on", + lineNumbersMinChars: 1, + scrollBeyondLastLine: false, + readOnly: false, }); - require(["vs/editor/editor.main"], function () { - const parentSelector = `#node-${nodeId}`; - const parentNode = document.querySelector(parentSelector); - - if (!parentNode) { - console.error(`Parent node with selector ${parentSelector} not found.`); - return; - } - - const codeContentElement = parentNode.querySelector(`.code-content`); - if (!codeContentElement) { - return; - } - - const node = editor.getNodeFromId(nodeId); - if (!node) { - console.error(`Node with ID ${nodeId} not found.`); - return; - } - - const editorInstance = monaco.editor.create(codeContentElement, { - value: node.data.args.code, - language: "python", - theme: "vs-light", - minimap: { - enabled: false, - }, - wordWrap: "on", - lineNumbersMinChars: 1, - scrollBeyondLastLine: false, - readOnly: false, - }); - - editorInstance.onDidChangeModelContent(function () { - const updatedNode = editor.getNodeFromId(nodeId); - if (updatedNode) { - updatedNode.data.args.code = editorInstance.getValue().trim(); - editor.updateNodeDataFromId(nodeId, updatedNode.data); - } - }); - - const resizeObserver = new ResizeObserver(() => { - editorInstance.layout(); - }); - resizeObserver.observe(parentNode); - parentNode.addEventListener('DOMNodeRemoved', function () { - resizeObserver.disconnect(); - }); + editorInstance.onDidChangeModelContent(function () { + const updatedNode = editor.getNodeFromId(nodeId); + if (updatedNode) { + updatedNode.data.args.code = editorInstance.getValue().trim(); + editor.updateNodeDataFromId(nodeId, updatedNode.data); + } + }); - }, function (error) { - console.error("Error encountered while loading monaco editor: ", error); + const resizeObserver = new ResizeObserver(() => { + editorInstance.layout(); + }); + resizeObserver.observe(parentNode); + parentNode.addEventListener("DOMNodeRemoved", function () { + resizeObserver.disconnect(); }); -} + }, function (error) { + console.error("Error encountered while loading monaco editor: ", error); + }); +} function updateSampleRate(nodeId) { - const newNode = document.getElementById(`node-${nodeId}`); - if (!newNode) { - console.error(`Node with ID node-${nodeId} not found.`); - return; - } + const newNode = document.getElementById(`node-${nodeId}`); + if (!newNode) { + console.error(`Node with ID node-${nodeId} not found.`); + return; + } - const modelNameInput = newNode.querySelector('#model_name'); + const modelNameInput = newNode.querySelector("#model_name"); - function updateSampleRateValue() { - const modelName = modelNameInput ? modelNameInput.value : ''; + function updateSampleRateValue() { + const modelName = modelNameInput ? modelNameInput.value : ""; - if (ModelNames48k.includes(modelName)) { - sampleRate = 48000 - } else { - sampleRate = 16000 - } + if (ModelNames48k.includes(modelName)) { + sampleRate = 48000; + } else { + sampleRate = 16000; + } - const sampleRateInput = newNode.querySelector('#sample_rate'); + const sampleRateInput = newNode.querySelector("#sample_rate"); - if (sampleRateInput) { - sampleRateInput.value = sampleRate; - var nodeData = editor.getNodeFromId(nodeId).data; - nodeData.args.sample_rate = sampleRate - nodeData.args.model = modelName - editor.updateNodeDataFromId(nodeId, nodeData); + if (sampleRateInput) { + sampleRateInput.value = sampleRate; + const nodeData = editor.getNodeFromId(nodeId).data; + nodeData.args.sample_rate = sampleRate; + nodeData.args.model = modelName; + editor.updateNodeDataFromId(nodeId, nodeData); - console.log(`${modelName} sample rate updated to: ${sampleRate}`); - } else { - console.log(`Sample Rate input not found.`); - } + console.log(`${modelName} sample rate updated to: ${sampleRate}`); + } else { + console.log("Sample Rate input not found."); } + } - if (modelNameInput) { - modelNameInput.addEventListener('input', updateSampleRateValue); - } + if (modelNameInput) { + modelNameInput.addEventListener("input", updateSampleRateValue); + } } function setupTextInputListeners(nodeId) { - const newNode = document.getElementById(`node-${nodeId}`); - if (newNode) { - const stopPropagation = function (event) { - event.stopPropagation(); - }; - newNode.addEventListener('mousedown', function (event) { - const target = event.target; - if (target.tagName === 'TEXTAREA' || target.tagName === 'INPUT' || target.closest('.code-content')) { - stopPropagation(event); - } - }, false); - } + const newNode = document.getElementById(`node-${nodeId}`); + if (newNode) { + const stopPropagation = function (event) { + event.stopPropagation(); + }; + newNode.addEventListener("mousedown", function (event) { + const target = event.target; + if (target.tagName === "TEXTAREA" || target.tagName === "INPUT" || target.closest(".code-content")) { + stopPropagation(event); + } + }, false); + } } function toggleAdvanced() { - var advancedBox = document.querySelector('.advanced-box'); - if (advancedBox.style.display === "none") { - advancedBox.style.display = "block"; - } else { - advancedBox.style.display = "none"; - } + const advancedBox = document.querySelector(".advanced-box"); + if (advancedBox.style.display === "none") { + advancedBox.style.display = "block"; + } else { + advancedBox.style.display = "none"; + } } function handleInputChange(event) { - const input = event.target; + const input = event.target; - if (input.type === 'number') { - const value = input.value; - const floatValue = parseFloat(value); - const nodeId = input.closest('.drawflow_content_node').parentElement.id.slice(5); + if (input.type === "number") { + const value = input.value; + const floatValue = parseFloat(value); + const nodeId = input.closest(".drawflow_content_node").parentElement.id.slice(5); - if (!isNaN(floatValue)) { - const node = editor.getNodeFromId(nodeId); - const dataAttributes = + if (!isNaN(floatValue)) { + const node = editor.getNodeFromId(nodeId); + const dataAttributes = Array.from(input.attributes).filter(attr => - attr.name.startsWith('df-args-')); - dataAttributes.forEach(attr => { - const attrName = attr.name; - if (attrName.startsWith('df-args-json_args-')) { - const dataAttribute = attrName.substring(18) - if - (node.data.args.json_args.hasOwnProperty(dataAttribute)) { - node.data.args.json_args[dataAttribute] = floatValue; - editor.updateNodeDataFromId(nodeId, node.data); - } - } else { - const dataAttribute = attrName.substring(8); - if (node.data.args.hasOwnProperty(dataAttribute)) { - node.data.args[dataAttribute] = floatValue; - editor.updateNodeDataFromId(nodeId, node.data); - } - } - }); + attr.name.startsWith("df-args-")); + dataAttributes.forEach(attr => { + const attrName = attr.name; + if (attrName.startsWith("df-args-json_args-")) { + const dataAttribute = attrName.substring(18); + if + (node.data.args.json_args.hasOwnProperty(dataAttribute)) { + node.data.args.json_args[dataAttribute] = floatValue; + editor.updateNodeDataFromId(nodeId, node.data); + } } else { - console.error("Invalid input value:", value); + const dataAttribute = attrName.substring(8); + if (node.data.args.hasOwnProperty(dataAttribute)) { + node.data.args[dataAttribute] = floatValue; + editor.updateNodeDataFromId(nodeId, node.data); + } } + }); + } else { + console.error("Invalid input value:", value); } + } } function addEventListenersToNumberInputs(nodeId) { - const nodeElement = document.getElementById(`node-${nodeId}`); - if (nodeElement) { - const numberInputs = nodeElement.querySelectorAll('input[type=number]'); - numberInputs.forEach(input => { - input.addEventListener('change', handleInputChange); - }); - } + const nodeElement = document.getElementById(`node-${nodeId}`); + if (nodeElement) { + const numberInputs = nodeElement.querySelectorAll("input[type=number]"); + numberInputs.forEach(input => { + input.addEventListener("change", handleInputChange); + }); + } } function validateTemperature(input) { - const value = input.valueAsNumber; - if (isNaN(value) || value < 0 || value >= 2) { - input.setCustomValidity('Temperature must be greater or equal than 0 and less than 2!'); - } else { - input.setCustomValidity(''); - } - input.reportValidity(); + const value = input.valueAsNumber; + if (isNaN(value) || value < 0 || value >= 2) { + input.setCustomValidity("Temperature must be greater or equal than 0 and less than 2!"); + } else { + input.setCustomValidity(""); + } + input.reportValidity(); } function validateSeed(input) { - const value = parseInt(input.value, 10); // Parse the value as an integer. - if (isNaN(value) || value < 0 || !Number.isInteger(parseFloat(input.value))) { - input.setCustomValidity('Seed must be a non-negative integer!'); - } else { - input.setCustomValidity(''); - } - input.reportValidity(); + const value = parseInt(input.value, 10); // Parse the value as an integer. + if (isNaN(value) || value < 0 || !Number.isInteger(parseFloat(input.value))) { + input.setCustomValidity("Seed must be a non-negative integer!"); + } else { + input.setCustomValidity(""); + } + input.reportValidity(); } function validateDuration(input) { - const value = parseInt(input.value, 10); // Parse the value as an integer. - if (isNaN(value) || value < 0 || !Number.isInteger(parseFloat(input.value))) { - input.setCustomValidity('Duration must be a non-negative integer!'); - } else { - input.setCustomValidity(''); - } - input.reportValidity(); + const value = parseInt(input.value, 10); // Parse the value as an integer. + if (isNaN(value) || value < 0 || !Number.isInteger(parseFloat(input.value))) { + input.setCustomValidity("Duration must be a non-negative integer!"); + } else { + input.setCustomValidity(""); + } + input.reportValidity(); } -document.addEventListener('input', function (event) { - const input = event.target; +document.addEventListener("input", function (event) { + const input = event.target; - if (input.getAttribute('df-args-temperature') !== null || - input.getAttribute('df-args-json_args-temperature') !== null) { - validateTemperature(input); - } + if (input.getAttribute("df-args-temperature") !== null || + input.getAttribute("df-args-json_args-temperature") !== null) { + validateTemperature(input); + } - if (input.getAttribute('df-args-seed') !== null || - input.getAttribute('df-args-json_args-seed') !== null) { - validateSeed(input); - } + if (input.getAttribute("df-args-seed") !== null || + input.getAttribute("df-args-json_args-seed") !== null) { + validateSeed(input); + } - if (input.getAttribute('df-args-duration') !== null) { - validateDuration(input) - } + if (input.getAttribute("df-args-duration") !== null) { + validateDuration(input); + } }); -var transform = ''; +var transform = ""; function updateReadmeAndTrimExtrasInHTML(htmlString, nodeId) { - const parser = new DOMParser(); - const doc = parser.parseFromString(htmlString, 'text/html'); - const containerDiv = doc.body.firstChild; + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, "text/html"); + const containerDiv = doc.body.firstChild; - removeNonReadmeChildren(containerDiv); - updateReadmeContent(containerDiv, nodeId); + removeNonReadmeChildren(containerDiv); + updateReadmeContent(containerDiv, nodeId); - return containerDiv.innerHTML; + return containerDiv.innerHTML; } function updateReadmeContent(containerDiv, nodeId) { - const readmeDiv = containerDiv.querySelector('.readme'); - if (readmeDiv) { - console.log("readmeDiv", readmeDiv); + const readmeDiv = containerDiv.querySelector(".readme"); + if (readmeDiv) { + console.log("readmeDiv", readmeDiv); - let newDiv = document.createElement('div'); - newDiv.innerHTML = `Copy from Node ID: ${nodeId}`; - readmeDiv.appendChild(newDiv); + const newDiv = document.createElement("div"); + newDiv.innerHTML = `Copy from Node ID: ${nodeId}`; + readmeDiv.appendChild(newDiv); - console.log("readmeDiv after", readmeDiv); - } + console.log("readmeDiv after", readmeDiv); + } } function removeNonReadmeChildren(containerDiv) { - const boxDiv = containerDiv.querySelector('.box'); - if (boxDiv) { - boxDiv.querySelectorAll('*:not(.readme)').forEach(child => child.remove()); - } + const boxDiv = containerDiv.querySelector(".box"); + if (boxDiv) { + boxDiv.querySelectorAll("*:not(.readme)").forEach(child => child.remove()); + } } function createNodeHTML(node, isCopy, originalNodeId) { - let modifiedHtml = isCopy ? processNodeCopyHTML(node.html) : node.html; - return updateReadmeAndTrimExtrasInHTML(modifiedHtml, originalNodeId); + const modifiedHtml = isCopy ? processNodeCopyHTML(node.html) : node.html; + return updateReadmeAndTrimExtrasInHTML(modifiedHtml, originalNodeId); } function processNodeCopyHTML(htmlString) { - const parser = new DOMParser(); - const doc = parser.parseFromString(htmlString, 'text/html'); + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, "text/html"); - ['.copy-button', 'div .node-id'].forEach(selector => { - const element = doc.querySelector(selector); - if (element) element.remove(); - }); + [".copy-button", "div .node-id"].forEach(selector => { + const element = doc.querySelector(selector); + if (element) { + element.remove(); + } + }); - return doc.body.innerHTML; + return doc.body.innerHTML; } function copyNode(originalNodeId) { - const originalNode = editor.getNodeFromId(originalNodeId); - originalNode.data.copies = originalNode.data.copies || []; + const originalNode = editor.getNodeFromId(originalNodeId); + originalNode.data.copies = originalNode.data.copies || []; - const newNodeHTML = createNodeHTML(originalNode, true, originalNodeId); - const [posX, posY] = [originalNode.pos_x + 30, originalNode.pos_y + 30]; + const newNodeHTML = createNodeHTML(originalNode, true, originalNodeId); + const [posX, posY] = [originalNode.pos_x + 30, originalNode.pos_y + 30]; - editor.addNode("CopyNode", - Object.keys(originalNode.inputs).length, - Object.keys(originalNode.outputs).length, - posX, posY, 'node-' + originalNode.name, {elements: [originalNodeId.toString()]}, - newNodeHTML); + editor.addNode("CopyNode", + Object.keys(originalNode.inputs).length, + Object.keys(originalNode.outputs).length, + posX, posY, "node-" + originalNode.name, {elements: [originalNodeId.toString()]}, + newNodeHTML); } function setupNodeCopyListens(nodeId) { - const newNode = document.getElementById(`node-${nodeId}`); - if (newNode) { - const copyButton = newNode.querySelector('.copy-button'); - if (copyButton) { - copyButton.addEventListener('click', function () { - copyNode(nodeId); - }); - } + const newNode = document.getElementById(`node-${nodeId}`); + if (newNode) { + const copyButton = newNode.querySelector(".copy-button"); + if (copyButton) { + copyButton.addEventListener("click", function () { + copyNode(nodeId); + }); } + } } function hideShowGroupNodes(groupId, show) { - const groupInfo = editor.getNodeFromId(groupId); - if (groupInfo && groupInfo.class === 'GROUP') { - groupInfo.data.elements.forEach(elementNodeId => { - const elementNode = document.getElementById(`node-${elementNodeId}`); - const childNodeInfo = editor.getNodeFromId(elementNodeId); - const contentBox = elementNode.querySelector('.box') || - elementNode.querySelector('.box-highlight'); - if (elementNode) { - elementNode.style.display = show ? '' : 'none'; - } - if (childNodeInfo.class === 'GROUP') { - if (!show || (contentBox && !contentBox.classList.contains('hidden'))) { - hideShowGroupNodes(elementNodeId, show); - } - } - }); - } + const groupInfo = editor.getNodeFromId(groupId); + if (groupInfo && groupInfo.class === "GROUP") { + groupInfo.data.elements.forEach(elementNodeId => { + const elementNode = document.getElementById(`node-${elementNodeId}`); + const childNodeInfo = editor.getNodeFromId(elementNodeId); + const contentBox = elementNode.querySelector(".box") || + elementNode.querySelector(".box-highlight"); + if (elementNode) { + elementNode.style.display = show ? "" : "none"; + } + if (childNodeInfo.class === "GROUP") { + if (!show || (contentBox && !contentBox.classList.contains("hidden"))) { + hideShowGroupNodes(elementNodeId, show); + } + } + }); + } } function setupConditionListeners(nodeId) { - const newNode = document.getElementById(`node-${nodeId}`); - if (newNode) { - const conditionOp = newNode.querySelector('#condition_op'); - const targetContainer = newNode.querySelector('#target-container'); - console.log(conditionOp, targetContainer); - - function updateTargetVisibility() { - const condition_op = conditionOp ? conditionOp.value : ''; - const hideConditions = ['', 'is empty', 'is null', 'is not empty', 'is not null']; - if (hideConditions.includes(condition_op)) { - targetContainer.style.display = 'none'; - } else { - targetContainer.style.display = 'block'; - } - } + const newNode = document.getElementById(`node-${nodeId}`); + if (newNode) { + const conditionOp = newNode.querySelector("#condition_op"); + const targetContainer = newNode.querySelector("#target-container"); + console.log(conditionOp, targetContainer); - if (conditionOp) { - conditionOp.addEventListener('input', updateTargetVisibility); - updateTargetVisibility(); - } + function updateTargetVisibility() { + const condition_op = conditionOp ? conditionOp.value : ""; + const hideConditions = ["", "is empty", "is null", "is not empty", "is not null"]; + if (hideConditions.includes(condition_op)) { + targetContainer.style.display = "none"; + } else { + targetContainer.style.display = "block"; + } + } + + if (conditionOp) { + conditionOp.addEventListener("input", updateTargetVisibility); + updateTargetVisibility(); } + } } function setupNodeListeners(nodeId) { - const newNode = document.getElementById(`node-${nodeId}`); - if (newNode) { + const newNode = document.getElementById(`node-${nodeId}`); + if (newNode) { - initializeMonacoEditor(nodeId); - setupConditionListeners(nodeId); - updateSampleRate(nodeId); - setupSwitchPipelineListeners(nodeId); + initializeMonacoEditor(nodeId); + setupConditionListeners(nodeId); + updateSampleRate(nodeId); + setupSwitchPipelineListeners(nodeId); - const titleBox = newNode.querySelector('.title-box'); - const contentBox = newNode.querySelector('.box') || - newNode.querySelector('.box-highlight'); + const titleBox = newNode.querySelector(".title-box"); + const contentBox = newNode.querySelector(".box") || + newNode.querySelector(".box-highlight"); - // Add resize handle to the bottom right corner of the node - const resizeHandleSE = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + // Add resize handle to the bottom right corner of the node + const resizeHandleSE = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - path.setAttribute('d', 'M932.37602347 512.88874453l-420.37602347 420.37602347 56.43525867 56.43525867 420.37602453-420.37602347-56.43525973-56.43525867z m-3.55497707-474.58942293L34.29997333 933.264768l56.43525867 56.43525867L985.25630613 95.1789536l-56.43525973-56.879632z'); + const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + path.setAttribute("d", "M932.37602347 512.88874453l-420.37602347 420.37602347 56.43525867 56.43525867 420.37602453-420.37602347-56.43525973-56.43525867z m-3.55497707-474.58942293L34.29997333 933.264768l56.43525867 56.43525867L985.25630613 95.1789536l-56.43525973-56.879632z"); - resizeHandleSE.setAttribute('viewBox', '0 0 1024 1024'); - resizeHandleSE.appendChild(path); + resizeHandleSE.setAttribute("viewBox", "0 0 1024 1024"); + resizeHandleSE.appendChild(path); - resizeHandleSE.classList.add('resize-handle-se'); + resizeHandleSE.classList.add("resize-handle-se"); - contentBox.appendChild(resizeHandleSE); + contentBox.appendChild(resizeHandleSE); - const toggleArrow = newNode.querySelector('.toggle-arrow'); + const toggleArrow = newNode.querySelector(".toggle-arrow"); - if (toggleArrow && contentBox && titleBox) { - toggleArrow.addEventListener('click', function () { - contentBox.classList.toggle('hidden'); + if (toggleArrow && contentBox && titleBox) { + toggleArrow.addEventListener("click", function () { + contentBox.classList.toggle("hidden"); - if (contentBox.classList.contains('hidden')) { - toggleArrow.textContent = "\u25BC"; - hideShowGroupNodes(nodeId, false); - } else { - toggleArrow.textContent = "\u25B2"; - hideShowGroupNodes(nodeId, true); - } - editor.updateConnectionNodes('node-' + nodeId); - }); + if (contentBox.classList.contains("hidden")) { + toggleArrow.textContent = "\u25BC"; + hideShowGroupNodes(nodeId, false); + } else { + toggleArrow.textContent = "\u25B2"; + hideShowGroupNodes(nodeId, true); + } + editor.updateConnectionNodes("node-" + nodeId); + }); - let startX, startY, startWidth, startHeight; + let startX, startY, startWidth, startHeight; - resizeHandleSE.addEventListener('mousedown', function (e) { - e.stopPropagation(); - document.addEventListener('mousemove', doDragSE, false); - document.addEventListener('mouseup', stopDragSE, false); + resizeHandleSE.addEventListener("mousedown", function (e) { + e.stopPropagation(); + document.addEventListener("mousemove", doDragSE, false); + document.addEventListener("mouseup", stopDragSE, false); - startX = e.clientX; - startY = e.clientY; - startWidth = parseInt(document.defaultView.getComputedStyle(contentBox).width, 10); - startHeight = parseInt(document.defaultView.getComputedStyle(contentBox).height, 10); - }); + startX = e.clientX; + startY = e.clientY; + startWidth = parseInt(document.defaultView.getComputedStyle(contentBox).width, 10); + startHeight = parseInt(document.defaultView.getComputedStyle(contentBox).height, 10); + }); - function doDragSE(e) { - newNode.style.width = 'auto'; + function doDragSE(e) { + newNode.style.width = "auto"; - const newWidth = (startWidth + e.clientX - startX); - if (newWidth > 200) { - contentBox.style.width = newWidth + 'px'; - titleBox.style.width = newWidth + 'px'; - } + const newWidth = (startWidth + e.clientX - startX); + if (newWidth > 200) { + contentBox.style.width = newWidth + "px"; + titleBox.style.width = newWidth + "px"; + } - const newHeight = (startHeight + e.clientY - startY); - contentBox.style.height = newHeight + 'px'; + const newHeight = (startHeight + e.clientY - startY); + contentBox.style.height = newHeight + "px"; - editor.updateConnectionNodes('node-' + nodeId); - } + editor.updateConnectionNodes("node-" + nodeId); + } - function stopDragSE(e) { - document.removeEventListener('mousemove', doDragSE, false); - document.removeEventListener('mouseup', stopDragSE, false); - } + function stopDragSE(e) { + document.removeEventListener("mousemove", doDragSE, false); + document.removeEventListener("mouseup", stopDragSE, false); + } - } } + } } function setupSwitchPipelineListeners(nodeId) { - const newNode = document.getElementById(`node-${nodeId}`); - if (!newNode) { - console.error(`Node with ID node-${nodeId} not found.`); - return; - } - const addCaseButton = newNode.querySelector('.add-case'); - if (!addCaseButton) { - return; - } - addCaseButton.addEventListener('click', function () { - var caseContainer = newNode.querySelector('.case-container'); - if (!caseContainer) { - console.error(`Case container not found in node-${nodeId}.`); - return; - } - var defaultCaseElement = caseContainer.querySelector('.default-case'); - if (defaultCaseElement) { - caseContainer.removeChild(defaultCaseElement); - } - var caseCount = caseContainer.getElementsByClassName('case-placeholder').length; - var caseElement = document.createElement('div'); - caseElement.classList.add('case-placeholder'); - - var caseText = document.createTextNode(`Case ${caseCount + 1}: `); - caseElement.appendChild(caseText); - - var inputElement = document.createElement('input'); - inputElement.type = 'text'; - inputElement.placeholder = `Case Pattern`; - - inputElement.dataset.caseIndex = caseCount; - - caseElement.appendChild(inputElement); - caseContainer.appendChild(caseElement); - - inputElement.addEventListener('input', function (e) { - var nodeData = editor.getNodeFromId(nodeId).data; - console.log("nodeData", nodeData); - var index = e.target.dataset.caseIndex; - console.log("index", index); - nodeData.args.cases[index] = e.target.value; - editor.updateNodeDataFromId(nodeId, nodeData); - }); - - editor.getNodeFromId(nodeId).data.args.cases.push(''); - - addDefaultCase(caseContainer); - editor.updateConnectionNodes('node-' + nodeId); - }); - - const removeCaseButton = newNode.querySelector('.remove-case'); - if (!removeCaseButton) { - return; - } - removeCaseButton.addEventListener('click', function () { - var caseContainer = newNode.querySelector('.case-container'); - var cases = caseContainer.getElementsByClassName('case-placeholder'); - if (cases.length > 1) { - caseContainer.removeChild(cases[cases.length - 2]); - var nodeData = editor.getNodeFromId(nodeId).data; - nodeData.args.cases.splice(nodeData.args.cases.length - 1, 1); - editor.updateNodeDataFromId(nodeId, nodeData); - } - editor.updateConnectionNodes('node-' + nodeId); - }); - - var caseContainer = newNode.querySelector('.case-container'); + const newNode = document.getElementById(`node-${nodeId}`); + if (!newNode) { + console.error(`Node with ID node-${nodeId} not found.`); + return; + } + const addCaseButton = newNode.querySelector(".add-case"); + if (!addCaseButton) { + return; + } + addCaseButton.addEventListener("click", function () { + const caseContainer = newNode.querySelector(".case-container"); if (!caseContainer) { - console.error(`Case container not found in node-${nodeId}.`); - return; + console.error(`Case container not found in node-${nodeId}.`); + return; } - - var defaultCaseElement = caseContainer.querySelector('.default-case'); + const defaultCaseElement = caseContainer.querySelector(".default-case"); if (defaultCaseElement) { - caseContainer.removeChild(defaultCaseElement); + caseContainer.removeChild(defaultCaseElement); } + const caseCount = caseContainer.getElementsByClassName("case-placeholder").length; + const caseElement = document.createElement("div"); + caseElement.classList.add("case-placeholder"); - var cases = editor.getNodeFromId(nodeId).data.args.cases; - for (var caseCount = 0; caseCount < cases.length; caseCount++) { + const caseText = document.createTextNode(`Case ${caseCount + 1}: `); + caseElement.appendChild(caseText); - var caseElement = document.createElement('div'); - caseElement.classList.add('case-placeholder'); + const inputElement = document.createElement("input"); + inputElement.type = "text"; + inputElement.placeholder = "Case Pattern"; - var caseText = document.createTextNode(`Case ${caseCount + 1}: `); - caseElement.appendChild(caseText); + inputElement.dataset.caseIndex = caseCount; - var inputElement = document.createElement('input'); - inputElement.type = 'text'; - inputElement.placeholder = `Case Pattern`; - inputElement.value = cases[caseCount]; + caseElement.appendChild(inputElement); + caseContainer.appendChild(caseElement); - inputElement.dataset.caseIndex = caseCount; + inputElement.addEventListener("input", function (e) { + const nodeData = editor.getNodeFromId(nodeId).data; + console.log("nodeData", nodeData); + const index = e.target.dataset.caseIndex; + console.log("index", index); + nodeData.args.cases[index] = e.target.value; + editor.updateNodeDataFromId(nodeId, nodeData); + }); - caseElement.appendChild(inputElement); - caseContainer.appendChild(caseElement); + editor.getNodeFromId(nodeId).data.args.cases.push(""); - inputElement.addEventListener('input', function (e) { - var nodeData = editor.getNodeFromId(nodeId).data; - console.log("nodeData", nodeData); - var index = e.target.dataset.caseIndex; - console.log("index", index); - nodeData.args.cases[index] = e.target.value; - editor.updateNodeDataFromId(nodeId, nodeData); - }); - } addDefaultCase(caseContainer); + editor.updateConnectionNodes("node-" + nodeId); + }); + + const removeCaseButton = newNode.querySelector(".remove-case"); + if (!removeCaseButton) { + return; + } + removeCaseButton.addEventListener("click", function () { + const caseContainer = newNode.querySelector(".case-container"); + const cases = caseContainer.getElementsByClassName("case-placeholder"); + if (cases.length > 1) { + caseContainer.removeChild(cases[cases.length - 2]); + const nodeData = editor.getNodeFromId(nodeId).data; + nodeData.args.cases.splice(nodeData.args.cases.length - 1, 1); + editor.updateNodeDataFromId(nodeId, nodeData); + } + editor.updateConnectionNodes("node-" + nodeId); + }); + + const caseContainer = newNode.querySelector(".case-container"); + if (!caseContainer) { + console.error(`Case container not found in node-${nodeId}.`); + return; + } + + const defaultCaseElement = caseContainer.querySelector(".default-case"); + if (defaultCaseElement) { + caseContainer.removeChild(defaultCaseElement); + } + + const cases = editor.getNodeFromId(nodeId).data.args.cases; + for (let caseCount = 0; caseCount < cases.length; caseCount++) { + + const caseElement = document.createElement("div"); + caseElement.classList.add("case-placeholder"); + + const caseText = document.createTextNode(`Case ${caseCount + 1}: `); + caseElement.appendChild(caseText); + + const inputElement = document.createElement("input"); + inputElement.type = "text"; + inputElement.placeholder = "Case Pattern"; + inputElement.value = cases[caseCount]; + + inputElement.dataset.caseIndex = caseCount; + + caseElement.appendChild(inputElement); + caseContainer.appendChild(caseElement); + + inputElement.addEventListener("input", function (e) { + const nodeData = editor.getNodeFromId(nodeId).data; + console.log("nodeData", nodeData); + const index = e.target.dataset.caseIndex; + console.log("index", index); + nodeData.args.cases[index] = e.target.value; + editor.updateNodeDataFromId(nodeId, nodeData); + }); + } + addDefaultCase(caseContainer); } function addDefaultCase(caseContainer) { - var defaultCaseElement = document.createElement('div'); - defaultCaseElement.classList.add('case-placeholder', 'default-case'); - defaultCaseElement.textContent = `Default Case`; - caseContainer.appendChild(defaultCaseElement); + const defaultCaseElement = document.createElement("div"); + defaultCaseElement.classList.add("case-placeholder", "default-case"); + defaultCaseElement.textContent = "Default Case"; + caseContainer.appendChild(defaultCaseElement); } function closemodal(e) { - e.target.closest(".drawflow-node").style.zIndex = "2"; - e.target.parentElement.parentElement.style.display = "none"; - editor.precanvas.style.transform = transform; - editor.precanvas.style.left = '0px'; - editor.precanvas.style.top = '0px'; - editor.editor_mode = "edit"; + e.target.closest(".drawflow-node").style.zIndex = "2"; + e.target.parentElement.parentElement.style.display = "none"; + editor.precanvas.style.transform = transform; + editor.precanvas.style.left = "0px"; + editor.precanvas.style.top = "0px"; + editor.editor_mode = "edit"; } function changeModule(event) { - var all = document.querySelectorAll(".menu ul li"); - for (var i = 0; i < all.length; i++) { - all[i].classList.remove('selected'); - } - event.target.classList.add('selected'); + const all = document.querySelectorAll(".menu ul li"); + for (let i = 0; i < all.length; i++) { + all[i].classList.remove("selected"); + } + event.target.classList.add("selected"); } function changeLockMode(option) { - let lockSvg = document.getElementById('lock-svg'); - let unlockSvg = document.getElementById('unlock-svg'); - if (option === 'lock') { - editor.editor_mode = 'edit'; - lockSvg.style.display = 'none'; - unlockSvg.style.display = 'block'; - } else { - editor.editor_mode = 'fixed'; - lockSvg.style.display = 'block'; - unlockSvg.style.display = 'none'; - } + const lockSvg = document.getElementById("lock-svg"); + const unlockSvg = document.getElementById("unlock-svg"); + if (option === "lock") { + editor.editor_mode = "edit"; + lockSvg.style.display = "none"; + unlockSvg.style.display = "block"; + } else { + editor.editor_mode = "fixed"; + lockSvg.style.display = "block"; + unlockSvg.style.display = "none"; + } } function toggleDraggable(element) { - var content = element.nextElementSibling; - if (content.classList.contains('visible')) { - content.classList.remove('visible'); - } else { - content.classList.add('visible'); - } + const content = element.nextElementSibling; + if (content.classList.contains("visible")) { + content.classList.remove("visible"); + } else { + content.classList.add("visible"); + } } function filterEmptyValues(obj) { - return Object.entries(obj).reduce((acc, [key, value]) => { - if (typeof value === 'object' && value !== null) { - const filteredNestedObj = filterEmptyValues(value); - if (Object.keys(filteredNestedObj).length > 0) { - acc[key] = filteredNestedObj; - } - } else if (value !== '') { - acc[key] = value; - } - return acc; - }, {}); + return Object.entries(obj).reduce((acc, [key, value]) => { + if (typeof value === "object" && value !== null) { + const filteredNestedObj = filterEmptyValues(value); + if (Object.keys(filteredNestedObj).length > 0) { + acc[key] = filteredNestedObj; + } + } else if (value !== "") { + acc[key] = value; + } + return acc; + }, {}); } // This function is the most important to AgentScope config. function reorganizeAndFilterConfigForAgentScope(inputData) { - // Assuming there's only one tab ('Home'), but adjust if there are more - const homeTab = inputData.drawflow.Home; - // Create a new object to hold the reorganized and filtered nodes - const filteredNodes = {}; - - // Iterate through the nodes and copy them to the filteredNodes object - Object.entries(homeTab.data).forEach(([key, node]) => { - // Skip the node if the name is 'welcome' or 'readme' - const nodeName = node.name.toLowerCase(); - if (nodeName === 'welcome' || nodeName === 'readme') { - return; - } + // Assuming there's only one tab ('Home'), but adjust if there are more + const homeTab = inputData.drawflow.Home; + // Create a new object to hold the reorganized and filtered nodes + const filteredNodes = {}; - // Create a copy of the node without 'html', 'typenode', 'class', 'id', and 'name' fields - const { - html, - typenode, - pos_x, - pos_y, - class: classField, - id, - ...cleanNode - } = node; - - if (cleanNode.data && cleanNode.data.args) { - cleanNode.data.args = filterEmptyValues(cleanNode.data.args); - } + // Iterate through the nodes and copy them to the filteredNodes object + Object.entries(homeTab.data).forEach(([key, node]) => { + // Skip the node if the name is 'welcome' or 'readme' + const nodeName = node.name.toLowerCase(); + if (nodeName === "welcome" || nodeName === "readme") { + return; + } - // Add the cleaned node to the filteredNodes object using its id as the key - filteredNodes[key] = cleanNode; - }); + // Create a copy of the node without 'html', 'typenode', 'class', 'id', and 'name' fields + const { + html, + typenode, + pos_x, + pos_y, + class: classField, + id, + ...cleanNode + } = node; + + if (cleanNode.data && cleanNode.data.args) { + cleanNode.data.args = filterEmptyValues(cleanNode.data.args); + } + + // Add the cleaned node to the filteredNodes object using its id as the key + filteredNodes[key] = cleanNode; + }); - // Return the filtered and reorganized nodes instead of the original structure - return filteredNodes; + // Return the filtered and reorganized nodes instead of the original structure + return filteredNodes; } function sortElementsByPosition(inputData) { - let hasError = false; - - Object.keys(inputData.drawflow).forEach((moduleKey) => { - const moduleData = inputData.drawflow[moduleKey]; - Object.entries(moduleData.data).forEach(([nodeId, node]) => { - if (node.class === 'GROUP') { - let elements = node.data.elements; - let elementsWithPosition = elements.map(elementId => { - const elementNode = document.querySelector(`#node-${elementId}`); - return elementNode ? { - id: elementId, - position: { - x: elementNode.style.left, - y: elementNode.style.top - } - } : null; - }).filter(el => el); - - try { - elementsWithPosition.sort((a, b) => { - let y1 = parseInt(a.position.y, 10); - let y2 = parseInt(b.position.y, 10); - if (y1 === y2) { - throw new Error(`Two elements have the same y position: Element ${a.id} and Element ${b.id}`); - } - return y1 - y2; - }); - } catch (error) { - alert(error.message); - hasError = true; - } - node.data.elements = elementsWithPosition.map(el => el.id); + let hasError = false; + + Object.keys(inputData.drawflow).forEach((moduleKey) => { + const moduleData = inputData.drawflow[moduleKey]; + Object.entries(moduleData.data).forEach(([nodeId, node]) => { + if (node.class === "GROUP") { + const elements = node.data.elements; + const elementsWithPosition = elements.map(elementId => { + const elementNode = document.querySelector(`#node-${elementId}`); + return elementNode ? { + id: elementId, + position: { + x: elementNode.style.left, + y: elementNode.style.top } - }); + } : null; + }).filter(el => el); + + try { + elementsWithPosition.sort((a, b) => { + const y1 = parseInt(a.position.y, 10); + const y2 = parseInt(b.position.y, 10); + if (y1 === y2) { + throw new Error(`Two elements have the same y position: Element ${a.id} and Element ${b.id}`); + } + return y1 - y2; + }); + } catch (error) { + alert(error.message); + hasError = true; + } + node.data.elements = elementsWithPosition.map(el => el.id); + } }); - return hasError; + }); + return hasError; } function checkConditions() { - let hasModelTypeError = false; - let hasAgentError = false; - let agentModelConfigNames = new Set(); - let modelConfigNames = new Set(); - let isApiKeyEmpty = false; - const nodesData = editor.export().drawflow.Home.data; - - for (let nodeId in nodesData) { - let node = nodesData[nodeId]; - console.log("node", node); - console.log("node.inputs", node.inputs); - - let nodeElement = document.getElementById('node-' + nodeId); - const requiredInputs = nodeElement.querySelectorAll('input[data-required="true"]'); - - let titleBox = nodeElement.querySelector('.title-box'); - - let titleText = titleBox.getAttribute("data-class"); - - for (const input of requiredInputs) { - if (input.value.trim() === '') { - let inputLabel = input.previousElementSibling; - if (inputLabel && inputLabel.tagName.toLowerCase() === "label") { - let labelText = inputLabel.textContent.trim(); - - Swal.fire({ - title: 'Value Missing!', - text: `${labelText} is missing in ${titleText}.`, - icon: 'error', - confirmButtonText: 'Ok' - }); - return false; - } - } - } - - if (node.data && node.data.args && node.data.args.model_type) { - hasModelTypeError = false; - modelConfigNames.add(node.data.args.config_name); - if (node.data.args.api_key === "") { - isApiKeyEmpty = isApiKeyEmpty || true; - } - } - if (node.name.includes('Agent') && "model_config_name" in node.data.args) { - hasAgentError = false; - if (node.data && node.data.args) { - agentModelConfigNames.add(node.data.args.model_config_name); - } - } - if (node.name === 'ReActAgent') { - const elements = node.data.elements; - for (const nodeId of elements) { - const childNode = nodesData[nodeId] - if (!childNode || !childNode.name.includes('Service')) { - Swal.fire({ - title: 'Invalid ReActAgent Configuration', - text: - `ReActAgent must only contain Tool nodes as child nodes.`, - icon: 'error', - confirmButtonText: 'Ok' - }); - return false; - } - } - } - if (node.name === 'IfElsePipeline') { - const elementsSize = node.data.elements.length; - if (elementsSize !== 1 && elementsSize !== 2) { - Swal.fire({ - title: 'Invalid IfElsePipeline Configuration', - text: `IfElsePipeline should have 1 or 2 elements, but has ${elementsSize}.`, - icon: 'error', - confirmButtonText: 'Ok' - }); - return false; - } - } - if (['ForLoopPipeline', 'WhileLoopPipeline', 'MsgHub'].includes(node.name)) { - if (node.data.elements.length !== 1) { - hasError = true; - Swal.fire({ - title: 'Invalid Configuration', - text: `${node.name} must have exactly one element.`, - icon: 'error', - confirmButtonText: 'Ok' - }); - return false; - } - let childNodeId = node.data.elements[0]; - let childNode = nodesData[childNodeId]; - if (!childNode || !childNode.name.includes('Pipeline')) { - Swal.fire({ - title: 'Invalid Configuration', - text: - ` ${childNode.name} contained in ${node.name} is not a Pipeline node.`, - icon: 'error', - confirmButtonText: 'Ok' - }); - return false; - } - } - if (node.name === 'Code') { - const code = node.data.args.code; - const pattern = /\bdef\s+function\s*\(/; - - if (!pattern.test(code)) { - Swal.fire({ - title: 'Invalid Code Function Name', - text: `${node.name} only support "function" as the function name.`, - icon: 'error', - confirmButtonText: 'Ok' - }); - return false; - } + let hasModelTypeError = false; + let hasAgentError = false; + const agentModelConfigNames = new Set(); + const modelConfigNames = new Set(); + let isApiKeyEmpty = false; + const nodesData = editor.export().drawflow.Home.data; + + for (const nodeId in nodesData) { + const node = nodesData[nodeId]; + console.log("node", node); + console.log("node.inputs", node.inputs); + + const nodeElement = document.getElementById("node-" + nodeId); + const requiredInputs = nodeElement.querySelectorAll("input[data-required=\"true\"]"); + + const titleBox = nodeElement.querySelector(".title-box"); + + const titleText = titleBox.getAttribute("data-class"); + + for (const input of requiredInputs) { + if (input.value.trim() === "") { + const inputLabel = input.previousElementSibling; + if (inputLabel && inputLabel.tagName.toLowerCase() === "label") { + const labelText = inputLabel.textContent.trim(); + + Swal.fire({ + title: "Value Missing!", + text: `${labelText} is missing in ${titleText}.`, + icon: "error", + confirmButtonText: "Ok" + }); + return false; + } + } + } + + if (node.data && node.data.args && node.data.args.model_type) { + hasModelTypeError = false; + modelConfigNames.add(node.data.args.config_name); + if (node.data.args.api_key === "") { + isApiKeyEmpty = isApiKeyEmpty || true; + } + } + if (node.name.includes("Agent") && "model_config_name" in node.data.args) { + hasAgentError = false; + if (node.data && node.data.args) { + agentModelConfigNames.add(node.data.args.model_config_name); + } + } + if (node.name === "ReActAgent") { + const elements = node.data.elements; + for (const nodeId of elements) { + const childNode = nodesData[nodeId]; + if (!childNode || !childNode.name.includes("Service")) { + Swal.fire({ + title: "Invalid ReActAgent Configuration", + text: + "ReActAgent must only contain Tool nodes as child nodes.", + icon: "error", + confirmButtonText: "Ok" + }); + return false; } + } } - - let unmatchedConfigNames = [...agentModelConfigNames].filter(name => !modelConfigNames.has(name)); - console.log("modelConfigNames", modelConfigNames); - console.log("agentModelConfigNames", agentModelConfigNames); - console.log("unmatchedConfigNames", unmatchedConfigNames); - if (hasModelTypeError) { + if (node.name === "IfElsePipeline") { + const elementsSize = node.data.elements.length; + if (elementsSize !== 1 && elementsSize !== 2) { Swal.fire({ - title: 'Error!', - text: - 'Error: At least one Model node must be present.', - icon: 'error', - confirmButtonText: 'Ok' + title: "Invalid IfElsePipeline Configuration", + text: `IfElsePipeline should have 1 or 2 elements, but has ${elementsSize}.`, + icon: "error", + confirmButtonText: "Ok" }); - } else if (hasAgentError) { + return false; + } + } + if (["ForLoopPipeline", "WhileLoopPipeline", "MsgHub"].includes(node.name)) { + if (node.data.elements.length !== 1) { + hasError = true; Swal.fire({ - title: 'No Agent Nodes Found', - text: "Error: At least one Agent node must be present.", - icon: 'error', - confirmButtonText: 'Ok' + title: "Invalid Configuration", + text: `${node.name} must have exactly one element.`, + icon: "error", + confirmButtonText: "Ok" }); - } else if (unmatchedConfigNames.length > 0) { + return false; + } + const childNodeId = node.data.elements[0]; + const childNode = nodesData[childNodeId]; + if (!childNode || !childNode.name.includes("Pipeline")) { Swal.fire({ - title: 'Configuration Mismatch', - html: - "Each Agent's 'Model config name' must match a Model node's 'Config Name'.
    Unmatched: " + unmatchedConfigNames.join(', '), - icon: 'error', - confirmButtonText: 'Ok' + title: "Invalid Configuration", + text: + ` ${childNode.name} contained in ${node.name} is not a Pipeline node.`, + icon: "error", + confirmButtonText: "Ok" }); - } else if (isApiKeyEmpty) { + return false; + } + } + if (node.name === "Code") { + const code = node.data.args.code; + const pattern = /\bdef\s+function\s*\(/; + + if (!pattern.test(code)) { Swal.fire({ - title: 'API KEY Missing', - text: - "API KEY is missing in your model nodes. Please either enter the API KEY in the corresponding position, or enter a random bit of content and replace it with the real value in the exported files.", - icon: 'error', - confirmButtonText: 'Ok' + title: "Invalid Code Function Name", + text: `${node.name} only support "function" as the function name.`, + icon: "error", + confirmButtonText: "Ok" }); - } else { - return true; + return false; + } } + } + + const unmatchedConfigNames = [...agentModelConfigNames].filter(name => !modelConfigNames.has(name)); + console.log("modelConfigNames", modelConfigNames); + console.log("agentModelConfigNames", agentModelConfigNames); + console.log("unmatchedConfigNames", unmatchedConfigNames); + if (hasModelTypeError) { + Swal.fire({ + title: "Error!", + text: + "Error: At least one Model node must be present.", + icon: "error", + confirmButtonText: "Ok" + }); + } else if (hasAgentError) { + Swal.fire({ + title: "No Agent Nodes Found", + text: "Error: At least one Agent node must be present.", + icon: "error", + confirmButtonText: "Ok" + }); + } else if (unmatchedConfigNames.length > 0) { + Swal.fire({ + title: "Configuration Mismatch", + html: + "Each Agent's 'Model config name' must match a Model node's 'Config Name'.
    Unmatched: " + unmatchedConfigNames.join(", "), + icon: "error", + confirmButtonText: "Ok" + }); + } else if (isApiKeyEmpty) { + Swal.fire({ + title: "API KEY Missing", + text: + "API KEY is missing in your model nodes. Please either enter the API KEY in the corresponding position, or enter a random bit of content and replace it with the real value in the exported files.", + icon: "error", + confirmButtonText: "Ok" + }); + } else { + return true; + } } function showCheckPopup() { - var btnCovers = document.querySelectorAll('.btn-cover'); - if (checkConditions()) { - Swal.fire({ - title: 'Validation Success', - text: "All checks are passed!", - icon: 'success', - confirmButtonText: 'Great!' - }); - btnCovers.forEach(function (btnCover) { - var button = btnCover.querySelector('.btn-disabled'); - if (button) { - button.classList.remove('btn-disabled'); - } - btnCover.removeAttribute('data-title'); - }); - } + const btnCovers = document.querySelectorAll(".btn-cover"); + if (checkConditions()) { + Swal.fire({ + title: "Validation Success", + text: "All checks are passed!", + icon: "success", + confirmButtonText: "Great!" + }); + btnCovers.forEach(function (btnCover) { + const button = btnCover.querySelector(".btn-disabled"); + if (button) { + button.classList.remove("btn-disabled"); + } + btnCover.removeAttribute("data-title"); + }); + } } function disableButtons() { - var btnCovers = document.querySelectorAll('.btn-cover'); + const btnCovers = document.querySelectorAll(".btn-cover"); - btnCovers.forEach(function (btnCover) { - var button = btnCover.querySelector('div'); - if (button) { - button.classList.add('btn-disabled'); - } - btnCover.setAttribute('data-title', - 'Please click the "Check" button first.'); - }); + btnCovers.forEach(function (btnCover) { + const button = btnCover.querySelector("div"); + if (button) { + button.classList.add("btn-disabled"); + } + btnCover.setAttribute("data-title", + "Please click the \"Check\" button first."); + }); } function showExportPyPopup() { - if (checkConditions()) { - const rawData = editor.export(); + if (checkConditions()) { + const rawData = editor.export(); - const hasError = sortElementsByPosition(rawData); - if (hasError) { - return; - } + const hasError = sortElementsByPosition(rawData); + if (hasError) { + return; + } - const filteredData = reorganizeAndFilterConfigForAgentScope(rawData); + const filteredData = reorganizeAndFilterConfigForAgentScope(rawData); - Swal.fire({ - title: 'Processing...', - text: 'Please wait.', - allowOutsideClick: false, - willOpen: () => { - Swal.showLoading() - } - }); + Swal.fire({ + title: "Processing...", + text: "Please wait.", + allowOutsideClick: false, + willOpen: () => { + Swal.showLoading(); + } + }); - fetch('/convert-to-py', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - data: JSON.stringify(filteredData, null, 4), - }) - }).then(response => { - if (!response.ok) { - throw new Error('Network error.'); - } - return response.json(); - }) - .then(data => { - Swal.close(); - if (data.is_success === 'True') { - Swal.fire({ - title: 'Workflow Python Code', - html: - '

    Save as main.py
    ' + - 'Then run the following command in your terminal:
    ' + - '

    python main.py

    ' + - 'or
    as_gradio main.py

    ' + - '
    ' +
    +    fetch("/convert-to-py", {
    +      method: "POST",
    +      headers: {
    +        "Content-Type": "application/json",
    +      },
    +      body: JSON.stringify({
    +        data: JSON.stringify(filteredData, null, 4),
    +      })
    +    }).then(response => {
    +      if (!response.ok) {
    +        throw new Error("Network error.");
    +      }
    +      return response.json();
    +    })
    +      .then(data => {
    +        Swal.close();
    +        if (data.is_success === "True") {
    +          Swal.fire({
    +            title: "Workflow Python Code",
    +            html:
    +                            "

    Save as main.py
    " + + "Then run the following command in your terminal:
    " + + "

    python main.py

    " + + "or
    as_gradio main.py

    " + + "
    " +
                                 data.py_code +
    -                            '
    ', - showCloseButton: true, - showCancelButton: true, - confirmButtonText: 'Copy', - cancelButtonText: 'Close', - willOpen: (element) => { - const codeElement = element.querySelector('code'); - Prism.highlightElement(codeElement); - const copyButton = Swal.getConfirmButton(); - copyButton.addEventListener('click', () => { - copyToClipboard(codeElement.textContent); - }); - } - }); - } else { - const errorMessage = ` + "
    ", + showCloseButton: true, + showCancelButton: true, + confirmButtonText: "Copy", + cancelButtonText: "Close", + willOpen: (element) => { + const codeElement = element.querySelector("code"); + Prism.highlightElement(codeElement); + const copyButton = Swal.getConfirmButton(); + copyButton.addEventListener("click", () => { + copyToClipboard(codeElement.textContent); + }); + } + }); + } else { + const errorMessage = `

    An error occurred during the Python code generation process. Please check the following error:

    ${data.py_code}
    `; - Swal.fire({ - title: 'Error!', - html: errorMessage, - icon: 'error', - customClass: { - popup: 'error-popup' - }, - confirmButtonText: 'Close', - willOpen: (element) => { - const codeElement = element.querySelector('code'); - Prism.highlightElement(codeElement); - } - }); - } - }) - .catch(error => { - console.error('Error:', error); - Swal.fire('Failed!', - 'There was an error generating your code.', - 'error'); - }); - } + Swal.fire({ + title: "Error!", + html: errorMessage, + icon: "error", + customClass: { + popup: "error-popup" + }, + confirmButtonText: "Close", + willOpen: (element) => { + const codeElement = element.querySelector("code"); + Prism.highlightElement(codeElement); + } + }); + } + }) + .catch(error => { + console.error("Error:", error); + Swal.fire("Failed!", + "There was an error generating your code.", + "error"); + }); + } } function showExportRunPopup(version) { - if (version === "local") { - showExportRunLocalPopup(); - } else { - showExportRunMSPopup(); - } + if (version === "local") { + showExportRunLocalPopup(); + } else { + showExportRunMSPopup(); + } } function showExportRunLocalPopup() { - if (checkConditions()) { - const rawData = editor.export(); - const hasError = sortElementsByPosition(rawData); - if (hasError) { - return; - } - const filteredData = reorganizeAndFilterConfigForAgentScope(rawData); + if (checkConditions()) { + const rawData = editor.export(); + const hasError = sortElementsByPosition(rawData); + if (hasError) { + return; + } + const filteredData = reorganizeAndFilterConfigForAgentScope(rawData); - Swal.fire({ - title: 'Processing...', - text: 'Please wait.', - allowOutsideClick: false, - willOpen: () => { - Swal.showLoading() - } - }); + Swal.fire({ + title: "Processing...", + text: "Please wait.", + allowOutsideClick: false, + willOpen: () => { + Swal.showLoading(); + } + }); - fetch('/convert-to-py-and-run', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - data: JSON.stringify(filteredData, null, 4), - }) - }).then(response => { - if (!response.ok) { - throw new Error('Network error.'); - } - return response.json(); - }) - .then(data => { - Swal.close(); - if (data.is_success === 'True') { - Swal.fire({ - title: 'Application Running in Background', - html: - '

    Your application has been successfully run ' + - 'in background.
    ' + - '

    Task ID:' + - data.run_id + '

    ' + - '
    ' +
    +    fetch("/convert-to-py-and-run", {
    +      method: "POST",
    +      headers: {
    +        "Content-Type": "application/json",
    +      },
    +      body: JSON.stringify({
    +        data: JSON.stringify(filteredData, null, 4),
    +      })
    +    }).then(response => {
    +      if (!response.ok) {
    +        throw new Error("Network error.");
    +      }
    +      return response.json();
    +    })
    +      .then(data => {
    +        Swal.close();
    +        if (data.is_success === "True") {
    +          Swal.fire({
    +            title: "Application Running in Background",
    +            html:
    +                            "

    Your application has been successfully run " + + "in background.
    " + + "

    Task ID:" + + data.run_id + "

    " + + "
    " +
                                 data.py_code +
    -                            '
    ', - showCloseButton: true, - showCancelButton: true, - confirmButtonText: 'Copy Code', - cancelButtonText: 'Close', - willOpen: (element) => { - const codeElement = element.querySelector('code'); - Prism.highlightElement(codeElement); - const copyButton = Swal.getConfirmButton(); - copyButton.addEventListener('click', () => { - copyToClipboard(codeElement.textContent); - }); - } - }); - } else { - const errorMessage = ` + "
    ", + showCloseButton: true, + showCancelButton: true, + confirmButtonText: "Copy Code", + cancelButtonText: "Close", + willOpen: (element) => { + const codeElement = element.querySelector("code"); + Prism.highlightElement(codeElement); + const copyButton = Swal.getConfirmButton(); + copyButton.addEventListener("click", () => { + copyToClipboard(codeElement.textContent); + }); + } + }); + } else { + const errorMessage = `

    An error occurred during the Python code running process. Please check the following error:

    ${data.py_code}
    `; - Swal.fire({ - title: 'Error!', - html: errorMessage, - icon: 'error', - customClass: { - popup: 'error-popup' - }, - confirmButtonText: 'Close', - willOpen: (element) => { - const codeElement = element.querySelector('code'); - Prism.highlightElement(codeElement); - } - }); - } - }) - .catch(error => { - console.error('Error:', error); - Swal.close(); - Swal.fire('Failed!', - 'There was an error running your workflow.', - 'error'); - }); - } + Swal.fire({ + title: "Error!", + html: errorMessage, + icon: "error", + customClass: { + popup: "error-popup" + }, + confirmButtonText: "Close", + willOpen: (element) => { + const codeElement = element.querySelector("code"); + Prism.highlightElement(codeElement); + } + }); + } + }) + .catch(error => { + console.error("Error:", error); + Swal.close(); + Swal.fire("Failed!", + "There was an error running your workflow.", + "error"); + }); + } } function filterOutApiKey(obj) { - for (let key in obj) { - if (typeof obj[key] === 'object' && obj[key] !== null) { - filterOutApiKey(obj[key]); - } - if (key === 'api_key') { - delete obj[key]; - } + for (const key in obj) { + if (typeof obj[key] === "object" && obj[key] !== null) { + filterOutApiKey(obj[key]); + } + if (key === "api_key") { + delete obj[key]; } + } } function showExportRunMSPopup() { - if (checkConditions()) { - Swal.fire({ - title: 'Are you sure to run the workflow in ModelScope Studio?', - text: + if (checkConditions()) { + Swal.fire({ + title: "Are you sure to run the workflow in ModelScope Studio?", + text: "You are about to navigate to another page. " + "Please make sure all the configurations are set " + "besides your api-key " + "(your api-key should be set in ModelScope Studio page).", - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: 'Yes, create it!', - cancelButtonText: 'Close' - }).then((result) => { - if (result.isConfirmed) { - const rawData = editor.export(); - const hasError = sortElementsByPosition(rawData); - if (hasError) { - return; - } - const filteredData = reorganizeAndFilterConfigForAgentScope(rawData); - filterOutApiKey(filteredData) - - Swal.fire({ - title: 'Processing...', - text: 'Please wait.', - allowOutsideClick: false, - willOpen: () => { - Swal.showLoading() - } - }); - fetch('/upload-to-oss', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - data: JSON.stringify(filteredData, null, 4), - }) - }) - .then(response => response.json()) - .then(data => { - const params = {'CONFIG_URL': data.config_url}; - const paramsStr = encodeURIComponent(JSON.stringify(params)); - const org = "agentscope"; - const fork_repo = "agentscope_workstation"; - const url = `https://www.modelscope.cn/studios/fork?target=${org}/${fork_repo}&overwriteEnv=${paramsStr}`; - window.open(url, '_blank'); - Swal.fire('Success!', '', 'success'); - }) - .catch(error => { - console.error('Error:', error); - Swal.fire('Failed', data.message || 'An error occurred while uploading to oss', 'error'); - }); - } + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, create it!", + cancelButtonText: "Close" + }).then((result) => { + if (result.isConfirmed) { + const rawData = editor.export(); + const hasError = sortElementsByPosition(rawData); + if (hasError) { + return; + } + const filteredData = reorganizeAndFilterConfigForAgentScope(rawData); + filterOutApiKey(filteredData); + + Swal.fire({ + title: "Processing...", + text: "Please wait.", + allowOutsideClick: false, + willOpen: () => { + Swal.showLoading(); + } + }); + fetch("/upload-to-oss", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + data: JSON.stringify(filteredData, null, 4), + }) }) - } + .then(response => response.json()) + .then(data => { + const params = {"CONFIG_URL": data.config_url}; + const paramsStr = encodeURIComponent(JSON.stringify(params)); + const org = "agentscope"; + const fork_repo = "agentscope_workstation"; + const url = `https://www.modelscope.cn/studios/fork?target=${org}/${fork_repo}&overwriteEnv=${paramsStr}`; + window.open(url, "_blank"); + Swal.fire("Success!", "", "success"); + }) + .catch(error => { + console.error("Error:", error); + Swal.fire("Failed", data.message || "An error occurred while uploading to oss", "error"); + }); + } + }); + } } function showExportHTMLPopup() { - const rawData = editor.export(); - - // Remove the html attribute from the nodes to avoid inconsistencies in html - removeHtmlFromUsers(rawData); - const hasError = sortElementsByPosition(rawData); - if (hasError) { - return; - } - - const exportData = JSON.stringify(rawData, null, 4); + const rawData = editor.export(); + + // Remove the html attribute from the nodes to avoid inconsistencies in html + removeHtmlFromUsers(rawData); + const hasError = sortElementsByPosition(rawData); + if (hasError) { + return; + } + + const exportData = JSON.stringify(rawData, null, 4); + + const escapedExportData = exportData + .replace(//g, ">"); + + Swal.fire({ + title: "Workflow HTML", + html: + "

    This is used for generating HTML code, not for running.
    " + + "

    "
    +            + escapedExportData +
    +            "
    ", + showCloseButton: true, + showCancelButton: true, + confirmButtonText: "Copy", + cancelButtonText: "Close", + willOpen: (element) => { + // Find the code element inside the Swal content + const codeElement = element.querySelector("code"); - const escapedExportData = exportData - .replace(//g, '>'); + // Now highlight the code element with Prism + Prism.highlightElement(codeElement); - Swal.fire({ - title: 'Workflow HTML', - html: - '

    This is used for generating HTML code, not for running.
    ' + - '

    '
    -            + escapedExportData +
    -            '
    ', - showCloseButton: true, - showCancelButton: true, - confirmButtonText: 'Copy', - cancelButtonText: 'Close', - willOpen: (element) => { - // Find the code element inside the Swal content - const codeElement = element.querySelector('code'); - - // Now highlight the code element with Prism - Prism.highlightElement(codeElement); - - // Copy to clipboard logic - const content = codeElement.textContent; - const copyButton = Swal.getConfirmButton(); - copyButton.addEventListener('click', () => { - copyToClipboard(content); - }); - } - }); + // Copy to clipboard logic + const content = codeElement.textContent; + const copyButton = Swal.getConfirmButton(); + copyButton.addEventListener("click", () => { + copyToClipboard(content); + }); + } + }); } function isValidDataStructure(data) { - if ( - data.hasOwnProperty('drawflow') && - data.drawflow.hasOwnProperty('Home') && - data.drawflow.Home.hasOwnProperty('data') - ) { - - for (const nodeId in data.drawflow.Home.data) { - const node = data.drawflow.Home.data[nodeId]; - - if ( - !node.hasOwnProperty('id') || - typeof node.id !== 'number' || - !node.hasOwnProperty('name') || - typeof node.name !== 'string' || - !node.hasOwnProperty('class') || - typeof node.class !== 'string' - ) { - return false; - } - } - return true; + if ( + data.hasOwnProperty("drawflow") && + data.drawflow.hasOwnProperty("Home") && + data.drawflow.Home.hasOwnProperty("data") + ) { + + for (const nodeId in data.drawflow.Home.data) { + const node = data.drawflow.Home.data[nodeId]; + + if ( + !node.hasOwnProperty("id") || + typeof node.id !== "number" || + !node.hasOwnProperty("name") || + typeof node.name !== "string" || + !node.hasOwnProperty("class") || + typeof node.class !== "string" + ) { + return false; + } } - return false; + return true; + } + return false; } function showImportHTMLPopup() { - Swal.fire({ - title: 'Import Workflow Data', - html: + Swal.fire({ + title: "Import Workflow Data", + html: "

    Please paste your HTML data below. Ensure that the source of the HTML data is trusted, as importing HTML from unknown or untrusted sources may pose security risks.

    ", - input: 'textarea', - inputLabel: 'Paste your HTML data here:', - inputPlaceholder: - 'Paste your HTML data generated from `Export HTML` button...', - inputAttributes: { - 'aria-label': 'Paste your HTML data here', - 'class': 'code' - }, - customClass: { - input: 'code' - }, - showCancelButton: true, - confirmButtonText: 'Import', - cancelButtonText: 'Cancel', - inputValidator: (value) => { - if (!value) { - return 'You need to paste code generated from `Export HTML` button!'; - } - try { - const parsedData = JSON.parse(value); - if (isValidDataStructure(parsedData)) { - - } else { - return 'The data is invalid. Please check your data and try again.'; - } - } catch (e) { - return 'Invalid data! You need to paste code generated from `Export HTML` button!'; - } - }, - preConfirm: (data) => { - try { - const parsedData = JSON.parse(data); - - // Add html source code to the nodes data - addHtmlAndReplacePlaceHolderBeforeImport(parsedData) - .then(() => { - editor.clear(); - editor.import(parsedData); - importSetupNodes(parsedData); - Swal.fire('Imported!', '', 'success'); - }); - - } catch (error) { - Swal.showValidationMessage(`Import error: ${error}`); - } - } - }); + input: "textarea", + inputLabel: "Paste your HTML data here:", + inputPlaceholder: + "Paste your HTML data generated from `Export HTML` button...", + inputAttributes: { + "aria-label": "Paste your HTML data here", + "class": "code" + }, + customClass: { + input: "code" + }, + showCancelButton: true, + confirmButtonText: "Import", + cancelButtonText: "Cancel", + inputValidator: (value) => { + if (!value) { + return "You need to paste code generated from `Export HTML` button!"; + } + try { + const parsedData = JSON.parse(value); + if (isValidDataStructure(parsedData)) { + + } else { + return "The data is invalid. Please check your data and try again."; + } + } catch (e) { + return "Invalid data! You need to paste code generated from `Export HTML` button!"; + } + }, + preConfirm: (data) => { + try { + const parsedData = JSON.parse(data); + + // Add html source code to the nodes data + addHtmlAndReplacePlaceHolderBeforeImport(parsedData) + .then(() => { + editor.clear(); + editor.import(parsedData); + importSetupNodes(parsedData); + Swal.fire("Imported!", "", "success"); + }); + + } catch (error) { + Swal.showValidationMessage(`Import error: ${error}`); + } + } + }); } function showSaveWorkflowPopup() { - Swal.fire({ - title: 'Save Workflow', - input: 'text', - inputPlaceholder: 'Enter filename', - showCancelButton: true, - confirmButtonText: 'Save', - cancelButtonText: 'Cancel' - }).then(result => { - if (result.isConfirmed) { - const filename = result.value; - saveWorkflow(filename); - } - }); + Swal.fire({ + title: "Save Workflow", + input: "text", + inputPlaceholder: "Enter filename", + showCancelButton: true, + confirmButtonText: "Save", + cancelButtonText: "Cancel" + }).then(result => { + if (result.isConfirmed) { + const filename = result.value; + saveWorkflow(filename); + } + }); } function saveWorkflow(fileName) { - const rawData = editor.export(); - filterOutApiKey(rawData) - - // Remove the html attribute from the nodes to avoid inconsistencies in html - removeHtmlFromUsers(rawData); - - const exportData = JSON.stringify(rawData, null, 4); - fetch('/save-workflow', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - filename: fileName, - workflow: exportData, - overwrite: false, - }) - }).then(response => response.json()) - .then(data => { - if (data.message === "Workflow file saved successfully") { - Swal.fire('Success', data.message, 'success'); - } else { - Swal.fire('Error', data.message || 'An error occurred while saving the workflow.', 'error'); - } - }) - .catch(error => { - console.error('Error:', error); - Swal.fire('Error', 'An error occurred while saving the workflow.', 'error'); - }); + const rawData = editor.export(); + filterOutApiKey(rawData); + + // Remove the html attribute from the nodes to avoid inconsistencies in html + removeHtmlFromUsers(rawData); + + const exportData = JSON.stringify(rawData, null, 4); + fetch("/save-workflow", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + filename: fileName, + workflow: exportData, + overwrite: false, + }) + }).then(response => response.json()) + .then(data => { + if (data.message === "Workflow file saved successfully") { + Swal.fire("Success", data.message, "success"); + } else { + Swal.fire("Error", data.message || "An error occurred while saving the workflow.", "error"); + } + }) + .catch(error => { + console.error("Error:", error); + Swal.fire("Error", "An error occurred while saving the workflow.", "error"); + }); } function showLoadWorkflowPopup() { - fetch('/list-workflows', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}) - }) - .then(response => response.json()) - .then(data => { - if (!Array.isArray(data.files)) { - throw new TypeError('The return data is not an array'); + fetch("/list-workflows", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({}) + }) + .then(response => response.json()) + .then(data => { + if (!Array.isArray(data.files)) { + throw new TypeError("The return data is not an array"); + } + const inputOptions = data.files.reduce((options, file) => { + options[file] = file; + return options; + }, {}); + Swal.fire({ + title: "Loading Workflow from Disks", + input: "select", + inputOptions: inputOptions, + inputPlaceholder: "Select", + showCancelButton: true, + showDenyButton: true, + confirmButtonText: "Load", + cancelButtonText: "Cancel", + denyButtonText: "Delete", + didOpen: () => { + const selectElement = Swal.getInput(); + selectElement.addEventListener("change", (event) => { + selectedFilename = event.target.value; + }); + } + }).then(result => { + if (result.isConfirmed) { + loadWorkflow(selectedFilename); + } else if (result.isDenied) { + Swal.fire({ + title: `Are you sure to delete ${selectedFilename}?`, + text: "This operation cannot be undone!", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#d33", + cancelButtonColor: "#3085d6", + confirmButtonText: "Delete", + cancelButtonText: "Cancel" + }).then((deleteResult) => { + if (deleteResult.isConfirmed) { + deleteWorkflow(selectedFilename); } - const inputOptions = data.files.reduce((options, file) => { - options[file] = file; - return options; - }, {}); - Swal.fire({ - title: 'Loading Workflow from Disks', - input: 'select', - inputOptions: inputOptions, - inputPlaceholder: 'Select', - showCancelButton: true, - showDenyButton: true, - confirmButtonText: 'Load', - cancelButtonText: 'Cancel', - denyButtonText: 'Delete', - didOpen: () => { - const selectElement = Swal.getInput(); - selectElement.addEventListener('change', (event) => { - selectedFilename = event.target.value; - }); - } - }).then(result => { - if (result.isConfirmed) { - loadWorkflow(selectedFilename); - } else if (result.isDenied) { - Swal.fire({ - title: `Are you sure to delete ${selectedFilename}?`, - text: "This operation cannot be undone!", - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#d33', - cancelButtonColor: '#3085d6', - confirmButtonText: 'Delete', - cancelButtonText: 'Cancel' - }).then((deleteResult) => { - if (deleteResult.isConfirmed) { - deleteWorkflow(selectedFilename); - } - }); - } - }); - }) - .catch(error => { - console.error('Error:', error); - Swal.fire('Error', 'An error occurred while loading the workflow.', 'error'); - }); + }); + } + }); + }) + .catch(error => { + console.error("Error:", error); + Swal.fire("Error", "An error occurred while loading the workflow.", "error"); + }); } function loadWorkflow(fileName) { - fetch('/load-workflow', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - filename: fileName, - }) - }).then(response => response.json()) - .then(data => { - if (data.error) { - Swal.fire('Error', data.error, 'error'); - } else { - console.log(data) - try { - // Add html source code to the nodes data - addHtmlAndReplacePlaceHolderBeforeImport(data) - .then(() => { - console.log(data) - editor.clear(); - editor.import(data); - importSetupNodes(data); - Swal.fire('Imported!', '', 'success'); - }); - - } catch (error) { - Swal.showValidationMessage(`Import error: ${error}`); - } - Swal.fire('Success', 'Workflow loaded successfully', 'success'); - } - }) - .catch(error => { - console.error('Error:', error); - Swal.fire('Error', 'An error occurred while loading the workflow.', 'error'); - }); + fetch("/load-workflow", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + filename: fileName, + }) + }).then(response => response.json()) + .then(data => { + if (data.error) { + Swal.fire("Error", data.error, "error"); + } else { + console.log(data); + try { + // Add html source code to the nodes data + addHtmlAndReplacePlaceHolderBeforeImport(data) + .then(() => { + console.log(data); + editor.clear(); + editor.import(data); + importSetupNodes(data); + Swal.fire("Imported!", "", "success"); + }); + + } catch (error) { + Swal.showValidationMessage(`Import error: ${error}`); + } + Swal.fire("Success", "Workflow loaded successfully", "success"); + } + }) + .catch(error => { + console.error("Error:", error); + Swal.fire("Error", "An error occurred while loading the workflow.", "error"); + }); } function deleteWorkflow(fileName) { - fetch('/delete-workflow', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - filename: fileName, - }) - }).then(response => response.json()) - .then(data => { - if (data.error) { - Swal.fire('Error', data.error, 'error'); - } else { - Swal.fire('Deleted!', 'Workflow has been deleted.', 'success'); - } - }) - .catch(error => { - console.error('Error:', error); - Swal.fire('Error', 'An error occurred while deleting the workflow.', 'error'); - }); + fetch("/delete-workflow", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + filename: fileName, + }) + }).then(response => response.json()) + .then(data => { + if (data.error) { + Swal.fire("Error", data.error, "error"); + } else { + Swal.fire("Deleted!", "Workflow has been deleted.", "success"); + } + }) + .catch(error => { + console.error("Error:", error); + Swal.fire("Error", "An error occurred while deleting the workflow.", "error"); + }); } function removeHtmlFromUsers(data) { - Object.keys(data.drawflow.Home.data).forEach((nodeId) => { - const node = data.drawflow.Home.data[nodeId]; - // Remove the html attribute from the node - delete node.html; - }); + Object.keys(data.drawflow.Home.data).forEach((nodeId) => { + const node = data.drawflow.Home.data[nodeId]; + // Remove the html attribute from the node + delete node.html; + }); } async function fetchHtmlSourceCodeByName(name) { - // Fetch the HTML source code from the cache if it exists - if (name in htmlCache) { - return htmlCache[name]; - } + // Fetch the HTML source code from the cache if it exists + if (name in htmlCache) { + return htmlCache[name]; + } - // Load the HTML source code - let htmlSourceCode = await fetchHtml(nameToHtmlFile[name]); - htmlCache[name] = htmlSourceCode; - return htmlSourceCode; + // Load the HTML source code + const htmlSourceCode = await fetchHtml(nameToHtmlFile[name]); + htmlCache[name] = htmlSourceCode; + return htmlSourceCode; } async function addHtmlAndReplacePlaceHolderBeforeImport(data) { - const idPlaceholderRegex = /ID_PLACEHOLDER/g; - for (const nodeId of Object.keys(data.drawflow.Home.data)) { - const node = data.drawflow.Home.data[nodeId]; - if (!node.html) { - if (node.name === "readme") { - // Remove the node if its name is "readme" - delete data.drawflow.Home.data[nodeId]; - continue; // Skip to the next iteration - } - console.log(node.name) - const sourceCode = await fetchHtmlSourceCodeByName(node.name); + const idPlaceholderRegex = /ID_PLACEHOLDER/g; + for (const nodeId of Object.keys(data.drawflow.Home.data)) { + const node = data.drawflow.Home.data[nodeId]; + if (!node.html) { + if (node.name === "readme") { + // Remove the node if its name is "readme" + delete data.drawflow.Home.data[nodeId]; + continue; // Skip to the next iteration + } + console.log(node.name); + const sourceCode = await fetchHtmlSourceCodeByName(node.name); - // Add new html attribute to the node - console.log(sourceCode) - node.html = sourceCode.replace(idPlaceholderRegex, nodeId); - } + // Add new html attribute to the node + console.log(sourceCode); + node.html = sourceCode.replace(idPlaceholderRegex, nodeId); } + } } function importSetupNodes(dataToImport) { - Object.keys(dataToImport.drawflow.Home.data).forEach((nodeId) => { - // import the node use addNode function - disableButtons(); - makeNodeTop(nodeId); - setupNodeListeners(nodeId); + Object.keys(dataToImport.drawflow.Home.data).forEach((nodeId) => { + // import the node use addNode function + disableButtons(); + makeNodeTop(nodeId); + setupNodeListeners(nodeId); + setupNodeCopyListens(nodeId); + addEventListenersToNumberInputs(nodeId); + setupTextInputListeners(nodeId); + reloadi18n(); + const nodeElement = document.getElementById(`node-${nodeId}`); + if (nodeElement) { + const copyButton = nodeElement.querySelector(".button.copy-button"); + if (copyButton) { setupNodeCopyListens(nodeId); - addEventListenersToNumberInputs(nodeId); - setupTextInputListeners(nodeId); - reloadi18n(); - const nodeElement = document.getElementById(`node-${nodeId}`); - if (nodeElement) { - const copyButton = nodeElement.querySelector('.button.copy-button'); - if (copyButton) { - setupNodeCopyListens(nodeId); - } - } - }); + } + } + }); } function copyToClipboard(contentToCopy) { - var tempTextarea = document.createElement("textarea"); - tempTextarea.value = contentToCopy; - document.body.appendChild(tempTextarea); - tempTextarea.select(); - tempTextarea.setSelectionRange(0, 99999); - - try { - var successful = document.execCommand("copy"); - if (successful) { - Swal.fire('Copied!', '', 'success'); - } else { - Swal.fire('Failed to copy', '', 'error'); - } - } catch (err) { - Swal.fire('Failed to copy', '', 'error'); + const tempTextarea = document.createElement("textarea"); + tempTextarea.value = contentToCopy; + document.body.appendChild(tempTextarea); + tempTextarea.select(); + tempTextarea.setSelectionRange(0, 99999); + + try { + const successful = document.execCommand("copy"); + if (successful) { + Swal.fire("Copied!", "", "success"); + } else { + Swal.fire("Failed to copy", "", "error"); } - document.body.removeChild(tempTextarea); + } catch (err) { + Swal.fire("Failed to copy", "", "error"); + } + document.body.removeChild(tempTextarea); } function fetchExample(index, processData) { - fetch('/read-examples', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - data: index, - // lang: getCookie('locale') || 'en', - lang: 'en', - }) - }).then(response => { - if (!response.ok) { - throw new Error('Network error.'); - } - return response.json(); + fetch("/read-examples", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + data: index, + // lang: getCookie('locale') || 'en', + lang: "en", }) - .then(processData); + }).then(response => { + if (!response.ok) { + throw new Error("Network error."); + } + return response.json(); + }) + .then(processData); } function importExample(index) { - fetchExample(index, data => { - const dataToImport = data.json; - - addHtmlAndReplacePlaceHolderBeforeImport(dataToImport) - .then(() => { - clearModuleSelected(); - editor.import(dataToImport); - Object.keys(dataToImport.drawflow.Home.data).forEach((nodeId) => { - setupNodeListeners(nodeId); - setupTextInputListeners(nodeId); - const nodeElement = document.getElementById(`node-${nodeId}`); - if (nodeElement) { - const copyButton = nodeElement.querySelector('.button.copy-button'); - if (copyButton) { - setupNodeCopyListens(nodeId); - } - } - }); - reloadi18n(); - }); - }) + fetchExample(index, data => { + const dataToImport = data.json; + + addHtmlAndReplacePlaceHolderBeforeImport(dataToImport) + .then(() => { + clearModuleSelected(); + editor.import(dataToImport); + Object.keys(dataToImport.drawflow.Home.data).forEach((nodeId) => { + setupNodeListeners(nodeId); + setupTextInputListeners(nodeId); + const nodeElement = document.getElementById(`node-${nodeId}`); + if (nodeElement) { + const copyButton = nodeElement.querySelector(".button.copy-button"); + if (copyButton) { + setupNodeCopyListens(nodeId); + } + } + }); + reloadi18n(); + }); + }); } function importExample_step(index) { - if (!localStorage.getItem('firstGuide')) { - localStorage.setItem('firstGuide', 'true'); - skipGuide(); - } - fetchExample(index, data => { - const dataToImportStep = data.json; - addHtmlAndReplacePlaceHolderBeforeImport(dataToImportStep).then(() => { - clearModuleSelected(); - descriptionStep = ["Readme", "Model", "UserAgent", - "DialogAgent"]; - initializeImport(dataToImportStep); - }) + if (!localStorage.getItem("firstGuide")) { + localStorage.setItem("firstGuide", "true"); + skipGuide(); + } + fetchExample(index, data => { + const dataToImportStep = data.json; + addHtmlAndReplacePlaceHolderBeforeImport(dataToImportStep).then(() => { + clearModuleSelected(); + descriptionStep = ["Readme", "Model", "UserAgent", + "DialogAgent"]; + initializeImport(dataToImportStep); }); + }); } function updateImportButtons() { - document.getElementById('import-prev').disabled = currentImportIndex + document.getElementById("import-prev").disabled = currentImportIndex <= 1; - document.getElementById('import-next').disabled = currentImportIndex >= importQueue.length; - document.getElementById('import-skip').disabled = currentImportIndex >= importQueue.length; - reloadi18n() + document.getElementById("import-next").disabled = currentImportIndex >= importQueue.length; + document.getElementById("import-skip").disabled = currentImportIndex >= importQueue.length; + reloadi18n(); } -function createElement(tag, id, html = '', parent = document.body) { - let element = document.getElementById(id) || document.createElement(tag); - element.id = id; - element.innerHTML = html; - if (!element.parentNode) { - parent.appendChild(element); - } - return element; +function createElement(tag, id, html = "", parent = document.body) { + const element = document.getElementById(id) || document.createElement(tag); + element.id = id; + element.innerHTML = html; + if (!element.parentNode) { + parent.appendChild(element); + } + return element; } function initializeImport(data) { - ['menu-btn', 'menu-btn-svg'].forEach(cls => { - let containers = document.getElementsByClassName(cls); - Array.from(containers).forEach(container => container.style.display = 'none'); - }); + ["menu-btn", "menu-btn-svg"].forEach(cls => { + const containers = document.getElementsByClassName(cls); + Array.from(containers).forEach(container => container.style.display = "none"); + }); - createElement('div', 'left-sidebar-blur', '', document.body).style.cssText = ` + createElement("div", "left-sidebar-blur", "", document.body).style.cssText = ` position: fixed; top: 60px; left: 0; bottom: 0; width: 250px; background: rgba(128, 128, 128, 0.7); filter: blur(2px); z-index: 1000; cursor: not-allowed; `; - createElement('div', 'import-buttons', '', document.body); + createElement("div", "import-buttons", "", document.body); - dataToImportStep = data; - importQueue = Object.keys(dataToImportStep.drawflow.Home.data); + dataToImportStep = data; + importQueue = Object.keys(dataToImportStep.drawflow.Home.data); - const importButtonsDiv = document.getElementById('import-buttons'); + const importButtonsDiv = document.getElementById("import-buttons"); - createElement('div', 'step-info', '', importButtonsDiv); - createElement('button', 'import-prev', - ' Previous', - importButtonsDiv).onclick = importPreviousComponent; - createElement('button', 'import-next', - ' Next', - importButtonsDiv).onclick = importNextComponent; - createElement('button', 'import-skip', - ' Skip', - importButtonsDiv).onclick = importSkipComponent; - createElement('button', 'import-quit', - ' Quit', - importButtonsDiv).onclick = importQuitComponent; - createElement('div', 'step-warning', - 'Caution: You are currently in the tutorial mode where modifications are restricted.
    Please click Quit to exit and start creating your custom multi-agent applications. ', document.body); + createElement("div", "step-info", "", importButtonsDiv); + createElement("button", "import-prev", + " Previous", + importButtonsDiv).onclick = importPreviousComponent; + createElement("button", "import-next", + " Next", + importButtonsDiv).onclick = importNextComponent; + createElement("button", "import-skip", + " Skip", + importButtonsDiv).onclick = importSkipComponent; + createElement("button", "import-quit", + " Quit", + importButtonsDiv).onclick = importQuitComponent; + createElement("div", "step-warning", + "Caution: You are currently in the tutorial mode where modifications are restricted.
    Please click Quit to exit and start creating your custom multi-agent applications. ", document.body); - accumulatedImportData = {}; - currentImportIndex = 0; - importNextComponent(); + accumulatedImportData = {}; + currentImportIndex = 0; + importNextComponent(); - updateImportButtons(); + updateImportButtons(); } function importPreviousComponent() { - if (currentImportIndex > 0) { - currentImportIndex--; - accumulatedImportData = Object.assign({}, ...importQueue.slice(0, currentImportIndex).map(k => ({[k]: dataToImportStep.drawflow.Home.data[k]}))); - editor.import({drawflow: {Home: {data: accumulatedImportData}}}); - updateStepInfo(); - } - updateImportButtons(); + if (currentImportIndex > 0) { + currentImportIndex--; + accumulatedImportData = Object.assign({}, ...importQueue.slice(0, currentImportIndex).map(k => ({[k]: dataToImportStep.drawflow.Home.data[k]}))); + editor.import({drawflow: {Home: {data: accumulatedImportData}}}); + updateStepInfo(); + } + updateImportButtons(); } function importNextComponent() { - const nodeId = importQueue[currentImportIndex]; - accumulatedImportData[nodeId] = dataToImportStep.drawflow.Home.data[nodeId]; + const nodeId = importQueue[currentImportIndex]; + accumulatedImportData[nodeId] = dataToImportStep.drawflow.Home.data[nodeId]; - editor.import({drawflow: {Home: {data: accumulatedImportData}}}); - currentImportIndex++; - updateStepInfo(); - updateImportButtons(); + editor.import({drawflow: {Home: {data: accumulatedImportData}}}); + currentImportIndex++; + updateStepInfo(); + updateImportButtons(); } function importSkipComponent() { - accumulatedImportData = Object.assign({}, ...importQueue.map(k => ({[k]: dataToImportStep.drawflow.Home.data[k]}))); - editor.import({drawflow: {Home: {data: accumulatedImportData}}}); - currentImportIndex = importQueue.length; - updateImportButtons(); - updateStepInfo(); + accumulatedImportData = Object.assign({}, ...importQueue.map(k => ({[k]: dataToImportStep.drawflow.Home.data[k]}))); + editor.import({drawflow: {Home: {data: accumulatedImportData}}}); + currentImportIndex = importQueue.length; + updateImportButtons(); + updateStepInfo(); } function importQuitComponent() { - clearModuleSelected(); - ['menu-btn', 'menu-btn-svg'].forEach(cls => { - let containers = document.getElementsByClassName(cls); - Array.from(containers).forEach(container => container.style.display = ''); - }); + clearModuleSelected(); + ["menu-btn", "menu-btn-svg"].forEach(cls => { + const containers = document.getElementsByClassName(cls); + Array.from(containers).forEach(container => container.style.display = ""); + }); } function updateStepInfo() { - let stepInfoDiv = document.getElementById('step-info'); - if (stepInfoDiv && currentImportIndex > 0) { - stepInfoDiv.innerHTML = + const stepInfoDiv = document.getElementById("step-info"); + if (stepInfoDiv && currentImportIndex > 0) { + stepInfoDiv.innerHTML = `Current Step (${currentImportIndex}/${importQueue.length})
    ${descriptionStep[currentImportIndex - 1]}`; - } else if (stepInfoDiv) { - stepInfoDiv.innerHTML = 'No steps to display.'; - } + } else if (stepInfoDiv) { + stepInfoDiv.innerHTML = "No steps to display."; + } } function clearModuleSelected() { - editor.clearModuleSelected(); + editor.clearModuleSelected(); - let importButtonsDiv = document.getElementById("import-buttons"); - if (importButtonsDiv) { - importButtonsDiv.remove(); - } + const importButtonsDiv = document.getElementById("import-buttons"); + if (importButtonsDiv) { + importButtonsDiv.remove(); + } - let stepWarningDiv = document.getElementById("step-warning"); - if (stepWarningDiv) { - stepWarningDiv.remove(); - } + const stepWarningDiv = document.getElementById("step-warning"); + if (stepWarningDiv) { + stepWarningDiv.remove(); + } - let blurDiv = document.getElementById('left-sidebar-blur'); - if (blurDiv) { - blurDiv.remove(); - } + const blurDiv = document.getElementById("left-sidebar-blur"); + if (blurDiv) { + blurDiv.remove(); + } } function getCookie(name) { - var matches = document.cookie.match(new RegExp( - "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)" - )); - return matches ? decodeURIComponent(matches[1]) : undefined; + const matches = document.cookie.match(new RegExp( + "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") + "=([^;]*)" + )); + return matches ? decodeURIComponent(matches[1]) : undefined; } function showSurveyModal() { - document.getElementById("surveyModal").style.display = "block"; + document.getElementById("surveyModal").style.display = "block"; } function hideSurveyModal() { - document.getElementById("surveyModal").style.display = "none"; + document.getElementById("surveyModal").style.display = "none"; } function reloadi18n() { - let currentLang = getCookie('locale') || 'en'; - $("[i18n]").i18n({ - defaultLang: currentLang, - filePath: "../static/i18n/", - filePrefix: "i18n_", - fileSuffix: "", - forever: true, - callback: function () { - } - }); + const currentLang = getCookie("locale") || "en"; + $("[i18n]").i18n({ + defaultLang: currentLang, + filePath: "../static/i18n/", + filePrefix: "i18n_", + fileSuffix: "", + forever: true, + callback: function () { + } + }); } -window.addEventListener('storage', function (event) { - if (event.key === 'locale') { - reloadi18n() - } +window.addEventListener("storage", function (event) { + if (event.key === "locale") { + reloadi18n(); + } }, false); function startGuide() { - const targetElement = document.querySelector('.guide-Example'); - const element = document.querySelector('.tour-guide'); - positionElementRightOf(element, targetElement); + const targetElement = document.querySelector(".guide-Example"); + const element = document.querySelector(".tour-guide"); + positionElementRightOf(element, targetElement); } function getElementCoordinates(targetElement) { - const style = window.getComputedStyle(targetElement); - const rect = targetElement.getBoundingClientRect(); - return { - left: rect.left + (parseFloat(style.left) || 0), - top: rect.top + (parseFloat(style.top) || 0), - right: rect.right + (parseFloat(style.top) || 0), - bottom: rect.bottom + (parseFloat(style.top) || 0), - width: rect.width, - height: rect.height, - x: rect.x, - y: rect.y, - }; + const style = window.getComputedStyle(targetElement); + const rect = targetElement.getBoundingClientRect(); + return { + left: rect.left + (parseFloat(style.left) || 0), + top: rect.top + (parseFloat(style.top) || 0), + right: rect.right + (parseFloat(style.top) || 0), + bottom: rect.bottom + (parseFloat(style.top) || 0), + width: rect.width, + height: rect.height, + x: rect.x, + y: rect.y, + }; } function positionElementRightOf(element, targetElement) { - const targetCoordinates = getElementCoordinates(targetElement); - const mask = document.querySelector(".overlay"); - mask.style.display = "block"; - element.style.position = 'absolute'; - element.style.display = 'block'; - element.style.left = `${targetCoordinates.x + targetCoordinates.right}px`; - element.style.top = `${targetCoordinates.y}px`; + const targetCoordinates = getElementCoordinates(targetElement); + const mask = document.querySelector(".overlay"); + mask.style.display = "block"; + element.style.position = "absolute"; + element.style.display = "block"; + element.style.left = `${targetCoordinates.x + targetCoordinates.right}px`; + element.style.top = `${targetCoordinates.y}px`; } function skipGuide() { - const element = document.querySelector(".tour-guide"); - const mask = document.querySelector(".overlay"); - localStorage.setItem('firstGuide', 'true'); - if (element) { - element.style.display = "none"; - element.remove(); - mask.style.display = "none"; - mask.remove(); - } + const element = document.querySelector(".tour-guide"); + const mask = document.querySelector(".overlay"); + localStorage.setItem("firstGuide", "true"); + if (element) { + element.style.display = "none"; + element.remove(); + mask.style.display = "none"; + mask.remove(); + } } class Notification { - static count = 0; - static instances = []; - - static clearInstances() { - Notification.count = 0; - Notification.instances = []; - } - - constructor(props) { - Notification.count += 1; - Notification.instances.push(this); - this.currentIndex = Notification.count; - this.position = 'bottom-right'; - this.title = 'Notification Title'; - this.content = 'Notification Content'; - this.element = null; - this.closeBtn = true; - this.progress = false; - this.intervalTime = 3000; - this.confirmBtn = false; - this.cancelBtn = false; + static initStatics() { + this.count = 0; + this.instances = []; + } + + static clearInstances() { + Notification.count = 0; + Notification.instances = []; + } + + constructor(props) { + Notification.count += 1; + Notification.instances.push(this); + this.currentIndex = Notification.count; + this.position = "bottom-right"; + this.title = "Notification Title"; + this.content = "Notification Content"; + this.element = null; + this.closeBtn = true; + this.progress = false; + this.intervalTime = 3000; + this.confirmBtn = false; + this.cancelBtn = false; + this.pause = true; + this.reduceNumber = 0; + + // 绑定 this + this.destroyAll = this.destroyAll.bind(this); + this.onCancelCallback = this.onCancelCallback.bind(this); + this.onConfirmCallback = this.onConfirmCallback.bind(this); + + this.init(props); + } + + init(props) { + this.setDefaultValues(props); + this.element = document.createElement("div"); + this.element.className = "notification"; + this.title && this.renderTitle(getCookie("locale") == "zh" ? props.i18nTitle : this.title); + this.closeBtn && this.renderCloseButton(); + this.content && this.renderContent(getCookie("locale") == "zh" ? props.i18nContent : this.content); + (this.confirmBtn || this.cancelBtn) && this.renderClickButton(); + this.progress && this.renderProgressBar(); + this.setPosition(this.position); + document.body.appendChild(this.element); + setTimeout(() => { + this.show(); + }, 10); + } + + isHTMLString(string) { + const doc = new DOMParser().parseFromString(string, "text/html"); + return Array.from(doc.body.childNodes).some(node => node.nodeType === 1); + } + + renderCloseButton() { + this.closeBtn = document.createElement("span"); + this.closeBtn.className = "notification-close"; + this.closeBtn.innerText = "X"; + this.closeBtn.onclick = this.destroyAll; + this.title.appendChild(this.closeBtn); + } + + renderTitle(component) { + if (this.isHTMLString(component)) { + this.title = document.createElement("div"); + this.title.className = "notification-title"; + this.title.innerHTML = component; + } else { + this.title = document.createElement("div"); + this.titleText = document.createElement("div"); + this.title.className = "notification-title"; + this.titleText.className = "notification-titleText"; + this.titleText.innerText = component; + this.title.appendChild(this.titleText); + } + this.element.appendChild(this.title); + } + + renderContent(component) { + if (this.isHTMLString(component)) { + this.content = document.createElement("div"); + this.content.className = "notification-content"; + this.content.innerHTML = component; + } else { + this.content = document.createElement("div"); + this.content.className = "notification-content"; + this.content.innerText = component; + } + this.element.appendChild(this.content); + } + + renderClickButton() { + if (this.confirmBtn || this.cancelBtn) { + this.clickBottonBox = document.createElement("div"); + this.clickBottonBox.className = "notification-clickBotton-box"; + } + if (this.confirmBtn) { + this.confirmBotton = document.createElement("button"); + this.confirmBotton.className = "notification-btn confirmBotton"; + this.confirmBotton.innerText = getCookie("locale") == "zh" ? this.i18nConfirmBtn : this.confirmBtn; + this.confirmBotton.onclick = this.onConfirmCallback; + this.clickBottonBox.appendChild(this.confirmBotton); + } + if (this.cancelBtn) { + this.cancelBotton = document.createElement("button"); + this.cancelBotton.className = "notification-btn cancelBotton"; + this.cancelBotton.innerText = getCookie("locale") == "zh" ? this.i18nCancelBtn : this.cancelBtn; + this.cancelBotton.onclick = this.onCancelCallback; + this.clickBottonBox.appendChild(this.cancelBotton); + } + this.element.appendChild(this.clickBottonBox); + } + + renderProgressBar() { + this.progressBar = document.createElement("div"); + this.progressBar.className = "notification-progress"; + this.element.appendChild(this.progressBar); + } + + stepProgressBar(callback) { + const startTime = performance.now(); + const step = (timestamp) => { + const progress = Math.min((timestamp + this.reduceNumber - startTime) / this.intervalTime, 1); + this.progressBar.style.width = (1 - progress) * 100 + "%"; + if (progress < 1 && this.pause === false) { + requestAnimationFrame(step); + } else { + this.reduceNumber = timestamp + this.reduceNumber - startTime; + } + if (progress === 1) { this.pause = true; this.reduceNumber = 0; - this.init(props); - } - - init(props) { - this.setDefaultValues(props); - this.element = document.createElement('div'); - // init notification-box css - this.element.className = 'notification'; - // render title - this.title && this.renderTitle(getCookie("locale") == "zh" ? props.i18nTitle : this.title); - // render closeButtion - this.closeBtn && this.renderCloseButton(); - // render content - this.content && this.renderContent(getCookie("locale") == "zh" ? props.i18nContent : this.content); - // render confirmBtn - (this.confirmBtn || this.cancelBtn) && this.renderClickButton(); - this.progress && this.renderProgressBar(); - // set position - this.setPosition(this.position); - document.body.appendChild(this.element); - setTimeout(() => { - this.show(); - }, 10) - } - - // check if string is HTML - isHTMLString(string) { - const doc = new DOMParser().parseFromString(string, 'text/html'); - return Array.from(doc.body.childNodes).some(node => node.nodeType === 1); - } - - // render closeButtion - renderCloseButton() { - this.closeBtn = document.createElement('span'); - this.closeBtn.className = 'notification-close'; - this.closeBtn.innerText = 'X'; - this.closeBtn.onclick = this.destroyAll.bind(this); - this.title.appendChild(this.closeBtn); - } - - // render title string or HTML - renderTitle(component) { - if (this.isHTMLString(component)) { - this.title = document.createElement('div'); - this.title.className = 'notification-title'; - this.title.innerHTML = component; - } else { - this.title = document.createElement('div'); - this.titleText = document.createElement('div'); - this.title.className = 'notification-title'; - this.titleText.className = 'notification-titleText'; - this.titleText.innerText = component; - this.title.appendChild(this.titleText); - } - this.element.appendChild(this.title); - } - - // render content string or HTML - renderContent(component) { - if (this.isHTMLString(component)) { - this.content = document.createElement('div'); - this.content.className = 'notification-content'; - this.content.innerHTML = component; - } else { - this.content = document.createElement('div'); - this.content.className = 'notification-content'; - this.content.innerText = component; - } - this.element.appendChild(this.content); - } - - // render clickbtn - renderClickButton() { - if (this.confirmBtn || this.cancelBtn) { - this.clickBottonBox = document.createElement('div'); - this.clickBottonBox.className = 'notification-clickBotton-box'; - } - if (this.confirmBtn) { - this.confirmBotton = document.createElement('button'); - this.confirmBotton.className = 'notification-btn confirmBotton'; - this.confirmBotton.innerText = getCookie("locale") == "zh" ? this.i18nConfirmBtn : this.confirmBtn; - this.confirmBotton.onclick = this.onConfirmCallback.bind(this); - this.clickBottonBox.appendChild(this.confirmBotton); - } - if (this.cancelBtn) { - this.cancelBotton = document.createElement('button'); - this.cancelBotton.className = 'notification-btn cancelBotton'; - this.cancelBotton.innerText = getCookie("locale") == "zh" ? this.i18nCancelBtn : this.cancelBtn; - this.cancelBotton.onclick = this.onCancelCallback.bind(this); - this.clickBottonBox.appendChild(this.cancelBotton); - } - this.element.appendChild(this.clickBottonBox); - } - - // render progress bar - renderProgressBar() { - this.progressBar = document.createElement('div'); - this.progressBar.className = 'notification-progress'; - this.element.appendChild(this.progressBar); - } - - // stepProgressBar - stepProgressBar(callback) { - let startTime = performance.now(); - const step = (timestamp) => { - const progress = Math.min((timestamp + this.reduceNumber - startTime) / this.intervalTime, 1); - this.progressBar.style.width = (1 - progress) * 100 + '%'; - if (progress < 1 && this.pause == false) { - requestAnimationFrame(step) - } else { - this.reduceNumber = timestamp + this.reduceNumber - startTime - } - if (progress == 1) { - this.pause == true; - this.reduceNumber = 0; - callback(); - this.removeChild(); - } - } - requestAnimationFrame(step); - } - - setDefaultValues(props) { - for (const key in props) { - if (props[key] === undefined) { - return; - } else { - this[key] = props[key]; - } - } - } - - setPosition() { - switch (this.position) { - case 'top-left': - this.element.style.top = '25px'; - this.element.style.left = '-100%'; - break; - case 'top-right': - this.element.style.top = '25px'; - this.element.style.right = '-100%'; - break; - case 'bottom-right': - this.element.style.bottom = '25px'; - this.element.style.right = '-100%'; - break; - case 'bottom-left': - this.element.style.bottom = '25px'; - this.element.style.left = '-100%'; - break; - } - } - - show() { - this.element.style.display = 'flex'; - switch (this.position) { - case 'top-left': - this.element.style.top = '25px'; - this.element.style.left = '25px'; - break; - case 'top-right': - this.element.style.top = '25px'; - this.element.style.right = '25px'; - break; - case 'bottom-right': - this.element.style.bottom = '25px'; - this.element.style.right = '25px'; - break; - case 'bottom-left': - this.element.style.bottom = '25px'; - this.element.style.left = '25px'; - break; - } - } - - // hide() { - // // this.element.style.display = 'none'; - // } - destroyAll() { - for (const instance of Notification.instances) { - document.body.removeChild(instance.element); - } - Notification.clearInstances(); - } - - removeChild() { - let removeIndex; - for (let i = 0; i < Notification.instances.length; i++) { - if (Notification.instances[i].currentIndex === this.currentIndex) { - removeIndex = i; - break; - } - } - if (removeIndex !== undefined) { - Notification.instances.splice(removeIndex, 1); - } - this.element.remove(); - } - - addCloseListener() { - this.closeBtn.addEventListener('click', () => { - this.removeChild(); - }); - } + callback(); + this.removeChild(); + } + }; + requestAnimationFrame(step); + } + + setDefaultValues(props) { + for (const key in props) { + if (props[key] !== undefined) { + this[key] = props[key]; + } + } + } + + setPosition() { + switch (this.position) { + case "top-left": + this.element.style.top = "25px"; + this.element.style.left = "-100%"; + break; + case "top-right": + this.element.style.top = "25px"; + this.element.style.right = "-100%"; + break; + case "bottom-right": + this.element.style.bottom = "25px"; + this.element.style.right = "-100%"; + break; + case "bottom-left": + this.element.style.bottom = "25px"; + this.element.style.left = "-100%"; + break; + } + } + + show() { + this.element.style.display = "flex"; + switch (this.position) { + case "top-left": + this.element.style.top = "25px"; + this.element.style.left = "25px"; + break; + case "top-right": + this.element.style.top = "25px"; + this.element.style.right = "25px"; + break; + case "bottom-right": + this.element.style.bottom = "25px"; + this.element.style.right = "25px"; + break; + case "bottom-left": + this.element.style.bottom = "25px"; + this.element.style.left = "25px"; + break; + } + } + + destroyAll() { + for (const instance of Notification.instances) { + document.body.removeChild(instance.element); + } + Notification.clearInstances(); + } + + removeChild() { + let removeIndex; + for (let i = 0; i < Notification.instances.length; i++) { + if (Notification.instances[i].currentIndex === this.currentIndex) { + removeIndex = i; + break; + } + } + if (removeIndex !== undefined) { + Notification.instances.splice(removeIndex, 1); + } + this.element.remove(); + } + + addCloseListener() { + this.closeBtn.addEventListener("click", () => { + this.removeChild(); + }); + } - onCancelCallback() { - if (typeof this.onCancel === 'function') { - this.onCancel(); - this.removeChild(); - } + onCancelCallback() { + if (typeof this.onCancel === "function") { + this.onCancel(); + this.removeChild(); } + } - onConfirmCallback() { - if (typeof this.onConfirm === 'function') { - this.pause = !this.pause - if (!this.pause) { - this.stepProgressBar(this.onConfirm); - this.confirmBotton.innerText = getCookie("locale") == "zh" ? '暂停' : 'pause' - } else { - this.confirmBotton.innerText = this.confirmBtn - } - } + onConfirmCallback() { + if (typeof this.onConfirm === "function") { + this.pause = !this.pause; + if (!this.pause) { + this.stepProgressBar(this.onConfirm); + this.confirmBotton.innerText = getCookie("locale") === "zh" ? "暂停" : "pause"; + } else { + this.confirmBotton.innerText = this.confirmBtn; + } } + } } +// 初始化静态属性 +Notification.initStatics(); + function createNotification(props) { - new Notification(props); + new Notification(props); } function setCookie(name, value, days) { - var expires = ""; - if (days) { - var date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - expires = "; expires=" + date.toUTCString(); - } - document.cookie = name + "=" + (value || "") + expires + "; path=/"; + let expires = ""; + if (days) { + const date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toUTCString(); + } + document.cookie = name + "=" + (value || "") + expires + "; path=/"; } -document.addEventListener('DOMContentLoaded', function() { - showTab('tab1'); +document.addEventListener("DOMContentLoaded", function () { + showTab("tab1"); }); function sendWorkflow(fileName) { - Swal.fire({ - text: 'Are you sure you want to import this workflow?', - icon: 'warning', - showCancelButton: true, - confirmButtonText: 'Yes, import it!', - cancelButtonText: 'Cancel' - }).then((result) => { - if (result.isConfirmed) { - const workstationUrl = '/workstation?filename=' + encodeURIComponent(fileName); - window.location.href = workstationUrl; - } - }); + Swal.fire({ + text: "Are you sure you want to import this workflow?", + icon: "warning", + showCancelButton: true, + confirmButtonText: "Yes, import it!", + cancelButtonText: "Cancel" + }).then((result) => { + if (result.isConfirmed) { + const workstationUrl = "/workstation?filename=" + encodeURIComponent(fileName); + window.location.href = workstationUrl; + } + }); } function showEditorTab() { - document.getElementById('col-right').style.display = 'block'; - document.getElementById('col-right2').style.display = 'none'; - console.log('Show Editor'); + document.getElementById("col-right").style.display = "block"; + document.getElementById("col-right2").style.display = "none"; + console.log("Show Editor"); } + function importGalleryWorkflow(data) { - try { - const parsedData = JSON.parse(data); - addHtmlAndReplacePlaceHolderBeforeImport(parsedData) - .then(() => { - editor.clear(); - editor.import(parsedData); - importSetupNodes(parsedData); - Swal.fire({ - title: 'Imported!', - icon: 'success', - showConfirmButton: true - }).then((result) => { - if (result.isConfirmed) { - showEditorTab(); - } - }); - }); - } catch (error) { - Swal.showValidationMessage(`Import error: ${error}`); - } + try { + const parsedData = JSON.parse(data); + addHtmlAndReplacePlaceHolderBeforeImport(parsedData) + .then(() => { + editor.clear(); + editor.import(parsedData); + importSetupNodes(parsedData); + Swal.fire({ + title: "Imported!", + icon: "success", + showConfirmButton: true + }).then((result) => { + if (result.isConfirmed) { + showEditorTab(); + } + }); + }); + } catch (error) { + Swal.showValidationMessage(`Import error: ${error}`); + } } function deleteWorkflow(fileName) { - Swal.fire({ - title: 'Are you sure?', - text: "Workflow will be deleted!", - icon: 'warning', - showCancelButton: true, - confirmButtonColor: '#3085d6', - cancelButtonColor: '#d33', - confirmButtonText: 'Yes, delete it!' - }).then((result) => { - if (result.isConfirmed) { - fetch('/delete-workflow', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - filename: fileName, - }) - }).then(response => response.json()) - .then(data => { - if (data.error) { - Swal.fire('Error', data.error, 'error'); - } else { - showLoadWorkflowList('tab2'); - Swal.fire('Deleted!', 'Your workflow has been deleted.', 'success'); - } - }) - .catch(error => { - console.error('Error:', error); - Swal.fire('Error', 'Delete workflow error.', 'error'); - }); - } - }); + Swal.fire({ + title: "Are you sure?", + text: "Workflow will be deleted!", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + fetch("/delete-workflow", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + filename: fileName, + }) + }).then(response => response.json()) + .then(data => { + if (data.error) { + Swal.fire("Error", data.error, "error"); + } else { + showLoadWorkflowList("tab2"); + Swal.fire("Deleted!", "Your workflow has been deleted.", "success"); + } + }) + .catch(error => { + console.error("Error:", error); + Swal.fire("Error", "Delete workflow error.", "error"); + }); + } + }); } function showTab(tabId) { - var tabs = document.getElementsByClassName("tab"); - for (var i = 0; i < tabs.length; i++) { - tabs[i].classList.remove("active"); - tabs[i].style.display = "none"; - } - var tab = document.getElementById(tabId); - if (tab) { - tab.classList.add("active"); - tab.style.display = "block"; - - var tabButtons = document.getElementsByClassName("tab-button"); - for (var j = 0; j < tabButtons.length; j++) { - tabButtons[j].classList.remove("active"); - } - var activeTabButton = document.querySelector(`.tab-button[onclick*="${tabId}"]`); - if (activeTabButton) { - activeTabButton.classList.add("active"); - } + const tabs = document.getElementsByClassName("tab"); + for (let i = 0; i < tabs.length; i++) { + tabs[i].classList.remove("active"); + tabs[i].style.display = "none"; + } + const tab = document.getElementById(tabId); + if (tab) { + tab.classList.add("active"); + tab.style.display = "block"; - if (tabId === "tab2") { - showLoadWorkflowList(tabId); - } else if (tabId === "tab1") { - showGalleryWorkflowList(tabId); - } + const tabButtons = document.getElementsByClassName("tab-button"); + for (let j = 0; j < tabButtons.length; j++) { + tabButtons[j].classList.remove("active"); + } + const activeTabButton = document.querySelector(`.tab-button[onclick*="${tabId}"]`); + if (activeTabButton) { + activeTabButton.classList.add("active"); } + + if (tabId === "tab2") { + showLoadWorkflowList(tabId); + } else if (tabId === "tab1") { + showGalleryWorkflowList(tabId); + } + } } let galleryWorkflows = []; function showGalleryWorkflowList(tabId) { - const container = document.getElementById(tabId).querySelector('.grid-container'); - container.innerHTML = ''; - fetch('/fetch-gallery', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}) - }) + const container = document.getElementById(tabId).querySelector(".grid-container"); + container.innerHTML = ""; + fetch("/fetch-gallery", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({}) + }) .then(response => response.json()) .then(data => { - galleryWorkflows = data.json || []; // 存储获取到的工作流数据 - galleryWorkflows.forEach((workflow, index) => { - const meta = workflow.meta; - const title = meta.title; - const author = meta.author; - const time = meta.time; - const thumbnail = meta.thumbnail || generateThumbnailFromContent(meta); - createGridItem(title, container, thumbnail, author, time, false, index); // 将index传递给createGridItem - }); + galleryWorkflows = data.json || []; // 存储获取到的工作流数据 + galleryWorkflows.forEach((workflow, index) => { + const meta = workflow.meta; + const title = meta.title; + const author = meta.author; + const time = meta.time; + const thumbnail = meta.thumbnail || generateThumbnailFromContent(meta); + createGridItem(title, container, thumbnail, author, time, false, index); // 将index传递给createGridItem + }); }) .catch(error => { - console.error('Error fetching gallery workflows:', error); + console.error("Error fetching gallery workflows:", error); }); } -function createGridItem(workflowName, container, thumbnail, author = '', time = '', showDeleteButton = false, index) { - var gridItem = document.createElement('div'); - gridItem.className = 'grid-item'; - gridItem.style.borderRadius = '15px'; - var gridItem = document.createElement('div'); - gridItem.className = 'grid-item'; - gridItem.style.borderRadius = '15px'; - gridItem.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; - - var img = document.createElement('div'); - img.className = 'thumbnail'; - img.style.backgroundImage = `url('${thumbnail}')`; - img.style.backgroundSize = 'cover'; - img.style.backgroundPosition = 'center'; - gridItem.appendChild(img); - - var caption = document.createElement('div'); - caption.className = 'caption'; - caption.style.backgroundColor = 'white'; - - var h6 = document.createElement('h6'); - h6.textContent = workflowName; - h6.style.margin = '1px 0'; - - var pAuthor = document.createElement('p'); - pAuthor.textContent = `Author: ${author}`; - pAuthor.style.margin = '1px 0'; - pAuthor.style.fontSize = '10px'; - - var pTime = document.createElement('p'); - pTime.textContent = `Date: ${time}`; - pTime.style.margin = '1px 0'; - pTime.style.fontSize = '10px'; - - var button = document.createElement('button'); - button.textContent = ' Load '; - button.className = 'button'; - button.style.backgroundColor = '#007aff'; - button.style.color = 'white'; - button.style.padding = '2px 7px'; - button.style.border = 'none'; - button.style.borderRadius = '8px'; - button.style.fontSize = '12px'; - button.style.cursor = 'pointer'; - button.style.transition = 'background 0.3s'; - - button.addEventListener('mouseover', function () { - button.style.backgroundColor = '#005bb5'; +function createGridItem(workflowName, container, thumbnail, author = "", time = "", showDeleteButton = false, index) { + var gridItem = document.createElement("div"); + gridItem.className = "grid-item"; + gridItem.style.borderRadius = "15px"; + var gridItem = document.createElement("div"); + gridItem.className = "grid-item"; + gridItem.style.borderRadius = "15px"; + gridItem.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.2)"; + + const img = document.createElement("div"); + img.className = "thumbnail"; + img.style.backgroundImage = `url('${thumbnail}')`; + img.style.backgroundSize = "cover"; + img.style.backgroundPosition = "center"; + gridItem.appendChild(img); + + const caption = document.createElement("div"); + caption.className = "caption"; + caption.style.backgroundColor = "white"; + + const h6 = document.createElement("h6"); + h6.textContent = workflowName; + h6.style.margin = "1px 0"; + + const pAuthor = document.createElement("p"); + pAuthor.textContent = `Author: ${author}`; + pAuthor.style.margin = "1px 0"; + pAuthor.style.fontSize = "10px"; + + const pTime = document.createElement("p"); + pTime.textContent = `Date: ${time}`; + pTime.style.margin = "1px 0"; + pTime.style.fontSize = "10px"; + + const button = document.createElement("button"); + button.textContent = " Load "; + button.className = "button"; + button.style.backgroundColor = "#007aff"; + button.style.color = "white"; + button.style.padding = "2px 7px"; + button.style.border = "none"; + button.style.borderRadius = "8px"; + button.style.fontSize = "12px"; + button.style.cursor = "pointer"; + button.style.transition = "background 0.3s"; + + button.addEventListener("mouseover", function () { + button.style.backgroundColor = "#005bb5"; + }); + + button.addEventListener("mouseout", function () { + button.style.backgroundColor = "#007aff"; + }); + button.onclick = function (e) { + e.preventDefault(); + if (showDeleteButton) { + sendWorkflow(workflowName); + } else { + const workflowData = galleryWorkflows[index]; + importGalleryWorkflow(JSON.stringify(workflowData)); + } + }; + + caption.appendChild(h6); + if (author) { + caption.appendChild(pAuthor); + } + if (time) { + caption.appendChild(pTime); + } + caption.appendChild(button); + + if (showDeleteButton) { + const deleteButton = document.createElement("button"); + deleteButton.textContent = "Delete"; + deleteButton.className = "button"; + deleteButton.style.backgroundColor = "#007aff"; + deleteButton.style.color = "white"; + deleteButton.style.padding = "2px 3px"; + deleteButton.style.border = "none"; + deleteButton.style.borderRadius = "8px"; + deleteButton.style.fontSize = "12px"; + deleteButton.style.cursor = "pointer"; + deleteButton.style.transition = "background 0.3s"; + + deleteButton.addEventListener("mouseover", function () { + deleteButton.style.backgroundColor = "#005bb5"; }); - - button.addEventListener('mouseout', function () { - button.style.backgroundColor = '#007aff'; + deleteButton.addEventListener("mouseout", function () { + deleteButton.style.backgroundColor = "#007aff"; }); - button.onclick = function (e) { - e.preventDefault(); - if (showDeleteButton) { - sendWorkflow(workflowName); - } else { - const workflowData = galleryWorkflows[index]; - importGalleryWorkflow(JSON.stringify(workflowData)); - } - }; - - caption.appendChild(h6); - if (author) caption.appendChild(pAuthor); - if (time) caption.appendChild(pTime); - caption.appendChild(button); - if (showDeleteButton) { - var deleteButton = document.createElement('button'); - deleteButton.textContent = 'Delete'; - deleteButton.className = 'button'; - deleteButton.style.backgroundColor = '#007aff'; - deleteButton.style.color = 'white'; - deleteButton.style.padding = '2px 3px'; - deleteButton.style.border = 'none'; - deleteButton.style.borderRadius = '8px'; - deleteButton.style.fontSize = '12px'; - deleteButton.style.cursor = 'pointer'; - deleteButton.style.transition = 'background 0.3s'; - - deleteButton.addEventListener('mouseover', function () { - deleteButton.style.backgroundColor = '#005bb5'; - }); - deleteButton.addEventListener('mouseout', function () { - deleteButton.style.backgroundColor = '#007aff'; - }); - - deleteButton.onclick = function (e) { - e.preventDefault(); - deleteWorkflow(workflowName); - }; + deleteButton.onclick = function (e) { + e.preventDefault(); + deleteWorkflow(workflowName); + }; - caption.appendChild(deleteButton); - } + caption.appendChild(deleteButton); + } - gridItem.appendChild(caption); - container.appendChild(gridItem); - console.log('Grid item appended:', gridItem); + gridItem.appendChild(caption); + container.appendChild(gridItem); + console.log("Grid item appended:", gridItem); } function showLoadWorkflowList(tabId) { - fetch('/list-workflows', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}) + fetch("/list-workflows", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({}) + }) + .then(response => response.json()) + .then(data => { + if (!Array.isArray(data.files)) { + throw new TypeError("The return data is not an array"); + } + + const container = document.getElementById(tabId).querySelector(".grid-container"); + container.innerHTML = ""; + + data.files.forEach(workflowName => { + const title = workflowName.replace(/\.json$/, ""); + const thumbnail = generateThumbnailFromContent({title}); + createGridItem(title, container, thumbnail, "", "", true); + }); }) - .then(response => response.json()) - .then(data => { - if (!Array.isArray(data.files)) { - throw new TypeError('The return data is not an array'); - } - - const container = document.getElementById(tabId).querySelector('.grid-container'); - container.innerHTML = ''; - - data.files.forEach(workflowName => { - const title = workflowName.replace(/\.json$/, ''); - const thumbnail = generateThumbnailFromContent({title}); - createGridItem(title, container, thumbnail, '', '', true); - }); - }) - .catch(error => { - console.error('Error fetching workflow list:', error); - alert('Fetch workflow list error.'); - }); + .catch(error => { + console.error("Error fetching workflow list:", error); + alert("Fetch workflow list error."); + }); } function generateThumbnailFromContent(content) { - const canvas = document.createElement('canvas'); - canvas.width = 150; - canvas.height = 150; - const ctx = canvas.getContext('2d'); + const canvas = document.createElement("canvas"); + canvas.width = 150; + canvas.height = 150; + const ctx = canvas.getContext("2d"); - ctx.fillStyle = '#f0f0f0'; - ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "#f0f0f0"; + ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.font = 'italic bold 14px "Helvetica Neue", sans-serif'; - ctx.textAlign = 'center'; - ctx.fillStyle = '#333'; + ctx.font = "italic bold 14px \"Helvetica Neue\", sans-serif"; + ctx.textAlign = "center"; + ctx.fillStyle = "#333"; - ctx.fillText(content.title, canvas.width / 2, canvas.height / 2 + 20); + ctx.fillText(content.title, canvas.width / 2, canvas.height / 2 + 20); - return canvas.toDataURL(); + return canvas.toDataURL(); } diff --git a/src/agentscope/studio/static/js/workstation_iframe.js b/src/agentscope/studio/static/js/workstation_iframe.js index cc6d9209a..2aa23dc24 100644 --- a/src/agentscope/studio/static/js/workstation_iframe.js +++ b/src/agentscope/studio/static/js/workstation_iframe.js @@ -1,3 +1,3 @@ -var iframe = document.getElementById('workstation-iframe'); -var currentUrl = window.location.protocol + '//' + window.location.host + '/workstation'; +var iframe = document.getElementById("workstation-iframe"); +var currentUrl = window.location.protocol + "//" + window.location.host + "/workstation"; iframe.src = currentUrl; \ No newline at end of file From 8c99a95f912843901119032b14c33f2235960b3b Mon Sep 17 00:00:00 2001 From: "weirui.kwr@alibaba-inc.com" Date: Mon, 28 Oct 2024 14:51:14 +0800 Subject: [PATCH 28/47] fix conflicts --- src/agentscope/service/browser/markpage.js | 204 +++++++++--------- .../studio/static/js/workstation.js | 3 +- 2 files changed, 104 insertions(+), 103 deletions(-) diff --git a/src/agentscope/service/browser/markpage.js b/src/agentscope/service/browser/markpage.js index f1c49cd54..6fdbe4f67 100644 --- a/src/agentscope/service/browser/markpage.js +++ b/src/agentscope/service/browser/markpage.js @@ -5,132 +5,132 @@ tarsier js code: https://github.com/reworkd/tarsier/blob/main/tarsier/tag_utils. //Get the visible interactive elements on the current web page function getInteractiveElements() { - let allElements = Array.prototype.slice.call( - document.querySelectorAll('*') - ) + const allElements = Array.prototype.slice.call( + document.querySelectorAll("*") + ); - var items = allElements.map(function(element) { - var vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); - var vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0); + var items = allElements.map(function(element) { + var vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); + var vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0); - var elementClassName = (typeof element.className === 'string' ? element.className : '').replace(/\s+/g, '.'); + var elementClassName = (typeof element.className === "string" ? element.className : "").replace(/\s+/g, "."); - var rects = [...element.getClientRects()].filter(bb => { - var center_x = bb.left + bb.width / 2; - var center_y = bb.top + bb.height / 2; - var elAtCenter = document.elementFromPoint(center_x, center_y); - return elAtCenter === element || element.contains(elAtCenter) - }).map(bb => { - const rect = { - left: Math.max(0, bb.left), - top: Math.max(0, bb.top), - right: Math.min(vw, bb.right), - bottom: Math.min(vh, bb.bottom) - }; - return { - ...rect, - width: rect.right - rect.left, - height: rect.bottom - rect.top - }; - }); - var area = rects.reduce((acc, rect) => acc + rect.width * rect.height, 0); - return { - element: element, - include: + var rects = [...element.getClientRects()].filter(bb => { + var center_x = bb.left + bb.width / 2; + var center_y = bb.top + bb.height / 2; + var elAtCenter = document.elementFromPoint(center_x, center_y); + return elAtCenter === element || element.contains(elAtCenter); + }).map(bb => { + const rect = { + left: Math.max(0, bb.left), + top: Math.max(0, bb.top), + right: Math.min(vw, bb.right), + bottom: Math.min(vh, bb.bottom) + }; + return { + ...rect, + width: rect.right - rect.left, + height: rect.bottom - rect.top + }; + }); + var area = rects.reduce((acc, rect) => acc + rect.width * rect.height, 0); + return { + element: element, + include: (element.tagName === "INPUT" || element.tagName === "TEXTAREA" || element.tagName === "SELECT") || (element.tagName === "BUTTON" || element.tagName === "A" || (element.onclick != null) || window.getComputedStyle(element).cursor == "pointer") || (element.tagName === "IFRAME" || element.tagName === "VIDEO" || element.tagName === "LI" || element.tagName === "TD" || element.tagName === "OPTION"), - area, - rects, - text: element.textContent.trim().replace(/\s{2,}/g, ' ') - }; - }).filter(item => - item.include && (item.area >= 20) - ); + area, + rects, + text: element.textContent.trim().replace(/\s{2,}/g, " ") + }; + }).filter(item => + item.include && (item.area >= 20) + ); - // Filter out buttons and elements that are contained by buttons or spans - const buttons = Array.from(document.querySelectorAll('button, a, input[type="button"], div[role="button"]' )); - items = items.filter(x => !buttons.some(y => items.some(z => z.element === y) && y.contains(x.element) && !(x.element === y))); - items = items.filter(x => - !(x.element.parentNode && - x.element.parentNode.tagName === 'SPAN' && + // Filter out buttons and elements that are contained by buttons or spans + const buttons = Array.from(document.querySelectorAll("button, a, input[type=\"button\"], div[role=\"button\"]" )); + items = items.filter(x => !buttons.some(y => items.some(z => z.element === y) && y.contains(x.element) && !(x.element === y))); + items = items.filter(x => + !(x.element.parentNode && + x.element.parentNode.tagName === "SPAN" && x.element.parentNode.children.length === 1 && - x.element.parentNode.getAttribute('role') && + x.element.parentNode.getAttribute("role") && items.some(y => y.element === x.element.parentNode))); - items = items.filter(x => !items.some(y => x.element.contains(y.element) && !(x == y))); + items = items.filter(x => !items.some(y => x.element.contains(y.element) && !(x == y))); - return items; + return items; } // Set interactive marks on the current web page function setInteractiveMarks() { - var items = getInteractiveElements(); - - // Init an array to record the item information - var itemsInfo = []; - items.forEach(function(item, index) { - item.rects.forEach((bbox) => { - // Create a mark element for each interactive element - let newElement = document.createElement("div"); - newElement.classList.add("agentscope-interactive-mark") + var items = getInteractiveElements(); - // border - var borderColor = "#000000"; - newElement.style.outline = `2px dashed ${borderColor}`; - newElement.style.position = "fixed"; - newElement.style.left = bbox.left + "px"; - newElement.style.top = bbox.top + "px"; - newElement.style.width = bbox.width + "px"; - newElement.style.height = bbox.height + "px"; - newElement.style.pointerEvents = "none"; - newElement.style.boxSizing = "border-box"; - newElement.style.zIndex = 2147483647; + // Init an array to record the item information + var itemsInfo = []; + items.forEach(function(item, index) { + item.rects.forEach((bbox) => { + // Create a mark element for each interactive element + const newElement = document.createElement("div"); + newElement.classList.add("agentscope-interactive-mark"); - // index label - var label = document.createElement("span"); - label.textContent = index; - label.style.position = "absolute"; - label.style.top = Math.max(-19, -bbox.top) + "px"; - label.style.left = Math.min(Math.floor(bbox.width / 5), 2) + "px"; - label.style.background = borderColor; - label.style.color = "white"; - label.style.padding = "2px 4px"; - label.style.fontSize = "12px"; - label.style.borderRadius = "2px"; - newElement.appendChild(label); + // border + var borderColor = "#000000"; + newElement.style.outline = `2px dashed ${borderColor}`; + newElement.style.position = "fixed"; + newElement.style.left = bbox.left + "px"; + newElement.style.top = bbox.top + "px"; + newElement.style.width = bbox.width + "px"; + newElement.style.height = bbox.height + "px"; + newElement.style.pointerEvents = "none"; + newElement.style.boxSizing = "border-box"; + newElement.style.zIndex = 2147483647; - document.body.appendChild(newElement); - }); + // index label + var label = document.createElement("span"); + label.textContent = index; + label.style.position = "absolute"; + label.style.top = Math.max(-19, -bbox.top) + "px"; + label.style.left = Math.min(Math.floor(bbox.width / 5), 2) + "px"; + label.style.background = borderColor; + label.style.color = "white"; + label.style.padding = "2px 4px"; + label.style.fontSize = "12px"; + label.style.borderRadius = "2px"; + newElement.appendChild(label); - // Record the item information - itemsInfo[index] = getElementInfo(index, item.element); + document.body.appendChild(newElement); }); - return {items, itemsInfo}; + + // Record the item information + itemsInfo[index] = getElementInfo(index, item.element); + }); + return {items, itemsInfo}; } // `${attr.name}="${attr.value}"`), + inner_text: element.innerText || element.textContent || "", + origin_x: rect.x, + origin_y: rect.y, + width: rect.width, + height: rect.height + }; } // Remove all interactive marks by class name function removeInteractiveMarks() { - var marks = document.getElementsByClassName("agentscope-interactive-mark"); - while (marks.length > 0) { - marks[0].parentNode.removeChild(marks[0]); - } + var marks = document.getElementsByClassName("agentscope-interactive-mark"); + while (marks.length > 0) { + marks[0].parentNode.removeChild(marks[0]); + } } \ No newline at end of file diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index e294cb4b8..e626e3a29 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -21,7 +21,8 @@ const nameToHtmlFile = { "Message": "message-msg.html", "DialogAgent": "agent-dialogagent.html", "UserAgent": "agent-useragent.html", - "TextToImageAgent": "agent-texttoimageagent.html", + // TODO: rename in config, + "TextToImageAgent": "service-text-to-image.html", "DictDialogAgent": "agent-dictdialogagent.html", "ReActAgent": "agent-reactagent.html", "Placeholder": "pipeline-placeholder.html", From 9cb6402c6501d3a9f685f6a5801c55988a1c9a8b Mon Sep 17 00:00:00 2001 From: qbc Date: Tue, 29 Oct 2024 15:23:34 +0800 Subject: [PATCH 29/47] Dev broadcast agent (#33) --- gallery/undercover.json | 471 ++++++++++++++++++ .../agent-broadcastagent.html | 32 ++ .../html-drag-components/agent-copyagent.html | 19 + .../tool-image-composition.html | 2 +- .../tool-image-motion.html | 2 +- .../html-drag-components/tool-post.html | 2 +- .../tool-video-composition.html | 2 +- .../studio/static/i18n/i18n_en.json | 13 +- .../studio/static/i18n/i18n_zh.json | 13 +- .../studio/static/js/workstation.js | 66 ++- .../studio/templates/workstation.html | 5 + .../studio/tools/broadcast_agent.py | 66 +++ .../web/workstation/workflow_node.py | 27 + 13 files changed, 700 insertions(+), 20 deletions(-) create mode 100644 gallery/undercover.json create mode 100644 src/agentscope/studio/static/html-drag-components/agent-broadcastagent.html create mode 100644 src/agentscope/studio/static/html-drag-components/agent-copyagent.html create mode 100644 src/agentscope/studio/tools/broadcast_agent.py diff --git a/gallery/undercover.json b/gallery/undercover.json new file mode 100644 index 000000000..cf749b37b --- /dev/null +++ b/gallery/undercover.json @@ -0,0 +1,471 @@ +{ + "meta": { + "index": 4, + "title": "Undercover", + "author": "AgentScopeTeam", + "keywords": [ + "undercover", + "game" + ], + "category": "game", + "time": "2024-09-23", + "thumbnail": "" + }, + "drawflow": { + "Home": { + "data": { + "2": { + "id": 2, + "name": "dashscope_chat", + "data": { + "args": { + "config_name": "qwen", + "model_name": "qwen-max", + "api_key": "****", + "temperature": 0, + "seed": 0, + "model_type": "dashscope_chat" + } + }, + "class": "dashscope_chat", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": -75, + "pos_y": -31 + }, + "3": { + "id": 3, + "name": "MsgHub", + "data": { + "elements": [ + "4" + ], + "args": { + "announcement": { + "name": "主持人", + "content": "游戏开始!" + } + } + }, + "class": "GROUP", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 281, + "pos_y": -34.111111111111114 + }, + "4": { + "id": 4, + "name": "ForLoopPipeline", + "data": { + "elements": [ + "5" + ], + "args": { + "max_loop": 5, + "condition_op": "contains", + "target_value": "结束" + } + }, + "class": "GROUP", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 319, + "pos_y": 27 + }, + "5": { + "id": 5, + "name": "SequentialPipeline", + "data": { + "elements": [ + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19" + ] + }, + "class": "GROUP", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 357, + "pos_y": 76 + }, + "6": { + "id": 6, + "name": "BroadcastAgent", + "data": { + "args": { + "name": "主持人", + "content": "现在是描述阶段,你需要用一句话来描述你的词。" + } + }, + "class": "BroadcastAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 387, + "pos_y": 107 + }, + "7": { + "id": 7, + "name": "DialogAgent", + "data": { + "args": { + "name": "玩家1", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家1。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 437, + "pos_y": 133 + }, + "8": { + "id": 8, + "name": "DialogAgent", + "data": { + "args": { + "name": "玩家2", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家2。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 477, + "pos_y": 158 + }, + "9": { + "id": 9, + "name": "DialogAgent", + "data": { + "args": { + "name": "玩家3", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家3。你的秘密词是“孔雀”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 512, + "pos_y": 178.88888888888889 + }, + "10": { + "id": 10, + "name": "DialogAgent", + "data": { + "args": { + "name": "玩家4", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家4。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 545, + "pos_y": 204 + }, + "11": { + "id": 11, + "name": "DialogAgent", + "data": { + "args": { + "name": "玩家5", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家5。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 583, + "pos_y": 230 + }, + "12": { + "id": 12, + "name": "BroadcastAgent", + "data": { + "args": { + "name": "主持人", + "content": "现在是投票阶段,你需要为你认为最可能是卧底的玩家投票。不要投给已经被淘汰的玩家。只需回答玩家的名字。" + } + }, + "class": "BroadcastAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 373, + "pos_y": 311.8888888888889 + }, + "13": { + "id": 13, + "name": "CopyNode", + "data": { + "elements": [ + "7" + ] + }, + "class": "node-DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 407, + "pos_y": 341 + }, + "14": { + "id": 14, + "name": "CopyNode", + "data": { + "elements": [ + "8" + ] + }, + "class": "node-DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 439, + "pos_y": 372 + }, + "15": { + "id": 15, + "name": "CopyNode", + "data": { + "elements": [ + "9" + ] + }, + "class": "node-DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 462.8888888888889, + "pos_y": 399 + }, + "16": { + "id": 16, + "name": "CopyNode", + "data": { + "elements": [ + "10" + ] + }, + "class": "node-DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 488, + "pos_y": 432 + }, + "17": { + "id": 17, + "name": "CopyNode", + "data": { + "elements": [ + "11" + ] + }, + "class": "node-DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 523.1111111111111, + "pos_y": 473 + }, + "18": { + "id": 18, + "name": "DialogAgent", + "data": { + "args": { + "name": "计票员", + "sys_prompt": "作为《谁是卧底》游戏中的计票员,你的职责是准确地统计玩家的选择投票,并确定结果。遵循以下准则:\n- 如果多名玩家获得最高票数并出现平票,例如玩家1和玩家2,你应该回应:“平票,没有玩家被淘汰。\n- 如果某一名玩家获得最高票数,例如玩家1,你应宣布结果为:“玩家1被淘汰。\n- 不要回应其他任何内容,包括你的分析和想法。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 549, + "pos_y": 512.8888888888889 + }, + "19": { + "id": 19, + "name": "DialogAgent", + "data": { + "args": { + "name": "裁判", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n作为《谁是卧底》游戏中的裁判角色,你的唯一责任是评估游戏是否已达到其结局:\n如果卧底玩家3被淘汰,则回复:“游戏结束,卧底输了。”\n如果玩家3存活,并且游戏中只剩下两名玩家,则回复:“游戏结束,卧底赢了。”\n在所有其他情况下,简单地声明:“游戏继续。” 请将你的回复限制在这些具体的结果上,不要添加任何个人评论、分析或其他备注。\"\"\",", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 586, + "pos_y": 546 + } + } + } + } +} \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/agent-broadcastagent.html b/src/agentscope/studio/static/html-drag-components/agent-broadcastagent.html new file mode 100644 index 000000000..aedb14fb1 --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/agent-broadcastagent.html @@ -0,0 +1,32 @@ +
    +
    +
    + + + + + BroadcastAgent +
    + +
    +
    +
    + + An agent that only broadcasts its content + +
    Node ID: ID_PLACEHOLDER
    +
    + + + +
    + + + +
    +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/agent-copyagent.html b/src/agentscope/studio/static/html-drag-components/agent-copyagent.html new file mode 100644 index 000000000..a6868005f --- /dev/null +++ b/src/agentscope/studio/static/html-drag-components/agent-copyagent.html @@ -0,0 +1,19 @@ +
    +
    +
    + + + + + NAME_PLACEHOLDER +
    +
    +
    +
    + README_PLACEHOLDER +
    Copy from Node ID: + ID_PLACEHOLDER
    +
    +
    +
    \ No newline at end of file diff --git a/src/agentscope/studio/static/html-drag-components/tool-image-composition.html b/src/agentscope/studio/static/html-drag-components/tool-image-composition.html index ef59afdcc..a373f9a69 100644 --- a/src/agentscope/studio/static/html-drag-components/tool-image-composition.html +++ b/src/agentscope/studio/static/html-drag-components/tool-image-composition.html @@ -10,7 +10,7 @@
    -
    Image composition Configurations
    +
    Composite images into one image


    +
    diff --git a/src/agentscope/studio/static/i18n/i18n_en.json b/src/agentscope/studio/static/i18n/i18n_en.json index 6a14fc5c8..f02defae5 100644 --- a/src/agentscope/studio/static/i18n/i18n_en.json +++ b/src/agentscope/studio/static/i18n/i18n_en.json @@ -200,6 +200,11 @@ "service-text-to-image-modelName": "Model Name", "service-text-to-image-numberofImages": "Number of Images", "service-text-to-image-size": "size", + "tool-image-synthesis-readme": "Integrate the Text to Image", + "tool-image_synthesis-modelName": "Model Name", + "tool-image_synthesis-numberofImages": "Number of Images", + "tool-image_synthesis-size": "size", + "tool-image_synthesis-saveDir": "Save dir", "tool-image-composition-readme": "Composite images into one image", "tool-image-composition-Titles": "Titles", "tool-image-composition-Outputpath": "Output path", diff --git a/src/agentscope/studio/static/i18n/i18n_zh.json b/src/agentscope/studio/static/i18n/i18n_zh.json index 32f732b03..885863ba5 100644 --- a/src/agentscope/studio/static/i18n/i18n_zh.json +++ b/src/agentscope/studio/static/i18n/i18n_zh.json @@ -200,6 +200,11 @@ "service-text-to-image-numberofImages": "图片数量", "service-text-to-image-size": "图片尺寸", "service-text-to-image-readme": "将文本转图像服务集成到 ReActAgent 中以增强智能体功能", + "tool-image-synthesis-readme": "文本生成图片", + "tool-image_synthesis-modelName": "模型名称", + "tool-image_synthesis-numberofImages": "图片数量", + "tool-image_synthesis-size": "图片尺寸", + "tool-image_synthesis-saveDir": "保存目录", "tool-image-composition-readme": "将多张图片合成一张", "tool-image-composition-Titles": "标题", "tool-image-composition-Outputpath": "输出路径", diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 98ce5f79a..045c294a0 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -21,8 +21,7 @@ const nameToHtmlFile = { "Message": "message-msg.html", "DialogAgent": "agent-dialogagent.html", "UserAgent": "agent-useragent.html", - // TODO: rename in config, - "TextToImageAgent": "service-text-to-image.html", + "ImageSynthesis": "tool-image-synthesis.html", "DictDialogAgent": "agent-dictdialogagent.html", "ReActAgent": "agent-reactagent.html", "BroadcastAgent": "agent-broadcastagent.html", @@ -833,6 +832,19 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { }, htmlSourceCode); break; + case "ImageSynthesis": + editor.addNode("ImageSynthesis", 1, 1, + pos_x, pos_y, "ImageSynthesis", { + "args": { + "model": "", + "api_key": "", + "n": 1, + "size": "", + "save_dir": "" + } + }, htmlSourceCode); + break; + case "ImageComposition": editor.addNode("ImageComposition", 1, 1, pos_x, pos_y, "ImageComposition", { @@ -2360,7 +2372,6 @@ async function addHtmlAndReplacePlaceHolderBeforeImport(data) { const classToReadmeDescription = { "node-DialogAgent": "A dialog agent that can interact with users or other agents", "node-UserAgent": "A proxy agent for user", - "node-TextToImageAgent": "Agent for text to image generation", "node-DictDialogAgent": "Agent that generates response in a dict format", "node-ReActAgent": "Agent for ReAct (reasoning and acting) with tools", "node-BroadcastAgent": "A broadcast agent that only broadcasts the messages it receives" diff --git a/src/agentscope/studio/templates/workstation.html b/src/agentscope/studio/templates/workstation.html index b9fa734f6..cbd47872a 100644 --- a/src/agentscope/studio/templates/workstation.html +++ b/src/agentscope/studio/templates/workstation.html @@ -338,6 +338,11 @@ Tool
      +
    • + Image Synthesis +
    • diff --git a/src/agentscope/studio/tools/broadcast_agent.py b/src/agentscope/studio/tools/broadcast_agent.py index 61f1d51b7..ef6b706ba 100644 --- a/src/agentscope/studio/tools/broadcast_agent.py +++ b/src/agentscope/studio/tools/broadcast_agent.py @@ -16,7 +16,6 @@ def __init__( sys_prompt: str = None, model_config_name: str = None, use_memory: bool = False, - memory_config: Optional[dict] = None, ) -> None: """Initialize the dummy agent. @@ -31,15 +30,12 @@ def __init__( configuration. use_memory (`bool`, defaults to `True`): Whether the agent has memory. - memory_config (`Optional[dict]`): - The config of memory. """ super().__init__( name=name, sys_prompt=sys_prompt, model_config_name=model_config_name, use_memory=use_memory, - memory_config=memory_config, ) self.content = content diff --git a/src/agentscope/studio/tools/image_synthesis.py b/src/agentscope/studio/tools/image_synthesis.py new file mode 100644 index 000000000..74cf9d89c --- /dev/null +++ b/src/agentscope/studio/tools/image_synthesis.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +"""Text to Image""" +from typing import Optional, Literal + +from agentscope.message import Msg +from agentscope.service import dashscope_text_to_image, ServiceExecStatus + + +def image_synthesis( + msg: Msg, + api_key: str, + n: int = 1, + size: Literal["1024*1024", "720*1280", "1280*720"] = "1024*1024", + model: str = "wanx-v1", + save_dir: Optional[str] = None, +) -> Msg: + """Generate image(s) based on the given Msg, and return Msg. + + Args: + msg (`Msg`): + The msg to generate image. + api_key (`str`): + The api key for the dashscope api. + n (`int`, defaults to `1`): + The number of images to generate. + size (`Literal["1024*1024", "720*1280", "1280*720"]`, defaults to + `"1024*1024"`): + Size of the image. + model (`str`, defaults to '"wanx-v1"'): + The model to use. + save_dir (`Optional[str]`, defaults to 'None'): + The directory to save the generated images. If not specified, + will return the web urls. + + Returns: + Msg + """ + + res = dashscope_text_to_image( + prompt=msg.content, + api_key=api_key, + n=n, + size=size, + model=model, + save_dir=save_dir, + ) + + if res.status == ServiceExecStatus.SUCCESS: + return Msg( + name="ImageSynthesis", + content=res.content, + url=res.content["image_urls"], + role="assistant", + echo=True, + ) + else: + return Msg( + name="ImageSynthesis", + content=res.content, + role="assistant", + echo=True, + ) diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index fe08a3f34..564701094 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -47,6 +47,7 @@ from agentscope.studio.tools.image_motion import create_video_or_gif_from_image from agentscope.studio.tools.video_composition import merge_videos from agentscope.studio.tools.condition_operator import eval_condition_operator +from agentscope.studio.tools.image_synthesis import image_synthesis from agentscope.studio.tools.web_post import web_post from agentscope.studio.tools.broadcast_agent import BroadcastAgent @@ -852,6 +853,29 @@ def compile(self) -> dict: } +class ImageSynthesisNode(WorkflowNode): + """ + Text to Image Tool Node + """ + + node_type = WorkflowNodeType.TOOL + + def _post_init(self) -> None: + super()._post_init() + self.pipeline = partial(image_synthesis, **self.opt_kwargs) + + def compile(self) -> dict: + return { + "imports": "from agentscope.studio.tools.image_synthesis import " + "image_synthesis\n" + "from functools import partial\n", + "inits": f"{self.var_name} = partial(image_synthesis," + f" {kwarg_converter(self.opt_kwargs)})", + "execs": f"{DEFAULT_FLOW_VAR} = {self.var_name}" + f"({DEFAULT_FLOW_VAR})", + } + + class ImageCompositionNode(WorkflowNode): """ Image Composition Node @@ -1050,6 +1074,7 @@ def compile(self) -> dict: "Post": PostNode, "TextToAudioService": TextToAudioServiceNode, "TextToImageService": TextToImageServiceNode, + "ImageSynthesis": ImageSynthesisNode, "ImageComposition": ImageCompositionNode, "Code": CodeNode, "ImageMotion": ImageMotionNode, From 8e676addcdaf413220288af7999fff9cd280b9a8 Mon Sep 17 00:00:00 2001 From: rabZhang <515184034@qq.com> Date: Wed, 30 Oct 2024 19:34:19 +0800 Subject: [PATCH 31/47] =?UTF-8?q?bugfix=E5=AF=BC=E5=85=A5html=E7=9A=84?= =?UTF-8?q?=E6=97=B6=E5=80=99=E8=BE=B9=E6=9C=AA=E5=A4=84=E4=BA=8E=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E4=BD=8D=E7=BD=AE=20(#49)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../studio/static/js/workstation.js | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 045c294a0..0f8aebc39 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -9,7 +9,7 @@ let dataToImportStep; let currentImportIndex; let accumulatedImportData; let descriptionStep; - +let allimportNodeId = []; const nameToHtmlFile = { "welcome": "welcome.html", @@ -2160,7 +2160,12 @@ function showImportHTMLPopup() { editor.clear(); editor.import(parsedData); importSetupNodes(parsedData); - Swal.fire("Imported!", "", "success"); + Swal.fire("Imported!", "", "success") + .then(() => { + setTimeout(() => { + updateImportNodes(); + }, 200); + });; }); } catch (error) { @@ -2303,7 +2308,11 @@ function loadWorkflow(fileName) { editor.clear(); editor.import(data); importSetupNodes(data); - Swal.fire("Imported!", "", "success"); + Swal.fire("Imported!", "", "success").then(() => { + setTimeout(() => { + updateImportNodes(); + }, 200); + }); }); } catch (error) { @@ -2368,6 +2377,7 @@ async function addHtmlAndReplacePlaceHolderBeforeImport(data) { const idPlaceholderRegex = /ID_PLACEHOLDER/g; const namePlaceholderRegex = /NAME_PLACEHOLDER/g; const readmePlaceholderRegex = /README_PLACEHOLDER/g; + allimportNodeId = []; const classToReadmeDescription = { "node-DialogAgent": "A dialog agent that can interact with users or other agents", @@ -2385,7 +2395,7 @@ async function addHtmlAndReplacePlaceHolderBeforeImport(data) { delete data.drawflow.Home.data[nodeId]; continue; } - + allimportNodeId.push(nodeId); node.html = await fetchHtmlSourceCodeByName(node.name); if (node.name === "CopyNode") { @@ -2403,6 +2413,11 @@ async function addHtmlAndReplacePlaceHolderBeforeImport(data) { } } +function updateImportNodes() { + allimportNodeId.forEach((nodeId) => { + editor.updateConnectionNodes(`node-${nodeId}`); + }); +} function importSetupNodes(dataToImport) { Object.keys(dataToImport.drawflow.Home.data).forEach((nodeId) => { @@ -3022,6 +3037,9 @@ function importGalleryWorkflow(data) { if (result.isConfirmed) { showEditorTab(); } + setTimeout(() => { + updateImportNodes(); + }, 200); }); }); } catch (error) { From 36f79d07794d872899d1bba1491a0d23f5b32fb3 Mon Sep 17 00:00:00 2001 From: rabZhang <515184034@qq.com> Date: Mon, 11 Nov 2024 11:31:04 +0800 Subject: [PATCH 32/47] Re act agent add service (#51) --- .../studio/static/css/workstation.css | 138 ++++- .../static/css_third_party/material-icons.css | 20 + .../agent-dialogagent.html | 2 +- .../agent-reactagent.html | 28 +- .../service-text-to-audio.html | 2 +- .../studio/static/i18n/i18n_en.json | 3 +- .../studio/static/i18n/i18n_zh.json | 3 +- .../studio/static/js/workstation.js | 487 +++++++++++++++++- .../static/js_third_party/Sortable.min.js | 2 + .../studio/static/js_third_party/anime.min.js | 8 + .../studio/templates/workstation.html | 50 +- 11 files changed, 684 insertions(+), 59 deletions(-) create mode 100644 src/agentscope/studio/static/css_third_party/material-icons.css create mode 100644 src/agentscope/studio/static/js_third_party/Sortable.min.js create mode 100644 src/agentscope/studio/static/js_third_party/anime.min.js diff --git a/src/agentscope/studio/static/css/workstation.css b/src/agentscope/studio/static/css/workstation.css index 32cc47c2f..2b7571adc 100644 --- a/src/agentscope/studio/static/css/workstation.css +++ b/src/agentscope/studio/static/css/workstation.css @@ -511,6 +511,13 @@ pre[class*='language-'] { transform: translateY(-2px); } +.pop-drawer { + background-image: linear-gradient(to right, #1861e5, #171ce4); +} +.pop-drawer:hover { + background-image: linear-gradient(to left, #1861e5, #171ce4); + transform: unset; +} .add-case { background-image: linear-gradient(to right, #56ab2f, #a8e063); } @@ -597,6 +604,98 @@ pre[class*='language-'] { color: #888; } + +.drawflow .drawflow-node.selected { + background: var(--main-color-very-light); + border: 1px solid var(--main-color-light); + box-shadow: 0 2px 10px 2px var(--main-color-light); +} + +.tool-box{ + display: flex; + gap: 10px; +} + +.add-service-list{ + list-style-type: none; + padding: 0; + margin: 0; + display: none; + position: absolute; + background-color: #f9f9f9; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; + border-radius: 5%; + overflow: hidden; +} + +.add-service-list li { + padding: 12px 16px; + text-decoration: none; + display: block; + color: black; +} + +.add-service-list li:hover { + background-color: #f1f1f1; +} + +.dropzone { + overflow: hidden; + display: flex; + flex-direction: column; +} + +.stacked-list { + list-style-type: none; + padding: 5px; + margin: 0; + overflow-y: auto; + flex-grow: 1; +} + +.stacked-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; + margin-bottom: 8px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 4px; +} + +.stacked-item-actions { + display: flex; + gap: 8px; +} + +.stacked-item-actions button { + background: none; + border: none; + cursor: pointer; + color: #333; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; +} + +.stacked-item-actions button:hover { + color: #4CAF50; +} + +.stacked-item-actions .material-icons { + font-size: 18px; +} + +.hidden-node { + display: none !important; +} +/* END */ + .swal2-container { z-index: 20000 !important; } @@ -1159,4 +1258,41 @@ pre[class*='language-'] { #navigation-bar-logo:hover { cursor: pointer; -} \ No newline at end of file +} + +.add-button-forJson{ + position: absolute; + top: 100%; + left: 50%; + transform: translate(-50%, 25%); + background: black; + color: white; + cursor: pointer; + border: none; + padding: 1em; + border-radius: 15%; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + font-size: 1.3rem; +} + +.add-button-forJson::before, +.add-button-forJson::after { + content: ''; + position: absolute; + width: 60%; /* 调整 "+" 的宽度 */ + height: 2px; /* "+" 的线条厚度 */ + background-color: currentColor; +} +.add-button-forJson::before { + transform: rotate(90deg); +} +.add-button-forJson::after { + transform: rotate(0deg); +} +.add-button-forJson:hover{ + transform: translate(-50%, 20%); ; + transition: all 0.3s ease-in-out; +} diff --git a/src/agentscope/studio/static/css_third_party/material-icons.css b/src/agentscope/studio/static/css_third_party/material-icons.css new file mode 100644 index 000000000..7f4534247 --- /dev/null +++ b/src/agentscope/studio/static/css_third_party/material-icons.css @@ -0,0 +1,20 @@ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/materialicons/v142/flUhRq6tzZclQEJ-Vdg-IuiaDsNZ.ttf) format('truetype'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; +} diff --git a/src/agentscope/studio/static/html-drag-components/agent-dialogagent.html b/src/agentscope/studio/static/html-drag-components/agent-dialogagent.html index dcb2a95e5..6f8cc9209 100644 --- a/src/agentscope/studio/static/html-drag-components/agent-dialogagent.html +++ b/src/agentscope/studio/static/html-drag-components/agent-dialogagent.html @@ -11,7 +11,7 @@
    -
    +
    An dialog agent that can interact with users or other agents. diff --git a/src/agentscope/studio/static/html-drag-components/agent-reactagent.html b/src/agentscope/studio/static/html-drag-components/agent-reactagent.html index 99582cd62..f02d778b7 100644 --- a/src/agentscope/studio/static/html-drag-components/agent-reactagent.html +++ b/src/agentscope/studio/static/html-drag-components/agent-reactagent.html @@ -41,12 +41,28 @@ placeholder="gpt-3.5-turbo" data-required="true">
    - -
    Please drag and - drop a Tool module into this area. Inserting other types of modules - may result in errors. +
    + +
    + +
      +
    • Bing Search
    • +
    • Google Search
    • +
    • Python Interpreter
    • +
    • Read Text
    • +
    • Write Text
    • +
    • Text to Audio
    • +
    • Text to Image
    • +
        +
    +
    +
    + + Please click the 'Service' button above to add tools. + +
    +
      +
      diff --git a/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html b/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html index 0f9fa0d77..6e79d5699 100644 --- a/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html +++ b/src/agentscope/studio/static/html-drag-components/service-text-to-audio.html @@ -39,5 +39,5 @@
      - +
      diff --git a/src/agentscope/studio/static/i18n/i18n_en.json b/src/agentscope/studio/static/i18n/i18n_en.json index f02defae5..ba55cd1a2 100644 --- a/src/agentscope/studio/static/i18n/i18n_en.json +++ b/src/agentscope/studio/static/i18n/i18n_en.json @@ -68,7 +68,8 @@ "agent-reactagent-SystemPrompt-textarea": "You're an assistant.", "agent-reactagent-ModelConfigName": "Model config name", "agent-reactagent-Tools": "Tools", - "agent-reactagent-Tools-placeholder": "Please drag and drop a Tool module into this area. Inserting other types of modules may result in errors.", + "agent-reactagent-add-Service": "Service", + "agent-reactagent-Tools-placeholder": "Please click the 'Service' button above to add tools.", "agent-reactagent-iterations": "Max reasoning-acting iterations", "agent-reactagent-Verbose": "Verbose", "agent-texttoimageagent-TextToImageAgent": "TextToImageAgent", diff --git a/src/agentscope/studio/static/i18n/i18n_zh.json b/src/agentscope/studio/static/i18n/i18n_zh.json index 885863ba5..0c4ce4140 100644 --- a/src/agentscope/studio/static/i18n/i18n_zh.json +++ b/src/agentscope/studio/static/i18n/i18n_zh.json @@ -68,7 +68,8 @@ "agent-reactagent-SystemPrompt-textarea": "你是一个助理。", "agent-reactagent-ModelConfigName": "模型配置名称", "agent-reactagent-Tools": "工具", - "agent-reactagent-Tools-placeholder": "请将工具模块拖放到此区域。插入其他类型的模块可能会导致错误。", + "agent-reactagent-add-Service": "服务", + "agent-reactagent-Tools-placeholder": "请点击上方的 '服务' 按钮添加工具。", "agent-reactagent-iterations": "最大推理迭代次数", "agent-reactagent-Verbose": "详细", "agent-texttoimageagent-TextToImageAgent": "文本转图像智能体", diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 0f8aebc39..ec5e3e0b5 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -10,6 +10,7 @@ let currentImportIndex; let accumulatedImportData; let descriptionStep; let allimportNodeId = []; +let currentNodeSelect; const nameToHtmlFile = { "welcome": "welcome.html", @@ -155,6 +156,7 @@ async function initializeWorkstationPage() { setupNodeCopyListens(id); addEventListenersToNumberInputs(id); setupTextInputListeners(id); + setupNodeServiceDrawer(id); reloadi18n(); }); @@ -1286,6 +1288,10 @@ function setupNodeListeners(nodeId) { if (toggleArrow && contentBox && titleBox) { toggleArrow.addEventListener("click", function () { + const serivceArr = ["BingSearchService", "GoogleSearchService", "PythonService", "ReadTextService", "WriteTextService", "TextToAudioService", "AudioToTextService"]; + if(serivceArr.includes(newNode.querySelector(".title-box").getAttribute("data-class"))){ + return; + } contentBox.classList.toggle("hidden"); if (contentBox.classList.contains("hidden")) { @@ -1556,7 +1562,7 @@ function sortElementsByPosition(inputData) { Object.keys(inputData.drawflow).forEach((moduleKey) => { const moduleData = inputData.drawflow[moduleKey]; Object.entries(moduleData.data).forEach(([nodeId, node]) => { - if (node.class === "GROUP") { + if (node.class === "GROUP" && node.name !=="ReActAgent") { const elements = node.data.elements; const elementsWithPosition = elements.map(elementId => { const elementNode = document.querySelector(`#node-${elementId}`); @@ -2420,7 +2426,7 @@ function updateImportNodes() { } function importSetupNodes(dataToImport) { - Object.keys(dataToImport.drawflow.Home.data).forEach((nodeId) => { + Object.entries(dataToImport.drawflow.Home.data).forEach(([nodeId,nodeValue]) => { // import the node use addNode function disableButtons(); makeNodeTop(nodeId); @@ -2428,6 +2434,7 @@ function importSetupNodes(dataToImport) { setupNodeCopyListens(nodeId); addEventListenersToNumberInputs(nodeId); setupTextInputListeners(nodeId); + setupNodeServiceDrawer(nodeId); reloadi18n(); const nodeElement = document.getElementById(`node-${nodeId}`); if (nodeElement) { @@ -2436,6 +2443,11 @@ function importSetupNodes(dataToImport) { setupNodeCopyListens(nodeId); } } + if(nodeValue.name === "ReActAgent"){ + nodeValue.data.elements.forEach((listNode) => { + dropNodeToDropzone(listNode,nodeElement); + }); + } }); } @@ -3293,3 +3305,474 @@ function generateThumbnailFromContent(content) { return canvas.toDataURL(); } + +function setupNodeServiceDrawer(nodeId) { + const newNode = document.getElementById(`node-${nodeId}`); + const popDrawer = newNode.querySelector(".pop-drawer"); + if (popDrawer) { + const contain = newNode.querySelector(".serivce-contain"); + const hiddenList = newNode.querySelector(".add-service-list"); + contain.addEventListener("mouseover", function() { + hiddenList.style.display = "block"; + }); + hiddenList.addEventListener("mouseover", function() { + hiddenList.style.display = "block"; + }); + contain.addEventListener("mouseout", function() { + hiddenList.style.display = "none"; + }); + + hiddenList.addEventListener("click", function(e) { + const target = e.target; + + if(target.localName == "li"){ + // createServiceNode(nodeId,target.getAttribute("data-node"),newNode,e.currentTarget.offsetLeft + newNode.offsetWidth ,e.currentTarget.offsetTop); + createServiceNode(nodeId,target.getAttribute("data-node")); + } + }); + } +} + +async function createServiceNode(nodeId,serivceName){ + const nodeElement = document.getElementById(`node-${nodeId}`); + const nodeElementRect = nodeElement.getBoundingClientRect(); + const node = editor.getNodeFromId(nodeId); + + + const dropzoneRect = nodeElement.querySelector(".tools-placeholder").getBoundingClientRect(); + + const createPos_x = Math.ceil(node.pos_x + (dropzoneRect.width * 2 / 3 ) / (editor.zoom)); + const createPos_y = Math.ceil(node.pos_y + (dropzoneRect.top - nodeElementRect.top ) / editor.zoom + 20); + + const dropNodeInfo = editor.getNodeFromId(nodeId); + const dropNodeInfoData = dropNodeInfo.data; + const createdId = await selectReActAgent(serivceName,createPos_x,createPos_y); + dropNodeInfoData.elements.push(`${createdId}`); + editor.updateNodeDataFromId(nodeId, dropNodeInfoData); + + dropzoneDetection(createdId); +} + +async function selectReActAgent(serivceName,pos_x,pos_y) { + const htmlSourceCode = await fetchHtmlSourceCodeByName(serivceName); + let createId; + switch (serivceName) { + case "BingSearchService": + createId = editor.addNode("BingSearchService", 0, 0, + pos_x, pos_y, "BingSearchService", { + "args": { + "api_key": "", + "num_results": 3, + } + }, htmlSourceCode); + break; + + case "GoogleSearchService": + createId = editor.addNode("GoogleSearchService", 0, 0, + pos_x, pos_y, "GoogleSearchService", { + "args": { + "api_key": "", + "cse_id": "", + "num_results": 3, + } + }, htmlSourceCode); + break; + + case "PythonService": + createId = editor.addNode("PythonService", 0, 0, + pos_x, pos_y, "PythonService", {}, htmlSourceCode); + break; + + case "ReadTextService": + createId = editor.addNode("ReadTextService", 0, 0, + pos_x, pos_y, "ReadTextService", {}, htmlSourceCode); + break; + + case "WriteTextService": + createId = editor.addNode("WriteTextService", 0, 0, + pos_x, pos_y, "WriteTextService", {}, htmlSourceCode); + break; + + case "TextToAudioService": + const TextToAudioServiceID = editor.addNode("TextToAudioService", 0, 0, + pos_x, pos_y, "TextToAudioService", { + "args": { + "model": "", + "api_key": "", + "sample_rate": "" + } + }, htmlSourceCode); + createId = TextToAudioServiceID; + updateSampleRate(TextToAudioServiceID); + break; + case "TextToImageService": + createId = editor.addNode("TextToImageService", 0, 0, + pos_x, pos_y, "TextToImageService", { + "args": { + "model": "", + "api_key": "", + "n": 1, + "size": "" + } + }, htmlSourceCode); + break; + } + return createId; +} + +// Added for the dropzone +function dropzoneDetection(nodeId) { + var node = editor.getNodeFromId(nodeId); + const nodeElement = document.getElementById(`node-${nodeId}`); + var dropzones = document.querySelectorAll(".dropzone"); + + dropzones.forEach(function (dropzone) { + // Prevent conflicts of drag-and-drop events between drawflow node and dropzone + if (!dropzone.hasDropEventListeners) { + dropzone.addEventListener("dragover", function (e) { + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + }); + + dropzone.hasDropEventListeners = true; + } + + // Check if the dropzone is not a child of the current node + if (!nodeElement.contains(dropzone)) { + if ( + isColliding(nodeElement, dropzone) && + node.name !== "dropzoneNode" && + !node.data.attachedToDropzone + ) { + console.log( + `Collision detected: Node "${node.name}" (ID: ${nodeId}) collided with ${dropzone.id}` + ); + dropNodeToDropzone(nodeId, dropzone); + } + } + }); +} + +function isColliding(element1, element2) { + var rect1 = element1.getBoundingClientRect(); + var rect2 = element2.getBoundingClientRect(); + + return !( + rect1.right < rect2.left || + rect1.left > rect2.right || + rect1.bottom < rect2.top || + rect1.top > rect2.bottom + ); +} + +function dropNodeToDropzone(nodeId, dropzoneElement) { + var node = editor.getNodeFromId(nodeId); + var dropzoneNodeId = dropzoneElement + .closest(".drawflow-node") + .id.split("-")[1]; + + if (nodeId === dropzoneNodeId) { + console.log(`Prevented node ${nodeId} from being dropped into itself`); + return; + } + var dropzoneNode = editor.getNodeFromId(dropzoneNodeId); + var stackedList = dropzoneElement.querySelector(".stacked-list"); + + if (!dropzoneNode.data.stackedItems) { + dropzoneNode.data.stackedItems = []; + } + + var nodeElement = document.getElementById(`node-${nodeId}`); + var nodeRect = nodeElement.getBoundingClientRect(); + var startX = nodeRect.left; + var startY = nodeRect.top; + + // Make sure the logic is the same + nodeElement.classList.add("hidden-node"); + + // Should be modified to display the header of the node + var stackedItem = document.createElement("li"); + stackedItem.className = "stacked-item"; + stackedItem.id = `stacked-item-${nodeId}`; + stackedItem.innerHTML = ` + ${node.name} +
      + + +
      + `; + // Keep track of the dropzone Id it attached to + stackedItem.dataset.attachedDropzoneId = dropzoneNodeId; + stackedItem.draggable = true; + stackedList.appendChild(stackedItem); + + // Try Sortable.js + // Mainly for reordering the stacked items + // Not necessary, but nice to have some animations + new Sortable(stackedList, { + group: "shared", + animation: 150, + filter: ".undraggable", + onEnd: function (evt) { + console.log("Item dropped"); + }, + }); + + // Makesure the stackedList in dropzone is actually a sortableInstance + stackedItem.addEventListener("mousedown", handleStackedItemMouseDown); + stackedItem.addEventListener("dragstart", handleStackedItemDragStart); + stackedItem.addEventListener("drag", handleStackedItemDrag); + stackedItem.addEventListener("dragend", handleStackedItemDragEnd); + + stackedItem + .querySelector(".remove-btn") + .addEventListener("click", function () { + removeNodeFromDropzone(node, dropzoneNode, stackedItem); + }); + stackedItem + .querySelector(".expand-btn") + .addEventListener("click", function (e) { + e.stopPropagation(); + expandNodeFromDropzone(node, dropzoneNode, stackedItem); + }); + + var itemRect = stackedItem.getBoundingClientRect(); + var endX = itemRect.left; + var endY = itemRect.top; + + anime({ + targets: stackedItem, + translateX: [startX - endX, 0], + translateY: [startY - endY, 0], + scale: [1.5, 1], + opacity: [0, 1], + duration: 800, + easing: "easeOutElastic(1, .8)", + complete: function () { + stackedItem.style.transform = "none"; + }, + }); + + if (node && node.data) { + node.data.attachedToDropzone = true; + node.data.attachedDropzoneId = dropzoneNodeId; + console.log(`Node ${nodeId} attached to the dropzone ${dropzoneNodeId}`); + } + dropzoneNode.data.stackedItems.push({ + id: nodeId, + name: node.name, + }); + console.log( + `Node ${nodeId} (${node.name}) added to the stackedItems in the dropzone ${dropzoneNodeId}` + ); + stackedItem + .querySelector(".expand-btn").click(); +} + +function handleStackedItemMouseDown(e) { + console.log("Mouse down"); + e.stopPropagation(); +} + +function handleStackedItemDragStart(e) { + console.log("Drag start"); + e.dataTransfer.setData("text/plain", e.target.id); + e.target.style.opacity = "0.5"; +} + +function handleStackedItemDrag(e) { + console.log("Dragging"); + const attachedDropzoneId = e.target.dataset.attachedDropzoneId; + const dropzone = document + .getElementById(`node-${attachedDropzoneId}`) + .querySelector(".dropzone"); + const nodeId = e.target.id.split("-")[2]; + const originalNode = document.getElementById(`node-${nodeId}`); + if ( + dropzone && + !dropzone.contains(document.elementFromPoint(e.clientX, e.clientY)) + ) { + console.log("Item dragged outside the dropzone"); + // display forbidden cursor + e.target.style.cursor = "not-allowed"; + } else { + console.log("Item dragged inside the dropzone"); + e.target.style.cursor = "grab"; + } +} + +function handleStackedItemDragEnd(e) { + console.log("Drag ended"); + e.target.style.opacity = ""; + e.target.style.cursor = "grab"; +} + +function detachNodeFromDropzone(node, dropzoneNode, stackedItem) { + dropzoneElement = document + .getElementById(`node-${dropzoneNode.id}`) + .querySelector(".dropzone"); + anime({ + targets: stackedItem, + translateX: [0, -20], + opacity: [1, 0], + duration: 300, + easing: "easeOutCubic", + complete: function () { + stackedItem.remove(); + dropzoneNode.data.stackedItems = dropzoneNode.data.stackedItems.filter( + (item) => item.id !== node.id + ); + if (node && node.data) { + node.data.attachedToDropzone = false; + node.data.attachedDropzoneId = null; + console.log( + `Node ${node.id} detached from the dropzone ${dropzoneNode.id}` + ); + nodeElement = document.getElementById(`node-${node.id}`); + nodeElement.classList.remove("hidden-node"); + const pos_x = node.data.pos_x; + const pos_y = node.data.pos_y; + anime({ + targets: nodeElement, + left: pos_x, + top: pos_y, + opacity: [0, 1], + duration: 300, + easing: "easeOutCubic", + }); + } + }, + }); +} + +function expandNodeFromDropzone(node, dropzoneNode, stackedItem) { + const nodeElement = document.getElementById(`node-${node.id}`); + const dropzoneElement = document.getElementById(`node-${dropzoneNode.id}`); + + // Store the original position + const originalPosition = { + x: node.pos_x, + y: node.pos_y, + }; + + // Unhide the node and set its position + nodeElement.classList.remove("hidden-node"); + nodeElement.style.position = "absolute"; + nodeElement.style.cursor = "unset"; + // Position the node next to the dropzone + const dropzoneRect = dropzoneElement.getBoundingClientRect(); + const stackedItemRect = stackedItem.getBoundingClientRect(); + const expandedLeft = dropzoneRect.right + 20; + const expandedTop = stackedItemRect.top; + + node.data.pos_x = expandedLeft; + node.data.pos_y = expandedTop; + + // Create and position the speech bubble tail + const tail = document.createElement("div"); + tail.className = "speech-bubble-tail"; + nodeElement.appendChild(tail); + + // Calculate tail position + const expandedRect = nodeElement.getBoundingClientRect(); + const tailSize = 15; // Size of the tail in pixels + let tailLeft, tailTop, tailStyle; + + if ( + stackedItemRect.top + stackedItemRect.height / 2 < + expandedRect.top + expandedRect.height / 2 + ) { + // Tail should be on the top-left corner + tailLeft = -tailSize; + tailTop = 0; + tailStyle = ` + border-top: ${tailSize}px solid transparent; + border-bottom: ${tailSize}px solid transparent; + border-right: ${tailSize}px solid transparent; + `; + } else { + // Tail should be on the bottom-left corner + tailLeft = -tailSize; + tailTop = expandedRect.height - 2 * tailSize; + tailStyle = ` + border-top: ${tailSize}px solid transparent; + border-bottom: ${tailSize}px solid transparent; + border-right: ${tailSize}px solid transparent; + `; + } + + tail.style.cssText = ` + position: absolute; + left: ${tailLeft}px; + top: ${tailTop}px; + width: 0; + height: 0; + ${tailStyle} + `; + + // Make the node undraggable by overriding Drawflow's drag behavior + const originalOnMouseDown = nodeElement.onmousedown; + nodeElement.onmousedown = function (e) { + if ( + e.target.tagName === "INPUT" || + e.target.tagName === "SELECT" || + e.target.tagName === "BUTTON" + ) { + return; + } + e.stopPropagation(); + }; + + // Highlight the stacked item + stackedItem.style.backgroundColor = "#45a049"; + stackedItem.classList.add("undraggable"); + + // Function to collapse the expanded node + function collapseNode() { + nodeElement.classList.add("hidden-node"); + stackedItem.style.backgroundColor = ""; + stackedItem.classList.remove("undraggable"); + nodeElement.onmousedown = originalOnMouseDown; + node.pos_x = originalPosition.x; + node.pos_y = originalPosition.y; + tail.remove(); + document.removeEventListener("click", handleOutsideClick); + } + + // Handle clicks outside the node + function handleOutsideClick(event) { + if (!nodeElement.contains(event.target) && event.target !== stackedItem) { + collapseNode(); + } + } + + nodeElement.querySelector(".toggle-arrow").addEventListener("click",(e) => { + collapseNode(); + }); + // Add event listener for outside clicks + setTimeout(() => { + document.addEventListener("click", handleOutsideClick); + }, 0); +} + +function removeNodeFromDropzone(node, dropzoneNode, stackedItem) { + anime({ + targets: stackedItem, + translateX: [0, -20], + opacity: [1, 0], + duration: 300, + easing: "easeOutCubic", + complete: function () { + stackedItem.remove(); + node.data.attachedToDropzone = false; + node.data.attachedDropzoneId = null; + node.data.stackedItems = dropzoneNode.data.stackedItems.filter( + (item) => item.id !== node.id + ); + editor.removeNodeId("node-" + node.id); + console.log( + `Node ${node.id} removed from the dropzone ${dropzoneNode.id}` + ); + }, + }); +} diff --git a/src/agentscope/studio/static/js_third_party/Sortable.min.js b/src/agentscope/studio/static/js_third_party/Sortable.min.js new file mode 100644 index 000000000..7ba6b5903 --- /dev/null +++ b/src/agentscope/studio/static/js_third_party/Sortable.min.js @@ -0,0 +1,2 @@ +/*! Sortable 1.14.0 - MIT | git://github.com/SortableJS/Sortable.git */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function e(e,t){var n,o=Object.keys(e);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(e),t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,n)),o}function A(o){for(var t=1;tt.length)&&(e=t.length);for(var n=0,o=new Array(e);n"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function N(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&p(t,e)||o&&t===n)return t}while(t!==n&&(t=(i=t).host&&i!==document&&i.host.nodeType?i.host:i.parentNode))}var i;return null}var g,m=/\s+/g;function I(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(m," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(m," ")))}function P(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function v(t,e){var n="";if("string"==typeof t)n=t;else do{var o=P(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function b(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=n.left-e&&i<=n.right+e,e=r>=n.top-e&&r<=n.bottom+e;return o&&e?a=t:void 0}}),a);if(e){var n,o={};for(n in t)t.hasOwnProperty(n)&&(o[n]=t[n]);o.target=o.rootEl=e,o.preventDefault=void 0,o.stopPropagation=void 0,e[j]._onDragOver(o)}}var i,r,a}function Yt(t){q&&q.parentNode[j]._isOutsideThisEl(t.target)}function Bt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[j]=this;var n,o,i={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return It(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Bt.supportPointer&&"PointerEvent"in window&&!u,emptyInsertThreshold:5};for(n in K.initializePlugins(this,t,i),i)n in e||(e[n]=i[n]);for(o in Pt(e),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&At,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?h(t,"pointerdown",this._onTapStart):(h(t,"mousedown",this._onTapStart),h(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(h(t,"dragover",this),h(t,"dragenter",this)),Et.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,x())}function Ft(t,e,n,o,i,r,a,l){var s,c,u=t[j],d=u.options.onMove;return!window.CustomEvent||y||w?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||k(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),c=d?d.call(u,s,a):c}function jt(t){t.draggable=!1}function Ht(){Ct=!1}function Lt(t){return setTimeout(t,0)}function Kt(t){return clearTimeout(t)}Bt.prototype={constructor:Bt,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(gt=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,q):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(!function(t){Tt.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&Tt.push(o)}}(o),!q&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!u||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=N(l,t.draggable,o,!1))&&l.animated||J===l)){if(nt=B(l),it=B(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return U({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),z("filter",n,{evt:e}),void(i&&e.cancelable&&e.preventDefault())}else if(c=c&&c.split(",").some(function(t){if(t=N(s,t.trim(),o,!1))return U({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),z("filter",n,{evt:e}),!0}))return void(i&&e.cancelable&&e.preventDefault());t.handle&&!N(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;n&&!q&&n.parentNode===r&&(o=k(n),$=r,V=(q=n).parentNode,Q=q.nextSibling,J=n,at=a.group,st={target:Bt.dragged=q,clientX:(e||t).clientX,clientY:(e||t).clientY},ht=st.clientX-o.left,ft=st.clientY-o.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,q.style["will-change"]="all",o=function(){z("delayEnded",i,{evt:t}),Bt.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!s&&i.nativeDraggable&&(q.draggable=!0),i._triggerDragStart(t,e),U({sortable:i,name:"choose",originalEvent:t}),I(q,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){b(q,t.trim(),jt)}),h(l,"dragover",Xt),h(l,"mousemove",Xt),h(l,"touchmove",Xt),h(l,"mouseup",i._onDrop),h(l,"touchend",i._onDrop),h(l,"touchcancel",i._onDrop),s&&this.nativeDraggable&&(this.options.touchStartThreshold=4,q.draggable=!0),z("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(w||y)?o():Bt.eventCanceled?this._onDrop():(h(l,"mouseup",i._disableDelayedDrag),h(l,"touchend",i._disableDelayedDrag),h(l,"touchcancel",i._disableDelayedDrag),h(l,"mousemove",i._delayedDragTouchMoveHandler),h(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&h(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)))},_delayedDragTouchMoveHandler:function(t){t=t.touches?t.touches[0]:t;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){q&&jt(q),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;f(t,"mouseup",this._disableDelayedDrag),f(t,"touchend",this._disableDelayedDrag),f(t,"touchcancel",this._disableDelayedDrag),f(t,"mousemove",this._delayedDragTouchMoveHandler),f(t,"touchmove",this._delayedDragTouchMoveHandler),f(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?h(document,"pointermove",this._onTouchMove):h(document,e?"touchmove":"mousemove",this._onTouchMove):(h(q,"dragend",this),h($,"dragstart",this._onDragStart));try{document.selection?Lt(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){var n;yt=!1,$&&q?(z("dragStarted",this,{evt:e}),this.nativeDraggable&&h(document,"dragover",Yt),n=this.options,t||I(q,n.dragClass,!1),I(q,n.ghostClass,!0),Bt.active=this,t&&this._appendGhost(),U({sortable:this,name:"start",originalEvent:e})):this._nulling()},_emulateDragOver:function(){if(ct){this._lastX=ct.clientX,this._lastY=ct.clientY,kt();for(var t=document.elementFromPoint(ct.clientX,ct.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(ct.clientX,ct.clientY))!==e;)e=t;if(q.parentNode[j]._isOutsideThisEl(t),e)do{if(e[j])if(e[j]._onDragOver({clientX:ct.clientX,clientY:ct.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}while(e=(t=e).parentNode);Rt()}},_onTouchMove:function(t){if(st){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=Z&&v(Z,!0),a=Z&&r&&r.a,l=Z&&r&&r.d,e=Ot&&bt&&E(bt),a=(i.clientX-st.clientX+o.x)/(a||1)+(e?e[0]-_t[0]:0)/(a||1),l=(i.clientY-st.clientY+o.y)/(l||1)+(e?e[1]-_t[1]:0)/(l||1);if(!Bt.active&&!yt){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))n.right+10||t.clientX<=n.right&&t.clientY>n.bottom&&t.clientX>=n.left:t.clientX>n.right&&t.clientY>n.top||t.clientX<=n.right&&t.clientY>n.bottom+10}(n,r,this)&&!g.animated){if(g===q)return O(!1);if((l=g&&a===n.target?g:l)&&(w=k(l)),!1!==Ft($,a,q,o,l,w,n,!!l))return x(),a.appendChild(q),V=a,M(),O(!0)}else if(g&&function(t,e,n){n=k(X(n.el,0,n.options,!0));return e?t.clientX-1}function u(n,e){return n.apply(null,e)}var i={arr:function(n){return Array.isArray(n)},obj:function(n){return o(Object.prototype.toString.call(n),"Object")},pth:function(n){return i.obj(n)&&n.hasOwnProperty("totalLength")},svg:function(n){return n instanceof SVGElement},inp:function(n){return n instanceof HTMLInputElement},dom:function(n){return n.nodeType||i.svg(n)},str:function(n){return"string"==typeof n},fnc:function(n){return"function"==typeof n},und:function(n){return void 0===n},nil:function(n){return i.und(n)||null===n},hex:function(n){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(n)},rgb:function(n){return/^rgb/.test(n)},hsl:function(n){return/^hsl/.test(n)},col:function(n){return i.hex(n)||i.rgb(n)||i.hsl(n)},key:function(t){return!n.hasOwnProperty(t)&&!e.hasOwnProperty(t)&&"targets"!==t&&"keyframes"!==t}};function c(n){var e=/\(([^)]+)\)/.exec(n);return e?e[1].split(",").map(function(n){return parseFloat(n)}):[]}function s(n,e){var t=c(n),o=a(i.und(t[0])?1:t[0],.1,100),u=a(i.und(t[1])?100:t[1],.1,100),s=a(i.und(t[2])?10:t[2],.1,100),f=a(i.und(t[3])?0:t[3],.1,100),l=Math.sqrt(u/o),d=s/(2*Math.sqrt(u*o)),p=d<1?l*Math.sqrt(1-d*d):0,v=1,h=d<1?(d*l-f)/p:-f+l;function g(n){var t=e?e*n/1e3:n;return t=d<1?Math.exp(-t*d*l)*(v*Math.cos(p*t)+h*Math.sin(p*t)):(v+h*t)*Math.exp(-t*l),0===n||1===n?n:1-t}return e?g:function(){var e=r.springs[n];if(e)return e;for(var t=0,a=0;;)if(1===g(t+=1/6)){if(++a>=16)break}else a=0;var o=t*(1/6)*1e3;return r.springs[n]=o,o}}function f(n){return void 0===n&&(n=10),function(e){return Math.ceil(a(e,1e-6,1)*n)*(1/n)}}var l,d,p=function(){var n=11,e=1/(n-1);function t(n,e){return 1-3*e+3*n}function r(n,e){return 3*e-6*n}function a(n){return 3*n}function o(n,e,o){return((t(e,o)*n+r(e,o))*n+a(e))*n}function u(n,e,o){return 3*t(e,o)*n*n+2*r(e,o)*n+a(e)}return function(t,r,a,i){if(0<=t&&t<=1&&0<=a&&a<=1){var c=new Float32Array(n);if(t!==r||a!==i)for(var s=0;s=.001?function(n,e,t,r){for(var a=0;a<4;++a){var i=u(e,t,r);if(0===i)return e;e-=(o(e,t,r)-n)/i}return e}(r,l,t,a):0===d?l:function(n,e,t,r,a){for(var u,i,c=0;(u=o(i=e+(t-e)/2,r,a)-n)>0?t=i:e=i,Math.abs(u)>1e-7&&++c<10;);return i}(r,i,i+e,t,a)}}}(),v=(l={linear:function(){return function(n){return n}}},d={Sine:function(){return function(n){return 1-Math.cos(n*Math.PI/2)}},Circ:function(){return function(n){return 1-Math.sqrt(1-n*n)}},Back:function(){return function(n){return n*n*(3*n-2)}},Bounce:function(){return function(n){for(var e,t=4;n<((e=Math.pow(2,--t))-1)/11;);return 1/Math.pow(4,3-t)-7.5625*Math.pow((3*e-2)/22-n,2)}},Elastic:function(n,e){void 0===n&&(n=1),void 0===e&&(e=.5);var t=a(n,1,10),r=a(e,.1,2);return function(n){return 0===n||1===n?n:-t*Math.pow(2,10*(n-1))*Math.sin((n-1-r/(2*Math.PI)*Math.asin(1/t))*(2*Math.PI)/r)}}},["Quad","Cubic","Quart","Quint","Expo"].forEach(function(n,e){d[n]=function(){return function(n){return Math.pow(n,e+2)}}}),Object.keys(d).forEach(function(n){var e=d[n];l["easeIn"+n]=e,l["easeOut"+n]=function(n,t){return function(r){return 1-e(n,t)(1-r)}},l["easeInOut"+n]=function(n,t){return function(r){return r<.5?e(n,t)(2*r)/2:1-e(n,t)(-2*r+2)/2}},l["easeOutIn"+n]=function(n,t){return function(r){return r<.5?(1-e(n,t)(1-2*r))/2:(e(n,t)(2*r-1)+1)/2}}}),l);function h(n,e){if(i.fnc(n))return n;var t=n.split("(")[0],r=v[t],a=c(n);switch(t){case"spring":return s(n,e);case"cubicBezier":return u(p,a);case"steps":return u(f,a);default:return u(r,a)}}function g(n){try{return document.querySelectorAll(n)}catch(n){return}}function m(n,e){for(var t=n.length,r=arguments.length>=2?arguments[1]:void 0,a=[],o=0;o1&&(t-=1),t<1/6?n+6*(e-n)*t:t<.5?e:t<2/3?n+(e-n)*(2/3-t)*6:n}if(0==u)e=t=r=i;else{var f=i<.5?i*(1+u):i+u-i*u,l=2*i-f;e=s(l,f,o+1/3),t=s(l,f,o),r=s(l,f,o-1/3)}return"rgba("+255*e+","+255*t+","+255*r+","+c+")"}(n):void 0;var e,t,r,a}function C(n){var e=/[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(n);if(e)return e[1]}function P(n,e){return i.fnc(n)?n(e.target,e.id,e.total):n}function I(n,e){return n.getAttribute(e)}function D(n,e,t){if(M([t,"deg","rad","turn"],C(e)))return e;var a=r.CSS[e+t];if(!i.und(a))return a;var o=document.createElement(n.tagName),u=n.parentNode&&n.parentNode!==document?n.parentNode:document.body;u.appendChild(o),o.style.position="absolute",o.style.width=100+t;var c=100/o.offsetWidth;u.removeChild(o);var s=c*parseFloat(e);return r.CSS[e+t]=s,s}function B(n,e,t){if(e in n.style){var r=e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),a=n.style[e]||getComputedStyle(n).getPropertyValue(r)||"0";return t?D(n,a,t):a}}function T(n,e){return i.dom(n)&&!i.inp(n)&&(!i.nil(I(n,e))||i.svg(n)&&n[e])?"attribute":i.dom(n)&&M(t,e)?"transform":i.dom(n)&&"transform"!==e&&B(n,e)?"css":null!=n[e]?"object":void 0}function E(n){if(i.dom(n)){for(var e,t=n.style.transform||"",r=/(\w+)\(([^)]*)\)/g,a=new Map;e=r.exec(t);)a.set(e[1],e[2]);return a}}function F(n,e,t,r){var a,u=o(e,"scale")?1:0+(o(a=e,"translate")||"perspective"===a?"px":o(a,"rotate")||o(a,"skew")?"deg":void 0),i=E(n).get(e)||u;return t&&(t.transforms.list.set(e,i),t.transforms.last=e),r?D(n,i,r):i}function A(n,e,t,r){switch(T(n,e)){case"transform":return F(n,e,r,t);case"css":return B(n,e,t);case"attribute":return I(n,e);default:return n[e]||0}}function N(n,e){var t=/^(\*=|\+=|-=)/.exec(n);if(!t)return n;var r=C(n)||0,a=parseFloat(e),o=parseFloat(n.replace(t[0],""));switch(t[0][0]){case"+":return a+o+r;case"-":return a-o+r;case"*":return a*o+r}}function S(n,e){if(i.col(n))return O(n);if(/\s/g.test(n))return n;var t=C(n),r=t?n.substr(0,n.length-t.length):n;return e?r+e:r}function L(n,e){return Math.sqrt(Math.pow(e.x-n.x,2)+Math.pow(e.y-n.y,2))}function j(n){for(var e,t=n.points,r=0,a=0;a0&&(r+=L(e,o)),e=o}return r}function q(n){if(n.getTotalLength)return n.getTotalLength();switch(n.tagName.toLowerCase()){case"circle":return o=n,2*Math.PI*I(o,"r");case"rect":return 2*I(a=n,"width")+2*I(a,"height");case"line":return L({x:I(r=n,"x1"),y:I(r,"y1")},{x:I(r,"x2"),y:I(r,"y2")});case"polyline":return j(n);case"polygon":return t=(e=n).points,j(e)+L(t.getItem(t.numberOfItems-1),t.getItem(0))}var e,t,r,a,o}function H(n,e){var t=e||{},r=t.el||function(n){for(var e=n.parentNode;i.svg(e)&&i.svg(e.parentNode);)e=e.parentNode;return e}(n),a=r.getBoundingClientRect(),o=I(r,"viewBox"),u=a.width,c=a.height,s=t.viewBox||(o?o.split(" "):[0,0,u,c]);return{el:r,viewBox:s,x:s[0]/1,y:s[1]/1,w:u,h:c,vW:s[2],vH:s[3]}}function V(n,e,t){function r(t){void 0===t&&(t=0);var r=e+t>=1?e+t:0;return n.el.getPointAtLength(r)}var a=H(n.el,n.svg),o=r(),u=r(-1),i=r(1),c=t?1:a.w/a.vW,s=t?1:a.h/a.vH;switch(n.property){case"x":return(o.x-a.x)*c;case"y":return(o.y-a.y)*s;case"angle":return 180*Math.atan2(i.y-u.y,i.x-u.x)/Math.PI}}function $(n,e){var t=/[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g,r=S(i.pth(n)?n.totalLength:n,e)+"";return{original:r,numbers:r.match(t)?r.match(t).map(Number):[0],strings:i.str(n)||e?r.split(t):[]}}function W(n){return m(n?y(i.arr(n)?n.map(b):b(n)):[],function(n,e,t){return t.indexOf(n)===e})}function X(n){var e=W(n);return e.map(function(n,t){return{target:n,id:t,total:e.length,transforms:{list:E(n)}}})}function Y(n,e){var t=x(e);if(/^spring/.test(t.easing)&&(t.duration=s(t.easing)),i.arr(n)){var r=n.length;2===r&&!i.obj(n[0])?n={value:n}:i.fnc(e.duration)||(t.duration=e.duration/r)}var a=i.arr(n)?n:[n];return a.map(function(n,t){var r=i.obj(n)&&!i.pth(n)?n:{value:n};return i.und(r.delay)&&(r.delay=t?0:e.delay),i.und(r.endDelay)&&(r.endDelay=t===a.length-1?e.endDelay:0),r}).map(function(n){return k(n,t)})}function Z(n,e){var t=[],r=e.keyframes;for(var a in r&&(e=k(function(n){for(var e=m(y(n.map(function(n){return Object.keys(n)})),function(n){return i.key(n)}).reduce(function(n,e){return n.indexOf(e)<0&&n.push(e),n},[]),t={},r=function(r){var a=e[r];t[a]=n.map(function(n){var e={};for(var t in n)i.key(t)?t==a&&(e.value=n[t]):e[t]=n[t];return e})},a=0;a0?requestAnimationFrame(e):void 0}return"undefined"!=typeof document&&document.addEventListener("visibilitychange",function(){en.suspendWhenDocumentHidden&&(nn()?n=cancelAnimationFrame(n):(K.forEach(function(n){return n._onDocumentVisibility()}),U()))}),function(){n||nn()&&en.suspendWhenDocumentHidden||!(K.length>0)||(n=requestAnimationFrame(e))}}();function nn(){return!!document&&document.hidden}function en(t){void 0===t&&(t={});var r,o=0,u=0,i=0,c=0,s=null;function f(n){var e=window.Promise&&new Promise(function(n){return s=n});return n.finished=e,e}var l,d,p,v,h,g,y,b,M=(d=w(n,l=t),p=w(e,l),v=Z(p,l),h=X(l.targets),g=_(h,v),y=R(g,p),b=J,J++,k(d,{id:b,children:[],animatables:h,animations:g,duration:y.duration,delay:y.delay,endDelay:y.endDelay}));f(M);function x(){var n=M.direction;"alternate"!==n&&(M.direction="normal"!==n?"normal":"reverse"),M.reversed=!M.reversed,r.forEach(function(n){return n.reversed=M.reversed})}function O(n){return M.reversed?M.duration-n:n}function C(){o=0,u=O(M.currentTime)*(1/en.speed)}function P(n,e){e&&e.seek(n-e.timelineOffset)}function I(n){for(var e=0,t=M.animations,r=t.length;e2||(b=Math.round(b*p)/p)),v.push(b)}var k=d.length;if(k){g=d[0];for(var O=0;O0&&(M.began=!0,D("begin")),!M.loopBegan&&M.currentTime>0&&(M.loopBegan=!0,D("loopBegin")),d<=t&&0!==M.currentTime&&I(0),(d>=l&&M.currentTime!==e||!e)&&I(e),d>t&&d=e&&(u=0,M.remaining&&!0!==M.remaining&&M.remaining--,M.remaining?(o=i,D("loopComplete"),M.loopBegan=!1,"alternate"===M.direction&&x()):(M.paused=!0,M.completed||(M.completed=!0,D("loopComplete"),D("complete"),!M.passThrough&&"Promise"in window&&(s(),f(M)))))}return M.reset=function(){var n=M.direction;M.passThrough=!1,M.currentTime=0,M.progress=0,M.paused=!0,M.began=!1,M.loopBegan=!1,M.changeBegan=!1,M.completed=!1,M.changeCompleted=!1,M.reversePlayback=!1,M.reversed="reverse"===n,M.remaining=M.loop,r=M.children;for(var e=c=r.length;e--;)M.children[e].reset();(M.reversed&&!0!==M.loop||"alternate"===n&&1===M.loop)&&M.remaining++,I(M.reversed?M.duration:0)},M._onDocumentVisibility=C,M.set=function(n,e){return z(n,e),M},M.tick=function(n){i=n,o||(o=i),B((i+(u-o))*en.speed)},M.seek=function(n){B(O(n))},M.pause=function(){M.paused=!0,C()},M.play=function(){M.paused&&(M.completed&&M.reset(),M.paused=!1,K.push(M),C(),U())},M.reverse=function(){x(),M.completed=!M.reversed,C()},M.restart=function(){M.reset(),M.play()},M.remove=function(n){rn(W(n),M)},M.reset(),M.autoplay&&M.play(),M}function tn(n,e){for(var t=e.length;t--;)M(n,e[t].animatable.target)&&e.splice(t,1)}function rn(n,e){var t=e.animations,r=e.children;tn(n,t);for(var a=r.length;a--;){var o=r[a],u=o.animations;tn(n,u),u.length||o.children.length||r.splice(a,1)}t.length||r.length||e.pause()}return en.version="3.2.1",en.speed=1,en.suspendWhenDocumentHidden=!0,en.running=K,en.remove=function(n){for(var e=W(n),t=K.length;t--;)rn(e,K[t])},en.get=A,en.set=z,en.convertPx=D,en.path=function(n,e){var t=i.str(n)?g(n)[0]:n,r=e||100;return function(n){return{property:n,el:t,svg:H(t),totalLength:q(t)*(r/100)}}},en.setDashoffset=function(n){var e=q(n);return n.setAttribute("stroke-dasharray",e),e},en.stagger=function(n,e){void 0===e&&(e={});var t=e.direction||"normal",r=e.easing?h(e.easing):null,a=e.grid,o=e.axis,u=e.from||0,c="first"===u,s="center"===u,f="last"===u,l=i.arr(n),d=l?parseFloat(n[0]):parseFloat(n),p=l?parseFloat(n[1]):0,v=C(l?n[1]:n)||0,g=e.start||0+(l?d:0),m=[],y=0;return function(n,e,i){if(c&&(u=0),s&&(u=(i-1)/2),f&&(u=i-1),!m.length){for(var h=0;h-1&&K.splice(o,1);for(var s=0;s - + + + {# TODO: remove#} + + + \ No newline at end of file From e3b6a2379a95b7296205fc3cbf5f1cab9706604d Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:15:20 +0800 Subject: [PATCH 38/47] update (#57) --- src/agentscope/studio/tools/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/agentscope/studio/tools/__init__.py diff --git a/src/agentscope/studio/tools/__init__.py b/src/agentscope/studio/tools/__init__.py new file mode 100644 index 000000000..e69de29bb From 98f147dbbed5e55cfcbeb55bb3233afea6e95d5a Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:22:31 +0800 Subject: [PATCH 39/47] update (#58) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a55b04d70..6ebd90a12 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ "isort", "playwright", "markdownify", + "opencv-python-headless", ] extra_distribute_requires = [ @@ -118,7 +119,6 @@ "babel==2.15.0", "gunicorn", "numpy", - "opencv-python-headless", ] with open("README.md", "r", encoding="UTF-8") as fh: From 8b9cd2f6d6ccca35ed9b6291ab667c5c90bb779a Mon Sep 17 00:00:00 2001 From: qbc Date: Mon, 11 Nov 2024 20:12:36 +0800 Subject: [PATCH 40/47] hitfix --- src/agentscope/studio/tools/condition_operator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/agentscope/studio/tools/condition_operator.py b/src/agentscope/studio/tools/condition_operator.py index 193be9b25..8e27e2f93 100644 --- a/src/agentscope/studio/tools/condition_operator.py +++ b/src/agentscope/studio/tools/condition_operator.py @@ -10,7 +10,8 @@ def eval_condition_operator( ) -> bool: """Eval condition operator only for Msg content or string""" if isinstance(actual_value, Msg): - actual_value = actual_value.get("content", "") + if hasattr(actual_value, "content"): + actual_value = actual_value.content operator_funcs = { "contains": lambda: target_value in actual_value, From 089dadb7a1d11430405b453fdcabb578150798ea Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Mon, 11 Nov 2024 20:56:00 +0800 Subject: [PATCH 41/47] update (#60) --- src/agentscope/web/workstation/workflow_node.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 491dfd65e..412f13773 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Workflow node opt.""" import ast +import copy from abc import ABC, abstractmethod from enum import IntEnum from functools import partial @@ -93,7 +94,7 @@ def __init__( self.only_compile = only_compile self.node_id = node_id - self.opt_kwargs = opt_kwargs + self.opt_kwargs = copy.deepcopy(opt_kwargs) self.source_kwargs = source_kwargs self.dep_opts = dep_opts self.dep_vars = [opt.var_name for opt in self.dep_opts] From b078f03861f9a9020f2b65de13f656863c5ce170 Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:47:16 +0800 Subject: [PATCH 42/47] i18n for login page (#61) --- .../studio/static/i18n/i18n_en.json | 459 +++++++++--------- .../studio/static/i18n/i18n_zh.json | 459 +++++++++--------- .../studio/static/js/workstation.js | 2 +- src/agentscope/studio/templates/login.html | 71 ++- 4 files changed, 518 insertions(+), 473 deletions(-) diff --git a/src/agentscope/studio/static/i18n/i18n_en.json b/src/agentscope/studio/static/i18n/i18n_en.json index 31ad390a2..1b6c27d88 100644 --- a/src/agentscope/studio/static/i18n/i18n_en.json +++ b/src/agentscope/studio/static/i18n/i18n_en.json @@ -1,226 +1,237 @@ { - "index-Tutorial": "Tutorial", - "index-API": "API Document", - "index-guide.introduction": "AgentScope Studio is a web-based platform for monitoring, managing and developing multi-agent applications in AgentScope! Feel free to explore the platform and its features.", - "index-guide.h2": "Start to explore AgentScope Studio with", - "index-guide.DashboardDetail": "Monitor and manage your AgentScope running instances.", - "index-guide.WorkstationDetail": "Develop your multi-agent applications by dragging.", - "index-guide.GalleryDetail": "Coming soon ...", - "index-guide.ServerDetail": "Manage your agent servers (In development).", - "dashboard-run-placeholder-Search":"Search", - "market-title": "Coming soon ...", - "dialogue-detail-dialogue-Nodata":"No messages available.", - "dialogue-detail-dialogue-User":"User", - "dialogue-detail-dialogue-textarea": "Input message here ...", - "dialogue-detail-dialogue-send-btn": "Send", - "condition-Operator": "Condition Operator", - "condition-TargetValue": "Target Value", - "workstation-Example":"Example", - "workstation-sidebar-subitem-Agents": "Two Agents", - "workstation-sidebar-subitem-Pipeline": "Pipeline", - "workstation-sidebar-subitem-Conversation": "Conversation", - "workstation-sidebar-subitem-GroupChat": "Group Chat", - "workstation-sidebar-item-Workflow": "Workflow", - "workstation-sidebar-item-Model": "Model", - "workstation-sidebar-item-Message": "Message", - "workstation-sidebar-item-Agent": "Agent", - "workstation-sidebar-item-Pipeline": "Pipeline", - "workstation-sidebar-item-Service": "Service", - "workstation-sidebar-item-Tool": "Tool", - "workstation-surveyContent": "We want to hear from you", - "workstation-surveyContent-Button": "Participation in questionnaire surveys", - "workstation-menu-btn-home": "Home", - "workstation-menu-btn-download": "Download workflow configuration", - "workstation-menu-btn-load": "Load workflow configuration", - "workstation-menu-btn-check": "Check the workflow", - "workstation-menu-btn-clear": "Clear", - "workstation-menu-btn-export": "Export Python code", - "workstation-menu-btn-run": "Run the workflow", - "workstation-menu-btn-save": "Save workflow", - "workstation-menu-btn-workflow": "Load workflow", - "agent-broadcast-Copy": "Copy", - "agent-broadcast-readme": "An agent that only broadcasts its content", - "agent-broadcast-labelName": "Name", - "agent-broadcast-labelName-Assistant": "Assistant", - "agent-broadcast-content": "content", - "agent-dialogagent-Copy": "Copy", - "agent-dialogagent-readme": "An dialog agent that can interact with users or other agents.", - "agent-dialogagent-labelName": "Name", - "agent-dialogagent-labelName-Assistant": "Assistant", - "agent-dialogagent-SystemPrompt": "System prompt", - "agent-dialogagent-textareaInput-placeholder": "You're an assistant.", - "agent-dialogagent-ModelConfigName": "Model config name", - "agent-dictdialogagent-Copy": "Copy", - "agent-dictdialogagent-readme": "Agent that generates response in a dict format", - "agent-dictdialogagent-labelName": "Name", - "agent-dictdialogagent-labelName-placeholder": "Assistant", - "agent-dictdialogagent-SystemPrompt": "System prompt", - "agent-dictdialogagent-SystemPrompt-textarea": "You're an assistant.", - "agent-dictdialogagent-ModelConfigName": "Model config name", - "agent-dictdialogagent-MaxRetries": "Max retries", - "agent-dictdialogagent-labelParse": "Parse function (Optional)", - "agent-dictdialogagent-labelFault": "Fault handler (Optional)", - "agent-reactagent-copy": "Copy", - "agent-reactagent-readme": "Agent for ReAct (reasoning and acting) with tools", - "agent-reactagent-labelName": "Name", - "agent-reactagent-labelName-Assistant": "Assistant", - "agent-reactagent-SystemPrompt": "System prompt", - "agent-reactagent-SystemPrompt-textarea": "You're an assistant.", - "agent-reactagent-ModelConfigName": "Model config name", - "agent-reactagent-Tools": "Tools", - "agent-reactagent-add-Service": "Service", - "agent-reactagent-Tools-placeholder": "Please click the 'Service' button above to add tools.", - "agent-reactagent-iterations": "Max reasoning-acting iterations", - "agent-reactagent-Verbose": "Verbose", - "agent-texttoimageagent-TextToImageAgent": "TextToImageAgent", - "agent-texttoimageagent-Copy": "Copy", - "agent-texttoimageagent-readme": "Agent for text to image generation", - "agent-texttoimageagent-labelName": "Name", - "agent-texttoimageagent-labelName-Assistant": "Assistant", - "agent-texttoimageagent-ModelConfigName": "Model config name", - "agent-useragent-Copy": "Copy", - "agent-useragent-readme": "A proxy agent for user", - "agent-useragent-labelName": "Name", - "agent-useragent-labelName-placeholder": "User", - "message-msg-labelName": "Name", - "message-msg-labelName-placeholder": "Host", - "message-msg-labelContent": "Content", - "message-msg-labelContent-placeholder": "Hello", - "message-msg-labelRole": "Role", - "message-msg-labelRole-placeholder": "assistant", - "message-msg-labelURL": "URL (Optional)", - "model-dashscope-chat-readme": "DashScope Chat Configurations (Your API key will NOT be stored and exposed to the website maintainer)", - "model-dashscope-chat-labelConfigName": "Config Name", - "model-dashscope-chat-labelModelName": "Model Name", - "model-dashscope-chat-labelTemperature": "Temperature", - "model-dashscope-chat-labelSeed": "Seed", - "model-openai-chat-readme": "OpenAI Chat Configurations (Your API key will NOT be stored and exposed to the website maintainer)", - "model-openai-chat-labelConfigName": "Config Name", - "model-openai-chat-labelModelName": "Model Name", - "model-openai-chat-Temperature": "Temperature", - "model-openai-chat-Seed": "Seed", - "model-openai-chat-Advanced": "Advanced ▼", - "model-openai-chat-advanced-box": "Base URL", - "model-post-api-chat-readme": "Post API Chat Configuration", - "model-post-api-chat-ConfigName": "Config Name", - "model-post-api-chat-ModelName": "Model Name", - "model-post-api-chat-Temperature": "Temperature", - "model-post-api-chat-Seed": "Seed", - "model-post-api-chat-API": "API url", - "model-post-api-chat-ContentType": "Content-Type", - "model-post-api-chat-Authorization": "Authorization", - "model-post-api-chat-MessagesKey": "Messages Key", - "model-post-api-chat-MessagesKey-placeholder": "messages", - "pipeline-forlooppipeline-readme":"A template pipeline for implementing control flow like for-loop", - "pipeline-forlooppipeline-MaxLoop": "Max Loop", - "pipeline-forlooppipeline-BreakFunction": "Break Function", - "pipeline-ifelsepipeline-readme": "A template pipeline for implementing control flow with if-else logic", - "pipeline-ifelsepipeline-Condition": "Condition Function", - "pipeline-msghub-readme": "MsgHub is used to share messages among a group of agents", - "pipeline-msghub-AnnouncementName": "Announcement Name", - "pipeline-msghub-AnnouncementName-placeholder": "Host", - "pipeline-msghub-AnnouncementContent": "Announcement Content", - "pipeline-msghub-AnnouncementContent-placeholder": "Welcome to the group chat!", - "pipeline-placeholder-readme":"A placeholder that do nothing", - "pipeline-sequentialpipeline-readme": "A template pipeline for implementing sequential logic (from top to bottom)", - "pipeline-switchpipeline-readme": "A template pipeline for implementing control flow with switch-case logic", - "pipeline-switchpipeline-Condition": "Condition Function", - "pipeline-switchpipeline-AddCase": "Add Case", - "pipeline-switchpipeline-RemoveCase": "Remove Case", - "pipeline-whilelooppipeline-readme": "A template pipeline for implementing control flow like while-loop", - "pipeline-whilelooppipeline-Condition": "Condition Function", - "service-bing-search-readme": "Integrate the Bing Search service within ReActAgent to enhance agent capabilities", - "service-bing-search-Results": "Results Number", - "service-execute-python-readme": "Integrate the Python Interpreter within ReActAgent to enhance agent capabilities", - "service-google-search-readme": "Integrate the Google Search service within ReActAgent to enhance agent capabilities", - "service-read-text-readme": "Integrate the Read Text service within ReActAgent to enhance agent capabilities", - "service-write-text-readme": "Integrate the Write Text Service within ReActAgent to enhance agent capabilities", - "Welcome-Welcome": "👏 Welcome!", - "welcome-Easy-to-use": "Easy-to-use Multi-Agent Platform:", - "welcome-Shortkeys": "Shortkeys:", - "welcome-Easy-to-Use": "🤝 Easy-to-Use", - "welcome-High-Robustness": "✅ High Robustness", - "welcome-Actor-Based": "🌐 Actor-Based Distribution", - "welcome-Documentations": "Documentations:", - "welcome-Quick-Start": "🚀 Quick Start", - "welcome-Examples": "💡 Examples", - "welcome-Tutorial": " 📘 Tutorial & API Reference", - "en1-json-readme": "📖 This is an example of how to program a one-round conversation in AgentScope.", - "en1-json-readme-h4": "Modules Used:", - "en1-json-readme-sentence1": "- Each application must contain a model configured.", - "en1-json-readme-sentence2": "- Represents a user in a application.", - "en1-json-readme-sentence3": "- Agent for dialog in an application.", - "en2-json-readme": "📖 This example demonstrates a one-round user-ReactAgent (with PythonService) conversation with Sequential Pipeline by AgentScope. ", - "en2-json-readme-h4": "Modules Used:", - "en2-json-readme-sentence1": "- Each application must contain a model configured.", - "en2-json-readme-sentence2": "- Make the message passes in a predefined, sequential order among agents.", - "en2-json-readme-sentence3": "- Represents a user in a application.", - "en2-json-readme-sentence4": "- An agent class that implements the ReAct algorithm.", - "en2-json-readme-sentence5": "- Execute a piece of python code.", - "en3-json-readme": "📖 This is a example of how to program a user-agent conversation in AgentScope. ", - "en3-json-readme-h4": "Modules Used:", - "en3-json-readme-sentence1": "- Each application must contain a model configured.", - "en3-json-readme-sentence2": "- Designed to perform repetitive operations.", - "en3-json-readme-sentence3": "- Make the message passes in a predefined, sequential order among agents.", - "en3-json-readme-sentence4": "- Represents a user in a application.", - "en3-json-readme-sentence5": "- Agent for dialog in an application.", - "en3-json-readme-moreDetails": "For more details, please see ", - "en3-json-readme-moreDetailsHref": "here", - "en4-json-readme": "📖 This example demonstrates a multi-agent group chat facilitated by AgentScope.", - "en4-json-readme-h4": "Modules Used:", - "en4-json-readme-sentence1": "- Each application must contain a model configured.", - "en4-json-readme-sentence2": "- MsgHub is where agents share their messages like a group chat.", - "en4-json-readme-sentence3": "- Message is flow passing among agents.", - "en4-json-readme-sentence4": "- Designed to perform repetitive operations.", - "en4-json-readme-sentence5": "- Make the message passes in a predefined, sequential order among agents.", - "en4-json-readme-sentence6": "- Represents a user in a application.", - "en4-json-readme-sentence7": "- Agent for dialog in an application.", - "en4-json-readme-moreDetails": "For more details, please see ", - "en4-json-readme-moreDetailsHref": "here", - "service-text-to-audio-reademe": "Integrate the Text to Audio Service within ReActAgent to enhance agent capabilities", - "service-text-to-audio-modelName": "Model Name", - "service-text-to-audio-sampleRate": "Sample Rate", - "service-text-to-image-readme": "Integrate the Text to Image Service within ReActAgent to enhance agent capabilities", - "service-text-to-image-modelName": "Model Name", - "service-text-to-image-numberofImages": "Number of Images", - "service-text-to-image-size": "size", - "tool-image-synthesis-readme": "Integrate the Text to Image", - "tool-image_synthesis-modelName": "Model Name", - "tool-image_synthesis-numberofImages": "Number of Images", - "tool-image_synthesis-size": "size", - "tool-image_synthesis-saveDir": "Save dir", - "tool-image-composition-readme": "Composite images into one image", - "tool-image-composition-Titles": "Titles", - "tool-image-composition-Outputpath": "Output path", - "tool-image-composition-row": "Row", - "tool-image-composition-Column": "Column", - "tool-image-composition-Spacing": "Spacing", - "tool-image-composition-titleHeight": "Title height", - "tool-image-composition-fontName": "Font name", - "tool-image-motion-readme": "Convert an image to an mp4 or a gif by shifting the perspective", - "tool-image-motion-outputPath": "Output path", - "tool-image-motion-outputFormat": "Output Format", - "tool-image-motion-labelDuration": "Duration", - "tool-image-motion-labelMotiondirection": "Motion direction", - "tool-post-readme": "Post a request and return the response", - "tool-post-label-outputPath": "Output path", - "tool-post-label-outputtype": "Output type", - "tool-video-composition-readme": "Composite videos into one video", - "tool-video-composition-outputPath": "Output path", - "tool-video-composition-targetVideoWidth": "Target Video Width", - "tool-video-composition-targetVideoHeight": "Target Video Height", - "tool-video-composition-targetVideoFps": "Target Video Fps", - "tool-code-readme": "Execute Python Code", - "tool-code-note": "Note:", - "tool-code-note-var": "- Input and Output variables must be a Msg or dict object.", - "tool-code-note-func": "- Please do not modify the function name.", - "tool-code-content": "Code", - "workstarionjs-import-prev": "Previous", - "workstarionjs-import-next": "Next", - "workstarionjs-import-skip": "Skip", - "workstarionjs-import-quit": "Quit", - "workstarionjs-import-caution": "Caution: You are currently in the tutorial mode where modifications are restricted.", - "workstarionjs-import-Caution-click": "Please click", - "workstarionjs-import-Caution-quit": "Quit", - "workstarionjs-import-Caution-exit": "to exit and start creating your custom multi-agent applications. " + "login-page-title": "AgentScope WorkStation Login Page", + "login-page-welcome": "Welcome to AgentScope WorkStation", + "login-page-terms-title": "Please log in and star the AgentScope repository", + "login-page-terms-acknowledgment": "By logging in, you acknowledge that:", + "login-page-terms-star": "This service will star🌟 AgentScope repository on your behalf.", + "login-page-terms-api": "Your API key will NOT be stored and exposed to the website maintainer.", + "login-page-terms-data": "No user data (e.g., drawn workflow) within the service will be saved unless you explicitly choose to save it.", + "login-page-terms-agree": "I agree to the terms of service", + "login-page-login-github": "Login with GitHub", + "login-page-login-guest": "Login as Guest", + "login-page-please-wait": "Please wait...", + "index-Tutorial": "Tutorial", + "index-API": "API Document", + "index-guide.introduction": "AgentScope Studio is a web-based platform for monitoring, managing and developing multi-agent applications in AgentScope! Feel free to explore the platform and its features.", + "index-guide.h2": "Start to explore AgentScope Studio with", + "index-guide.DashboardDetail": "Monitor and manage your AgentScope running instances.", + "index-guide.WorkstationDetail": "Develop your multi-agent applications by dragging.", + "index-guide.GalleryDetail": "Coming soon ...", + "index-guide.ServerDetail": "Manage your agent servers (In development).", + "dashboard-run-placeholder-Search": "Search", + "market-title": "Coming soon ...", + "dialogue-detail-dialogue-Nodata": "No messages available.", + "dialogue-detail-dialogue-User": "User", + "dialogue-detail-dialogue-textarea": "Input message here ...", + "dialogue-detail-dialogue-send-btn": "Send", + "condition-Operator": "Condition Operator", + "condition-TargetValue": "Target Value", + "workstation-Example": "Example", + "workstation-sidebar-subitem-Agents": "Two Agents", + "workstation-sidebar-subitem-Pipeline": "Pipeline", + "workstation-sidebar-subitem-Conversation": "Conversation", + "workstation-sidebar-subitem-GroupChat": "Group Chat", + "workstation-sidebar-item-Workflow": "Workflow", + "workstation-sidebar-item-Model": "Model", + "workstation-sidebar-item-Message": "Message", + "workstation-sidebar-item-Agent": "Agent", + "workstation-sidebar-item-Pipeline": "Pipeline", + "workstation-sidebar-item-Service": "Service", + "workstation-sidebar-item-Tool": "Tool", + "workstation-surveyContent": "We want to hear from you", + "workstation-surveyContent-Button": "Participation in questionnaire surveys", + "workstation-menu-btn-home": "Home", + "workstation-menu-btn-download": "Download workflow configuration", + "workstation-menu-btn-load": "Load workflow configuration", + "workstation-menu-btn-check": "Check the workflow", + "workstation-menu-btn-clear": "Clear", + "workstation-menu-btn-export": "Export Python code", + "workstation-menu-btn-run": "Run the workflow", + "workstation-menu-btn-save": "Save workflow", + "workstation-menu-btn-workflow": "Load workflow", + "agent-broadcast-Copy": "Copy", + "agent-broadcast-readme": "An agent that only broadcasts its content", + "agent-broadcast-labelName": "Name", + "agent-broadcast-labelName-Assistant": "Assistant", + "agent-broadcast-content": "content", + "agent-dialogagent-Copy": "Copy", + "agent-dialogagent-readme": "An dialog agent that can interact with users or other agents.", + "agent-dialogagent-labelName": "Name", + "agent-dialogagent-labelName-Assistant": "Assistant", + "agent-dialogagent-SystemPrompt": "System prompt", + "agent-dialogagent-textareaInput-placeholder": "You're an assistant.", + "agent-dialogagent-ModelConfigName": "Model config name", + "agent-dictdialogagent-Copy": "Copy", + "agent-dictdialogagent-readme": "Agent that generates response in a dict format", + "agent-dictdialogagent-labelName": "Name", + "agent-dictdialogagent-labelName-placeholder": "Assistant", + "agent-dictdialogagent-SystemPrompt": "System prompt", + "agent-dictdialogagent-SystemPrompt-textarea": "You're an assistant.", + "agent-dictdialogagent-ModelConfigName": "Model config name", + "agent-dictdialogagent-MaxRetries": "Max retries", + "agent-dictdialogagent-labelParse": "Parse function (Optional)", + "agent-dictdialogagent-labelFault": "Fault handler (Optional)", + "agent-reactagent-copy": "Copy", + "agent-reactagent-readme": "Agent for ReAct (reasoning and acting) with tools", + "agent-reactagent-labelName": "Name", + "agent-reactagent-labelName-Assistant": "Assistant", + "agent-reactagent-SystemPrompt": "System prompt", + "agent-reactagent-SystemPrompt-textarea": "You're an assistant.", + "agent-reactagent-ModelConfigName": "Model config name", + "agent-reactagent-Tools": "Tools", + "agent-reactagent-add-Service": "Service", + "agent-reactagent-Tools-placeholder": "Please click the 'Service' button above to add tools.", + "agent-reactagent-iterations": "Max reasoning-acting iterations", + "agent-reactagent-Verbose": "Verbose", + "agent-texttoimageagent-TextToImageAgent": "TextToImageAgent", + "agent-texttoimageagent-Copy": "Copy", + "agent-texttoimageagent-readme": "Agent for text to image generation", + "agent-texttoimageagent-labelName": "Name", + "agent-texttoimageagent-labelName-Assistant": "Assistant", + "agent-texttoimageagent-ModelConfigName": "Model config name", + "agent-useragent-Copy": "Copy", + "agent-useragent-readme": "A proxy agent for user", + "agent-useragent-labelName": "Name", + "agent-useragent-labelName-placeholder": "User", + "message-msg-labelName": "Name", + "message-msg-labelName-placeholder": "Host", + "message-msg-labelContent": "Content", + "message-msg-labelContent-placeholder": "Hello", + "message-msg-labelRole": "Role", + "message-msg-labelRole-placeholder": "assistant", + "message-msg-labelURL": "URL (Optional)", + "model-dashscope-chat-readme": "DashScope Chat Configurations (Your API key will NOT be stored and exposed to the website maintainer)", + "model-dashscope-chat-labelConfigName": "Config Name", + "model-dashscope-chat-labelModelName": "Model Name", + "model-dashscope-chat-labelTemperature": "Temperature", + "model-dashscope-chat-labelSeed": "Seed", + "model-openai-chat-readme": "OpenAI Chat Configurations (Your API key will NOT be stored and exposed to the website maintainer)", + "model-openai-chat-labelConfigName": "Config Name", + "model-openai-chat-labelModelName": "Model Name", + "model-openai-chat-Temperature": "Temperature", + "model-openai-chat-Seed": "Seed", + "model-openai-chat-Advanced": "Advanced ▼", + "model-openai-chat-advanced-box": "Base URL", + "model-post-api-chat-readme": "Post API Chat Configuration", + "model-post-api-chat-ConfigName": "Config Name", + "model-post-api-chat-ModelName": "Model Name", + "model-post-api-chat-Temperature": "Temperature", + "model-post-api-chat-Seed": "Seed", + "model-post-api-chat-API": "API url", + "model-post-api-chat-ContentType": "Content-Type", + "model-post-api-chat-Authorization": "Authorization", + "model-post-api-chat-MessagesKey": "Messages Key", + "model-post-api-chat-MessagesKey-placeholder": "messages", + "pipeline-forlooppipeline-readme": "A template pipeline for implementing control flow like for-loop", + "pipeline-forlooppipeline-MaxLoop": "Max Loop", + "pipeline-forlooppipeline-BreakFunction": "Break Function", + "pipeline-ifelsepipeline-readme": "A template pipeline for implementing control flow with if-else logic", + "pipeline-ifelsepipeline-Condition": "Condition Function", + "pipeline-msghub-readme": "MsgHub is used to share messages among a group of agents", + "pipeline-msghub-AnnouncementName": "Announcement Name", + "pipeline-msghub-AnnouncementName-placeholder": "Host", + "pipeline-msghub-AnnouncementContent": "Announcement Content", + "pipeline-msghub-AnnouncementContent-placeholder": "Welcome to the group chat!", + "pipeline-placeholder-readme": "A placeholder that do nothing", + "pipeline-sequentialpipeline-readme": "A template pipeline for implementing sequential logic (from top to bottom)", + "pipeline-switchpipeline-readme": "A template pipeline for implementing control flow with switch-case logic", + "pipeline-switchpipeline-Condition": "Condition Function", + "pipeline-switchpipeline-AddCase": "Add Case", + "pipeline-switchpipeline-RemoveCase": "Remove Case", + "pipeline-whilelooppipeline-readme": "A template pipeline for implementing control flow like while-loop", + "pipeline-whilelooppipeline-Condition": "Condition Function", + "service-bing-search-readme": "Integrate the Bing Search service within ReActAgent to enhance agent capabilities", + "service-bing-search-Results": "Results Number", + "service-execute-python-readme": "Integrate the Python Interpreter within ReActAgent to enhance agent capabilities", + "service-google-search-readme": "Integrate the Google Search service within ReActAgent to enhance agent capabilities", + "service-read-text-readme": "Integrate the Read Text service within ReActAgent to enhance agent capabilities", + "service-write-text-readme": "Integrate the Write Text Service within ReActAgent to enhance agent capabilities", + "Welcome-Welcome": "👏 Welcome!", + "welcome-Easy-to-use": "Easy-to-use Multi-Agent Platform:", + "welcome-Shortkeys": "Shortkeys:", + "welcome-Easy-to-Use": "🤝 Easy-to-Use", + "welcome-High-Robustness": "✅ High Robustness", + "welcome-Actor-Based": "🌐 Actor-Based Distribution", + "welcome-Documentations": "Documentations:", + "welcome-Quick-Start": "🚀 Quick Start", + "welcome-Examples": "💡 Examples", + "welcome-Tutorial": " 📘 Tutorial & API Reference", + "en1-json-readme": "📖 This is an example of how to program a one-round conversation in AgentScope.", + "en1-json-readme-h4": "Modules Used:", + "en1-json-readme-sentence1": "- Each application must contain a model configured.", + "en1-json-readme-sentence2": "- Represents a user in a application.", + "en1-json-readme-sentence3": "- Agent for dialog in an application.", + "en2-json-readme": "📖 This example demonstrates a one-round user-ReactAgent (with PythonService) conversation with Sequential Pipeline by AgentScope. ", + "en2-json-readme-h4": "Modules Used:", + "en2-json-readme-sentence1": "- Each application must contain a model configured.", + "en2-json-readme-sentence2": "- Make the message passes in a predefined, sequential order among agents.", + "en2-json-readme-sentence3": "- Represents a user in a application.", + "en2-json-readme-sentence4": "- An agent class that implements the ReAct algorithm.", + "en2-json-readme-sentence5": "- Execute a piece of python code.", + "en3-json-readme": "📖 This is a example of how to program a user-agent conversation in AgentScope. ", + "en3-json-readme-h4": "Modules Used:", + "en3-json-readme-sentence1": "- Each application must contain a model configured.", + "en3-json-readme-sentence2": "- Designed to perform repetitive operations.", + "en3-json-readme-sentence3": "- Make the message passes in a predefined, sequential order among agents.", + "en3-json-readme-sentence4": "- Represents a user in a application.", + "en3-json-readme-sentence5": "- Agent for dialog in an application.", + "en3-json-readme-moreDetails": "For more details, please see ", + "en3-json-readme-moreDetailsHref": "here", + "en4-json-readme": "📖 This example demonstrates a multi-agent group chat facilitated by AgentScope.", + "en4-json-readme-h4": "Modules Used:", + "en4-json-readme-sentence1": "- Each application must contain a model configured.", + "en4-json-readme-sentence2": "- MsgHub is where agents share their messages like a group chat.", + "en4-json-readme-sentence3": "- Message is flow passing among agents.", + "en4-json-readme-sentence4": "- Designed to perform repetitive operations.", + "en4-json-readme-sentence5": "- Make the message passes in a predefined, sequential order among agents.", + "en4-json-readme-sentence6": "- Represents a user in a application.", + "en4-json-readme-sentence7": "- Agent for dialog in an application.", + "en4-json-readme-moreDetails": "For more details, please see ", + "en4-json-readme-moreDetailsHref": "here", + "service-text-to-audio-reademe": "Integrate the Text to Audio Service within ReActAgent to enhance agent capabilities", + "service-text-to-audio-modelName": "Model Name", + "service-text-to-audio-sampleRate": "Sample Rate", + "service-text-to-image-readme": "Integrate the Text to Image Service within ReActAgent to enhance agent capabilities", + "service-text-to-image-modelName": "Model Name", + "service-text-to-image-numberofImages": "Number of Images", + "service-text-to-image-size": "size", + "tool-image-synthesis-readme": "Integrate the Text to Image", + "tool-image_synthesis-modelName": "Model Name", + "tool-image_synthesis-numberofImages": "Number of Images", + "tool-image_synthesis-size": "size", + "tool-image_synthesis-saveDir": "Save dir", + "tool-image-composition-readme": "Composite images into one image", + "tool-image-composition-Titles": "Titles", + "tool-image-composition-Outputpath": "Output path", + "tool-image-composition-row": "Row", + "tool-image-composition-Column": "Column", + "tool-image-composition-Spacing": "Spacing", + "tool-image-composition-titleHeight": "Title height", + "tool-image-composition-fontName": "Font name", + "tool-image-motion-readme": "Convert an image to an mp4 or a gif by shifting the perspective", + "tool-image-motion-outputPath": "Output path", + "tool-image-motion-outputFormat": "Output Format", + "tool-image-motion-labelDuration": "Duration", + "tool-image-motion-labelMotiondirection": "Motion direction", + "tool-post-readme": "Post a request and return the response", + "tool-post-label-outputPath": "Output path", + "tool-post-label-outputtype": "Output type", + "tool-video-composition-readme": "Composite videos into one video", + "tool-video-composition-outputPath": "Output path", + "tool-video-composition-targetVideoWidth": "Target Video Width", + "tool-video-composition-targetVideoHeight": "Target Video Height", + "tool-video-composition-targetVideoFps": "Target Video Fps", + "tool-code-readme": "Execute Python Code", + "tool-code-note": "Note:", + "tool-code-note-var": "- Input and Output variables must be a Msg or dict object.", + "tool-code-note-func": "- Please do not modify the function name.", + "tool-code-content": "Code", + "workstarionjs-import-prev": "Previous", + "workstarionjs-import-next": "Next", + "workstarionjs-import-skip": "Skip", + "workstarionjs-import-quit": "Quit", + "workstarionjs-import-caution": "Caution: You are currently in the tutorial mode where modifications are restricted.", + "workstarionjs-import-Caution-click": "Please click", + "workstarionjs-import-Caution-quit": "Quit", + "workstarionjs-import-Caution-exit": "to exit and start creating your custom multi-agent applications. " } diff --git a/src/agentscope/studio/static/i18n/i18n_zh.json b/src/agentscope/studio/static/i18n/i18n_zh.json index 055dfd566..1f4a68847 100644 --- a/src/agentscope/studio/static/i18n/i18n_zh.json +++ b/src/agentscope/studio/static/i18n/i18n_zh.json @@ -1,226 +1,237 @@ { - "index-Tutorial": "教程", - "index-API": "API 文档", - "index-guide.introduction": "AgentScope Studio 是一个基于 Web 的平台,用于在 AgentScope 中监控、管理和开发多智能体应用程序!请随意探索该平台及其功能", - "index-guide.h2": "开始探索 AgentScope Studio", - "index-guide.DashboardDetail": "监控和管理您的 AgentScope 运行实例.", - "index-guide.WorkstationDetail": "通过拖动来开发您的多智能体应用程序。", - "index-guide.GalleryDetail": "即将推出 ...", - "index-guide.ServerDetail": "管理您的智能体服务器(开发中)。", - "market-title": "即将推出 ...", - "dashboard-run-placeholder-Search":"查询", - "dialogue-detail-dialogue-Nodata":"无可用消息", - "dialogue-detail-dialogue-User":"用户", - "dialogue-detail-dialogue-textarea": "在此输入消息...", - "dialogue-detail-dialogue-send-btn": "发送", - "condition-Operator": "条件运算", - "condition-TargetValue": "目标值", - "workstation-Example":"示例", - "workstation-sidebar-subitem-Agents": "两个智能体", - "workstation-sidebar-subitem-Pipeline": "流程", - "workstation-sidebar-subitem-Conversation": "会话", - "workstation-sidebar-subitem-GroupChat": "群聊", - "workstation-sidebar-item-Workflow": "工作流", - "workstation-sidebar-item-Model": "模型", - "workstation-sidebar-item-Message": "消息", - "workstation-sidebar-item-Agent": "智能体", - "workstation-sidebar-item-Pipeline": "流程", - "workstation-sidebar-item-Service": "服务", - "workstation-sidebar-item-Tool": "工具", - "workstation-surveyContent": "我们希望听到您的意见", - "workstation-surveyContent-Button": "参与问卷调查", - "workstation-menu-btn-home": "家", - "workstation-menu-btn-download": "下载工作流配置", - "workstation-menu-btn-load": "加载工作流配置", - "workstation-menu-btn-check": "检查工作流程", - "workstation-menu-btn-clear": "清除", - "workstation-menu-btn-export": "导出 Python 代码", - "workstation-menu-btn-run": "运行工作流", - "workstation-menu-btn-save": "保存工作流", - "workstation-menu-btn-workflow": "加载工作流", - "agent-broadcast-Copy": "复制", - "agent-broadcast-readme": "只会广播其内容框中文本的智能体", - "agent-broadcast-labelName": "名称", - "agent-broadcast-labelName-Assistant": "助理", - "agent-broadcast-content": "内容", - "agent-dialogagent-Copy": "复制", - "agent-dialogagent-readme": "可与用户或其他智能体互动的对话智能体。", - "agent-dialogagent-labelName": "名称", - "agent-dialogagent-labelName-Assistant": "助理", - "agent-dialogagent-SystemPrompt": "系统提示", - "agent-dialogagent-textareaInput-placeholder": "你是助理。", - "agent-dialogagent-ModelConfigName": "模型配置名称", - "agent-dictdialogagent-Copy": "复制", - "agent-dictdialogagent-readme": "生成字典格式响应的智能体", - "agent-dictdialogagent-labelName": "名称", - "agent-dictdialogagent-labelName-placeholder": "助理", - "agent-dictdialogagent-SystemPrompt": "系统提示", - "agent-dictdialogagent-SystemPrompt-textarea": "你是助理。", - "agent-dictdialogagent-ModelConfigName": "模型配置名称", - "agent-dictdialogagent-MaxRetries": "最大重试次数", - "agent-dictdialogagent-labelParse": "解析函数(可选)", - "agent-dictdialogagent-labelFault": "故障处理程序(可选)", - "agent-reactagent-copy": "复制", - "agent-reactagent-readme": "带有工具的 ReAct(推理和行动)智能体", - "agent-reactagent-labelName": "名称", - "agent-reactagent-labelName-Assistant": "助理", - "agent-reactagent-SystemPrompt": "系统提示", - "agent-reactagent-SystemPrompt-textarea": "你是一个助理。", - "agent-reactagent-ModelConfigName": "模型配置名称", - "agent-reactagent-Tools": "工具", - "agent-reactagent-add-Service": "服务", - "agent-reactagent-Tools-placeholder": "请点击上方的 '服务' 按钮添加工具。", - "agent-reactagent-iterations": "最大推理迭代次数", - "agent-reactagent-Verbose": "详细", - "agent-texttoimageagent-TextToImageAgent": "文本转图像智能体", - "agent-texttoimageagent-Copy": "复制", - "agent-texttoimageagent-readme": "文本到图像生成智能体", - "agent-texttoimageagent-labelName": "名称", - "agent-texttoimageagent-labelName-Assistant": "助理", - "agent-texttoimageagent-ModelConfigName": "模型配置名称", - "agent-useragent-Copy": "复制", - "agent-useragent-readme": "用户的智能体服务器", - "agent-useragent-labelName": "名称", - "agent-useragent-labelName-placeholder": "用户", - "message-msg-labelName": "名称", - "message-msg-labelName-placeholder": "主持人", - "message-msg-labelContent": "内容", - "message-msg-labelContent-placeholder": "你好", - "message-msg-labelRole": "角色", - "message-msg-labelRole-placeholder": "assistant", - "message-msg-labelURL": "网址(可选)", - "model-dashscope-chat-readme": "DashScope 聊天配置(您的 API 密钥不会被存储或暴露给网站维护者)", - "model-dashscope-chat-labelConfigName": "配置名称", - "model-dashscope-chat-labelModelName": "模型名称", - "model-dashscope-chat-labelTemperature": "温度", - "model-dashscope-chat-labelSeed": "种子", - "model-openai-chat-readme": "OpenAI 聊天配置(您的 API 密钥不会被保存或暴露给网站维护者", - "model-openai-chat-labelConfigName": "配置名称", - "model-openai-chat-labelModelName": "模型名称", - "model-openai-chat-Temperature": "温度", - "model-openai-chat-Seed": "种子", - "model-openai-chat-Advanced": "高级 ▼", - "model-openai-chat-advanced-box": "基准 URL", - "model-post-api-chat-readme": "Post API 聊天配置", - "model-post-api-chat-ConfigName": "配置名称", - "model-post-api-chat-ModelName": "模型名称", - "model-post-api-chat-Temperature": "温度", - "model-post-api-chat-Seed": "种子", - "model-post-api-chat-API": "API 地址", - "model-post-api-chat-ContentType": "Content-Type", - "model-post-api-chat-Authorization": "授权", - "model-post-api-chat-MessagesKey": "Messages Key", - "model-post-api-chat-MessagesKey-placeholder": "消息", - "pipeline-forlooppipeline-readme":"用于实现 for 循环等控制流的模板流程", - "pipeline-forlooppipeline-MaxLoop": "最大循环次数", - "pipeline-forlooppipeline-BreakFunction": "中断函数", - "pipeline-ifelsepipeline-readme": "用 if-else 逻辑实现控制流的模板流程", - "pipeline-ifelsepipeline-Condition": "条件函数", - "pipeline-msghub-readme": "MsgHub 用于在一组智能体之间共享信息", - "pipeline-msghub-AnnouncementName": "公告名称", - "pipeline-msghub-AnnouncementName-placeholder": "host", - "pipeline-msghub-AnnouncementContent": "公告内容", - "pipeline-msghub-AnnouncementContent-placeholder": "欢迎加入群聊!", - "pipeline-placeholder-readme":"不执行任何操作的占位符", - "pipeline-sequentialpipeline-readme": "实现顺序逻辑的模板流程(从上到下)", - "pipeline-switchpipeline-readme": "用于使用 switch-case 逻辑实现控制流的模板流程", - "pipeline-switchpipeline-Condition": "条件函数", - "pipeline-switchpipeline-AddCase": "添加案例", - "pipeline-switchpipeline-RemoveCase": "删除案例", - "pipeline-whilelooppipeline-readme": "用于实现 while 循环等控制流的模板流程", - "pipeline-whilelooppipeline-Condition": "条件函数", - "service-bing-search-readme": "在 ReActAgent 中集成必应搜索服务,以增强智能体功能", - "service-bing-search-Results": "结果编号", - "service-execute-python-readme": "在 ReActAgent中集成 Python 解释器,以增强智能体功能", - "service-google-search-readme": "将 Google 搜索服务集成到 ReActAgent 中以增强智能体功能", - "service-read-text-readme": "将阅读文本服务集成到 ReActAgent 中以增强智能体功能", - "service-write-text-readme": "将写入文本服务集成到 ReActAgent 中提升智能体能力", - "Welcome-Welcome":"👏 欢迎!", - "welcome-Easy-to-use": "易于使用的多智能体平台:", - "welcome-Shortkeys": "要点:", - "welcome-Easy-to-Use": "🤝 易于使用", - "welcome-High-Robustness": "✅ 高鲁棒性", - "welcome-Actor-Based": "🌐 基于Actor模型的分布式", - "welcome-Documentations": "文档:", - "welcome-Quick-Start": "🚀 快速入门", - "welcome-Examples": "💡 例子", - "welcome-Tutorial": " 📘 教程和 API 参考", - "en1-json-readme": "📖 这是如何在 AgentScope 中编写单轮对话的示例", - "en1-json-readme-h4": "模块用于:", - "en1-json-readme-sentence1": "- 每个应用程序都必须配置一个模型。", - "en1-json-readme-sentence2": "- 代表应用程序中的用户。", - "en1-json-readme-sentence3": "- 应用程序中的对话智能体。", - "en2-json-readme": "📖 此示例演示了通过 AgentScope 与 Sequential Pipeline 进行的一轮用户 ReactAgent(使用 PythonService)对话", - "en2-json-readme-h4": "模块用于:", - "en2-json-readme-sentence1": "- 每个应用程序都必须配置一个模型。", - "en2-json-readme-sentence2": "- 使信息按预定顺序在智能体之间传递。", - "en2-json-readme-sentence3": "- 代表应用程序中的用户。", - "en2-json-readme-sentence4": "- 实现 ReAct 算法的智能体类。", - "en2-json-readme-sentence5": "- 执行一段 python 代码。", - "en3-json-readme": "📖 这是如何在 AgentScope 中对用户智能体对话进行编程的示例。 ", - "en3-json-readme-h4": "模块用于:", - "en3-json-readme-sentence1": "- 每个应用程序都必须配置一个模型。", - "en3-json-readme-sentence2": "- 设计用于执行重复性操作。", - "en3-json-readme-sentence3": "- 使信息按预定顺序在智能体之间传递。", - "en3-json-readme-sentence4": "- 代表应用程序中的用户。", - "en3-json-readme-sentence5": "- 应用程序中的对话智能体。", - "en3-json-readme-moreDetails": "更多详情,请参阅", - "en3-json-readme-moreDetailsHref": "此处", - "en4-json-readme": "📖 此例演示了 AgentScope 推动的多智能体群组聊天", - "en4-json-readme-h4": "模块用于:", - "en4-json-readme-sentence1": "- 每个应用程序都必须配置一个模型。", - "en4-json-readme-sentence2": "- MsgHub 是智能体像群聊一样分享信息的地方", - "en4-json-readme-sentence3": "- 信息在智能体之间流动传递。", - "en4-json-readme-sentence4": "- 设计用于执行重复性操作。", - "en4-json-readme-sentence5": "- 使信息按预定顺序在智能体之间传递。", - "en4-json-readme-sentence6": "- 代表应用程序中的用户。", - "en4-json-readme-sentence7": "- 应用程序中的对话智能体。", - "en4-json-readme-moreDetails": "更多详情,请参阅", - "en4-json-readme-moreDetailsHref": "此处", - "service-text-to-audio-reademe": "将文本转音频服务集成到 ReActAgent 中以增强智能体功能", - "service-text-to-audio-modelName": "模型名称", - "service-text-to-audio-sampleRate": "采样率", - "service-text-to-image-modelName": "模型名称", - "service-text-to-image-numberofImages": "图片数量", - "service-text-to-image-size": "图片尺寸", - "service-text-to-image-readme": "将文本转图像服务集成到 ReActAgent 中以增强智能体功能", - "tool-image-synthesis-readme": "文本生成图片", - "tool-image_synthesis-modelName": "模型名称", - "tool-image_synthesis-numberofImages": "图片数量", - "tool-image_synthesis-size": "图片尺寸", - "tool-image_synthesis-saveDir": "保存目录", - "tool-image-composition-readme": "将多张图片合成一张", - "tool-image-composition-Titles": "标题", - "tool-image-composition-Outputpath": "输出路径", - "tool-image-composition-row": "行", - "tool-image-composition-Column": "列", - "tool-image-composition-Spacing": "间距", - "tool-image-composition-titleHeight": "标题高度", - "tool-image-composition-fontName": "字体名称", - "tool-image-motion-readme": "通过视角移动将图片转化为mp4或者gif格式", - "tool-image-motion-outputPath": "输出路径", - "tool-image-motion-outputFormat": "输出格式", - "tool-image-motion-labelDuration": "持续时间", - "tool-image-motion-labelMotiondirection": "运动方向", - "tool-post-readme": "提交Post请求并返回结果", - "tool-post-label-outputPath": "输出路径", - "tool-post-label-outputtype": "输出类型", - "tool-video-composition-readme": "将多个视频合成一个", - "tool-video-composition-outputPath": "输出路径", - "tool-video-composition-targetVideoWidth": "目标视频宽度", - "tool-video-composition-targetVideoHeight": "目标视频高度", - "tool-video-composition-targetVideoFps": "目标视频帧率", - "tool-code-readme": "执行Python代码", - "tool-code-note": "注意:", - "tool-code-note-var": "- 输入和输出参数必须是Msg或者dict对象。", - "tool-code-note-func": "- 请勿修改函数名称。", - "tool-code-content": "代码", - "workstarionjs-import-prev": "上一步", - "workstarionjs-import-next": "下一步", - "workstarionjs-import-skip": "跳过", - "workstarionjs-import-quit": "退出", - "workstarionjs-import-caution": "注意:您当前处于教程模式,修改受到限制。", - "workstarionjs-import-Caution-click": "请单击", - "workstarionjs-import-Caution-quit": "退出", - "workstarionjs-import-Caution-exit": "退出并开始创建自定义多代理应用程序。" + "login-page-title": "AgentScope WorkStation 登陆页", + "login-page-welcome": "欢迎来到AgentScope工作站", + "login-page-terms-title": "请登录并为AgentScope仓库点星", + "login-page-terms-acknowledgment": "通过登录,您知晓并同意:", + "login-page-terms-star": "使用该服务将代表您自愿为AgentScope仓库点星🌟。", + "login-page-terms-api": "您的API密钥不会被存储和暴露给网站维护者。", + "login-page-terms-data": "除非你明确选择保存,否则本网站不会保存任何用户数据(如绘制的工作流程)。", + "login-page-terms-agree": "我同意服务条款", + "login-page-login-github": "使用GitHub登陆", + "login-page-login-guest": "游客登陆", + "login-page-please-wait": "请稍后...", + "index-Tutorial": "教程", + "index-API": "API 文档", + "index-guide.introduction": "AgentScope Studio 是一个基于 Web 的平台,用于在 AgentScope 中监控、管理和开发多智能体应用程序!请随意探索该平台及其功能", + "index-guide.h2": "开始探索 AgentScope Studio", + "index-guide.DashboardDetail": "监控和管理您的 AgentScope 运行实例.", + "index-guide.WorkstationDetail": "通过拖动来开发您的多智能体应用程序。", + "index-guide.GalleryDetail": "即将推出 ...", + "index-guide.ServerDetail": "管理您的智能体服务器(开发中)。", + "market-title": "即将推出 ...", + "dashboard-run-placeholder-Search": "查询", + "dialogue-detail-dialogue-Nodata": "无可用消息", + "dialogue-detail-dialogue-User": "用户", + "dialogue-detail-dialogue-textarea": "在此输入消息...", + "dialogue-detail-dialogue-send-btn": "发送", + "condition-Operator": "条件运算", + "condition-TargetValue": "目标值", + "workstation-Example": "示例", + "workstation-sidebar-subitem-Agents": "两个智能体", + "workstation-sidebar-subitem-Pipeline": "流程", + "workstation-sidebar-subitem-Conversation": "会话", + "workstation-sidebar-subitem-GroupChat": "群聊", + "workstation-sidebar-item-Workflow": "工作流", + "workstation-sidebar-item-Model": "模型", + "workstation-sidebar-item-Message": "消息", + "workstation-sidebar-item-Agent": "智能体", + "workstation-sidebar-item-Pipeline": "流程", + "workstation-sidebar-item-Service": "服务", + "workstation-sidebar-item-Tool": "工具", + "workstation-surveyContent": "我们希望听到您的反馈", + "workstation-surveyContent-Button": "参与问卷调查", + "workstation-menu-btn-home": "主页", + "workstation-menu-btn-download": "下载工作流配置", + "workstation-menu-btn-load": "加载工作流配置", + "workstation-menu-btn-check": "检查工作流程", + "workstation-menu-btn-clear": "清除", + "workstation-menu-btn-export": "导出 Python 代码", + "workstation-menu-btn-run": "运行工作流", + "workstation-menu-btn-save": "保存工作流", + "workstation-menu-btn-workflow": "加载工作流", + "agent-broadcast-Copy": "复制", + "agent-broadcast-readme": "只会广播其内容框中文本的智能体", + "agent-broadcast-labelName": "名称", + "agent-broadcast-labelName-Assistant": "助理", + "agent-broadcast-content": "内容", + "agent-dialogagent-Copy": "复制", + "agent-dialogagent-readme": "可与用户或其他智能体互动的对话智能体。", + "agent-dialogagent-labelName": "名称", + "agent-dialogagent-labelName-Assistant": "助理", + "agent-dialogagent-SystemPrompt": "系统提示", + "agent-dialogagent-textareaInput-placeholder": "你是助理。", + "agent-dialogagent-ModelConfigName": "模型配置名称", + "agent-dictdialogagent-Copy": "复制", + "agent-dictdialogagent-readme": "生成字典格式响应的智能体", + "agent-dictdialogagent-labelName": "名称", + "agent-dictdialogagent-labelName-placeholder": "助理", + "agent-dictdialogagent-SystemPrompt": "系统提示", + "agent-dictdialogagent-SystemPrompt-textarea": "你是助理。", + "agent-dictdialogagent-ModelConfigName": "模型配置名称", + "agent-dictdialogagent-MaxRetries": "最大重试次数", + "agent-dictdialogagent-labelParse": "解析函数(可选)", + "agent-dictdialogagent-labelFault": "故障处理程序(可选)", + "agent-reactagent-copy": "复制", + "agent-reactagent-readme": "带有工具的 ReAct(推理和行动)智能体", + "agent-reactagent-labelName": "名称", + "agent-reactagent-labelName-Assistant": "助理", + "agent-reactagent-SystemPrompt": "系统提示", + "agent-reactagent-SystemPrompt-textarea": "你是一个助理。", + "agent-reactagent-ModelConfigName": "模型配置名称", + "agent-reactagent-Tools": "工具", + "agent-reactagent-add-Service": "服务", + "agent-reactagent-Tools-placeholder": "请点击上方的 '服务' 按钮添加工具。", + "agent-reactagent-iterations": "最大推理迭代次数", + "agent-reactagent-Verbose": "详细", + "agent-texttoimageagent-TextToImageAgent": "文本转图像智能体", + "agent-texttoimageagent-Copy": "复制", + "agent-texttoimageagent-readme": "文本到图像生成智能体", + "agent-texttoimageagent-labelName": "名称", + "agent-texttoimageagent-labelName-Assistant": "助理", + "agent-texttoimageagent-ModelConfigName": "模型配置名称", + "agent-useragent-Copy": "复制", + "agent-useragent-readme": "用户的智能体服务器", + "agent-useragent-labelName": "名称", + "agent-useragent-labelName-placeholder": "用户", + "message-msg-labelName": "名称", + "message-msg-labelName-placeholder": "主持人", + "message-msg-labelContent": "内容", + "message-msg-labelContent-placeholder": "你好", + "message-msg-labelRole": "角色", + "message-msg-labelRole-placeholder": "assistant", + "message-msg-labelURL": "网址(可选)", + "model-dashscope-chat-readme": "DashScope 聊天配置(您的 API 密钥不会被存储或暴露给网站维护者)", + "model-dashscope-chat-labelConfigName": "配置名称", + "model-dashscope-chat-labelModelName": "模型名称", + "model-dashscope-chat-labelTemperature": "温度", + "model-dashscope-chat-labelSeed": "种子", + "model-openai-chat-readme": "OpenAI 聊天配置(您的 API 密钥不会被保存或暴露给网站维护者", + "model-openai-chat-labelConfigName": "配置名称", + "model-openai-chat-labelModelName": "模型名称", + "model-openai-chat-Temperature": "温度", + "model-openai-chat-Seed": "种子", + "model-openai-chat-Advanced": "高级 ▼", + "model-openai-chat-advanced-box": "基准 URL", + "model-post-api-chat-readme": "Post API 聊天配置", + "model-post-api-chat-ConfigName": "配置名称", + "model-post-api-chat-ModelName": "模型名称", + "model-post-api-chat-Temperature": "温度", + "model-post-api-chat-Seed": "种子", + "model-post-api-chat-API": "API 地址", + "model-post-api-chat-ContentType": "Content-Type", + "model-post-api-chat-Authorization": "授权", + "model-post-api-chat-MessagesKey": "Messages Key", + "model-post-api-chat-MessagesKey-placeholder": "消息", + "pipeline-forlooppipeline-readme": "用于实现 for 循环等控制流的模板流程", + "pipeline-forlooppipeline-MaxLoop": "最大循环次数", + "pipeline-forlooppipeline-BreakFunction": "中断函数", + "pipeline-ifelsepipeline-readme": "用 if-else 逻辑实现控制流的模板流程", + "pipeline-ifelsepipeline-Condition": "条件函数", + "pipeline-msghub-readme": "MsgHub 用于在一组智能体之间共享信息", + "pipeline-msghub-AnnouncementName": "公告名称", + "pipeline-msghub-AnnouncementName-placeholder": "host", + "pipeline-msghub-AnnouncementContent": "公告内容", + "pipeline-msghub-AnnouncementContent-placeholder": "欢迎加入群聊!", + "pipeline-placeholder-readme": "不执行任何操作的占位符", + "pipeline-sequentialpipeline-readme": "实现顺序逻辑的模板流程(从上到下)", + "pipeline-switchpipeline-readme": "用于使用 switch-case 逻辑实现控制流的模板流程", + "pipeline-switchpipeline-Condition": "条件函数", + "pipeline-switchpipeline-AddCase": "添加案例", + "pipeline-switchpipeline-RemoveCase": "删除案例", + "pipeline-whilelooppipeline-readme": "用于实现 while 循环等控制流的模板流程", + "pipeline-whilelooppipeline-Condition": "条件函数", + "service-bing-search-readme": "在 ReActAgent 中集成必应搜索服务,以增强智能体功能", + "service-bing-search-Results": "结果编号", + "service-execute-python-readme": "在 ReActAgent中集成 Python 解释器,以增强智能体功能", + "service-google-search-readme": "将 Google 搜索服务集成到 ReActAgent 中以增强智能体功能", + "service-read-text-readme": "将阅读文本服务集成到 ReActAgent 中以增强智能体功能", + "service-write-text-readme": "将写入文本服务集成到 ReActAgent 中提升智能体能力", + "Welcome-Welcome": "👏 欢迎!", + "welcome-Easy-to-use": "易于使用的多智能体平台:", + "welcome-Shortkeys": "要点:", + "welcome-Easy-to-Use": "🤝 易于使用", + "welcome-High-Robustness": "✅ 高鲁棒性", + "welcome-Actor-Based": "🌐 基于Actor模型的分布式", + "welcome-Documentations": "文档:", + "welcome-Quick-Start": "🚀 快速入门", + "welcome-Examples": "💡 例子", + "welcome-Tutorial": " 📘 教程和 API 参考", + "en1-json-readme": "📖 这是如何在 AgentScope 中编写单轮对话的示例", + "en1-json-readme-h4": "模块用于:", + "en1-json-readme-sentence1": "- 每个应用程序都必须配置一个模型。", + "en1-json-readme-sentence2": "- 代表应用程序中的用户。", + "en1-json-readme-sentence3": "- 应用程序中的对话智能体。", + "en2-json-readme": "📖 此示例演示了通过 AgentScope 与 Sequential Pipeline 进行的一轮用户 ReactAgent(使用 PythonService)对话", + "en2-json-readme-h4": "模块用于:", + "en2-json-readme-sentence1": "- 每个应用程序都必须配置一个模型。", + "en2-json-readme-sentence2": "- 使信息按预定顺序在智能体之间传递。", + "en2-json-readme-sentence3": "- 代表应用程序中的用户。", + "en2-json-readme-sentence4": "- 实现 ReAct 算法的智能体类。", + "en2-json-readme-sentence5": "- 执行一段 python 代码。", + "en3-json-readme": "📖 这是如何在 AgentScope 中对用户智能体对话进行编程的示例。 ", + "en3-json-readme-h4": "模块用于:", + "en3-json-readme-sentence1": "- 每个应用程序都必须配置一个模型。", + "en3-json-readme-sentence2": "- 设计用于执行重复性操作。", + "en3-json-readme-sentence3": "- 使信息按预定顺序在智能体之间传递。", + "en3-json-readme-sentence4": "- 代表应用程序中的用户。", + "en3-json-readme-sentence5": "- 应用程序中的对话智能体。", + "en3-json-readme-moreDetails": "更多详情,请参阅", + "en3-json-readme-moreDetailsHref": "此处", + "en4-json-readme": "📖 此例演示了 AgentScope 推动的多智能体群组聊天", + "en4-json-readme-h4": "模块用于:", + "en4-json-readme-sentence1": "- 每个应用程序都必须配置一个模型。", + "en4-json-readme-sentence2": "- MsgHub 是智能体像群聊一样分享信息的地方", + "en4-json-readme-sentence3": "- 信息在智能体之间流动传递。", + "en4-json-readme-sentence4": "- 设计用于执行重复性操作。", + "en4-json-readme-sentence5": "- 使信息按预定顺序在智能体之间传递。", + "en4-json-readme-sentence6": "- 代表应用程序中的用户。", + "en4-json-readme-sentence7": "- 应用程序中的对话智能体。", + "en4-json-readme-moreDetails": "更多详情,请参阅", + "en4-json-readme-moreDetailsHref": "此处", + "service-text-to-audio-reademe": "将文本转音频服务集成到 ReActAgent 中以增强智能体功能", + "service-text-to-audio-modelName": "模型名称", + "service-text-to-audio-sampleRate": "采样率", + "service-text-to-image-modelName": "模型名称", + "service-text-to-image-numberofImages": "图片数量", + "service-text-to-image-size": "图片尺寸", + "service-text-to-image-readme": "将文本转图像服务集成到 ReActAgent 中以增强智能体功能", + "tool-image-synthesis-readme": "文本生成图片", + "tool-image_synthesis-modelName": "模型名称", + "tool-image_synthesis-numberofImages": "图片数量", + "tool-image_synthesis-size": "图片尺寸", + "tool-image_synthesis-saveDir": "保存目录", + "tool-image-composition-readme": "将多张图片合成一张", + "tool-image-composition-Titles": "标题", + "tool-image-composition-Outputpath": "输出路径", + "tool-image-composition-row": "行", + "tool-image-composition-Column": "列", + "tool-image-composition-Spacing": "间距", + "tool-image-composition-titleHeight": "标题高度", + "tool-image-composition-fontName": "字体名称", + "tool-image-motion-readme": "通过视角移动将图片转化为mp4或者gif格式", + "tool-image-motion-outputPath": "输出路径", + "tool-image-motion-outputFormat": "输出格式", + "tool-image-motion-labelDuration": "持续时间", + "tool-image-motion-labelMotiondirection": "运动方向", + "tool-post-readme": "提交Post请求并返回结果", + "tool-post-label-outputPath": "输出路径", + "tool-post-label-outputtype": "输出类型", + "tool-video-composition-readme": "将多个视频合成一个", + "tool-video-composition-outputPath": "输出路径", + "tool-video-composition-targetVideoWidth": "目标视频宽度", + "tool-video-composition-targetVideoHeight": "目标视频高度", + "tool-video-composition-targetVideoFps": "目标视频帧率", + "tool-code-readme": "执行Python代码", + "tool-code-note": "注意:", + "tool-code-note-var": "- 输入和输出参数必须是Msg或者dict对象。", + "tool-code-note-func": "- 请勿修改函数名称。", + "tool-code-content": "代码", + "workstarionjs-import-prev": "上一步", + "workstarionjs-import-next": "下一步", + "workstarionjs-import-skip": "跳过", + "workstarionjs-import-quit": "退出", + "workstarionjs-import-caution": "注意:您当前处于教程模式,修改受到限制。", + "workstarionjs-import-Caution-click": "请单击", + "workstarionjs-import-Caution-quit": "退出", + "workstarionjs-import-Caution-exit": "退出并开始创建自定义多代理应用程序。" } diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index e9cf849c6..27a0efa76 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -2392,7 +2392,7 @@ async function addHtmlAndReplacePlaceHolderBeforeImport(data) { // Adjust the height of the box div let styleString = ""; if (node.width) { - styleString += `width: ${node.width}; `; + const originalWidth = parseInt(node.width, 10); const adjustedWidth = originalWidth - 22; styleString += `width: ${adjustedWidth}px; `; } diff --git a/src/agentscope/studio/templates/login.html b/src/agentscope/studio/templates/login.html index 1df6d5ef3..dca28e383 100644 --- a/src/agentscope/studio/templates/login.html +++ b/src/agentscope/studio/templates/login.html @@ -2,11 +2,12 @@ - {{ _("AgentScope WorkStation Login Page") }} + AgentScope WorkStation Login Page + 中文
      -

      {{ _("Welcome to AgentScope WorkStation") }}

      +

      Welcome to AgentScope WorkStation

      {{ _("Welcome to AgentScope WorkStation") }} AgentScope Animation -

      {{ _("Please log in and star the AgentScope repository") }}.

      +

      Please log in and star the AgentScope + repository

      - {{ _("By logging in, you acknowledge that") }}: +

      By logging in, you + acknowledge that:

        -
      • {{ _("This service will star🌟 AgentScope repository on your - behalf") }}. +
      • This service will star🌟 + AgentScope repository on your behalf.
      • -
      • {{ _("Your API key will NOT be stored and exposed to the website - maintainer") }}. +
      • Your API key will NOT be stored and + exposed to the website maintainer.
      • -
      • {{ _("No user data (e.g., drawn workflow) within the service - will be saved") }}. +
      • No user data (e.g., drawn + workflow) within the service will be saved unless you + explicitly choose to save it.
      - +


      × -

      {{ _("We want to hear from you") }}

      -
      @@ -173,13 +179,6 @@

      {{ _("We want to hear from you") }}

      } }); }); - - function getCookie(name) { - var matches = document.cookie.match(new RegExp( - "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)" - )); - return matches ? decodeURIComponent(matches[1]) : undefined; - } }); function showSurveyModal() { @@ -199,6 +198,30 @@

      {{ _("We want to hear from you") }}

      }); setTimeout(showSurveyModal, 500); + + function getCookie(name) { + var matches = document.cookie.match(new RegExp( + "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)" + )); + return matches ? decodeURIComponent(matches[1]) : undefined; + } + + function reloadi18n() { + const currentLang = getCookie("locale") || "en"; + $("[i18n]").i18n({ + defaultLang: currentLang, + filePath: "../static/i18n/", + filePrefix: "i18n_", + fileSuffix: "", + forever: true, + callback: function () { + } + }); + } + + document.addEventListener('DOMContentLoaded', (event) => { + reloadi18n(); + }); \ No newline at end of file From 6c1674be13463d6394b698e61d73b012d1469885 Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:04:53 +0800 Subject: [PATCH 43/47] update (#64) --- src/agentscope/studio/tools/image_synthesis.py | 2 +- src/agentscope/utils/common.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/agentscope/studio/tools/image_synthesis.py b/src/agentscope/studio/tools/image_synthesis.py index 74cf9d89c..5906bb7df 100644 --- a/src/agentscope/studio/tools/image_synthesis.py +++ b/src/agentscope/studio/tools/image_synthesis.py @@ -48,7 +48,7 @@ def image_synthesis( if res.status == ServiceExecStatus.SUCCESS: return Msg( name="ImageSynthesis", - content=res.content, + content="Image synthesis succeed.", url=res.content["image_urls"], role="assistant", echo=True, diff --git a/src/agentscope/utils/common.py b/src/agentscope/utils/common.py index 372d9ca66..d97696ca7 100644 --- a/src/agentscope/utils/common.py +++ b/src/agentscope/utils/common.py @@ -217,7 +217,9 @@ def _guess_type_by_extension( url: str, ) -> Literal["image", "audio", "video", "file"]: """Guess the type of the file by its extension.""" - extension = url.split(".")[-1].lower() + parsed_url = urlparse(url) + path = parsed_url.path + extension = path.split(".")[-1].lower() if "." in path else "" if extension in [ "bmp", From 19b168a63eadbc71d111799bab83cc1586477d0e Mon Sep 17 00:00:00 2001 From: myh-0521 <95087599+myh-0521@users.noreply.github.com> Date: Tue, 12 Nov 2024 19:11:27 +0800 Subject: [PATCH 44/47] Fix W/H Workstation gallery (#63) --- .../studio/static/js/workstation.js | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 27a0efa76..8e85dc6c0 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -12,6 +12,7 @@ let descriptionStep; let allimportNodeId = []; let imporTempData; + const nameToHtmlFile = { "welcome": "welcome.html", "dashscope_chat": "model-dashscope-chat.html", @@ -2393,13 +2394,17 @@ async function addHtmlAndReplacePlaceHolderBeforeImport(data) { let styleString = ""; if (node.width) { const originalWidth = parseInt(node.width, 10); - const adjustedWidth = originalWidth - 22; - styleString += `width: ${adjustedWidth}px; `; + if (!isNaN(originalWidth)) { + const adjustedWidth = originalWidth - 31; // Consistently applying width reduction + styleString += `width: ${adjustedWidth}px; `; + } } if (node.height) { const originalHeight = parseInt(node.height, 10); - const adjustedHeight = originalHeight - 91; - styleString += `height: ${adjustedHeight}px; `; + if (!isNaN(originalHeight)) { + const adjustedHeight = originalHeight - 91; // Consistently applying height reduction + styleString += `height: ${adjustedHeight}px; `; + } } if (styleString !== "") { node.html = node.html.replace(boxDivRegex, `
      `); @@ -2429,8 +2434,18 @@ function importSetupNodes(dataToImport) { if (nodeElement) { const nodeData = dataToImport.drawflow.Home.data[nodeId]; if (nodeData.width) { - nodeElement.style.width = nodeData.width; - // nodeElement.style.height = nodeData.height; + const originalWidth = parseInt(nodeData.width, 10); + if (!isNaN(originalWidth)) { + const adjustedWidth = originalWidth - 31; + nodeElement.style.width = `${adjustedWidth}px`; + } + } + if (nodeData.height) { + const originalHeight = parseInt(nodeData.height, 10); + if (!isNaN(originalHeight)) { + const adjustedHeight = originalHeight - 31; + nodeElement.style.height = `${adjustedHeight}px`; + } } const copyButton = nodeElement.querySelector(".copy-button"); if (copyButton) { From a72c4cd681e422c08349e893530902c392a906d1 Mon Sep 17 00:00:00 2001 From: qbc Date: Fri, 15 Nov 2024 11:54:36 +0800 Subject: [PATCH 45/47] Add __call__ for imagesynthesisnode (#67) --- .../web/workstation/workflow_node.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/agentscope/web/workstation/workflow_node.py b/src/agentscope/web/workstation/workflow_node.py index 412f13773..7d288792e 100644 --- a/src/agentscope/web/workstation/workflow_node.py +++ b/src/agentscope/web/workstation/workflow_node.py @@ -860,7 +860,10 @@ class ImageSynthesisNode(WorkflowNode): def _post_init(self) -> None: super()._post_init() - self.pipeline = partial(image_synthesis, **self.opt_kwargs) + self.function_executor = partial(image_synthesis, **self.opt_kwargs) + + def __call__(self, x: dict = None) -> dict: + return self.function_executor(x) def compile(self) -> dict: return { @@ -883,12 +886,15 @@ class ImageCompositionNode(WorkflowNode): def _post_init(self) -> None: super()._post_init() - self.pipeline = partial(stitch_images_with_grid, **self.opt_kwargs) + self.function_executor = partial( + stitch_images_with_grid, + **self.opt_kwargs, + ) def __call__(self, x: list = None) -> dict: if isinstance(x, dict): x = [x] - return self.pipeline(x) + return self.function_executor(x) def compile(self) -> dict: return { @@ -911,13 +917,13 @@ class ImageMotionNode(WorkflowNode): def _post_init(self) -> None: super()._post_init() - self.pipeline = partial( + self.function_executor = partial( create_video_or_gif_from_image, **self.opt_kwargs, ) def __call__(self, x: dict = None) -> dict: - return self.pipeline(x) + return self.function_executor(x) def compile(self) -> dict: return { @@ -941,10 +947,10 @@ class VideoCompositionNode(WorkflowNode): def _post_init(self) -> None: super()._post_init() - self.pipeline = partial(merge_videos, **self.opt_kwargs) + self.function_executor = partial(merge_videos, **self.opt_kwargs) def __call__(self, x: dict = None) -> dict: - return self.pipeline(x) + return self.function_executor(x) def compile(self) -> dict: return { From c3048a8b9f5153a810cdc630ce88ed21f6171996 Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:18:39 +0800 Subject: [PATCH 46/47] Automated build workflow PR (#81) --- gallery/meme.json | 7 +- gallery/monkey.json | 9 +- gallery/remove_bg.json | 7 +- gallery/story.json | 1025 ++++++++--------- gallery/undercover.json | 927 ++++++++------- src/agentscope/studio/_app.py | 10 + src/agentscope/studio/_app_online.py | 139 ++- .../studio/static/css/workstation.css | 60 +- .../studio/static/html/index-guide.html | 12 +- .../studio/static/i18n/i18n_en.json | 12 +- .../studio/static/i18n/i18n_zh.json | 12 +- .../studio/static/js/workstation.js | 253 +++- .../studio/templates/workstation.html | 11 + src/agentscope/studio/utils.py | 173 ++- 14 files changed, 1574 insertions(+), 1083 deletions(-) diff --git a/gallery/meme.json b/gallery/meme.json index 40f6ee1b1..02052f4f0 100644 --- a/gallery/meme.json +++ b/gallery/meme.json @@ -3,13 +3,10 @@ "index": 1, "title": "Meme Generator", "author": "AgentScopeTeam", - "keywords": [ - "meme", - "image" - ], "category": "tool", "time": "2024-09-04", - "thumbnail": "" + "thumbnail": "", + "description": "meme generator" }, "drawflow": { "Home": { diff --git a/gallery/monkey.json b/gallery/monkey.json index 3eaabeb06..795c1df7c 100644 --- a/gallery/monkey.json +++ b/gallery/monkey.json @@ -3,13 +3,10 @@ "index": 0, "title": "Adventure of Monkey", "author": "AgentScopeTeam", - "keywords": [ - "game", - "multi-modal" - ], "category": "game", "time": "2024-08-20", - "thumbnail": "" + "thumbnail": "", + "description": "Multi-modal game" }, "drawflow": { "Home": { @@ -115,7 +112,7 @@ "data": { "args": { "name": "图片提示词生成器", - "sys_prompt": "任务目标:根据输入的分镜,创造一组描述性短语,旨在用于指导文生图模型生成具有相应风格和特性的高质量绘本画面。 \\n描述应该是简洁明了的关键词或短语,以逗号分隔,以确保在画面生成时能够清楚地反映绘本所描述的画面。\\n如果包含人物的话,需要当下人物的神情。比如一个好的描述可能包含:人物+神情+动作描述+场景描述。人物的主角是孙悟空,因此你的输出应该以孙悟空开头。", + "sys_prompt": "任务目标:根据输入的分镜,创造一组描述性短语,旨在用于指导文生图模型生成具有相应风格和特性的高质量绘本画面。 \\\\n描述应该是简洁明了的关键词或短语,以逗号分隔,以确保在画面生成时能够清楚地反映绘本所描述的画面。\\\\n如果包含人物的话,需要当下人物的神情。比如一个好的描述可能包含:人物+神情+动作描述+场景描述。人物的主角是孙悟空,因此你的输出应该以孙悟空开头。你禁止输出其他额外的内容。\n", "model_config_name": "qwen-max" } }, diff --git a/gallery/remove_bg.json b/gallery/remove_bg.json index 6232d7133..07b9158c1 100644 --- a/gallery/remove_bg.json +++ b/gallery/remove_bg.json @@ -3,13 +3,10 @@ "index": 2, "title": "Background Remover", "author": "AgentScopeTeam", - "keywords": [ - "background", - "image" - ], "category": "tool", "time": "2024-09-04", - "thumbnail": "" + "thumbnail": "", + "description": "Remove image background via post api" }, "drawflow": { "Home": { diff --git a/gallery/story.json b/gallery/story.json index 0e3dec5ad..c21009927 100644 --- a/gallery/story.json +++ b/gallery/story.json @@ -1,531 +1,528 @@ { - "meta": { - "index": 3, - "title": "Story", - "author": "AgentScopeTeam", - "keywords": [ - "story", - "multi-modal" - ], - "category": "tool", - "time": "2024-09-04", - "thumbnail": "" - }, - "drawflow": { - "Home": { - "data": { - "2": { - "id": 2, - "name": "dashscope_chat", - "data": { - "args": { - "config_name": "qwen", - "model_name": "qwen-max", - "api_key": "", - "temperature": 0, - "seed": 0, - "model_type": "dashscope_chat", - "messages_key": "input" - } - }, - "class": "dashscope_chat", - "typenode": false, - "inputs": {}, - "outputs": {}, - "pos_x": -10, - "pos_y": 11 + "meta": { + "index": 3, + "title": "Story", + "author": "AgentScopeTeam", + "category": "tool", + "time": "2024-09-04", + "thumbnail": "", + "description": "multi-modal story generator" + }, + "drawflow": { + "Home": { + "data": { + "2": { + "id": 2, + "name": "dashscope_chat", + "data": { + "args": { + "config_name": "qwen", + "model_name": "qwen-max", + "api_key": "", + "temperature": 0, + "seed": 0, + "model_type": "dashscope_chat", + "messages_key": "input" + } + }, + "class": "dashscope_chat", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": -10, + "pos_y": 11 + }, + "4": { + "id": 4, + "name": "Message", + "data": { + "args": { + "name": "Host", + "content": "请输入您想生成的绘本故事大纲", + "url": "", + "role": "assistant" + } + }, + "class": "Message", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "5", + "output": "input_1" + } + ] + } + }, + "pos_x": -15, + "pos_y": 300 + }, + "5": { + "id": 5, + "name": "UserAgent", + "data": { + "args": { + "name": "User" + } + }, + "class": "UserAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "4", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "6", + "output": "input_1" + }, + { + "node": "7", + "output": "input_1" + }, + { + "node": "8", + "output": "input_1" + }, + { + "node": "9", + "output": "input_1" + } + ] + } + }, + "pos_x": -18, + "pos_y": 462 + }, + "6": { + "id": 6, + "name": "DialogAgent", + "data": { + "args": { + "name": "第一幕", + "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据大纲,发挥想象,只生成第一幕的描述,不超过30个字。\n# 输出格式为\n第一幕:xxxx", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "5", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "7", + "output": "input_1" + }, + { + "node": "10", + "output": "input_1" }, - "4": { - "id": 4, - "name": "Message", - "data": { - "args": { - "name": "Host", - "content": "请输入您想生成的绘本故事大纲", - "url": "", - "role": "assistant" - } - }, - "class": "Message", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [ - { - "node": "5", - "output": "input_1" - } - ] - } - }, - "pos_x": -15, - "pos_y": 300 + { + "node": "14", + "output": "input_1" }, - "5": { - "id": 5, - "name": "UserAgent", - "data": { - "args": { - "name": "User" - } - }, - "class": "UserAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [ - { - "node": "4", - "input": "output_1" - } - ] - } - }, - "outputs": { - "output_1": { - "connections": [ - { - "node": "6", - "output": "input_1" - }, - { - "node": "7", - "output": "input_1" - }, - { - "node": "8", - "output": "input_1" - }, - { - "node": "9", - "output": "input_1" - } - ] - } - }, - "pos_x": -18, - "pos_y": 462 + { + "node": "8", + "output": "input_1" }, - "6": { - "id": 6, - "name": "DialogAgent", - "data": { - "args": { - "name": "第一幕", - "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据大纲,发挥想象,只生成第一幕的描述,不超过30个字。\n# 输出格式为\n第一幕:xxxx", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [ - { - "node": "5", - "input": "output_1" - } - ] - } - }, - "outputs": { - "output_1": { - "connections": [ - { - "node": "7", - "output": "input_1" - }, - { - "node": "10", - "output": "input_1" - }, - { - "node": "14", - "output": "input_1" - }, - { - "node": "8", - "output": "input_1" - }, - { - "node": "9", - "output": "input_1" - } - ] - } - }, - "pos_x": 354, - "pos_y": 37 + { + "node": "9", + "output": "input_1" + } + ] + } + }, + "pos_x": 354, + "pos_y": 37 + }, + "7": { + "id": 7, + "name": "DialogAgent", + "data": { + "args": { + "name": "第二幕", + "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据用户提供的绘本大纲和第一幕的内容,只生成第二幕的描述,不超过30个字。\n# 输出格式为\n第二幕:xxxx", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "6", + "input": "output_1" }, - "7": { - "id": 7, - "name": "DialogAgent", - "data": { - "args": { - "name": "第二幕", - "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据用户提供的绘本大纲和第一幕的内容,只生成第二幕的描述,不超过30个字。\n# 输出格式为\n第二幕:xxxx", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [ - { - "node": "6", - "input": "output_1" - }, - { - "node": "5", - "input": "output_1" - } - ] - } - }, - "outputs": { - "output_1": { - "connections": [ - { - "node": "8", - "output": "input_1" - }, - { - "node": "11", - "output": "input_1" - }, - { - "node": "14", - "output": "input_1" - }, - { - "node": "9", - "output": "input_1" - } - ] - } - }, - "pos_x": 359, - "pos_y": 160 + { + "node": "5", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "8", + "output": "input_1" }, - "8": { - "id": 8, - "name": "DialogAgent", - "data": { - "args": { - "name": "第三幕", - "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据用户提供的大纲和前二幕的内容,只生成第三幕的描述,不超过30个字。\n# 输出格式为\n第三幕:xxxx", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [ - { - "node": "7", - "input": "output_1" - }, - { - "node": "6", - "input": "output_1" - }, - { - "node": "5", - "input": "output_1" - } - ] - } - }, - "outputs": { - "output_1": { - "connections": [ - { - "node": "9", - "output": "input_1" - }, - { - "node": "12", - "output": "input_1" - }, - { - "node": "14", - "output": "input_1" - } - ] - } - }, - "pos_x": 362, - "pos_y": 287 + { + "node": "11", + "output": "input_1" }, - "9": { - "id": 9, - "name": "DialogAgent", - "data": { - "args": { - "name": "第四幕", - "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据用户提供的大纲和前三幕的内容,只生成第四幕的描述,不超过30个字。\n# 输出格式为\n第四幕:xxxx", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [ - { - "node": "8", - "input": "output_1" - }, - { - "node": "5", - "input": "output_1" - }, - { - "node": "6", - "input": "output_1" - }, - { - "node": "7", - "input": "output_1" - } - ] - } - }, - "outputs": { - "output_1": { - "connections": [ - { - "node": "13", - "output": "input_1" - }, - { - "node": "14", - "output": "input_1" - } - ] - } - }, - "pos_x": 356, - "pos_y": 424 + { + "node": "14", + "output": "input_1" }, - "10": { - "id": 10, - "name": "ImageSynthesis", - "data": { - "args": { - "model": "wanx-v1", - "api_key": "", - "n": 1, - "size": "1024*1024", - "save_dir": "" - } - }, - "class": "ImageSynthesis", - "typenode": false, - "inputs": { - "input_1": { - "connections": [ - { - "node": "6", - "input": "output_1" - } - ] - } - }, - "outputs": { - "output_1": { - "connections": [ - { - "node": "14", - "output": "input_1" - } - ] - } - }, - "pos_x": 715, - "pos_y": 28 + { + "node": "9", + "output": "input_1" + } + ] + } + }, + "pos_x": 359, + "pos_y": 160 + }, + "8": { + "id": 8, + "name": "DialogAgent", + "data": { + "args": { + "name": "第三幕", + "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据用户提供的大纲和前二幕的内容,只生成第三幕的描述,不超过30个字。\n# 输出格式为\n第三幕:xxxx", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "7", + "input": "output_1" }, - "11": { - "id": 11, - "name": "ImageSynthesis", - "data": { - "args": { - "model": "wanx-v1", - "api_key": "", - "n": 1, - "size": "1024*1024", - "save_dir": "" - } - }, - "class": "ImageSynthesis", - "typenode": false, - "inputs": { - "input_1": { - "connections": [ - { - "node": "7", - "input": "output_1" - } - ] - } - }, - "outputs": { - "output_1": { - "connections": [ - { - "node": "14", - "output": "input_1" - } - ] - } - }, - "pos_x": 722, - "pos_y": 136 + { + "node": "6", + "input": "output_1" }, - "12": { - "id": 12, - "name": "ImageSynthesis", - "data": { - "args": { - "model": "wanx-v1", - "api_key": "", - "n": 1, - "size": "1024*1024", - "save_dir": "" - } - }, - "class": "ImageSynthesis", - "typenode": false, - "inputs": { - "input_1": { - "connections": [ - { - "node": "8", - "input": "output_1" - } - ] - } - }, - "outputs": { - "output_1": { - "connections": [ - { - "node": "14", - "output": "input_1" - } - ] - } - }, - "pos_x": 733, - "pos_y": 264 + { + "node": "5", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "9", + "output": "input_1" + }, + { + "node": "12", + "output": "input_1" + }, + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 362, + "pos_y": 287 + }, + "9": { + "id": 9, + "name": "DialogAgent", + "data": { + "args": { + "name": "第四幕", + "sys_prompt": "你是一个绘本故事生成助手,绘本故事一共有四幕。请根据用户提供的大纲和前三幕的内容,只生成第四幕的描述,不超过30个字。\n# 输出格式为\n第四幕:xxxx", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "8", + "input": "output_1" }, - "13": { - "id": 13, - "name": "ImageSynthesis", - "data": { - "args": { - "model": "wanx-v1", - "api_key": "", - "n": 1, - "size": "1024*1024", - "save_dir": "" - } - }, - "class": "ImageSynthesis", - "typenode": false, - "inputs": { - "input_1": { - "connections": [ - { - "node": "9", - "input": "output_1" - } - ] - } - }, - "outputs": { - "output_1": { - "connections": [ - { - "node": "14", - "output": "input_1" - } - ] - } - }, - "pos_x": 727, - "pos_y": 412 + { + "node": "5", + "input": "output_1" }, - "14": { - "id": 14, - "name": "ImageComposition", - "data": { - "args": { - "titles": "[\"第一幕\", \"第二幕\", \"第三幕\", \"第四幕\"]", - "output_path": "test.png", - "row": 2, - "column": 2, - "spacing": 10, - "title_height": 100, - "font_name": "PingFang" - } - }, - "class": "ImageComposition", - "typenode": false, - "inputs": { - "input_1": { - "connections": [ - { - "node": "10", - "input": "output_1" - }, - { - "node": "11", - "input": "output_1" - }, - { - "node": "12", - "input": "output_1" - }, - { - "node": "13", - "input": "output_1" - }, - { - "node": "6", - "input": "output_1" - }, - { - "node": "7", - "input": "output_1" - }, - { - "node": "8", - "input": "output_1" - }, - { - "node": "9", - "input": "output_1" - } - ] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 1101, - "pos_y": -17.88888888888889 + { + "node": "6", + "input": "output_1" + }, + { + "node": "7", + "input": "output_1" } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "13", + "output": "input_1" + }, + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 356, + "pos_y": 424 + }, + "10": { + "id": 10, + "name": "ImageSynthesis", + "data": { + "args": { + "model": "wanx-v1", + "api_key": "", + "n": 1, + "size": "1024*1024", + "save_dir": "" + } + }, + "class": "ImageSynthesis", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "6", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 715, + "pos_y": 28 + }, + "11": { + "id": 11, + "name": "ImageSynthesis", + "data": { + "args": { + "model": "wanx-v1", + "api_key": "", + "n": 1, + "size": "1024*1024", + "save_dir": "" + } + }, + "class": "ImageSynthesis", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "7", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 722, + "pos_y": 136 + }, + "12": { + "id": 12, + "name": "ImageSynthesis", + "data": { + "args": { + "model": "wanx-v1", + "api_key": "", + "n": 1, + "size": "1024*1024", + "save_dir": "" + } + }, + "class": "ImageSynthesis", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "8", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 733, + "pos_y": 264 + }, + "13": { + "id": 13, + "name": "ImageSynthesis", + "data": { + "args": { + "model": "wanx-v1", + "api_key": "", + "n": 1, + "size": "1024*1024", + "save_dir": "" + } + }, + "class": "ImageSynthesis", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "9", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [ + { + "node": "14", + "output": "input_1" + } + ] + } + }, + "pos_x": 727, + "pos_y": 412 + }, + "14": { + "id": 14, + "name": "ImageComposition", + "data": { + "args": { + "titles": "[\"第一幕\", \"第二幕\", \"第三幕\", \"第四幕\"]", + "output_path": "test.png", + "row": 2, + "column": 2, + "spacing": 10, + "title_height": 100, + "font_name": "PingFang" + } + }, + "class": "ImageComposition", + "typenode": false, + "inputs": { + "input_1": { + "connections": [ + { + "node": "10", + "input": "output_1" + }, + { + "node": "11", + "input": "output_1" + }, + { + "node": "12", + "input": "output_1" + }, + { + "node": "13", + "input": "output_1" + }, + { + "node": "6", + "input": "output_1" + }, + { + "node": "7", + "input": "output_1" + }, + { + "node": "8", + "input": "output_1" + }, + { + "node": "9", + "input": "output_1" + } + ] + } + }, + "outputs": { + "output_1": { + "connections": [] } + }, + "pos_x": 1101, + "pos_y": -17.88888888888889 } + } } + } } \ No newline at end of file diff --git a/gallery/undercover.json b/gallery/undercover.json index cf749b37b..c297546ba 100644 --- a/gallery/undercover.json +++ b/gallery/undercover.json @@ -1,471 +1,468 @@ { "meta": { - "index": 4, - "title": "Undercover", - "author": "AgentScopeTeam", - "keywords": [ - "undercover", - "game" - ], - "category": "game", - "time": "2024-09-23", - "thumbnail": "" - }, - "drawflow": { - "Home": { - "data": { - "2": { - "id": 2, - "name": "dashscope_chat", - "data": { - "args": { - "config_name": "qwen", - "model_name": "qwen-max", - "api_key": "****", - "temperature": 0, - "seed": 0, - "model_type": "dashscope_chat" - } - }, - "class": "dashscope_chat", - "typenode": false, - "inputs": {}, - "outputs": {}, - "pos_x": -75, - "pos_y": -31 - }, - "3": { - "id": 3, - "name": "MsgHub", - "data": { - "elements": [ - "4" - ], - "args": { - "announcement": { - "name": "主持人", - "content": "游戏开始!" - } - } - }, - "class": "GROUP", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 281, - "pos_y": -34.111111111111114 - }, - "4": { - "id": 4, - "name": "ForLoopPipeline", - "data": { - "elements": [ - "5" - ], - "args": { - "max_loop": 5, - "condition_op": "contains", - "target_value": "结束" - } - }, - "class": "GROUP", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 319, - "pos_y": 27 - }, - "5": { - "id": 5, - "name": "SequentialPipeline", - "data": { - "elements": [ - "6", - "7", - "8", - "9", - "10", - "11", - "12", - "13", - "14", - "15", - "16", - "17", - "18", - "19" - ] - }, - "class": "GROUP", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 357, - "pos_y": 76 - }, - "6": { - "id": 6, - "name": "BroadcastAgent", - "data": { - "args": { - "name": "主持人", - "content": "现在是描述阶段,你需要用一句话来描述你的词。" - } - }, - "class": "BroadcastAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 387, - "pos_y": 107 - }, - "7": { - "id": 7, - "name": "DialogAgent", - "data": { - "args": { - "name": "玩家1", - "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家1。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 437, - "pos_y": 133 - }, - "8": { - "id": 8, - "name": "DialogAgent", - "data": { - "args": { - "name": "玩家2", - "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家2。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 477, - "pos_y": 158 - }, - "9": { - "id": 9, - "name": "DialogAgent", - "data": { - "args": { - "name": "玩家3", - "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家3。你的秘密词是“孔雀”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 512, - "pos_y": 178.88888888888889 - }, - "10": { - "id": 10, - "name": "DialogAgent", - "data": { - "args": { - "name": "玩家4", - "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家4。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 545, - "pos_y": 204 - }, - "11": { - "id": 11, - "name": "DialogAgent", - "data": { - "args": { - "name": "玩家5", - "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家5。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 583, - "pos_y": 230 - }, - "12": { - "id": 12, - "name": "BroadcastAgent", - "data": { - "args": { - "name": "主持人", - "content": "现在是投票阶段,你需要为你认为最可能是卧底的玩家投票。不要投给已经被淘汰的玩家。只需回答玩家的名字。" - } - }, - "class": "BroadcastAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 373, - "pos_y": 311.8888888888889 - }, - "13": { - "id": 13, - "name": "CopyNode", - "data": { - "elements": [ - "7" - ] - }, - "class": "node-DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 407, - "pos_y": 341 - }, - "14": { - "id": 14, - "name": "CopyNode", - "data": { - "elements": [ - "8" - ] - }, - "class": "node-DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 439, - "pos_y": 372 - }, - "15": { - "id": 15, - "name": "CopyNode", - "data": { - "elements": [ - "9" - ] - }, - "class": "node-DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 462.8888888888889, - "pos_y": 399 - }, - "16": { - "id": 16, - "name": "CopyNode", - "data": { - "elements": [ - "10" - ] - }, - "class": "node-DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 488, - "pos_y": 432 - }, - "17": { - "id": 17, - "name": "CopyNode", - "data": { - "elements": [ - "11" - ] - }, - "class": "node-DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 523.1111111111111, - "pos_y": 473 - }, - "18": { - "id": 18, - "name": "DialogAgent", - "data": { - "args": { - "name": "计票员", - "sys_prompt": "作为《谁是卧底》游戏中的计票员,你的职责是准确地统计玩家的选择投票,并确定结果。遵循以下准则:\n- 如果多名玩家获得最高票数并出现平票,例如玩家1和玩家2,你应该回应:“平票,没有玩家被淘汰。\n- 如果某一名玩家获得最高票数,例如玩家1,你应宣布结果为:“玩家1被淘汰。\n- 不要回应其他任何内容,包括你的分析和想法。", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 549, - "pos_y": 512.8888888888889 - }, - "19": { - "id": 19, - "name": "DialogAgent", - "data": { - "args": { - "name": "裁判", - "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n作为《谁是卧底》游戏中的裁判角色,你的唯一责任是评估游戏是否已达到其结局:\n如果卧底玩家3被淘汰,则回复:“游戏结束,卧底输了。”\n如果玩家3存活,并且游戏中只剩下两名玩家,则回复:“游戏结束,卧底赢了。”\n在所有其他情况下,简单地声明:“游戏继续。” 请将你的回复限制在这些具体的结果上,不要添加任何个人评论、分析或其他备注。\"\"\",", - "model_config_name": "qwen" - } - }, - "class": "DialogAgent", - "typenode": false, - "inputs": { - "input_1": { - "connections": [] - } - }, - "outputs": { - "output_1": { - "connections": [] - } - }, - "pos_x": 586, - "pos_y": 546 - } + "index": 4, + "title": "Undercover", + "author": "AgentScopeTeam", + "category": "game", + "time": "2024-09-23", + "thumbnail": "", + "description": "Undercover game" + }, + "drawflow": { + "Home": { + "data": { + "2": { + "id": 2, + "name": "dashscope_chat", + "data": { + "args": { + "config_name": "qwen", + "model_name": "qwen-max", + "api_key": "****", + "temperature": 0, + "seed": 0, + "model_type": "dashscope_chat" } + }, + "class": "dashscope_chat", + "typenode": false, + "inputs": {}, + "outputs": {}, + "pos_x": -75, + "pos_y": -31 + }, + "3": { + "id": 3, + "name": "MsgHub", + "data": { + "elements": [ + "4" + ], + "args": { + "announcement": { + "name": "主持人", + "content": "游戏开始!" + } + } + }, + "class": "GROUP", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 281, + "pos_y": -34.111111111111114 + }, + "4": { + "id": 4, + "name": "ForLoopPipeline", + "data": { + "elements": [ + "5" + ], + "args": { + "max_loop": 5, + "condition_op": "contains", + "target_value": "结束" + } + }, + "class": "GROUP", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 319, + "pos_y": 27 + }, + "5": { + "id": 5, + "name": "SequentialPipeline", + "data": { + "elements": [ + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19" + ] + }, + "class": "GROUP", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 357, + "pos_y": 76 + }, + "6": { + "id": 6, + "name": "BroadcastAgent", + "data": { + "args": { + "name": "主持人", + "content": "现在是描述阶段,你需要用一句话来描述你的词。" + } + }, + "class": "BroadcastAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 387, + "pos_y": 107 + }, + "7": { + "id": 7, + "name": "DialogAgent", + "data": { + "args": { + "name": "玩家1", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家1。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 437, + "pos_y": 133 + }, + "8": { + "id": 8, + "name": "DialogAgent", + "data": { + "args": { + "name": "玩家2", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家2。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 477, + "pos_y": 158 + }, + "9": { + "id": 9, + "name": "DialogAgent", + "data": { + "args": { + "name": "玩家3", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家3。你的秘密词是“孔雀”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 512, + "pos_y": 178.88888888888889 + }, + "10": { + "id": 10, + "name": "DialogAgent", + "data": { + "args": { + "name": "玩家4", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家4。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 545, + "pos_y": 204 + }, + "11": { + "id": 11, + "name": "DialogAgent", + "data": { + "args": { + "name": "玩家5", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n你的名字是玩家5。你的秘密词是“凤凰”。在游戏中使用第一人称视角进行交流,并记得在你的回复中避免使用你的名字。你的目标是微妙地描述你的词,并通过投票努力识别卧底,同时不暴露你的身份。回复中不要包含你的思考过程。只有在“计票员”说你被淘汰,你才是被淘汰。不要在回复中带有你的名字。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 583, + "pos_y": 230 + }, + "12": { + "id": 12, + "name": "BroadcastAgent", + "data": { + "args": { + "name": "主持人", + "content": "现在是投票阶段,你需要为你认为最可能是卧底的玩家投票。不要投给已经被淘汰的玩家。只需回答玩家的名字。" + } + }, + "class": "BroadcastAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 373, + "pos_y": 311.8888888888889 + }, + "13": { + "id": 13, + "name": "CopyNode", + "data": { + "elements": [ + "7" + ] + }, + "class": "node-DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 407, + "pos_y": 341 + }, + "14": { + "id": 14, + "name": "CopyNode", + "data": { + "elements": [ + "8" + ] + }, + "class": "node-DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 439, + "pos_y": 372 + }, + "15": { + "id": 15, + "name": "CopyNode", + "data": { + "elements": [ + "9" + ] + }, + "class": "node-DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 462.8888888888889, + "pos_y": 399 + }, + "16": { + "id": 16, + "name": "CopyNode", + "data": { + "elements": [ + "10" + ] + }, + "class": "node-DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 488, + "pos_y": 432 + }, + "17": { + "id": 17, + "name": "CopyNode", + "data": { + "elements": [ + "11" + ] + }, + "class": "node-DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 523.1111111111111, + "pos_y": 473 + }, + "18": { + "id": 18, + "name": "DialogAgent", + "data": { + "args": { + "name": "计票员", + "sys_prompt": "作为《谁是卧底》游戏中的计票员,你的职责是准确地统计玩家的选择投票,并确定结果。遵循以下准则:\n- 如果多名玩家获得最高票数并出现平票,例如玩家1和玩家2,你应该回应:“平票,没有玩家被淘汰。\n- 如果某一名玩家获得最高票数,例如玩家1,你应宣布结果为:“玩家1被淘汰。\n- 不要回应其他任何内容,包括你的分析和想法。", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 549, + "pos_y": 512.8888888888889 + }, + "19": { + "id": 19, + "name": "DialogAgent", + "data": { + "args": { + "name": "裁判", + "sys_prompt": "《谁是卧底》游戏规则:\n\n概览:\n《谁是卧底》是一款基于推理的猜谜游戏,适合5名玩家:玩家1、玩家2、玩家3、玩家4和玩家5。\n\n目标:\n通过战略性的描述和投票揭露参与者中的卧底玩家,同时卧底的目标是保持不被发现。\n\n游戏设置:\n- 在游戏开始时,每位玩家秘密被分配一个词。四名玩家收到相同的词,而剩余的一名玩家得到一个不同的词,成为卧底。\n- 卧底的词应对其他玩家保密。\n\n游戏玩法:\n游戏由多个轮次组成,每个轮次分为两个阶段:描述阶段和投票阶段。\n\n1. 描述阶段:\n - 玩家轮流用一句话描述他们的词,目标是微妙地向其他玩家指示自己的词,而不将其泄露给卧底。\n2. 投票阶段:\n - 玩家投票选出他们怀疑是卧底的人。得票最多的玩家被淘汰。如果出现平票,游戏进入下一轮。\n - 被淘汰的玩家在他们的回合中只能回应“该玩家已被淘汰”。\n \n获胜条件:\n- 如果只剩下两名玩家,其中一名是卧底,卧底获胜。\n- 如果在此之前卧底被淘汰,其他玩家获胜。\n\n策略:\n- 玩家应该模糊地描述他们的词,以防卧底察觉。\n- 投票应被战略性地使用来识别并淘汰卧底。\n\n注意:\n一旦被淘汰,该玩家唯一允许的回复是“该玩家已被淘汰。”\n作为《谁是卧底》游戏中的裁判角色,你的唯一责任是评估游戏是否已达到其结局:\n如果卧底玩家3被淘汰,则回复:“游戏结束,卧底输了。”\n如果玩家3存活,并且游戏中只剩下两名玩家,则回复:“游戏结束,卧底赢了。”\n在所有其他情况下,简单地声明:“游戏继续。” 请将你的回复限制在这些具体的结果上,不要添加任何个人评论、分析或其他备注。\"\"\",", + "model_config_name": "qwen" + } + }, + "class": "DialogAgent", + "typenode": false, + "inputs": { + "input_1": { + "connections": [] + } + }, + "outputs": { + "output_1": { + "connections": [] + } + }, + "pos_x": 586, + "pos_y": 546 } + } } + } } \ No newline at end of file diff --git a/src/agentscope/studio/_app.py b/src/agentscope/studio/_app.py index c6b62015e..9416c5db5 100644 --- a/src/agentscope/studio/_app.py +++ b/src/agentscope/studio/_app.py @@ -707,6 +707,16 @@ def _fetch_gallery() -> Response: except (IOError, json.JSONDecodeError) as e: print(f"Error reading {file_path}: {e}") + def sort_key(item: Any) -> Tuple: # mypy: ignore + meta = item.get("meta", {}) + index = meta.get("index", float("inf")) + time = meta.get("time", "") + time = datetime.strptime(time, "%Y-%m-%d") if time else datetime.min + is_negative = index < 0 + return (is_negative, -index if is_negative else index, time) + + gallery_items.sort(key=sort_key) + return jsonify(json=gallery_items) diff --git a/src/agentscope/studio/_app_online.py b/src/agentscope/studio/_app_online.py index 1edaad8c5..d5295b6e7 100644 --- a/src/agentscope/studio/_app_online.py +++ b/src/agentscope/studio/_app_online.py @@ -3,12 +3,13 @@ import ipaddress import json import os +import traceback import uuid import time import secrets import tempfile from typing import Tuple, Any -from datetime import timedelta +from datetime import timedelta, datetime import requests import oss2 @@ -28,7 +29,17 @@ from dotenv import load_dotenv from agentscope.constants import EXPIRATION_SECONDS, FILE_SIZE_LIMIT -from agentscope.studio.utils import _require_auth, generate_jwt +from agentscope.studio.utils import ( + _require_auth, + generate_jwt, + decode_jwt, + get_user_status, + star_repository, + fork_repo, + create_branch_with_timestamp, + upload_file, + open_pull_request, +) from agentscope.studio._app import ( _convert_config_to_py, _read_examples, @@ -151,35 +162,6 @@ def generate_verification_token() -> str: return secrets.token_urlsafe() -def star_repository(access_token: str) -> int: - """ - Star the Repo. - """ - url = f"https://api.github.com/user/starred/{OWNER}/{REPO}" - headers = { - "Authorization": f"token {access_token}", - "Content-Length": "0", - "Accept": "application/vnd.github.v3+json", - } - response = requests.put(url, headers=headers) - return response.status_code == 204 - - -def get_user_status(access_token: str) -> Any: - """ - Get user status. - """ - url = "https://api.github.com/user" - headers = { - "Authorization": f"token {access_token}", - "Accept": "application/vnd.github.v3+json", - } - response = requests.get(url, headers=headers) - if response.status_code == 200: - return response.json() - return None - - @_app.route("/") def _home() -> str: """ @@ -252,7 +234,7 @@ def oauth_callback() -> str: user_login = user_status.get("login") - if star_repository(access_token=access_token): + if star_repository(access_token=access_token, owner=OWNER, repo=REPO): verification_token = generate_verification_token() # Used for compare with `verification_token` in `jwt_token` session["verification_token"] = verification_token @@ -401,6 +383,99 @@ def _load_workflow_online(**kwargs: Any) -> Response: return _load_workflow() +@_app.route("/create-gallery-pr", methods=["POST"]) +@_require_auth(fail_with_exception=True, secret_key=SECRET_KEY) +def create_gallery_pr(**kwargs: Any) -> Response: + # pylint: disable=unused-argument + """ + Create a workflow PR for gallery. + """ + try: + meta_info = request.json.get("meta") + data = request.json.get("data") + + assert meta_info.get("author") == session.get("user_login") + + meta_info = { + "index": -1, + "title": meta_info.get("title"), + "author": session.get("user_login"), + "description": meta_info.get("description"), + "category": meta_info.get("category"), + "time": datetime.now().strftime("%Y-%m-%d"), + "thumbnail": meta_info.get("thumbnail", ""), + } + + gallery_data = { + "meta": meta_info, + "drawflow": data, + } + + jwt_token = session.get("jwt_token") + payload = decode_jwt(jwt_token, secret_key=SECRET_KEY) + jwt_token = session.get("jwt_token") + payload = decode_jwt(jwt_token, secret_key=SECRET_KEY) + access_token = payload.get("access_token") + user_login = session.get("user_login") + + # Fork the repository if not already forked + if not fork_repo(access_token, OWNER, REPO, user_login): + return jsonify({"message": "Failed to fork repository."}), 500 + + # Create a new branch with a unique name + new_branch = create_branch_with_timestamp( + access_token, + OWNER, + REPO, + user_login, + "main", + ) + if not new_branch: + return jsonify({"message": "Failed to create branch."}), 500 + + # Create file path and write gallery data + file_name = ( + f"{datetime.now().strftime('%Y%m%d')}" + f"_{meta_info['title']}_{user_login}.json" + ) + file_name = file_name.replace(" ", "_") + file_path = f"gallery/{file_name}" + file_content = json.dumps(gallery_data, indent=2) + encoded_content = file_content.encode("utf-8").decode("ascii") + + if not upload_file( + access_token, + user_login, + REPO, + new_branch, + file_path, + encoded_content, + f"Add gallery data for {meta_info['title']} [skip ci]", + ): + return jsonify({"message": "Failed to write file."}), 500 + + # Create a pull request + pr_title = f"[Gallery] Contribute Workflow: {meta_info['title']}" + pr_response = open_pull_request( + access_token, + OWNER, + REPO, + pr_title, + f"{user_login}:{new_branch}", + "main", + body_content=meta_info.get("description"), + ) + if not pr_response: + return jsonify({"message": "Failed to create pull request."}), 500 + + return jsonify({"message": "PR created successfully!"}), 200 + + except Exception as e: + print("Error:", e) + print("Trace:", traceback.format_exc()) + return jsonify({"message": "Failed to create PR."}), 500 + + @_app.route("/set_locale") def set_locale() -> Response: """ diff --git a/src/agentscope/studio/static/css/workstation.css b/src/agentscope/studio/static/css/workstation.css index 2b7571adc..c139547ec 100644 --- a/src/agentscope/studio/static/css/workstation.css +++ b/src/agentscope/studio/static/css/workstation.css @@ -514,10 +514,12 @@ pre[class*='language-'] { .pop-drawer { background-image: linear-gradient(to right, #1861e5, #171ce4); } + .pop-drawer:hover { background-image: linear-gradient(to left, #1861e5, #171ce4); transform: unset; } + .add-case { background-image: linear-gradient(to right, #56ab2f, #a8e063); } @@ -611,12 +613,12 @@ pre[class*='language-'] { box-shadow: 0 2px 10px 2px var(--main-color-light); } -.tool-box{ +.tool-box { display: flex; gap: 10px; } -.add-service-list{ +.add-service-list { list-style-type: none; padding: 0; margin: 0; @@ -624,7 +626,7 @@ pre[class*='language-'] { position: absolute; background-color: #f9f9f9; min-width: 160px; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); z-index: 1; border-radius: 5%; overflow: hidden; @@ -694,10 +696,43 @@ pre[class*='language-'] { .hidden-node { display: none !important; } + /* END */ .swal2-container { - z-index: 20000 !important; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +} + +.swal2-input { + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 10px; + font-size: 16px; + width: 95%; + margin-bottom: 10px; + box-shadow: none; + outline: none; + transition: border 0.3s; +} + +.swal2-input:focus { + border-color: #007aff; +} + +.category-button { + margin: 5px; + padding: 10px 15px; + border: none; + border-radius: 8px; + background-color: #f0f0f0; + color: #333; + cursor: pointer; + transition: background-color 0.3s, color 0.3s; +} + +.category-button.selected { + background-color: #007aff; + color: #fff; } .modules-info { @@ -1260,7 +1295,7 @@ pre[class*='language-'] { cursor: pointer; } -.add-button-forJson{ +.add-button-forJson { position: absolute; top: 100%; left: 50%; @@ -1286,13 +1321,24 @@ pre[class*='language-'] { height: 2px; /* "+" 的线条厚度 */ background-color: currentColor; } + .add-button-forJson::before { transform: rotate(90deg); } + .add-button-forJson::after { transform: rotate(0deg); } -.add-button-forJson:hover{ - transform: translate(-50%, 20%); ; + +.add-button-forJson:hover { + transform: translate(-50%, 20%);; transition: all 0.3s ease-in-out; } + +.special-icon { + position: absolute; + top: -5px; + right: -5px; + font-size: 24px; + line-height: 1; +} diff --git a/src/agentscope/studio/static/html/index-guide.html b/src/agentscope/studio/static/html/index-guide.html index 83b81a250..552aea8bd 100644 --- a/src/agentscope/studio/static/html/index-guide.html +++ b/src/agentscope/studio/static/html/index-guide.html @@ -1,19 +1,19 @@

      Welcome to AgentScope Studio

      -

      +

      AgentScope Studio is a web-based platform for monitoring, managing and developing multi-agent applications in AgentScope! Feel free to explore the platform and its features.

      -

      Start to explore AgentScope Studio with

      +

      Start to explore AgentScope Studio with

      Dashboard
      -
      +
      Monitor and manage your AgentScope running instances.
      @@ -22,7 +22,7 @@

      Start to explore AgentScope Studio with

      onclick="loadTabPage('static/html/workstation_iframe.html', 'static/js/workstation_iframe.js');return false;" >
      Workstation
      -
      +
      Develop your multi-agent applications by dragging.
      @@ -30,14 +30,14 @@

      Start to explore AgentScope Studio with

      Gallery
      -
      Coming soon ...
      +
      Coming soon ...
      Server Manager
      -
      +
      Manage your agent servers (In development).
      diff --git a/src/agentscope/studio/static/i18n/i18n_en.json b/src/agentscope/studio/static/i18n/i18n_en.json index 1b6c27d88..9984dd8fe 100644 --- a/src/agentscope/studio/static/i18n/i18n_en.json +++ b/src/agentscope/studio/static/i18n/i18n_en.json @@ -12,12 +12,12 @@ "login-page-please-wait": "Please wait...", "index-Tutorial": "Tutorial", "index-API": "API Document", - "index-guide.introduction": "AgentScope Studio is a web-based platform for monitoring, managing and developing multi-agent applications in AgentScope! Feel free to explore the platform and its features.", - "index-guide.h2": "Start to explore AgentScope Studio with", - "index-guide.DashboardDetail": "Monitor and manage your AgentScope running instances.", - "index-guide.WorkstationDetail": "Develop your multi-agent applications by dragging.", - "index-guide.GalleryDetail": "Coming soon ...", - "index-guide.ServerDetail": "Manage your agent servers (In development).", + "index-guide-introduction": "AgentScope Studio is a web-based platform for monitoring, managing and developing multi-agent applications in AgentScope! Feel free to explore the platform and its features.", + "index-guide-h2": "Start to explore AgentScope Studio with", + "index-guide-DashboardDetail": "Monitor and manage your AgentScope running instances.", + "index-guide-WorkstationDetail": "Develop your multi-agent applications by dragging.", + "index-guide-GalleryDetail": "Coming soon ...", + "index-guide-ServerDetail": "Manage your agent servers (In development).", "dashboard-run-placeholder-Search": "Search", "market-title": "Coming soon ...", "dialogue-detail-dialogue-Nodata": "No messages available.", diff --git a/src/agentscope/studio/static/i18n/i18n_zh.json b/src/agentscope/studio/static/i18n/i18n_zh.json index 1f4a68847..3fb831f6b 100644 --- a/src/agentscope/studio/static/i18n/i18n_zh.json +++ b/src/agentscope/studio/static/i18n/i18n_zh.json @@ -12,12 +12,12 @@ "login-page-please-wait": "请稍后...", "index-Tutorial": "教程", "index-API": "API 文档", - "index-guide.introduction": "AgentScope Studio 是一个基于 Web 的平台,用于在 AgentScope 中监控、管理和开发多智能体应用程序!请随意探索该平台及其功能", - "index-guide.h2": "开始探索 AgentScope Studio", - "index-guide.DashboardDetail": "监控和管理您的 AgentScope 运行实例.", - "index-guide.WorkstationDetail": "通过拖动来开发您的多智能体应用程序。", - "index-guide.GalleryDetail": "即将推出 ...", - "index-guide.ServerDetail": "管理您的智能体服务器(开发中)。", + "index-guide-introduction": "AgentScope Studio 是一个基于 Web 的平台,用于在 AgentScope 中监控、管理和开发多智能体应用程序!请随意探索该平台及其功能", + "index-guide-h2": "开始探索 AgentScope Studio", + "index-guide-DashboardDetail": "监控和管理您的 AgentScope 运行实例.", + "index-guide-WorkstationDetail": "通过拖动来开发您的多智能体应用程序。", + "index-guide-GalleryDetail": "即将推出 ...", + "index-guide-ServerDetail": "管理您的智能体服务器(开发中)。", "market-title": "即将推出 ...", "dashboard-run-placeholder-Search": "查询", "dialogue-detail-dialogue-Nodata": "无可用消息", diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 8e85dc6c0..4d3b1a12e 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -654,8 +654,7 @@ async function addNodeToDrawFlow(name, pos_x, pos_y) { const CopyNodeID = editor.addNode("CopyNode", 1, 1, pos_x, pos_y, - "CopyNode", { - }, htmlSourceCode); + "CopyNode", {}, htmlSourceCode); var nodeElement = document.querySelector(`#node-${CopyNodeID} .node-id`); if (nodeElement) { nodeElement.textContent = nodeElement; @@ -1241,7 +1240,7 @@ function setupNodeListeners(nodeId) { if (toggleArrow && contentBox && titleBox) { toggleArrow.addEventListener("click", function () { const serivceArr = ["BingSearchService", "GoogleSearchService", "PythonService", "ReadTextService", "WriteTextService", "TextToAudioService", "AudioToTextService"]; - if(serivceArr.includes(newNode.querySelector(".title-box").getAttribute("data-class"))){ + if (serivceArr.includes(newNode.querySelector(".title-box").getAttribute("data-class"))) { return; } contentBox.classList.toggle("hidden"); @@ -1271,7 +1270,7 @@ function setupNodeListeners(nodeId) { function doDragSE(e) { newNode.style.width = "auto"; - newNode.style.height= "auto"; + newNode.style.height = "auto"; const newWidth = (startWidth + e.clientX - startX); if (newWidth > 200) { @@ -1527,7 +1526,7 @@ function sortElementsByPosition(inputData) { Object.keys(inputData.drawflow).forEach((moduleKey) => { const moduleData = inputData.drawflow[moduleKey]; Object.entries(moduleData.data).forEach(([nodeId, node]) => { - if (node.class === "GROUP" && node.name !=="ReActAgent") { + if (node.class === "GROUP" && node.name !== "ReActAgent") { const elements = node.data.elements; const elementsWithPosition = elements.map(elementId => { const elementNode = document.querySelector(`#node-${elementId}`); @@ -2065,6 +2064,197 @@ function showExportHTMLPopup() { } +function showContributePopup(userLogin) { + if (userLogin.startsWith("guest_") || userLogin === "local_user") { + if(getCookie("locale") == "zh"){ + Swal.fire( + "Error", + "要分享您的workflow,您需要通过 agentscope.io 登录 GitHub账号。请登录后重试。", + "error" + ); + return; + } else { + Swal.fire( + "Error", + "You need to be logged into GitHub via agentscope.io to contribute. Please log in and try again.", + "error" + ); + return; + } + } + let swalObj = { + title: "Contribute Your Workflow to AgentScope", + text: `You are about to perform the following actions: + 1. Create a new branch in your forked repository. + 2. Add your workflow file to this branch. + 3. Create a Pull Request (PR) from your branch to the AgentScope Gallery. + + These operations will allow you to share your workflow with the community on AgentScope. + Please ensure that any API keys or sensitive information are not included in your submission. + By proceeding, you grant permission for these actions to be executed.`, + icon: "info", + showCancelButton: true, + confirmButtonText: "Yes, I'd like to contribute!", + cancelButtonText: "No, maybe later" + }; + let swalisConfirmedHtml = ` +
      + + + + + + + + + + +
      + + + + + + + + + + + + + + +
      + +
      + `; + let swalisConfirmedTitle = "Fill the Form to Create PR"; + if(getCookie("locale") == "zh"){ + swalObj = { + title: "将您的工作流程贡献给 AgentScope", + text: `您即将执行以下操作: + 1.在您的分叉仓库中创建一个新分支。 + 2.将您的工作流程文件添加到此分支。 + 3.从您的分支创建一个 Pull Request(PR)到 AgentScope Gallery。 + 这些操作将允许您与 AgentScope 上的社区分享您的工作流程。 + 请确保您的提交中不包含任何 API 密钥或敏感信息。 + 继续操作即表示您授予执行这些操作的权限。`, + icon: "info", + showCancelButton: true, + confirmButtonText: "是的,我想贡献!", + cancelButtonText: "不,也许以后再说。" + }; + swalisConfirmedTitle = "填写表单以创建 PR(Pull Request)"; + swalisConfirmedHtml = ` +
      + + + + + + + + + + +
      + + + + + + + + + + + + + + +
      + +
      + `; + } + + swal.fire(swalObj).then(async (result) => { + if (result.isConfirmed) { + const {value: formValues} = await Swal.fire({ + title: swalisConfirmedTitle, + html: swalisConfirmedHtml, + focusConfirm: false, + showCancelButton: true, + preConfirm: () => { + const selectedCategories = Array.from(document.querySelectorAll(".category-button.selected")) + .map(button => button.getAttribute("data-value")); + return { + title: document.getElementById("swal-input1").value, + author: userLogin, + description: document.getElementById("swal-input2").value, + category: selectedCategories, + thumbnail: document.getElementById("swal-input3").value + }; + }, + didOpen: () => { + const buttons = document.querySelectorAll(".category-button"); + buttons.forEach(button => { + button.addEventListener("click", () => { + button.classList.toggle("selected"); + }); + }); + } + }); + + if (formValues) { + try { + const rawData = editor.export(); + const hasError = sortElementsByPosition(rawData); + if (hasError) { + return; + } + const filteredData = reorganizeAndFilterConfigForAgentScope(rawData); + filterOutApiKey(filteredData); + + const response = await fetch("/create-gallery-pr", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + meta: formValues, + data: JSON.stringify(filteredData, null, 4), + }) + }); + + if (response.ok) { + if(getCookie("locale") == "zh"){ + swal.fire("Success", + "谢谢!您的工作流程已提交至图库。我们的维护人员会对其进行审核,一旦获得批准,您将被认可为我们主页上的 AgentScope 开发者!", + "success"); + } else{ + swal.fire("Success", + "Thank you! Your workflow has been submitted to the gallery. It will be reviewed by our maintainers and, once approved, you'll be recognized as an AgentScope developer on our homepage!", + "success"); + } + } else { + if(getCookie("locale") == "zh"){ + swal.fire("Error", "提交您的工作流程时出现错误。请稍后再试。", "error"); + }else { + swal.fire("Error", "There was an error while submitting your workflow. Please try again later.", "error"); + } + } + } catch (error) { + if(getCookie("locale") == "zh"){ + swal.fire("Error", "提交您的工作流程时出现错误。请稍后再试。", "error"); + } else { + swal.fire("Error", "There was an error while submitting your workflow. Please try again later.", "error"); + } + } + } + } + }); +} function isValidDataStructure(data) { if ( @@ -2142,7 +2332,8 @@ function showImportHTMLPopup() { setTimeout(() => { updateImportNodes(); }, 200); - });; + }); + ; }); } catch (error) { @@ -2419,9 +2610,10 @@ function updateImportNodes() { editor.updateConnectionNodes(`node-${nodeId}`); }); } + function importSetupNodes(dataToImport) { imporTempData = dataToImport; - Object.entries(dataToImport.drawflow.Home.data).forEach(([nodeId,nodeValue]) => { + Object.entries(dataToImport.drawflow.Home.data).forEach(([nodeId, nodeValue]) => { disableButtons(); makeNodeTop(nodeId); setupNodeCopyListens(nodeId); @@ -2452,9 +2644,9 @@ function importSetupNodes(dataToImport) { setupNodeCopyListens(nodeId); } } - if(nodeValue.name === "ReActAgent"){ + if (nodeValue.name === "ReActAgent") { nodeValue.data.elements.forEach((listNode) => { - dropNodeToDropzone(listNode,nodeElement); + dropNodeToDropzone(listNode, nodeElement); }); } }); @@ -3068,6 +3260,7 @@ function showEditorTab() { document.getElementById("col-right2").style.display = "none"; console.log("Show Editor"); } + function importGalleryWorkflow(data) { try { const parsedData = JSON.parse(data); @@ -3345,28 +3538,28 @@ function setupNodeServiceDrawer(nodeId) { if (popDrawer) { const contain = newNode.querySelector(".serivce-contain"); const hiddenList = newNode.querySelector(".add-service-list"); - contain.addEventListener("mouseover", function() { + contain.addEventListener("mouseover", function () { hiddenList.style.display = "block"; }); - hiddenList.addEventListener("mouseover", function() { + hiddenList.addEventListener("mouseover", function () { hiddenList.style.display = "block"; }); - contain.addEventListener("mouseout", function() { + contain.addEventListener("mouseout", function () { hiddenList.style.display = "none"; }); - hiddenList.addEventListener("click", function(e) { + hiddenList.addEventListener("click", function (e) { const target = e.target; - if(target.localName == "li"){ + if (target.localName == "li") { // createServiceNode(nodeId,target.getAttribute("data-node"),newNode,e.currentTarget.offsetLeft + newNode.offsetWidth ,e.currentTarget.offsetTop); - createServiceNode(nodeId,target.getAttribute("data-node")); + createServiceNode(nodeId, target.getAttribute("data-node")); } }); } } -async function createServiceNode(nodeId,serivceName){ +async function createServiceNode(nodeId, serivceName) { const nodeElement = document.getElementById(`node-${nodeId}`); const nodeElementRect = nodeElement.getBoundingClientRect(); const node = editor.getNodeFromId(nodeId); @@ -3374,19 +3567,19 @@ async function createServiceNode(nodeId,serivceName){ const dropzoneRect = nodeElement.querySelector(".tools-placeholder").getBoundingClientRect(); - const createPos_x = Math.ceil(node.pos_x + (dropzoneRect.width * 2 / 3 ) / (editor.zoom)); - const createPos_y = Math.ceil(node.pos_y + (dropzoneRect.top - nodeElementRect.top ) / editor.zoom + 20); + const createPos_x = Math.ceil(node.pos_x + (dropzoneRect.width * 2 / 3) / (editor.zoom)); + const createPos_y = Math.ceil(node.pos_y + (dropzoneRect.top - nodeElementRect.top) / editor.zoom + 20); const dropNodeInfo = editor.getNodeFromId(nodeId); const dropNodeInfoData = dropNodeInfo.data; - const createdId = await selectReActAgent(serivceName,createPos_x,createPos_y); + const createdId = await selectReActAgent(serivceName, createPos_x, createPos_y); dropNodeInfoData.elements.push(`${createdId}`); editor.updateNodeDataFromId(nodeId, dropNodeInfoData); dropzoneDetection(createdId); } -async function selectReActAgent(serivceName,pos_x,pos_y) { +async function selectReActAgent(serivceName, pos_x, pos_y) { const htmlSourceCode = await fetchHtmlSourceCodeByName(serivceName); let createId; switch (serivceName) { @@ -3474,8 +3667,8 @@ function dropzoneDetection(nodeId) { if (!nodeElement.contains(dropzone)) { if ( isColliding(nodeElement, dropzone) && - node.name !== "dropzoneNode" && - !node.data.attachedToDropzone + node.name !== "dropzoneNode" && + !node.data.attachedToDropzone ) { console.log( `Collision detected: Node "${node.name}" (ID: ${nodeId}) collided with ${dropzone.id}` @@ -3492,9 +3685,9 @@ function isColliding(element1, element2) { return !( rect1.right < rect2.left || - rect1.left > rect2.right || - rect1.bottom < rect2.top || - rect1.top > rect2.bottom + rect1.left > rect2.right || + rect1.bottom < rect2.top || + rect1.top > rect2.bottom ); } @@ -3623,7 +3816,7 @@ function handleStackedItemDrag(e) { const originalNode = document.getElementById(`node-${nodeId}`); if ( dropzone && - !dropzone.contains(document.elementFromPoint(e.clientX, e.clientY)) + !dropzone.contains(document.elementFromPoint(e.clientX, e.clientY)) ) { console.log("Item dragged outside the dropzone"); // display forbidden cursor @@ -3713,7 +3906,7 @@ function expandNodeFromDropzone(node, dropzoneNode, stackedItem) { if ( stackedItemRect.top + stackedItemRect.height / 2 < - expandedRect.top + expandedRect.height / 2 + expandedRect.top + expandedRect.height / 2 ) { // Tail should be on the top-left corner tailLeft = -tailSize; @@ -3748,8 +3941,8 @@ function expandNodeFromDropzone(node, dropzoneNode, stackedItem) { nodeElement.onmousedown = function (e) { if ( e.target.tagName === "INPUT" || - e.target.tagName === "SELECT" || - e.target.tagName === "BUTTON" + e.target.tagName === "SELECT" || + e.target.tagName === "BUTTON" ) { return; } @@ -3779,7 +3972,7 @@ function expandNodeFromDropzone(node, dropzoneNode, stackedItem) { } } - nodeElement.querySelector(".toggle-arrow").addEventListener("click",(e) => { + nodeElement.querySelector(".toggle-arrow").addEventListener("click", (e) => { collapseNode(); }); // Add event listener for outside clicks diff --git a/src/agentscope/studio/templates/workstation.html b/src/agentscope/studio/templates/workstation.html index 6f19141a9..aab156966 100644 --- a/src/agentscope/studio/templates/workstation.html +++ b/src/agentscope/studio/templates/workstation.html @@ -417,6 +417,17 @@ Load workflow
      + + +
      diff --git a/src/agentscope/studio/utils.py b/src/agentscope/studio/utils.py index bbab18889..1047732b1 100644 --- a/src/agentscope/studio/utils.py +++ b/src/agentscope/studio/utils.py @@ -7,11 +7,14 @@ _require_auth - A decorator for protecting views by requiring authentication. """ +import json +import base64 from datetime import datetime, timedelta from functools import wraps -from typing import Any, Callable +from typing import Any, Callable, Dict, Optional import jwt +import requests from flask import session, redirect, url_for, abort from agentscope.constants import TOKEN_EXP_TIME @@ -133,3 +136,171 @@ def decode_jwt(token: str, secret_key: str) -> Any: description="The provided token is invalid. Please log in again.", ) return None + + +def get_github_headers(access_token: str) -> Dict[str, str]: + """ + Build headers + """ + return { + "Authorization": f"token {access_token}", + "Accept": "application/vnd.github.v3+json", + } + + +def get_user_status(access_token: str) -> Any: + """ + Get user status. + """ + url = "https://api.github.com/user" + headers = get_github_headers(access_token) + response = requests.get(url, headers=headers) + if response.status_code == 200: + return response.json() + return None + + +def star_repository( + access_token: str, + owner: str, + repo: str, +) -> int: + """ + Star the Repo. + """ + url = f"https://api.github.com/user/starred/{owner}/{repo}" + headers = get_github_headers(access_token) + response = requests.put(url, headers=headers) + return response.status_code == 204 + + +def get_branch_sha( + owner: str, + repo: str, + branch: str, + headers: Dict[str, str], +) -> Optional[str]: + """ + Get branch sha. + """ + url = ( + f"https://api.github.com/repos/{owner}/{repo}/git/refs/heads/{branch}" + ) + response = requests.get(url, headers=headers) + if response.status_code == 200: + return response.json()["object"]["sha"] + return None + + +def upload_file( + access_token: str, + owner: str, + repo: str, + branch: str, + file_path: str, + content: str, + commit_message: str, +) -> Optional[str]: + """ + Upload file to branch + """ + headers = get_github_headers(access_token) + url = f"https://api.github.com/repos/{owner}/{repo}/contents/{file_path}" + encoded_content = base64.b64encode(content.encode("utf-8")).decode("utf-8") + + data = { + "message": commit_message, + "content": encoded_content, + "branch": branch, + } + response = requests.put(url, headers=headers, data=json.dumps(data)) + if response.status_code == 201: + return response.json()["content"]["sha"] + return None + + +def open_pull_request( + access_token: str, + owner: str, + repo: str, + title: str, + head_branch: str, + base_branch: str = "main", + body_content: str = "", +) -> Optional[Dict[str, Any]]: + """ + Open a pull request + """ + headers = get_github_headers(access_token) + url = f"https://api.github.com/repos/{owner}/{repo}/pulls" + data = { + "title": title, + "head": head_branch, + "base": base_branch, + "body": f"This is an automated pull request.\n\n" + f"Workflow description:\n" + f"{body_content}\n\n[skip ci]", + } + response = requests.post(url, headers=headers, json=data) + if response.status_code == 201: + return response.json() + return None + + +def fork_repo(access_token: str, owner: str, repo: str, user: str) -> bool: + """ + Fork a repo + """ + headers = get_github_headers(access_token) + + # Check if the repository is already forked + forked_repo_url = f"https://api.github.com/repos/{user}/{repo}" + response = requests.get(forked_repo_url, headers=headers) + if response.status_code == 200: + print("Repository is already forked.") + return True + + # Fork the repository + fork_url = f"https://api.github.com/repos/{owner}/{repo}/forks" + response = requests.post(fork_url, headers=headers) + if response.status_code == 202: + print("Repository forked successfully.") + return True + else: + print("Failed to fork the repository.") + return False + + +def create_branch_with_timestamp( + access_token: str, + owner: str, + repo: str, + user: str, + base_branch: str = "main", +) -> Optional[str]: + """ + Create a branch with a timestamp in the forked repository based on the + upstream base branch. + """ + headers = get_github_headers(access_token) + sha = get_branch_sha(owner, repo, base_branch, headers) + if not sha: + print(f"Failed to get the upstream {base_branch} branch.") + return None + + # Generate a unique branch name using a timestamp + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") + new_branch = f"feature/{timestamp}" + + create_url = f"https://api.github.com/repos/{user}/{repo}/git/refs" + data = { + "ref": f"refs/heads/{new_branch}", + "sha": sha, + } + response = requests.post(create_url, headers=headers, json=data) + if response.status_code == 201: + print(f"Branch '{new_branch}' created successfully.") + return new_branch + else: + print("Failed to create the branch.") + return None From a50eebac8005ec7e3191847de2c4d3e002449887 Mon Sep 17 00:00:00 2001 From: Weirui Kuang <39145382+rayrayraykk@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:29:26 +0800 Subject: [PATCH 47/47] Weirui dev1203 (#83) --- src/agentscope/studio/_app_online.py | 5 ++--- src/agentscope/studio/static/js/workstation.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/agentscope/studio/_app_online.py b/src/agentscope/studio/_app_online.py index d5295b6e7..eea1707f1 100644 --- a/src/agentscope/studio/_app_online.py +++ b/src/agentscope/studio/_app_online.py @@ -11,6 +11,7 @@ from typing import Tuple, Any from datetime import timedelta, datetime +import json5 import requests import oss2 from loguru import logger @@ -408,11 +409,9 @@ def create_gallery_pr(**kwargs: Any) -> Response: gallery_data = { "meta": meta_info, - "drawflow": data, + "drawflow": json5.loads(data), } - jwt_token = session.get("jwt_token") - payload = decode_jwt(jwt_token, secret_key=SECRET_KEY) jwt_token = session.get("jwt_token") payload = decode_jwt(jwt_token, secret_key=SECRET_KEY) access_token = payload.get("access_token") diff --git a/src/agentscope/studio/static/js/workstation.js b/src/agentscope/studio/static/js/workstation.js index 4d3b1a12e..9730445d7 100644 --- a/src/agentscope/studio/static/js/workstation.js +++ b/src/agentscope/studio/static/js/workstation.js @@ -2170,7 +2170,7 @@ function showContributePopup(userLogin) { - +