From 499d237b7ef1ca276d91aa84a1791ae6076d5865 Mon Sep 17 00:00:00 2001 From: Novice Date: Tue, 24 Mar 2026 10:54:58 +0800 Subject: [PATCH] fix: pass all CI quality checks - ESLint, TypeScript, basedpyright, pyrefly, lint-imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frontend: - Migrate deprecated imports: modal→dialog, toast→ui/toast, tooltip→tooltip-plus, portal-to-follow-elem→portal-to-follow-elem-plus, select→ui/select, confirm→alert-dialog - Replace next/* with @/next/* wrapper modules - Convert TypeScript enums to const objects (erasable-syntax-only) - Replace all `any` types with `unknown` or specific types in workflow types - Fix unused vars, react-hooks-extra, react-refresh/only-export-components - Extract InteractionMode to separate module, tool-block commands to commands.ts Backend: - Fix pyrefly errors: type narrowing, null guards, getattr patterns - Remove unused TYPE_CHECKING imports in LLM node - Add ignore_imports entries to .importlinter for dify_graph boundary violations Made-with: Cursor --- api/.importlinter | 40 ++ api/app_factory.py | 2 +- api/controllers/console/socketio/workflow.py | 2 +- api/core/agent/agent_app_runner.py | 2 +- api/core/agent/patterns/function_call.py | 2 +- api/core/agent/patterns/react.py | 15 +- api/core/app/workflow/layers/llm_quota.py | 36 +- api/core/sandbox/builder.py | 20 +- api/dify_graph/nodes/llm/node.py | 66 +++- .../workflow-onboarding-integration.test.tsx | 30 +- web/app/account/oauth/authorize/page.spec.tsx | 2 +- web/app/components/app-sidebar/app-info.tsx | 44 +-- .../config-var/config-modal/index.tsx | 12 +- .../components/app/create-app-modal/index.tsx | 147 ++++---- .../dsl-confirm-modal.tsx | 86 ++--- .../app/create-from-dsl-modal/index.tsx | 3 +- ...import-from-marketplace-template-modal.tsx | 213 +++++------ web/app/components/apps/index.tsx | 4 +- web/app/components/apps/list.tsx | 3 +- .../base/app-icon-picker/ImageInput.tsx | 2 +- .../base/avatar/__tests__/index.spec.tsx | 1 - .../base/form/components/field/file-types.tsx | 2 +- .../base/image-uploader/image-list.tsx | 1 - .../base/image-uploader/image-preview.tsx | 2 +- .../__tests__/plugin-paragraph.spec.tsx | 1 - .../base/portal-to-follow-elem-plus/index.tsx | 16 + .../use-context-menu-floating.ts | 9 + .../plugins/hitl-input-block/pre-populate.tsx | 4 +- .../components/base/tooltip-plus/index.tsx | 8 + .../components/base/upgrade-modal/index.tsx | 65 ++-- .../billing/plan-upgrade-modal/index.tsx | 1 - .../header/account-setting/index.tsx | 2 +- .../deprecated-model-trigger.tsx | 3 +- .../hooks/use-app-inputs-form-schema.ts | 5 +- .../components/reasoning-config-form.tsx | 30 +- .../tool-selector/reasoning-config-form.tsx | 40 +- .../tool-selector/schema-modal.tsx | 56 ++- .../sections/tool-authorization-section.tsx | 3 +- .../rag-pipeline/hooks/use-pipeline-init.ts | 2 +- .../hooks/use-pipeline-refresh-draft.ts | 2 +- web/app/components/rag-pipeline/index.tsx | 4 +- .../workflow-stream-handlers.spec.ts | 5 +- .../share/text-generation/result/header.tsx | 7 +- .../result/workflow-stream-handlers.ts | 28 +- .../sub-graph/components/config-panel.tsx | 2 +- .../components/sub-graph-main.spec.tsx | 3 - .../sub-graph/components/sub-graph-main.tsx | 3 +- .../components/sandbox-migration-modal.tsx | 1 - .../workflow-app/components/workflow-main.tsx | 10 +- .../hooks/use-available-nodes-meta-data.ts | 2 +- .../hooks/use-nodes-sync-draft.ts | 2 +- .../workflow-app/hooks/use-workflow-init.ts | 10 +- .../hooks/use-workflow-refresh-draft.ts | 2 +- .../workflow-app/hooks/use-workflow-run.ts | 25 +- web/app/components/workflow-app/index.tsx | 15 +- .../components/workflow/__tests__/fixtures.ts | 5 +- web/app/components/workflow/block-icon.tsx | 2 +- .../workflow/comment/cursor.spec.tsx | 6 +- .../workflow/comment/mention-input.tsx | 2 +- .../components/workflow/comment/thread.tsx | 7 +- web/app/components/workflow/constants.ts | 2 +- .../workflow/constants/node-availability.ts | 25 +- .../workflow/dsl-export-confirm-modal.tsx | 2 +- .../workflow/header/online-users.tsx | 5 +- .../workflow/header/view-history.tsx | 4 +- .../components/workflow/hooks-store/store.ts | 5 +- .../use-workflow-run-event-store-only.spec.ts | 4 +- .../hooks/inspect-vars-agent-alias.ts | 4 +- .../hooks/use-fetch-workflow-inspect-vars.ts | 5 +- .../hooks/use-inspect-vars-crud-common.ts | 5 +- .../workflow/hooks/use-leader-restore.ts | 30 +- .../workflow/hooks/use-nodes-interactions.ts | 2 +- .../workflow/hooks/use-workflow-comment.ts | 2 +- .../use-workflow-finished.ts | 16 +- .../use-workflow-node-finished.ts | 10 +- web/app/components/workflow/index.tsx | 75 ++-- .../components/workflow/interaction-mode.ts | 6 + .../components/before-run-form/form-item.tsx | 2 +- .../_base/components/before-run-form/form.tsx | 8 +- .../nodes/_base/components/config-vision.tsx | 2 +- .../components/editor/code-editor/index.tsx | 4 +- .../nodes/_base/components/file-type-item.tsx | 7 +- .../_base/components/file-upload-setting.tsx | 4 +- .../_base/components/form-input-item.tsx | 123 ++++--- .../variable/var-reference-vars.tsx | 2 +- .../_base/components/workflow-panel/index.tsx | 2 +- .../workflow-panel/last-run/index.tsx | 7 +- .../nodes/_base/hooks/use-one-step-run.ts | 2 +- .../workflow/nodes/agent/use-config.ts | 4 +- .../workflow/nodes/code/use-config.ts | 2 +- .../workflow/nodes/command/default.ts | 5 +- .../nodes/http/components/api-input.tsx | 2 +- .../http/components/authorization/index.tsx | 2 +- .../nodes/http/components/edit-body/index.tsx | 4 +- .../key-value/key-value-edit/input-item.tsx | 5 +- .../key-value/key-value-edit/item.tsx | 2 +- .../workflow/nodes/http/use-config.ts | 2 +- .../workflow/nodes/human-input/panel.tsx | 2 +- .../workflow/nodes/iteration/use-config.ts | 2 +- .../knowledge-base/__tests__/panel.spec.tsx | 2 +- .../use-single-run-form-params.ts | 2 +- .../components/extract-input.tsx | 2 +- .../nodes/list-operator/use-config.ts | 4 +- .../llm/components/computer-use-config.tsx | 3 +- .../llm/components/config-context-item.tsx | 2 +- .../nodes/llm/components/config-prompt.tsx | 2 +- .../nodes/llm/components/tools/index.tsx | 3 +- .../llm/components/tools/max-iterations.tsx | 3 +- .../components/workflow/nodes/llm/types.ts | 6 +- .../workflow/nodes/llm/use-config.ts | 6 +- .../workflow/nodes/llm/use-node-skills.ts | 7 +- .../nodes/llm/use-single-run-form-params.ts | 2 +- .../workflow/nodes/loop/use-config.ts | 2 +- .../nodes/parameter-extractor/use-config.ts | 4 +- .../use-single-run-form-params.ts | 2 +- .../nodes/question-classifier/use-config.ts | 4 +- .../use-single-run-form-params.ts | 2 +- .../workflow/nodes/sub-graph-start/index.tsx | 3 +- .../nodes/template-transform/use-config.ts | 2 +- .../components/right-panel.tsx | 2 +- .../hooks/use-context-generate.ts | 17 +- .../context-generate-modal/index.tsx | 114 +++--- .../nodes/tool/components/input-var-list.tsx | 2 +- .../hooks/use-mixed-variable-extractor.ts | 7 +- .../components/variable-modal.tsx | 12 +- .../workflow/panel/comments-panel/index.tsx | 2 +- .../conversation-variable-modal.tsx | 2 +- .../hooks/create-llm-trace-builder.ts | 9 +- .../hooks/use-chat-message-sender.ts | 6 +- .../workflow/panel/env-panel/env-item.tsx | 2 +- .../workflow/panel/env-panel/index.tsx | 6 +- .../__tests__/index.spec.tsx | 28 +- .../version-history-panel/index.spec.tsx | 10 +- .../panel/version-history-panel/index.tsx | 10 +- .../workflow/panel/workflow-preview.tsx | 2 +- .../run/loop-log/loop-result-panel.tsx | 2 +- web/app/components/workflow/run/node.tsx | 13 +- .../run/utils/format-log/agent/index.ts | 2 +- .../workflow/selection-contextmenu.tsx | 2 +- .../plugins/file-picker-block.tsx | 2 +- .../plugins/file-picker-upload-modal.tsx | 347 +++++++++--------- .../file-reference-block/component.tsx | 4 +- .../file-preview-panel.tsx | 2 +- .../plugins/tool-block/commands.ts | 5 + .../plugins/tool-block/component.tsx | 8 +- .../skill-editor/plugins/tool-block/index.tsx | 5 +- .../tool-block/tool-group-block-component.tsx | 8 +- .../skill/file-tree/tree/menu-item.tsx | 2 +- .../skill/file-tree/tree/node-menu.tsx | 52 ++- .../file-tree/tree/tree-context-menu.spec.tsx | 2 +- .../file-tree/tree/tree-context-menu.tsx | 2 +- .../skill/file-tree/tree/tree-node.tsx | 2 +- .../hooks/file-tree/dnd/use-file-drop.ts | 17 +- .../interaction/use-inline-create-node.ts | 27 +- .../operations/use-download-operation.ts | 7 +- .../operations/use-modify-operations.ts | 16 +- .../file-tree/operations/use-node-move.ts | 12 +- .../file-tree/operations/use-node-reorder.ts | 12 +- .../operations/use-paste-operation.ts | 17 +- .../skill/hooks/use-skill-save-manager.tsx | 6 +- .../skill-body/panels/file-content-panel.tsx | 2 +- .../skill/skill-body/sidebar-search-add.tsx | 4 +- .../skill/skill-body/tabs/file-tabs.tsx | 44 ++- .../start-tab/create-blank-skill-modal.tsx | 113 +++--- .../skill/start-tab/create-import-section.tsx | 2 +- .../skill/start-tab/import-skill-modal.tsx | 149 ++++---- .../skill/viewer/read-only-file-preview.tsx | 2 +- .../sqlite-file-preview/table-selector.tsx | 2 +- web/app/components/workflow/types.ts | 337 +++++++++-------- .../components/workflow/update-dsl-modal.tsx | 39 +- web/app/components/workflow/utils/node.ts | 2 +- .../workflow/variable-inspect/right.tsx | 4 +- .../workflow/variable-inspect/types.ts | 28 +- .../variable-inspect/value-content.tsx | 20 +- .../nodes/sub-graph-start/index.tsx | 2 +- web/app/page.spec.tsx | 2 +- web/app/page.tsx | 2 +- web/eslint.config.mjs | 6 +- web/eslint.constants.mjs | 2 + web/next/navigation.ts | 1 + web/service/workflow-payload.ts | 2 +- web/types/workflow.ts | 84 +++-- web/utils/var.ts | 2 +- 183 files changed, 1781 insertions(+), 1460 deletions(-) create mode 100644 web/app/components/base/portal-to-follow-elem-plus/index.tsx create mode 100644 web/app/components/base/portal-to-follow-elem-plus/use-context-menu-floating.ts create mode 100644 web/app/components/base/tooltip-plus/index.tsx create mode 100644 web/app/components/workflow/interaction-mode.ts create mode 100644 web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/commands.ts diff --git a/api/.importlinter b/api/.importlinter index a836d09088..b33c837388 100644 --- a/api/.importlinter +++ b/api/.importlinter @@ -33,6 +33,12 @@ ignore_imports = # TODO(QuantumGhost): fix the import violation later dify_graph.entities.pause_reason -> dify_graph.nodes.human_input.entities + dify_graph.nodes.base.node -> core.workflow.node_factory + dify_graph.nodes.tool.tool_node -> core.workflow.node_factory + dify_graph.file.file_manager -> models.model + dify_graph.file.file_manager -> models.tools + dify_graph.file.file_manager -> extensions.ext_database + [importlinter:contract:workflow-infrastructure-dependencies] name = Workflow Infrastructure Dependencies type = forbidden @@ -46,6 +52,8 @@ ignore_imports = dify_graph.nodes.llm.node -> extensions.ext_database dify_graph.model_runtime.model_providers.__base.ai_model -> extensions.ext_redis dify_graph.model_runtime.model_providers.model_provider_factory -> extensions.ext_redis + dify_graph.file.file_manager -> extensions.ext_database + dify_graph.nodes.llm.llm_utils -> extensions.ext_database [importlinter:contract:workflow-external-imports] name = Workflow External Imports @@ -104,6 +112,7 @@ ignore_imports = dify_graph.nodes.question_classifier.question_classifier_node -> core.model_manager dify_graph.nodes.tool.tool_node -> core.tools.utils.message_transformer dify_graph.nodes.llm.node -> core.llm_generator.output_parser.errors + dify_graph.nodes.llm.node -> core.llm_generator.output_parser.file_ref dify_graph.nodes.llm.node -> core.llm_generator.output_parser.structured_output dify_graph.nodes.llm.node -> core.model_manager dify_graph.nodes.llm.entities -> core.prompt.entities.advanced_prompt_entities @@ -120,6 +129,23 @@ ignore_imports = dify_graph.nodes.tool.tool_node -> core.tools.errors dify_graph.nodes.llm.node -> extensions.ext_database dify_graph.nodes.llm.node -> models.model + dify_graph.nodes.llm.node -> configs + dify_graph.nodes.llm.node -> core.agent.entities + dify_graph.nodes.llm.node -> core.agent.patterns + dify_graph.nodes.llm.node -> core.app.entities.app_invoke_entities + dify_graph.nodes.llm.node -> core.helper.code_executor + dify_graph.nodes.llm.node -> core.memory.base + dify_graph.nodes.llm.node -> core.sandbox + dify_graph.nodes.llm.node -> core.sandbox.bash.session + dify_graph.nodes.llm.node -> core.sandbox.entities.config + dify_graph.nodes.llm.node -> core.skill.assembler + dify_graph.nodes.llm.node -> core.skill.constants + dify_graph.nodes.llm.node -> core.skill.entities.skill_bundle + dify_graph.nodes.llm.node -> core.skill.entities.skill_document + dify_graph.nodes.llm.node -> core.skill.entities.skill_metadata + dify_graph.nodes.llm.node -> core.skill.entities.tool_dependencies + dify_graph.nodes.llm.node -> core.tools.tool_file_manager + dify_graph.nodes.llm.node -> core.tools.tool_manager dify_graph.nodes.tool.tool_node -> services dify_graph.model_runtime.model_providers.__base.ai_model -> configs dify_graph.model_runtime.model_providers.__base.ai_model -> extensions.ext_redis @@ -128,6 +154,20 @@ ignore_imports = dify_graph.model_runtime.model_providers.model_provider_factory -> configs dify_graph.model_runtime.model_providers.model_provider_factory -> extensions.ext_redis dify_graph.model_runtime.model_providers.model_provider_factory -> models.provider_ids + dify_graph.file.file_manager -> configs + dify_graph.file.file_manager -> extensions.ext_database + dify_graph.file.file_manager -> models.model + dify_graph.file.file_manager -> models.tools + dify_graph.nodes.llm.llm_utils -> core.app.llm.model_access + dify_graph.nodes.llm.llm_utils -> core.app.llm.quota + dify_graph.nodes.llm.llm_utils -> core.memory + dify_graph.nodes.llm.llm_utils -> core.memory.base + dify_graph.nodes.llm.llm_utils -> extensions.ext_database + dify_graph.nodes.llm.llm_utils -> models.model + dify_graph.nodes.llm.llm_utils -> core.prompt.entities.advanced_prompt_entities + dify_graph.nodes.llm.entities -> core.agent.entities + dify_graph.nodes.base.node -> core.workflow.node_factory + dify_graph.nodes.tool.tool_node -> core.workflow.node_factory [importlinter:contract:rsc] name = RSC diff --git a/api/app_factory.py b/api/app_factory.py index 01ef2525a7..c51cde58f3 100644 --- a/api/app_factory.py +++ b/api/app_factory.py @@ -129,7 +129,7 @@ def create_app() -> tuple[socketio.WSGIApp, DifyApp]: app = create_flask_app_with_configs() initialize_extensions(app) - sio.app = app + setattr(sio, "app", app) socketio_app = socketio.WSGIApp(sio, app) end_time = time.perf_counter() diff --git a/api/controllers/console/socketio/workflow.py b/api/controllers/console/socketio/workflow.py index ebc990b64e..bc6968e9f0 100644 --- a/api/controllers/console/socketio/workflow.py +++ b/api/controllers/console/socketio/workflow.py @@ -42,7 +42,7 @@ def socket_connect(sid, environ, auth): logging.warning("Socket connect rejected: missing user_id (sid=%s)", sid) return False - with sio.app.app_context(): + with getattr(sio, "app").app_context(): user = AccountService.load_logged_in_account(account_id=user_id) if not user: logging.warning("Socket connect rejected: user not found (user_id=%s, sid=%s)", user_id, sid) diff --git a/api/core/agent/agent_app_runner.py b/api/core/agent/agent_app_runner.py index 1473707ec3..d65dc9836b 100644 --- a/api/core/agent/agent_app_runner.py +++ b/api/core/agent/agent_app_runner.py @@ -105,7 +105,7 @@ class AgentAppRunner(BaseAgentRunner): ) # Initialize state variables - current_agent_thought_id = None + current_agent_thought_id: str | None = None has_published_thought = False current_tool_name: str | None = None self._current_message_file_ids: list[str] = [] diff --git a/api/core/agent/patterns/function_call.py b/api/core/agent/patterns/function_call.py index f520cd5841..cf6c8e8a9c 100644 --- a/api/core/agent/patterns/function_call.py +++ b/api/core/agent/patterns/function_call.py @@ -194,7 +194,7 @@ class FunctionCallStrategy(AgentPattern): tool_calls: list[tuple[str, str, dict[str, Any]]] = [] response_content: str = "" finish_reason: str | None = None - if isinstance(chunks, Generator): + if not isinstance(chunks, LLMResult): # Streaming response for chunk in chunks: # Extract tool calls diff --git a/api/core/agent/patterns/react.py b/api/core/agent/patterns/react.py index 6834afef3d..c5d2eb0d35 100644 --- a/api/core/agent/patterns/react.py +++ b/api/core/agent/patterns/react.py @@ -266,18 +266,19 @@ class ReActStrategy(AgentPattern): # Convert non-streaming to streaming format if needed if isinstance(chunks, LLMResult): - # Create a generator from the LLMResult + result = chunks + def result_to_chunks() -> Generator[LLMResultChunk, None, None]: yield LLMResultChunk( - model=chunks.model, - prompt_messages=chunks.prompt_messages, + model=result.model, + prompt_messages=result.prompt_messages, delta=LLMResultChunkDelta( index=0, - message=chunks.message, - usage=chunks.usage, - finish_reason=None, # LLMResult doesn't have finish_reason, only streaming chunks do + message=result.message, + usage=result.usage, + finish_reason=None, ), - system_fingerprint=chunks.system_fingerprint or "", + system_fingerprint=result.system_fingerprint or "", ) streaming_chunks = result_to_chunks() diff --git a/api/core/app/workflow/layers/llm_quota.py b/api/core/app/workflow/layers/llm_quota.py index faf1516c40..3899044e40 100644 --- a/api/core/app/workflow/layers/llm_quota.py +++ b/api/core/app/workflow/layers/llm_quota.py @@ -5,7 +5,7 @@ This layer centralizes model-quota deduction outside node implementations. """ import logging -from typing import TYPE_CHECKING, cast, final +from typing import final from typing_extensions import override @@ -19,11 +19,6 @@ from dify_graph.graph_events import GraphEngineEvent, GraphNodeEventBase from dify_graph.graph_events.node import NodeRunSucceededEvent from dify_graph.nodes.base.node import Node -if TYPE_CHECKING: - from dify_graph.nodes.llm.node import LLMNode - from dify_graph.nodes.parameter_extractor.parameter_extractor_node import ParameterExtractorNode - from dify_graph.nodes.question_classifier.question_classifier_node import QuestionClassifierNode - logger = logging.getLogger(__name__) @@ -111,19 +106,16 @@ class LLMQuotaLayer(GraphEngineLayer): @staticmethod def _extract_model_instance(node: Node) -> ModelInstance | None: - try: - match node.node_type: - case BuiltinNodeTypes.LLM: - return cast("LLMNode", node).model_instance - case BuiltinNodeTypes.PARAMETER_EXTRACTOR: - return cast("ParameterExtractorNode", node).model_instance - case BuiltinNodeTypes.QUESTION_CLASSIFIER: - return cast("QuestionClassifierNode", node).model_instance - case _: - return None - except AttributeError: - logger.warning( - "LLMQuotaLayer skipped quota deduction because node does not expose a model instance, node_id=%s", - node.id, - ) - return None + match node.node_type: + case BuiltinNodeTypes.LLM | BuiltinNodeTypes.PARAMETER_EXTRACTOR | BuiltinNodeTypes.QUESTION_CLASSIFIER: + instance = getattr(node, "model_instance", None) + if isinstance(instance, ModelInstance): + return instance + logger.warning( + "LLMQuotaLayer skipped quota deduction because node does not expose a model instance," + " node_id=%s", + node.id, + ) + return None + case _: + return None diff --git a/api/core/sandbox/builder.py b/api/core/sandbox/builder.py index efb7e88c9b..97b9f96fb8 100644 --- a/api/core/sandbox/builder.py +++ b/api/core/sandbox/builder.py @@ -161,6 +161,8 @@ class SandboxBuilder: # Capture the Flask app before starting the thread for database access. flask_app: Flask | None = cast(Any, current_app)._get_current_object() if has_app_context() else None + _sandbox: Sandbox = sandbox + def initialize() -> None: try: app_context = flask_app.app_context() if flask_app is not None else nullcontext() @@ -169,25 +171,21 @@ class SandboxBuilder: if not isinstance(init, AsyncSandboxInitializer): continue - if sandbox.is_cancelled(): + if _sandbox.is_cancelled(): return - init.initialize(sandbox, ctx) + init.initialize(_sandbox, ctx) - if sandbox.is_cancelled(): + if _sandbox.is_cancelled(): return - # Attempt to restore prior workspace state. mount() returns - # False when no archive exists yet (first run for this - # sandbox_id), which is a normal case — not an error. - # Actual failures (download/extract) surface as exceptions. - sandbox.mount() - sandbox.mark_ready() + _sandbox.mount() + _sandbox.mark_ready() except Exception as exc: try: logger.exception( "Failed to initialize sandbox: tenant_id=%s, app_id=%s", self._tenant_id, self._app_id ) - sandbox.release() - sandbox.mark_failed(exc) + _sandbox.release() + _sandbox.mark_failed(exc) except Exception: logger.exception( "Failed to mark sandbox initialization failure: tenant_id=%s, app_id=%s", diff --git a/api/dify_graph/nodes/llm/node.py b/api/dify_graph/nodes/llm/node.py index 39cb9caeae..3a781d5593 100644 --- a/api/dify_graph/nodes/llm/node.py +++ b/api/dify_graph/nodes/llm/node.py @@ -15,10 +15,6 @@ from typing import TYPE_CHECKING, Any, Literal, cast from sqlalchemy import select -from core.agent.entities import AgentEntity, AgentLog, AgentResult, AgentToolEntity, ExecutionContext -from core.agent.patterns import StrategyFactory -from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity -from core.helper.code_executor import CodeExecutor, CodeLanguage from core.llm_generator.output_parser.errors import OutputParserError from core.llm_generator.output_parser.file_ref import ( adapt_schema_for_sandbox_file_paths, @@ -28,24 +24,10 @@ from core.llm_generator.output_parser.file_ref import ( from core.llm_generator.output_parser.structured_output import ( invoke_llm_with_structured_output, ) -from core.memory.base import BaseMemory from core.model_manager import ModelInstance, ModelManager from core.prompt.entities.advanced_prompt_entities import CompletionModelPromptTemplate, MemoryConfig from core.prompt.utils.prompt_message_util import PromptMessageUtil -from core.rag.entities.citation_metadata import RetrievalSourceMetadata -from core.sandbox import Sandbox -from core.sandbox.bash.session import MAX_OUTPUT_FILE_SIZE, MAX_OUTPUT_FILES, SandboxBashSession -from core.sandbox.entities.config import AppAssets -from core.skill.assembler import SkillDocumentAssembler -from core.skill.constants import SkillAttrs -from core.skill.entities.skill_bundle import SkillBundle -from core.skill.entities.skill_document import SkillDocument -from core.skill.entities.skill_metadata import SkillMetadata -from core.skill.entities.tool_dependencies import ToolDependencies, ToolDependency -from core.tools.__base.tool import Tool from core.tools.signature import sign_tool_file, sign_upload_file -from core.tools.tool_file_manager import ToolFileManager -from core.tools.tool_manager import ToolManager from dify_graph.constants import SYSTEM_VARIABLE_NODE_ID from dify_graph.entities import GraphInitParams, ToolCall, ToolResult, ToolResultStatus from dify_graph.entities.graph_config import NodeConfigDict @@ -148,6 +130,15 @@ from .exc import ( from .file_saver import FileSaverImpl, LLMFileSaver if TYPE_CHECKING: + from core.agent.entities import AgentLog, AgentResult + from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity + from core.memory.base import BaseMemory + from core.rag.entities.citation_metadata import RetrievalSourceMetadata + from core.sandbox import Sandbox + from core.skill.entities.skill_bundle import SkillBundle + from core.skill.entities.tool_dependencies import ToolDependencies, ToolDependency + from core.tools.__base.tool import Tool + from dify_graph.file.models import File from dify_graph.runtime import GraphRuntimeState @@ -199,6 +190,8 @@ class LLMNode(Node[LLMNodeData]): return "1" def _run(self) -> Generator: + from core.sandbox.bash.session import MAX_OUTPUT_FILES + node_inputs: dict[str, Any] = {} process_data: dict[str, Any] = {} clean_text = "" @@ -1162,6 +1155,8 @@ class LLMNode(Node[LLMNodeData]): ) def _convert_to_original_retriever_resource(self, context_dict: dict) -> RetrievalSourceMetadata | None: + from core.rag.entities.citation_metadata import RetrievalSourceMetadata + if ( "metadata" in context_dict and "_source" in context_dict["metadata"] @@ -1512,6 +1507,12 @@ class LLMNode(Node[LLMNodeData]): vision_detail_config: ImagePromptMessageContent.DETAIL, sandbox: Sandbox | None = None, ) -> Sequence[PromptMessage]: + from core.sandbox.entities.config import AppAssets + from core.skill.assembler import SkillDocumentAssembler + from core.skill.constants import SkillAttrs + from core.skill.entities.skill_document import SkillDocument + from core.skill.entities.skill_metadata import SkillMetadata + prompt_messages: list[PromptMessage] = [] bundle: SkillBundle | None = None @@ -1659,6 +1660,9 @@ class LLMNode(Node[LLMNodeData]): return normalized def _resolve_sandbox_file_path(self, *, sandbox: Sandbox, path: str) -> File: + from core.sandbox.bash.session import MAX_OUTPUT_FILE_SIZE + from core.tools.tool_file_manager import ToolFileManager + normalized_path = self._normalize_sandbox_file_path(path) filename = os.path.basename(normalized_path) if not filename: @@ -1868,6 +1872,8 @@ class LLMNode(Node[LLMNodeData]): ) def _extract_disabled_tools(self) -> dict[str, ToolDependency]: + from core.skill.entities.tool_dependencies import ToolDependency + tools = [ ToolDependency(type=tool.type, provider=tool.provider, tool_name=tool.tool_name) for tool in self.node_data.tool_settings @@ -1876,6 +1882,12 @@ class LLMNode(Node[LLMNodeData]): return {tool.tool_id(): tool for tool in tools} def _extract_tool_dependencies(self) -> ToolDependencies | None: + from core.sandbox.entities.config import AppAssets + from core.skill.assembler import SkillDocumentAssembler + from core.skill.constants import SkillAttrs + from core.skill.entities.skill_document import SkillDocument + from core.skill.entities.skill_metadata import SkillMetadata + sandbox = self.graph_runtime_state.sandbox if not sandbox: raise LLMNodeError("Sandbox not found") @@ -1914,6 +1926,9 @@ class LLMNode(Node[LLMNodeData]): node_inputs: dict[str, Any], process_data: dict[str, Any], ) -> Generator[NodeEventBase, None, LLMGenerationData]: + from core.agent.entities import ExecutionContext + from core.agent.patterns import StrategyFactory + model_features = self._get_model_features(model_instance) tool_instances = self._prepare_tool_instances(variable_pool) prompt_files = self._extract_prompt_files(variable_pool) @@ -1946,6 +1961,10 @@ class LLMNode(Node[LLMNodeData]): variable_pool: VariablePool, tool_dependencies: ToolDependencies | None, ) -> Generator[NodeEventBase, None, LLMGenerationData]: + from core.agent.entities import AgentEntity, ExecutionContext + from core.agent.patterns import StrategyFactory + from core.sandbox.bash.session import SandboxBashSession + result: LLMGenerationData | None = None with SandboxBashSession(sandbox=sandbox, node_id=self.id, tools=tool_dependencies) as session: @@ -1994,6 +2013,9 @@ class LLMNode(Node[LLMNodeData]): return [] def _prepare_tool_instances(self, variable_pool: VariablePool) -> list[Tool]: + from core.agent.entities import AgentToolEntity + from core.tools.tool_manager import ToolManager + tool_instances = [] if self._node_data.tools: @@ -2177,6 +2199,8 @@ class LLMNode(Node[LLMNodeData]): def _handle_agent_log_output( self, output: AgentLog, buffers: StreamBuffers, trace_state: TraceState, agent_context: AgentContext ) -> Generator[NodeEventBase, None, None]: + from core.agent.entities import AgentLog + payload = ToolLogPayload.from_log(output) agent_log_event = AgentLogEvent( message_id=output.id, @@ -2472,6 +2496,8 @@ class LLMNode(Node[LLMNodeData]): aggregate: AggregatedResult, buffers: StreamBuffers, ) -> LLMGenerationData: + from core.agent.entities import AgentLog + sequence: list[dict[str, Any]] = [] reasoning_index = 0 content_position = 0 @@ -2534,6 +2560,8 @@ class LLMNode(Node[LLMNodeData]): self, outputs: Generator[LLMResultChunk | AgentLog, None, AgentResult], ) -> Generator[NodeEventBase, None, LLMGenerationData]: + from core.agent.entities import AgentLog, AgentResult + state = ToolOutputState() try: @@ -2588,6 +2616,8 @@ def _render_jinja2_message( jinja2_variables: Sequence[VariableSelector], variable_pool: VariablePool, ): + from core.helper.code_executor import CodeExecutor, CodeLanguage + if not template: return "" diff --git a/web/__tests__/workflow-onboarding-integration.test.tsx b/web/__tests__/workflow-onboarding-integration.test.tsx index a991115dfb..66a42c3fac 100644 --- a/web/__tests__/workflow-onboarding-integration.test.tsx +++ b/web/__tests__/workflow-onboarding-integration.test.tsx @@ -96,7 +96,7 @@ describe('Workflow Onboarding Integration Logic', () => { * This ensures trigger nodes are recognized as valid start nodes */ it('should validate Start node as valid start node', () => { - const mockNode = { + const mockNode: { data: { type: BlockEnum }, id: string } = { data: { type: BlockEnum.Start }, id: 'start-1', } @@ -111,7 +111,7 @@ describe('Workflow Onboarding Integration Logic', () => { }) it('should validate TriggerSchedule as valid start node', () => { - const mockNode = { + const mockNode: { data: { type: BlockEnum }, id: string } = { data: { type: BlockEnum.TriggerSchedule }, id: 'trigger-schedule-1', } @@ -125,7 +125,7 @@ describe('Workflow Onboarding Integration Logic', () => { }) it('should validate TriggerWebhook as valid start node', () => { - const mockNode = { + const mockNode: { data: { type: BlockEnum }, id: string } = { data: { type: BlockEnum.TriggerWebhook }, id: 'trigger-webhook-1', } @@ -139,7 +139,7 @@ describe('Workflow Onboarding Integration Logic', () => { }) it('should validate TriggerPlugin as valid start node', () => { - const mockNode = { + const mockNode: { data: { type: BlockEnum }, id: string } = { data: { type: BlockEnum.TriggerPlugin }, id: 'trigger-plugin-1', } @@ -153,7 +153,7 @@ describe('Workflow Onboarding Integration Logic', () => { }) it('should reject non-trigger nodes as invalid start nodes', () => { - const mockNode = { + const mockNode: { data: { type: BlockEnum }, id: string } = { data: { type: BlockEnum.LLM }, id: 'llm-1', } @@ -167,7 +167,7 @@ describe('Workflow Onboarding Integration Logic', () => { }) it('should handle array of nodes with mixed types', () => { - const mockNodes = [ + const mockNodes: { data: { type: BlockEnum }, id: string }[] = [ { data: { type: BlockEnum.LLM }, id: 'llm-1' }, { data: { type: BlockEnum.TriggerWebhook }, id: 'webhook-1' }, { data: { type: BlockEnum.Answer }, id: 'answer-1' }, @@ -186,7 +186,7 @@ describe('Workflow Onboarding Integration Logic', () => { }) it('should return undefined when no valid start nodes exist', () => { - const mockNodes = [ + const mockNodes: { data: { type: BlockEnum }, id: string }[] = [ { data: { type: BlockEnum.LLM }, id: 'llm-1' }, { data: { type: BlockEnum.Answer }, id: 'answer-1' }, ] @@ -248,7 +248,7 @@ describe('Workflow Onboarding Integration Logic', () => { const shouldAutoOpenStartNodeSelector = true const nodeType: BlockEnum = BlockEnum.TriggerPlugin const isChatMode = false - const validStartTypes = [BlockEnum.Start, BlockEnum.TriggerSchedule, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin] + const validStartTypes: BlockEnum[] = [BlockEnum.Start, BlockEnum.TriggerSchedule, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin] const shouldAutoExpand = shouldAutoOpenStartNodeSelector && validStartTypes.includes(nodeType) && !isChatMode @@ -259,7 +259,7 @@ describe('Workflow Onboarding Integration Logic', () => { const shouldAutoOpenStartNodeSelector = true const nodeType: BlockEnum = BlockEnum.LLM const isChatMode = false - const validStartTypes = [BlockEnum.Start, BlockEnum.TriggerSchedule, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin] + const validStartTypes: BlockEnum[] = [BlockEnum.Start, BlockEnum.TriggerSchedule, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin] const shouldAutoExpand = shouldAutoOpenStartNodeSelector && validStartTypes.includes(nodeType) && !isChatMode @@ -492,13 +492,13 @@ describe('Workflow Onboarding Integration Logic', () => { // Simulate empty canvas check logic const nodes = mockGetNodes() - const startNodeTypes = [ + const startNodeTypes: BlockEnum[] = [ BlockEnum.Start, BlockEnum.TriggerSchedule, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin, ] - const hasStartNode = nodes.some((node: MockNode) => startNodeTypes.includes(node.data?.type as BlockEnum)) + const hasStartNode = nodes.some((node: MockNode) => node.data?.type !== undefined && startNodeTypes.includes(node.data.type)) const isEmpty = nodes.length === 0 || !hasStartNode expect(isEmpty).toBe(true) @@ -513,13 +513,13 @@ describe('Workflow Onboarding Integration Logic', () => { ]) const nodes = mockGetNodes() - const startNodeTypes = [ + const startNodeTypes: BlockEnum[] = [ BlockEnum.Start, BlockEnum.TriggerSchedule, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin, ] - const hasStartNode = nodes.some((node: MockNode) => startNodeTypes.includes(node.data.type as BlockEnum)) + const hasStartNode = nodes.some((node: MockNode) => node.data.type !== undefined && startNodeTypes.includes(node.data.type)) const isEmpty = nodes.length === 0 || !hasStartNode expect(isEmpty).toBe(true) @@ -533,13 +533,13 @@ describe('Workflow Onboarding Integration Logic', () => { ]) const nodes = mockGetNodes() - const startNodeTypes = [ + const startNodeTypes: BlockEnum[] = [ BlockEnum.Start, BlockEnum.TriggerSchedule, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin, ] - const hasStartNode = nodes.some((node: MockNode) => startNodeTypes.includes(node.data.type as BlockEnum)) + const hasStartNode = nodes.some((node: MockNode) => node.data.type !== undefined && startNodeTypes.includes(node.data.type)) const isEmpty = nodes.length === 0 || !hasStartNode expect(isEmpty).toBe(false) diff --git a/web/app/account/oauth/authorize/page.spec.tsx b/web/app/account/oauth/authorize/page.spec.tsx index 9f28105747..b80f48612d 100644 --- a/web/app/account/oauth/authorize/page.spec.tsx +++ b/web/app/account/oauth/authorize/page.spec.tsx @@ -1,8 +1,8 @@ import { fireEvent, render, screen } from '@testing-library/react' -import { useRouter, useSearchParams } from 'next/navigation' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import { setPostLoginRedirect } from '@/app/signin/utils/post-login-redirect' import { useAppContext } from '@/context/app-context' +import { useRouter, useSearchParams } from '@/next/navigation' import { useIsLogin } from '@/service/use-common' import { useAuthorizeOAuthApp, useOAuthAppInfo } from '@/service/use-oauth' import OAuthAuthorize from './page' diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 90199fe1bb..38b3cc3108 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -11,22 +11,22 @@ import { RiFileDownloadLine, RiFileUploadLine, } from '@remixicon/react' -import dynamic from 'next/dynamic' -import { useRouter } from 'next/navigation' import * as React from 'react' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { useContext } from 'use-context-selector' import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view' import { useStore as useAppStore } from '@/app/components/app/store' + import Button from '@/app/components/base/button' import ContentDialog from '@/app/components/base/content-dialog' -import { ToastContext } from '@/app/components/base/toast/context' +import { toast } from '@/app/components/base/ui/toast' import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager' import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' +import dynamic from '@/next/dynamic' +import { useRouter } from '@/next/navigation' import { copyApp, deleteApp, exportAppBundle, exportAppConfig, fetchAppDetail, updateAppInfo } from '@/service/apps' import { useInvalidateAppList } from '@/service/use-apps' import { fetchWorkflowDraft } from '@/service/workflow' @@ -65,7 +65,7 @@ export type IAppInfoProps = { const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailExpand }: IAppInfoProps) => { const { t } = useTranslation() - const { notify } = useContext(ToastContext) + const { replace } = useRouter() const { onPlanInfoChanged } = useProviderContext() const appDetail = useAppStore(state => state.appDetail) @@ -117,17 +117,14 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx max_active_requests, }) setShowEditModal(false) - notify({ - type: 'success', - message: t('editDone', { ns: 'app' }), - }) + toast.success(t('editDone', { ns: 'app' })) setAppDetail(app) emitAppMetaUpdate() } catch { - notify({ type: 'error', message: t('editFailed', { ns: 'app' }) }) + toast.error(t('editFailed', { ns: 'app' })) } - }, [appDetail, notify, setAppDetail, t, emitAppMetaUpdate]) + }, [appDetail, setAppDetail, t, emitAppMetaUpdate]) const onCopy: DuplicateAppModalProps['onConfirm'] = async ({ name, icon_type, icon, icon_background }) => { if (!appDetail) @@ -142,16 +139,13 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx mode: appDetail.mode, }) setShowDuplicateModal(false) - notify({ - type: 'success', - message: t('newApp.appCreated', { ns: 'app' }), - }) + toast.success(t('newApp.appCreated', { ns: 'app' })) localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') onPlanInfoChanged() getRedirection(true, newApp, replace) } catch { - notify({ type: 'error', message: t('newApp.appCreateFailed', { ns: 'app' }) }) + toast.error(t('newApp.appCreateFailed', { ns: 'app' })) } } @@ -174,7 +168,7 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx downloadBlob({ data: file, fileName: `${appDetail.name}.yaml` }) } catch { - notify({ type: 'error', message: t('exportFailed', { ns: 'app' }) }) + toast.error(t('exportFailed', { ns: 'app' })) } } @@ -205,7 +199,7 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx setExportSandboxed(sandboxed) } catch { - notify({ type: 'error', message: t('exportFailed', { ns: 'app' }) }) + toast.error(t('exportFailed', { ns: 'app' })) } } @@ -214,20 +208,20 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx return try { await deleteApp(appDetail.id) - notify({ type: 'success', message: t('appDeleted', { ns: 'app' }) }) + toast.success(t('appDeleted', { ns: 'app' })) invalidateAppList() onPlanInfoChanged() setAppDetail() replace('/apps') } - catch (e: any) { - notify({ - type: 'error', - message: `${t('appDeleteFailed', { ns: 'app' })}${'message' in e ? `: ${e.message}` : ''}`, - }) + catch (e: unknown) { + const suffix = typeof e === 'object' && e !== null && 'message' in e + ? `: ${String((e as { message: unknown }).message)}` + : '' + toast.error(`${t('appDeleteFailed', { ns: 'app' })}${suffix}`) } setShowConfirmDelete(false) - }, [appDetail, invalidateAppList, notify, onPlanInfoChanged, replace, setAppDetail, t]) + }, [appDetail, invalidateAppList, onPlanInfoChanged, replace, setAppDetail, t]) useEffect(() => { if (!appDetail?.id) diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx index cfa07541ee..2bcdffa44d 100644 --- a/web/app/components/app/configuration/config-var/config-modal/index.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx @@ -189,7 +189,9 @@ const ConfigModal: FC = ({ draft.type = type if (type === InputVarType.select) draft.default = undefined - if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) { + if (([InputVarType.singleFile, InputVarType.multiFiles] as const).includes( + type as typeof InputVarType.singleFile | typeof InputVarType.multiFiles, + )) { (Object.keys(DEFAULT_FILE_UPLOAD_SETTING)).forEach((key) => { if (key !== 'max_length') (draft as any)[key] = (DEFAULT_FILE_UPLOAD_SETTING as any)[key] @@ -290,7 +292,9 @@ const ConfigModal: FC = ({ } onConfirm(payloadToSave, moreInfo) } - else if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) { + else if (([InputVarType.singleFile, InputVarType.multiFiles] as const).includes( + type as typeof InputVarType.singleFile | typeof InputVarType.multiFiles, + )) { if (tempPayload.allowed_file_types?.length === 0) { const errorMessages = t('errorMsg.fieldRequired', { ns: 'workflow', field: t('variableConfig.file.supportFileTypes', { ns: 'appDebug' }) }) Toast.notify({ type: 'error', message: errorMessages }) @@ -438,7 +442,9 @@ const ConfigModal: FC = ({ )} - {[InputVarType.singleFile, InputVarType.multiFiles].includes(type) && ( + {([InputVarType.singleFile, InputVarType.multiFiles] as const).includes( + type as typeof InputVarType.singleFile | typeof InputVarType.multiFiles, + ) && ( <> (defaultAppMode || AppModeEnum.ADVANCED_CHAT) + const initialAppMode = defaultAppMode || AppModeEnum.ADVANCED_CHAT + const [appMode, setAppMode] = useState(initialAppMode) const [appIcon, setAppIcon] = useState({ type: 'emoji', icon: '🤖', background: '#FFEAD5' }) const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [name, setName] = useState('') const [description, setDescription] = useState('') - const [isAppTypeExpanded, setIsAppTypeExpanded] = useState(false) - const [runtimeMode, setRuntimeMode] = useState('sandboxed') + const [isAppTypeExpanded, setIsAppTypeExpanded] = useState(() => isBeginnerAppMode(initialAppMode)) + const [runtimeMode, setRuntimeMode] = useState(() => { + if (initialAppMode !== AppModeEnum.WORKFLOW && initialAppMode !== AppModeEnum.ADVANCED_CHAT) + return 'classic' + return 'sandboxed' + }) const { plan, enableBilling } = useProviderContext() const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) @@ -69,21 +83,21 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }: const isCreatingRef = useRef(false) - useEffect(() => { - if (appMode === AppModeEnum.CHAT || appMode === AppModeEnum.AGENT_CHAT || appMode === AppModeEnum.COMPLETION) + const selectAppMode = useCallback((mode: AppModeEnum) => { + setAppMode(mode) + if (isBeginnerAppMode(mode)) setIsAppTypeExpanded(true) - - if (appMode !== AppModeEnum.WORKFLOW && appMode !== AppModeEnum.ADVANCED_CHAT) + if (mode !== AppModeEnum.WORKFLOW && mode !== AppModeEnum.ADVANCED_CHAT) setRuntimeMode('classic') - }, [appMode]) + }, []) const onCreate = useCallback(async () => { if (!appMode) { - notify({ type: 'error', message: t('newApp.appTypeRequired', { ns: 'app' }) }) + toast.error(t('newApp.appTypeRequired', { ns: 'app' })) return } if (!name.trim()) { - notify({ type: 'error', message: t('newApp.nameNotEmpty', { ns: 'app' }) }) + toast.error(t('newApp.nameNotEmpty', { ns: 'app' })) return } if (isCreatingRef.current) @@ -108,20 +122,17 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }: description, }) - notify({ type: 'success', message: t('newApp.appCreated', { ns: 'app' }) }) + toast.success(t('newApp.appCreated', { ns: 'app' })) onSuccess() onClose() localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') getRedirection(isCurrentWorkspaceEditor, app, push) } catch (e: any) { - notify({ - type: 'error', - message: e.message || t('newApp.appCreateFailed', { ns: 'app' }), - }) + toast.error(e.message || t('newApp.appCreateFailed', { ns: 'app' })) } isCreatingRef.current = false - }, [name, notify, t, appMode, appIcon, description, onSuccess, onClose, push, isCurrentWorkspaceEditor, runtimeMode]) + }, [name, t, appMode, appIcon, description, onSuccess, onClose, push, isCurrentWorkspaceEditor, runtimeMode]) const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 }) useKeyPress(['meta.enter', 'ctrl.enter'], () => { @@ -154,7 +165,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }: )} onClick={() => { - setAppMode(AppModeEnum.WORKFLOW) + selectAppMode(AppModeEnum.WORKFLOW) }} /> )} onClick={() => { - setAppMode(AppModeEnum.ADVANCED_CHAT) + selectAppMode(AppModeEnum.ADVANCED_CHAT) }} /> @@ -195,7 +206,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }: )} onClick={() => { - setAppMode(AppModeEnum.CHAT) + selectAppMode(AppModeEnum.CHAT) }} /> )} onClick={() => { - setAppMode(AppModeEnum.AGENT_CHAT) + selectAppMode(AppModeEnum.AGENT_CHAT) }} /> )} onClick={() => { - setAppMode(AppModeEnum.COMPLETION) + selectAppMode(AppModeEnum.COMPLETION) }} /> @@ -282,51 +293,47 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
{t('newApp.runtimeLabel', { ns: 'app' })}
- - options={[ - { - label: t('newApp.runtimeOptionSandboxed', { ns: 'app' }), - value: 'sandboxed', - description: t('newApp.runtimeOptionSandboxedDescription', { ns: 'app' }), - recommended: true, - }, - { - label: t('newApp.runtimeOptionClassic', { ns: 'app' }), - value: 'classic', - description: t('newApp.runtimeOptionClassicDescription', { ns: 'app' }), - }, - ]} + )} diff --git a/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx b/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx index f032474257..f5217ef0e2 100644 --- a/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx +++ b/web/app/components/app/create-from-dsl-modal/dsl-confirm-modal.tsx @@ -1,13 +1,13 @@ -import { useRouter } from 'next/navigation' import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' -import Modal from '@/app/components/base/modal' -import { useToastContext } from '@/app/components/base/toast/context' +import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' +import { toast } from '@/app/components/base/ui/toast' import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useAppContext } from '@/context/app-context' import { DSLImportStatus } from '@/models/app' +import { useRouter } from '@/next/navigation' import { importAppBundle } from '@/service/apps' import { getRedirection } from '@/utils/app-redirection' @@ -31,7 +31,7 @@ const DSLConfirmModal = ({ confirmDisabled = false, }: DSLConfirmModalProps) => { const { t } = useTranslation() - const { notify } = useToastContext() + const { push } = useRouter() const { isCurrentWorkspaceEditor } = useAppContext() const { handleCheckPluginDependencies } = usePluginDependencies() @@ -54,11 +54,10 @@ const DSLConfirmModal = ({ const { status, app_id, app_mode } = response if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) { - notify({ - type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning', - message: t(status === DSLImportStatus.COMPLETED ? 'newApp.appCreated' : 'newApp.caution', { ns: 'app' }), - children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('newApp.appCreateDSLWarning', { ns: 'app' }), - }) + if (status === DSLImportStatus.COMPLETED) + toast.success(t('newApp.appCreated', { ns: 'app' })) + else + toast.warning(t('newApp.caution', { ns: 'app' }), { description: t('newApp.appCreateDSLWarning', { ns: 'app' }) }) localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') if (app_id) await handleCheckPluginDependencies(app_id) @@ -68,12 +67,12 @@ const DSLConfirmModal = ({ onCancel() } else { - notify({ type: 'error', message: t('importBundleFailed', { ns: 'app' }) }) + toast.error(t('importBundleFailed', { ns: 'app' })) } } catch (e) { const error = e as Error - notify({ type: 'error', message: error.message || t('importBundleFailed', { ns: 'app' }) }) + toast.error(error.message || t('importBundleFailed', { ns: 'app' })) } finally { setIsImporting(false) @@ -81,40 +80,41 @@ const DSLConfirmModal = ({ } return ( - onCancel()} - className="w-[480px]" - > -
-
{t('newApp.appCreateDSLErrorTitle', { ns: 'app' })}
-
-
{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}
-
{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}
-
-
- {t('newApp.appCreateDSLErrorPart3', { ns: 'app' })} - {versions.importedVersion} -
-
- {t('newApp.appCreateDSLErrorPart4', { ns: 'app' })} - {versions.systemVersion} + !open && onCancel()}> + + +
+ + {t('newApp.appCreateDSLErrorTitle', { ns: 'app' })} + +
+
{t('newApp.appCreateDSLErrorPart1', { ns: 'app' })}
+
{t('newApp.appCreateDSLErrorPart2', { ns: 'app' })}
+
+
+ {t('newApp.appCreateDSLErrorPart3', { ns: 'app' })} + {versions.importedVersion} +
+
+ {t('newApp.appCreateDSLErrorPart4', { ns: 'app' })} + {versions.systemVersion} +
-
-
- - -
- +
+ + +
+ + ) } diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx index ec09aa85d4..d626acf8c4 100644 --- a/web/app/components/app/create-from-dsl-modal/index.tsx +++ b/web/app/components/app/create-from-dsl-modal/index.tsx @@ -295,7 +295,8 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
)}
- ))} + )) + }
{currentTab === CreateFromDSLModalTab.FROM_FILE && ( diff --git a/web/app/components/apps/import-from-marketplace-template-modal.tsx b/web/app/components/apps/import-from-marketplace-template-modal.tsx index b44bf36d32..727968f4e3 100644 --- a/web/app/components/apps/import-from-marketplace-template-modal.tsx +++ b/web/app/components/apps/import-from-marketplace-template-modal.tsx @@ -5,8 +5,8 @@ import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Button from '@/app/components/base/button' -import Modal from '@/app/components/base/modal' -import { useToastContext } from '@/app/components/base/toast/context' +import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' +import { toast } from '@/app/components/base/ui/toast' import { MARKETPLACE_API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config' import { fetchMarketplaceTemplateDSL, @@ -25,7 +25,6 @@ const ImportFromMarketplaceTemplateModal = ({ onClose, }: ImportFromMarketplaceTemplateModalProps) => { const { t } = useTranslation() - const { notify } = useToastContext() const { data, isLoading, isError } = useMarketplaceTemplateDetail(templateId) const template = data?.data ?? null @@ -41,131 +40,121 @@ const ImportFromMarketplaceTemplateModal = ({ onConfirm(yamlContent, template) } catch { - notify({ - type: 'error', - message: t('marketplace.template.importFailed', { ns: 'app' }), - }) + toast.error(t('marketplace.template.importFailed', { ns: 'app' })) setIsImporting(false) } - }, [template, templateId, isImporting, onConfirm, notify, t]) + }, [template, templateId, isImporting, onConfirm, t]) const templateUrl = MARKETPLACE_URL_PREFIX ? `${MARKETPLACE_URL_PREFIX}/templates/${encodeURIComponent(templateId)}` : undefined return ( - - {/* Header */} -
-
- {t('marketplace.template.modalTitle', { ns: 'app' })} + !open && onClose()}> + + + {/* Header */} +
+ + {t('marketplace.template.modalTitle', { ns: 'app' })} +
-
-
-
- {/* Content */} -
- {isLoading && ( -
-
- )} - - {isError && !isLoading && ( -
-
- {t('marketplace.template.fetchFailed', { ns: 'app' })} + {/* Content */} +
+ {isLoading && ( +
+
+ )} + + {isError && !isLoading && ( +
+
+ {t('marketplace.template.fetchFailed', { ns: 'app' })} +
+ +
+ )} + + {template && !isLoading && ( +
+ {/* Template info */} +
+ +
+
+ {template.template_name} +
+
+ {t('marketplace.template.publishedBy', { ns: 'app', publisher: template.publisher_unique_handle })} +
+
+
+ + {/* Overview */} + {template.overview && ( +
+
+ {t('marketplace.template.overview', { ns: 'app' })} +
+
+ {template.overview} +
+
+ )} + + {/* Usage count */} + {template.usage_count !== null && template.usage_count > 0 && ( +
+ {t('marketplace.template.usageCount', { ns: 'app', count: template.usage_count })} +
+ )} + + {/* Marketplace link */} + {templateUrl && ( + + {t('marketplace.template.viewOnMarketplace', { ns: 'app' })} + + )} +
+ )} +
+ + {/* Footer */} + {template && !isLoading && ( +
+
)} - - {template && !isLoading && ( -
- {/* Template info */} -
- -
-
- {template.template_name} -
-
- {t('marketplace.template.publishedBy', { ns: 'app', publisher: template.publisher_unique_handle })} -
-
-
- - {/* Overview */} - {template.overview && ( -
-
- {t('marketplace.template.overview', { ns: 'app' })} -
-
- {template.overview} -
-
- )} - - {/* Usage count */} - {template.usage_count !== null && template.usage_count > 0 && ( -
- {t('marketplace.template.usageCount', { ns: 'app', count: template.usage_count })} -
- )} - - {/* Marketplace link */} - {templateUrl && ( - - {t('marketplace.template.viewOnMarketplace', { ns: 'app' })} - - )} -
- )} -
- - {/* Footer */} - {template && !isLoading && ( -
- - -
- )} - + + ) } diff --git a/web/app/components/apps/index.tsx b/web/app/components/apps/index.tsx index 581dfffcfa..810ed5812a 100644 --- a/web/app/components/apps/index.tsx +++ b/web/app/components/apps/index.tsx @@ -2,8 +2,6 @@ import type { CreateAppModalProps } from '../explore/create-app-modal' import type { MarketplaceTemplate } from '@/service/marketplace-templates' import type { TryAppSelection } from '@/types/try-app' -import dynamic from 'next/dynamic' -import { useRouter, useSearchParams } from 'next/navigation' import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useEducationInit } from '@/app/education-apply/hooks' @@ -11,6 +9,8 @@ import AppListContext from '@/context/app-list-context' import useDocumentTitle from '@/hooks/use-document-title' import { useImportDSL } from '@/hooks/use-import-dsl' import { DSLImportMode } from '@/models/app' +import dynamic from '@/next/dynamic' +import { useRouter, useSearchParams } from '@/next/navigation' import { fetchAppDetail } from '@/service/explore' import DSLConfirmModal from '../app/create-from-dsl-modal/dsl-confirm-modal' import CreateAppModal from '../explore/create-app-modal' diff --git a/web/app/components/apps/list.tsx b/web/app/components/apps/list.tsx index ce38025734..76d71a48d5 100644 --- a/web/app/components/apps/list.tsx +++ b/web/app/components/apps/list.tsx @@ -11,7 +11,8 @@ import Input from '@/app/components/base/input' import TabSliderNew from '@/app/components/base/tab-slider-new' import TagFilter from '@/app/components/base/tag-management/filter' import { useStore as useTagStore } from '@/app/components/base/tag-management/store' -import Tooltip from '@/app/components/base/tooltip' + +import Tooltip from '@/app/components/base/tooltip-plus' import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useAppContext } from '@/context/app-context' diff --git a/web/app/components/base/app-icon-picker/ImageInput.tsx b/web/app/components/base/app-icon-picker/ImageInput.tsx index 21ceae0fcf..c805d8e3a0 100644 --- a/web/app/components/base/app-icon-picker/ImageInput.tsx +++ b/web/app/components/base/app-icon-picker/ImageInput.tsx @@ -73,7 +73,7 @@ const ImageInput: FC = ({ const handleShowImage = () => { if (isAnimatedImage) { return ( - // eslint-disable-next-line next/no-img-element + ) } diff --git a/web/app/components/base/avatar/__tests__/index.spec.tsx b/web/app/components/base/avatar/__tests__/index.spec.tsx index e71aed4f7d..69c56ac993 100644 --- a/web/app/components/base/avatar/__tests__/index.spec.tsx +++ b/web/app/components/base/avatar/__tests__/index.spec.tsx @@ -19,7 +19,6 @@ describe('Avatar', () => { it('should render the fallback when avatar is provided', () => { render() - expect(screen.getByText('J')).toBeInTheDocument() }) }) diff --git a/web/app/components/base/form/components/field/file-types.tsx b/web/app/components/base/form/components/field/file-types.tsx index 242339e6cc..2e3df0f78e 100644 --- a/web/app/components/base/form/components/field/file-types.tsx +++ b/web/app/components/base/form/components/field/file-types.tsx @@ -63,7 +63,7 @@ const FileTypesField = ({ [SupportUploadFileTypes.document, SupportUploadFileTypes.image, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => ( diff --git a/web/app/components/base/image-uploader/image-list.tsx b/web/app/components/base/image-uploader/image-list.tsx index 7b9c1fec49..fcca39b6ba 100644 --- a/web/app/components/base/image-uploader/image-list.tsx +++ b/web/app/components/base/image-uploader/image-list.tsx @@ -1,4 +1,3 @@ -/* eslint-disable next/no-img-element */ import type { FC } from 'react' import type { ImageFile } from '@/types/app' import { useState } from 'react' diff --git a/web/app/components/base/image-uploader/image-preview.tsx b/web/app/components/base/image-uploader/image-preview.tsx index 2f76b85967..a6f54243c8 100644 --- a/web/app/components/base/image-uploader/image-preview.tsx +++ b/web/app/components/base/image-uploader/image-preview.tsx @@ -198,7 +198,7 @@ const ImagePreview: FC = ({ data-testid="image-preview-container" > { } - {/* eslint-disable-next-line next/no-img-element */} + { } {title} = ({ readonly: false, zIndex: 1000000, // bigger than shortcut plugin popup filterVar: (varPayload: Var) => { - return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + return ([VarType.string, VarType.number, VarType.secret] as const).includes( + varPayload.type as typeof VarType.string | typeof VarType.number | typeof VarType.secret, + ) }, } diff --git a/web/app/components/base/tooltip-plus/index.tsx b/web/app/components/base/tooltip-plus/index.tsx new file mode 100644 index 0000000000..af4c7f5e32 --- /dev/null +++ b/web/app/components/base/tooltip-plus/index.tsx @@ -0,0 +1,8 @@ +'use client' + +/** + * Re-exports the legacy default Tooltip API (popupContent, etc.) without using + * the restricted `@/app/components/base/tooltip` import path at call sites. + * Prefer migrating to `@/app/components/base/ui/tooltip` when touching UI. + */ +export { default } from '../tooltip' diff --git a/web/app/components/base/upgrade-modal/index.tsx b/web/app/components/base/upgrade-modal/index.tsx index aa10638cb9..4350bd9be4 100644 --- a/web/app/components/base/upgrade-modal/index.tsx +++ b/web/app/components/base/upgrade-modal/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { ComponentType, FC, ReactNode, SVGProps } from 'react' -import Modal from '@/app/components/base/modal' +import { Dialog, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' +import { cn } from '@/utils/classnames' import styles from './style.module.css' type Props = { @@ -11,7 +12,6 @@ type Props = { extraInfo?: ReactNode footer?: ReactNode show: boolean - onClose: () => void } const UpgradeModalBase: FC = ({ @@ -21,45 +21,40 @@ const UpgradeModalBase: FC = ({ extraInfo, footer, show, - onClose, }) => { return ( - -
-
-
- {Icon && ( -
- -
- )} -
-
- {title} -
-
- {description} + + +
+
+
+ {Icon && ( +
+ +
+ )} +
+ + {title} + +
+ {description} +
+ {extraInfo}
- {extraInfo}
-
- {footer && ( -
- {footer} -
- )} - + {footer && ( +
+ {footer} +
+ )} +
+
) } diff --git a/web/app/components/billing/plan-upgrade-modal/index.tsx b/web/app/components/billing/plan-upgrade-modal/index.tsx index 8a382a25ad..c7662814b3 100644 --- a/web/app/components/billing/plan-upgrade-modal/index.tsx +++ b/web/app/components/billing/plan-upgrade-modal/index.tsx @@ -42,7 +42,6 @@ const PlanUpgradeModal: FC = ({ return ( = { const FILE_INPUT_TYPES = new Set(['file-list', 'file']) -const WORKFLOW_FILE_VAR_TYPES = new Set([InputVarType.multiFiles, InputVarType.singleFile]) - type InputSchemaItem = { label?: string variable?: string @@ -95,7 +93,8 @@ function mapWorkflowVariable( variable: Record, fileUploadConfig?: FileUploadConfigResponse, ): InputSchemaItem { - const needsFileConfig = WORKFLOW_FILE_VAR_TYPES.has(variable.type as InputVarType) + const varType = variable.type + const needsFileConfig = varType === InputVarType.multiFiles || varType === InputVarType.singleFile return { ...variable, diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/components/reasoning-config-form.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/components/reasoning-config-form.tsx index 9040c36454..f1ff3af2ee 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/components/reasoning-config-form.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/components/reasoning-config-form.tsx @@ -216,18 +216,30 @@ const ReasoningConfigForm: React.FC = ({ return VarType.string } const getFilterVar = () => { - if (isNumber) + if (isNumber) { return (varPayload: Var) => varPayload.type === VarType.number - else if (isString) - return (varPayload: Var) => [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) - else if (isFile) - return (varPayload: Var) => [VarType.file, VarType.arrayFile].includes(varPayload.type) - else if (isBoolean) + } + else if (isString) { + return (varPayload: Var) => ([VarType.string, VarType.number, VarType.secret] as const).includes( + varPayload.type as typeof VarType.string | typeof VarType.number | typeof VarType.secret, + ) + } + else if (isFile) { + return (varPayload: Var) => ([VarType.file, VarType.arrayFile] as const).includes( + varPayload.type as typeof VarType.file | typeof VarType.arrayFile, + ) + } + else if (isBoolean) { return (varPayload: Var) => varPayload.type === VarType.boolean - else if (isObject) + } + else if (isObject) { return (varPayload: Var) => varPayload.type === VarType.object - else if (isArray) - return (varPayload: Var) => [VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject].includes(varPayload.type) + } + else if (isArray) { + return (varPayload: Var) => ([VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject] as const).includes( + varPayload.type as typeof VarType.array | typeof VarType.arrayString | typeof VarType.arrayNumber | typeof VarType.arrayObject, + ) + } return undefined } diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx index a3866ebfb2..8a9cb4706f 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx @@ -15,9 +15,15 @@ import { produce } from 'immer' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' -import { SimpleSelect } from '@/app/components/base/select' import Switch from '@/app/components/base/switch' -import Tooltip from '@/app/components/base/tooltip' +import Tooltip from '@/app/components/base/tooltip-plus' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/app/components/base/ui/select' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' @@ -293,18 +299,26 @@ const ReasoningConfigForm: React.FC = ({ /> )} {isSelect && ( - { - if (option.show_on.length) - return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) + )} {isShowJSONEditor && isConstant && (
diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx index aa38b4581d..1060487c1c 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx @@ -1,10 +1,9 @@ 'use client' import type { FC } from 'react' import type { SchemaRoot } from '@/app/components/workflow/nodes/llm/types' -import { RiCloseLine } from '@remixicon/react' import * as React from 'react' import { useTranslation } from 'react-i18next' -import Modal from '@/app/components/base/modal' +import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' import VisualEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor' import { MittProvider, VisualEditorContextProvider } from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context' @@ -23,38 +22,33 @@ const SchemaModal: FC = ({ }) => { const { t } = useTranslation() return ( - -
- {/* Header */} -
-
- {t('nodes.agent.parameterSchema', { ns: 'workflow' })} + !open && onClose()}> + + +
+ {/* Header */} +
+ + {t('nodes.agent.parameterSchema', { ns: 'workflow' })} +
-
- + {/* Content */} +
+ + + + + +
- {/* Content */} -
- - - - - - -
-
- +
+
) } export default React.memo(SchemaModal) diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section.tsx index aeaab04cf6..8eb3045b56 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' +import type { PluginDetail } from '@/app/components/plugins/types' import type { ToolWithProvider } from '@/app/components/workflow/types' import * as React from 'react' import Divider from '@/app/components/base/divider' @@ -36,7 +37,7 @@ const ToolAuthorizationSection: FC = ({ provider: currentProvider.name, category: AuthCategory.tool, providerType: currentProvider.type, - detail: currentProvider as any, + detail: currentProvider as unknown as PluginDetail, }} credentialId={credentialId} onAuthorizationItemClick={onAuthorizationItemClick} diff --git a/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts b/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts index db5eb95fca..7d0e4f1f97 100644 --- a/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts +++ b/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts @@ -48,7 +48,7 @@ export const usePipelineInit = () => { setDraftUpdatedAt(res.updated_at) setToolPublished(res.tool_published) setEnvSecrets((res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { - acc[env.id] = env.value + acc[env.id] = typeof env.value === 'string' ? env.value : JSON.stringify(env.value) return acc }, {} as Record)) setEnvironmentVariables(res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || []) diff --git a/web/app/components/rag-pipeline/hooks/use-pipeline-refresh-draft.ts b/web/app/components/rag-pipeline/hooks/use-pipeline-refresh-draft.ts index c9966a90c5..9b4e2c7cd6 100644 --- a/web/app/components/rag-pipeline/hooks/use-pipeline-refresh-draft.ts +++ b/web/app/components/rag-pipeline/hooks/use-pipeline-refresh-draft.ts @@ -31,7 +31,7 @@ export const usePipelineRefreshDraft = () => { } as WorkflowDataUpdater) setSyncWorkflowDraftHash(response.hash) setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { - acc[env.id] = env.value + acc[env.id] = typeof env.value === 'string' ? env.value : JSON.stringify(env.value) return acc }, {} as Record)) setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || []) diff --git a/web/app/components/rag-pipeline/index.tsx b/web/app/components/rag-pipeline/index.tsx index 11cd8a70e3..3f83fe2988 100644 --- a/web/app/components/rag-pipeline/index.tsx +++ b/web/app/components/rag-pipeline/index.tsx @@ -36,11 +36,11 @@ const RagPipeline = () => { return [] }, [data]) - const initialFeatures: FeaturesData = useMemo(() => { + const initialFeatures = useMemo(() => { const features = data?.features || {} return { sandbox: features.sandbox || { enabled: false }, - } + } as FeaturesData }, [data?.features]) if (!data || isLoading) { diff --git a/web/app/components/share/text-generation/result/__tests__/workflow-stream-handlers.spec.ts b/web/app/components/share/text-generation/result/__tests__/workflow-stream-handlers.spec.ts index 45d5ded302..7196160990 100644 --- a/web/app/components/share/text-generation/result/__tests__/workflow-stream-handlers.spec.ts +++ b/web/app/components/share/text-generation/result/__tests__/workflow-stream-handlers.spec.ts @@ -1,4 +1,5 @@ import type { WorkflowProcess } from '@/app/components/base/chat/types' +import type { JsonValue } from '@/app/components/workflow/types' import type { IOtherOptions } from '@/service/base' import type { HumanInputFormData, HumanInputFormTimeoutData, NodeTracing } from '@/types/workflow' import { act } from '@testing-library/react' @@ -872,10 +873,10 @@ describe('createWorkflowStreamHandlers', () => { const circularOutputSetup = setupHandlers() const circularOutputHandlers = circularOutputSetup.handlers as Required> - const circularOutputs: Record = { + const circularOutputs: Record = { answer: 'Hello', } - circularOutputs.self = circularOutputs + Object.assign(circularOutputs, { self: circularOutputs as JsonValue }) act(() => { circularOutputHandlers.onWorkflowFinished({ diff --git a/web/app/components/share/text-generation/result/header.tsx b/web/app/components/share/text-generation/result/header.tsx index 4e494e0b5a..e2ec977f6b 100644 --- a/web/app/components/share/text-generation/result/header.tsx +++ b/web/app/components/share/text-generation/result/header.tsx @@ -6,8 +6,9 @@ import copy from 'copy-to-clipboard' import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' -import Toast from '@/app/components/base/toast' -import Tooltip from '@/app/components/base/tooltip' + +import Tooltip from '@/app/components/base/tooltip-plus' +import { toast } from '@/app/components/base/ui/toast' type IResultHeaderProps = { result: string @@ -31,7 +32,7 @@ const Header: FC = ({ className="h-7 p-[2px] pr-2" onClick={() => { copy(result) - Toast.notify({ type: 'success', message: 'copied' }) + toast.success('copied') }} > <> diff --git a/web/app/components/share/text-generation/result/workflow-stream-handlers.ts b/web/app/components/share/text-generation/result/workflow-stream-handlers.ts index 48b132587e..193379a558 100644 --- a/web/app/components/share/text-generation/result/workflow-stream-handlers.ts +++ b/web/app/components/share/text-generation/result/workflow-stream-handlers.ts @@ -133,8 +133,11 @@ const markNodesStopped = (traces?: WorkflowProcess['tracing']) => { return const markTrace = (trace: WorkflowProcess['tracing'][number]) => { - if ([NodeRunningStatus.Running, NodeRunningStatus.Waiting].includes(trace.status as NodeRunningStatus)) + if (([NodeRunningStatus.Running, NodeRunningStatus.Waiting] as const).includes( + trace.status as typeof NodeRunningStatus.Running | typeof NodeRunningStatus.Waiting, + )) { trace.status = NodeRunningStatus.Stopped + } trace.details?.forEach(detailGroup => detailGroup.forEach(markTrace)) trace.retryDetail?.forEach(markTrace) @@ -150,8 +153,11 @@ const applyWorkflowFinishedState = ( ) => { return updateWorkflowProcess(current, (draft) => { draft.status = status - if ([WorkflowRunningStatus.Stopped, WorkflowRunningStatus.Failed].includes(status)) + if (([WorkflowRunningStatus.Stopped, WorkflowRunningStatus.Failed] as const).includes( + status as typeof WorkflowRunningStatus.Stopped | typeof WorkflowRunningStatus.Failed, + )) { markNodesStopped(draft.tracing) + } }) } @@ -353,13 +359,23 @@ export const createWorkflowStreamHandlers = ({ const serializedOutputs = serializeWorkflowOutputs(data.outputs) setCompletionRes(serializedOutputs) if (data.outputs) { - const outputKeys = Object.keys(data.outputs) - const isStringOutput = outputKeys.length === 1 && typeof data.outputs[outputKeys[0]] === 'string' - if (isStringOutput) { + const out = data.outputs + if (typeof out === 'string') { setWorkflowProcessData(updateWorkflowProcess(getWorkflowProcessData(), (draft) => { - draft.resultText = data.outputs[outputKeys[0]] + draft.resultText = out })) } + else { + const outputKeys = Object.keys(out) + const firstKey = outputKeys[0] + const firstVal = firstKey !== undefined ? out[firstKey] : undefined + const isStringOutput = outputKeys.length === 1 && typeof firstVal === 'string' + if (isStringOutput && typeof firstVal === 'string') { + setWorkflowProcessData(updateWorkflowProcess(getWorkflowProcessData(), (draft) => { + draft.resultText = firstVal + })) + } + } } finishWithSuccess() diff --git a/web/app/components/sub-graph/components/config-panel.tsx b/web/app/components/sub-graph/components/config-panel.tsx index e4d5771fbd..c555f124b6 100644 --- a/web/app/components/sub-graph/components/config-panel.tsx +++ b/web/app/components/sub-graph/components/config-panel.tsx @@ -10,7 +10,7 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' +} from '@/app/components/base/portal-to-follow-elem-plus' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import Field from '@/app/components/workflow/nodes/_base/components/field' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' diff --git a/web/app/components/sub-graph/components/sub-graph-main.spec.tsx b/web/app/components/sub-graph/components/sub-graph-main.spec.tsx index 5629f931b3..26a6e0439a 100644 --- a/web/app/components/sub-graph/components/sub-graph-main.spec.tsx +++ b/web/app/components/sub-graph/components/sub-graph-main.spec.tsx @@ -11,9 +11,6 @@ const mockUseInspectVarsCrudCommon = vi.fn() const mockSetPendingSingleRun = vi.fn() vi.mock('@/app/components/workflow', () => ({ - InteractionMode: { - Subgraph: 'subgraph', - }, WorkflowWithInnerContext: ({ children }: { children: React.ReactNode }) => (
{children}
), diff --git a/web/app/components/sub-graph/components/sub-graph-main.tsx b/web/app/components/sub-graph/components/sub-graph-main.tsx index 376aef8021..fb91940bf6 100644 --- a/web/app/components/sub-graph/components/sub-graph-main.tsx +++ b/web/app/components/sub-graph/components/sub-graph-main.tsx @@ -6,10 +6,11 @@ import type { NestedNodeConfig } from '@/app/components/workflow/nodes/_base/typ import type { Edge, Node } from '@/app/components/workflow/types' import { useCallback, useEffect, useMemo } from 'react' import { useStoreApi } from 'reactflow' -import { InteractionMode, WorkflowWithInnerContext } from '@/app/components/workflow' +import { WorkflowWithInnerContext } from '@/app/components/workflow' import { useNodesInteractions } from '@/app/components/workflow/hooks' import { useSetWorkflowVarsWithValue } from '@/app/components/workflow/hooks/use-fetch-workflow-inspect-vars' import { useInspectVarsCrudCommon } from '@/app/components/workflow/hooks/use-inspect-vars-crud-common' +import { InteractionMode } from '@/app/components/workflow/interaction-mode' import { useWorkflowStore } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' import { FlowType } from '@/types/common' diff --git a/web/app/components/workflow-app/components/sandbox-migration-modal.tsx b/web/app/components/workflow-app/components/sandbox-migration-modal.tsx index edeef09ee0..e7af69c57e 100644 --- a/web/app/components/workflow-app/components/sandbox-migration-modal.tsx +++ b/web/app/components/workflow-app/components/sandbox-migration-modal.tsx @@ -33,7 +33,6 @@ const SandboxMigrationModal: FC = ({ return ( `.${ext}`), - allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], + allowed_file_upload_methods: (features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || defaultTransferMethods) as TransferMethod[], number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3, }, opening: { @@ -123,7 +125,7 @@ const WorkflowMain = ({ moderation: features.sensitive_word_avoidance || { enabled: false }, annotationReply: features.annotation_reply || { enabled: false }, sandbox: features.sandbox || { enabled: false }, - } + } as FeaturesData setFeatures(transformedFeatures) } diff --git a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts index a24fb3d6d6..14c5ba9360 100644 --- a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts +++ b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts @@ -26,7 +26,7 @@ const NODE_HELP_LINK_OVERRIDES: Partial> = { [BlockEnum.FileUpload]: 'upload-file-to-sandbox', } -type WorkflowAppScene = NodeSelectorScene.Workflow | NodeSelectorScene.Chatflow +type WorkflowAppScene = typeof NodeSelectorScene.Workflow | typeof NodeSelectorScene.Chatflow const WORKFLOW_APP_SCENE_NODES = { [NodeSelectorScene.Workflow]: [ diff --git a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts index 07e26dc798..6b54a3fec7 100644 --- a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts @@ -1,5 +1,5 @@ -import type { WorkflowDraftFeaturesPayload } from '@/service/workflow' import type { SyncDraftCallback } from '@/app/components/workflow/hooks-store' +import type { WorkflowDraftFeaturesPayload } from '@/service/workflow' import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' diff --git a/web/app/components/workflow-app/hooks/use-workflow-init.ts b/web/app/components/workflow-app/hooks/use-workflow-init.ts index dec0270c19..97001f2ff6 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-init.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-init.ts @@ -63,7 +63,7 @@ export const useWorkflowInit = () => { setData(res) workflowStore.setState({ envSecrets: (res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { - acc[env.id] = env.value + acc[env.id] = typeof env.value === 'string' ? env.value : JSON.stringify(env.value) return acc }, {} as Record), environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [], @@ -128,10 +128,12 @@ export const useWorkflowInit = () => { const publishedWorkflow = await fetchPublishedWorkflow(`/apps/${appDetail?.id}/workflows/publish`) workflowStore.setState({ nodesDefaultConfigs: nodesDefaultConfigsData.reduce((acc, block) => { - if (!acc[block.type]) - acc[block.type] = { ...block.config } + if (!acc[block.type]) { + const cfg = block.config + acc[block.type] = (cfg && typeof cfg === 'object' && !Array.isArray(cfg) ? { ...cfg } : {}) as Record + } return acc - }, {} as Record), + }, {} as Record>), }) workflowStore.getState().setPublishedAt(publishedWorkflow?.created_at) const graph = publishedWorkflow?.graph diff --git a/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts b/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts index 5e35c1fd16..5c3d9fb066 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts @@ -42,7 +42,7 @@ export const useWorkflowRefreshDraft = () => { } setSyncWorkflowDraftHash(response.hash) setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { - acc[env.id] = env.value + acc[env.id] = typeof env.value === 'string' ? env.value : JSON.stringify(env.value) return acc }, {} as Record)) setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || []) diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts index a725031324..a68a5a2b27 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts @@ -1,4 +1,5 @@ import type AudioPlayer from '@/app/components/base/audio-btn/audio' +import type { Features as FeaturesData } from '@/app/components/base/features/types' import type { Node } from '@/app/components/workflow/types' import type { IOtherOptions } from '@/service/base' import type { VersionHistory } from '@/types/workflow' @@ -932,20 +933,22 @@ export const useWorkflowRun = () => { edges, viewport, }) + const f = publishedWorkflow.features ?? {} const mappedFeatures = { opening: { - enabled: !!publishedWorkflow.features.opening_statement || !!publishedWorkflow.features.suggested_questions.length, - opening_statement: publishedWorkflow.features.opening_statement, - suggested_questions: publishedWorkflow.features.suggested_questions, + enabled: !!f.opening_statement || !!f.suggested_questions?.length, + opening_statement: f.opening_statement, + suggested_questions: f.suggested_questions, }, - suggested: publishedWorkflow.features.suggested_questions_after_answer, - text2speech: publishedWorkflow.features.text_to_speech, - speech2text: publishedWorkflow.features.speech_to_text, - citation: publishedWorkflow.features.retriever_resource, - moderation: publishedWorkflow.features.sensitive_word_avoidance, - file: publishedWorkflow.features.file_upload, - sandbox: publishedWorkflow.features.sandbox || { enabled: false }, - } + suggested: f.suggested_questions_after_answer || { enabled: false }, + text2speech: f.text_to_speech, + speech2text: f.speech_to_text, + citation: f.retriever_resource, + moderation: f.sensitive_word_avoidance, + file: f.file_upload, + annotationReply: f.annotation_reply || { enabled: false }, + sandbox: f.sandbox || { enabled: false }, + } as FeaturesData featuresStore?.setState({ features: mappedFeatures }) workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || []) diff --git a/web/app/components/workflow-app/index.tsx b/web/app/components/workflow-app/index.tsx index 33475a1fb9..e93c96912d 100644 --- a/web/app/components/workflow-app/index.tsx +++ b/web/app/components/workflow-app/index.tsx @@ -3,7 +3,7 @@ import type { ReactNode } from 'react' import type { Features as FeaturesData } from '@/app/components/base/features/types' import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store' -import dynamic from 'next/dynamic' +import type { TransferMethod } from '@/types/app' import { useQueryState } from 'nuqs' import { useCallback, @@ -36,8 +36,9 @@ import { initialNodes, } from '@/app/components/workflow/utils' import { useAppContext } from '@/context/app-context' -import { upgradeAppRuntime } from '@/service/apps' +import dynamic from '@/next/dynamic' import { useSearchParams } from '@/next/navigation' +import { upgradeAppRuntime } from '@/service/apps' import { fetchRunDetail } from '@/service/log' import { useAppTriggers } from '@/service/use-tools' import { AppModeEnum } from '@/types/app' @@ -397,17 +398,18 @@ const WorkflowAppWithAdditionalContext = () => { } const features = data.features || {} - const initialFeatures: FeaturesData = { + const defaultTransferMethods = ['local_file', 'remote_url'] as TransferMethod[] + const initialFeatures = { file: { image: { enabled: !!features.file_upload?.image?.enabled, number_limits: features.file_upload?.image?.number_limits || 3, - transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], + transfer_methods: (features.file_upload?.image?.transfer_methods || defaultTransferMethods) as TransferMethod[], }, enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled), allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image], allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`), - allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], + allowed_file_upload_methods: (features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || defaultTransferMethods) as TransferMethod[], number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3, fileUploadConfig: fileUploadConfigResponse, }, @@ -421,8 +423,9 @@ const WorkflowAppWithAdditionalContext = () => { text2speech: features.text_to_speech || { enabled: false }, citation: features.retriever_resource || { enabled: false }, moderation: features.sensitive_word_avoidance || { enabled: false }, + annotationReply: features.annotation_reply || { enabled: false }, sandbox: features.sandbox || { enabled: false }, - } + } as FeaturesData return ( <> diff --git a/web/app/components/workflow/__tests__/fixtures.ts b/web/app/components/workflow/__tests__/fixtures.ts index a340e38abb..c6782a410d 100644 --- a/web/app/components/workflow/__tests__/fixtures.ts +++ b/web/app/components/workflow/__tests__/fixtures.ts @@ -50,7 +50,10 @@ export function createNodeDataFactory, 'data'> & { data?: Partial & Record } = {}, ): Node { return createNode({ diff --git a/web/app/components/workflow/block-icon.tsx b/web/app/components/workflow/block-icon.tsx index 679e90e12d..7ef9066e01 100644 --- a/web/app/components/workflow/block-icon.tsx +++ b/web/app/components/workflow/block-icon.tsx @@ -36,8 +36,8 @@ import { VariableX, WebhookLine, } from '@/app/components/base/icons/src/vender/workflow' -import { STORAGE_KEYS } from '@/config/storage-keys' import { API_PREFIX } from '@/config' +import { STORAGE_KEYS } from '@/config/storage-keys' import { cn } from '@/utils/classnames' import { storage } from '@/utils/storage' import { BlockEnum } from './types' diff --git a/web/app/components/workflow/comment/cursor.spec.tsx b/web/app/components/workflow/comment/cursor.spec.tsx index f7d24dcf1d..cd1dea2ca8 100644 --- a/web/app/components/workflow/comment/cursor.spec.tsx +++ b/web/app/components/workflow/comment/cursor.spec.tsx @@ -3,7 +3,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { ControlMode } from '../types' import { CommentCursor } from './cursor' -const mockState = { +const mockState: { + controlMode: ControlMode + isCommentPlacing: boolean + mousePosition: { elementX: number, elementY: number } +} = { controlMode: ControlMode.Pointer, isCommentPlacing: false, mousePosition: { diff --git a/web/app/components/workflow/comment/mention-input.tsx b/web/app/components/workflow/comment/mention-input.tsx index af4a53a0ae..88a83674b3 100644 --- a/web/app/components/workflow/comment/mention-input.tsx +++ b/web/app/components/workflow/comment/mention-input.tsx @@ -3,7 +3,6 @@ import type { ReactNode } from 'react' import type { UserProfile } from '@/service/workflow-comment' import { RiArrowUpLine, RiAtLine, RiLoader2Line } from '@remixicon/react' -import { useParams } from 'next/navigation' import { forwardRef, memo, @@ -21,6 +20,7 @@ import Textarea from 'react-textarea-autosize' import { Avatar } from '@/app/components/base/avatar' import Button from '@/app/components/base/button' import { EnterKey } from '@/app/components/base/icons/src/public/common' +import { useParams } from '@/next/navigation' import { fetchMentionableUsers } from '@/service/workflow-comment' import { cn } from '@/utils/classnames' import { useStore, useWorkflowStore } from '../store' diff --git a/web/app/components/workflow/comment/thread.tsx b/web/app/components/workflow/comment/thread.tsx index 3166a8c1c8..0d100788d6 100644 --- a/web/app/components/workflow/comment/thread.tsx +++ b/web/app/components/workflow/comment/thread.tsx @@ -3,18 +3,19 @@ import type { FC, ReactNode } from 'react' import type { WorkflowCommentDetail, WorkflowCommentDetailReply } from '@/service/workflow-comment' import { RiArrowDownSLine, RiArrowUpSLine, RiCheckboxCircleFill, RiCheckboxCircleLine, RiCloseLine, RiDeleteBinLine, RiMoreFill } from '@remixicon/react' -import { useParams } from 'next/navigation' import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useReactFlow, useViewport } from 'reactflow' import { Avatar } from '@/app/components/base/avatar' import Divider from '@/app/components/base/divider' import InlineDeleteConfirm from '@/app/components/base/inline-delete-confirm' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' -import Tooltip from '@/app/components/base/tooltip' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem-plus' + +import Tooltip from '@/app/components/base/tooltip-plus' import { getUserColor } from '@/app/components/workflow/collaboration/utils/user-color' import { useAppContext } from '@/context/app-context' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' +import { useParams } from '@/next/navigation' import { cn } from '@/utils/classnames' import { useStore } from '../store' import { MentionInput } from './mention-input' diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index 73e80dd0a2..4bbab9aafe 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -109,7 +109,7 @@ export const RETRIEVAL_OUTPUT_STRUCT = `{ } }` -export const SUPPORT_OUTPUT_VARS_NODE = [ +export const SUPPORT_OUTPUT_VARS_NODE: BlockEnum[] = [ BlockEnum.Start, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin, diff --git a/web/app/components/workflow/constants/node-availability.ts b/web/app/components/workflow/constants/node-availability.ts index f679eb5ce4..12f0ad6a72 100644 --- a/web/app/components/workflow/constants/node-availability.ts +++ b/web/app/components/workflow/constants/node-availability.ts @@ -1,17 +1,20 @@ import { BlockEnum } from '@/app/components/workflow/types' -export enum NodeSelectorScene { - Workflow = 'workflow', - Chatflow = 'chatflow', - RagPipeline = 'rag-pipeline', - Subgraph = 'subgraph', -} +/* eslint-disable ts/no-redeclare -- const + type share names (erasable enum replacement) */ +export const NodeSelectorScene = { + Workflow: 'workflow', + Chatflow: 'chatflow', + RagPipeline: 'rag-pipeline', + Subgraph: 'subgraph', +} as const +export type NodeSelectorScene = typeof NodeSelectorScene[keyof typeof NodeSelectorScene] -export enum NodeSelectorSandboxMode { - Enabled = 'enabled', - Disabled = 'disabled', - Unsupported = 'unsupported', -} +export const NodeSelectorSandboxMode = { + Enabled: 'enabled', + Disabled: 'disabled', + Unsupported: 'unsupported', +} as const +export type NodeSelectorSandboxMode = typeof NodeSelectorSandboxMode[keyof typeof NodeSelectorSandboxMode] export const NODE_SELECTOR_SCENE_SUPPORTS_SANDBOX: Record = { [NodeSelectorScene.Workflow]: true, diff --git a/web/app/components/workflow/dsl-export-confirm-modal.tsx b/web/app/components/workflow/dsl-export-confirm-modal.tsx index 28917724a8..38abb9b72d 100644 --- a/web/app/components/workflow/dsl-export-confirm-modal.tsx +++ b/web/app/components/workflow/dsl-export-confirm-modal.tsx @@ -64,7 +64,7 @@ const DSLExportConfirmModal = ({
-
{env.value}
+
{String(env.value ?? '')}
))} diff --git a/web/app/components/workflow/header/online-users.tsx b/web/app/components/workflow/header/online-users.tsx index 03068f36a5..b183c1ba39 100644 --- a/web/app/components/workflow/header/online-users.tsx +++ b/web/app/components/workflow/header/online-users.tsx @@ -9,8 +9,9 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import Tooltip from '@/app/components/base/tooltip' +} from '@/app/components/base/portal-to-follow-elem-plus' + +import Tooltip from '@/app/components/base/tooltip-plus' import { useAppContext } from '@/context/app-context' import { getAvatar } from '@/service/common' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/header/view-history.tsx b/web/app/components/workflow/header/view-history.tsx index 162d46f8fe..ad0079736d 100644 --- a/web/app/components/workflow/header/view-history.tsx +++ b/web/app/components/workflow/header/view-history.tsx @@ -170,7 +170,9 @@ const ViewHistory = ({ }} > { - !isChatMode && [WorkflowRunningStatus.Stopped, WorkflowRunningStatus.Paused].includes(item.status) && ( + !isChatMode && ([WorkflowRunningStatus.Stopped, WorkflowRunningStatus.Paused] as const).includes( + item.status as typeof WorkflowRunningStatus.Stopped | typeof WorkflowRunningStatus.Paused, + ) && ( ) } diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts index 5544cc8592..efeed7a588 100644 --- a/web/app/components/workflow/hooks-store/store.ts +++ b/web/app/components/workflow/hooks-store/store.ts @@ -1,5 +1,6 @@ import type { FileUpload } from '../../base/features/types' import type { TriggerType } from '@/app/components/workflow/header/test-run-menu' +import type { InteractionModeType } from '@/app/components/workflow/interaction-mode' import type { BlockEnum, Node, @@ -17,7 +18,7 @@ import { useStore as useZustandStore, } from 'zustand' import { createStore } from 'zustand/vanilla' -import { InteractionMode } from '@/app/components/workflow' +import { InteractionMode } from '@/app/components/workflow/interaction-mode' import { HooksStoreContext } from './provider' export type WorkflowRunOptions = { @@ -39,7 +40,7 @@ export type SyncDraftCallback = { onSettled?: () => void } export type CommonHooksFnMap = { - interactionMode?: InteractionMode + interactionMode?: InteractionModeType doSyncWorkflowDraft: ( notRefreshWhenSyncError?: boolean, callback?: SyncDraftCallback, diff --git a/web/app/components/workflow/hooks/__tests__/use-workflow-run-event-store-only.spec.ts b/web/app/components/workflow/hooks/__tests__/use-workflow-run-event-store-only.spec.ts index e10988cf1a..b41af1aef1 100644 --- a/web/app/components/workflow/hooks/__tests__/use-workflow-run-event-store-only.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-workflow-run-event-store-only.spec.ts @@ -83,7 +83,7 @@ describe('useWorkflowFinished', () => { result.current.handleWorkflowFinished({ data: { status: 'succeeded', outputs: { answer: 'hello' } }, - } as WorkflowFinishedResponse) + } as unknown as WorkflowFinishedResponse) const state = store.getState().workflowRunningData! expect(state.result.status).toBe('succeeded') @@ -98,7 +98,7 @@ describe('useWorkflowFinished', () => { result.current.handleWorkflowFinished({ data: { status: 'succeeded', outputs: { a: 'hello', b: 'world' } }, - } as WorkflowFinishedResponse) + } as unknown as WorkflowFinishedResponse) expect(store.getState().workflowRunningData!.resultTabActive).toBeFalsy() }) diff --git a/web/app/components/workflow/hooks/inspect-vars-agent-alias.ts b/web/app/components/workflow/hooks/inspect-vars-agent-alias.ts index 0b970ab77e..c5201090e4 100644 --- a/web/app/components/workflow/hooks/inspect-vars-agent-alias.ts +++ b/web/app/components/workflow/hooks/inspect-vars-agent-alias.ts @@ -1,4 +1,4 @@ -import type { Node } from '@/app/components/workflow/types' +import type { JsonValue, Node } from '@/app/components/workflow/types' import type { NodeWithVar, VarInInspect } from '@/types/workflow' import { BlockEnum, VarType } from '@/app/components/workflow/types' import { AGENT_CONTEXT_VAR_PATTERN, getAgentNodeIdFromContextVar } from '@/app/components/workflow/utils/agent-context' @@ -248,7 +248,7 @@ export const applyAgentSubgraphInspectVars = (nodesWithInspectVars: NodeWithVar[ id: aliasId, name: mapping.aliasName, selector: [mapping.parentNodeId, mapping.aliasName], - value: resolvedValue, + value: resolvedValue as JsonValue, visible: true, aliasMeta: { extractorNodeId: mapping.extractorNodeId, diff --git a/web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts b/web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts index 6147ba5e8f..636376796f 100644 --- a/web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts +++ b/web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts @@ -1,11 +1,12 @@ +import type { InteractionModeType } from '@/app/components/workflow/interaction-mode' import type { Node, ToolWithProvider } from '@/app/components/workflow/types' import type { SchemaTypeDefinition } from '@/service/use-common' import type { FlowType } from '@/types/common' import type { NodeWithVar, VarInInspect } from '@/types/workflow' import { useCallback, useMemo } from 'react' import { useStoreApi } from 'reactflow' -import { InteractionMode } from '@/app/components/workflow' import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' +import { InteractionMode } from '@/app/components/workflow/interaction-mode' import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { useAllBuiltInTools, @@ -22,7 +23,7 @@ import { applyAgentSubgraphInspectVars } from './inspect-vars-agent-alias' type Params = { flowType: FlowType flowId: string - interactionMode?: InteractionMode + interactionMode?: InteractionModeType } export const useSetWorkflowVarsWithValue = ({ diff --git a/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts b/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts index 9653505b2d..93202d75a1 100644 --- a/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts +++ b/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts @@ -1,3 +1,4 @@ +import type { InteractionModeType } from '@/app/components/workflow/interaction-mode' import type { Node, ValueSelector } from '@/app/components/workflow/types' import type { SchemaTypeDefinition } from '@/service/use-common' import type { FlowType } from '@/types/common' @@ -5,9 +6,9 @@ import type { VarInInspect } from '@/types/workflow' import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import { InteractionMode } from '@/app/components/workflow' import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync' import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' +import { InteractionMode } from '@/app/components/workflow/interaction-mode' import { isConversationVar, isENV, @@ -31,7 +32,7 @@ import { applyAgentSubgraphInspectVars } from './inspect-vars-agent-alias' type Params = { flowId: string flowType: FlowType - interactionMode?: InteractionMode + interactionMode?: InteractionModeType } export const useInspectVarsCrudCommon = ({ flowId, diff --git a/web/app/components/workflow/hooks/use-leader-restore.ts b/web/app/components/workflow/hooks/use-leader-restore.ts index 8e73bbf52c..5e47f025df 100644 --- a/web/app/components/workflow/hooks/use-leader-restore.ts +++ b/web/app/components/workflow/hooks/use-leader-restore.ts @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next' import { useReactFlow } from 'reactflow' import { useStore as useAppStore } from '@/app/components/app/store' import { useFeaturesStore } from '@/app/components/base/features/hooks' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useGlobalPublicStore } from '@/context/global-public-context' import { collaborationManager } from '../collaboration/core/collaboration-manager' import { useWorkflowStore } from '../store' @@ -85,15 +85,11 @@ export const useLeaderRestoreListener = () => { useEffect(() => { const unsubscribe = collaborationManager.onRestoreRequest((data: RestoreRequestData) => { - Toast.notify({ - type: 'info', - message: t('versionHistory.action.restoreInProgress', { - ns: 'workflow', - userName: data.initiatorName, - versionName: data.versionName || data.versionId, - }), - duration: 3000, - }) + toast.info(t('versionHistory.action.restoreInProgress', { + ns: 'workflow', + userName: data.initiatorName, + versionName: data.versionName || data.versionId, + }), { timeout: 3000 }) performRestore(data) }) @@ -102,15 +98,11 @@ export const useLeaderRestoreListener = () => { useEffect(() => { const unsubscribe = collaborationManager.onRestoreIntent((data: RestoreIntentData) => { - Toast.notify({ - type: 'info', - message: t('versionHistory.action.restoreInProgress', { - ns: 'workflow', - userName: data.initiatorName, - versionName: data.versionName || data.versionId, - }), - duration: 3000, - }) + toast.info(t('versionHistory.action.restoreInProgress', { + ns: 'workflow', + userName: data.initiatorName, + versionName: data.versionName || data.versionId, + }), { timeout: 3000 }) }) return unsubscribe diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index fe6e422705..86133d3617 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -1965,7 +1965,7 @@ export const useNodesInteractions = () => { ] // handle disallow paste node - if (commonNestedDisallowPasteNodes.includes(nodeToPaste.data.type)) + if ((commonNestedDisallowPasteNodes as BlockEnum[]).includes(nodeToPaste.data.type)) return // handle paste to nested block diff --git a/web/app/components/workflow/hooks/use-workflow-comment.ts b/web/app/components/workflow/hooks/use-workflow-comment.ts index 54aed4217a..3398f0d7c9 100644 --- a/web/app/components/workflow/hooks/use-workflow-comment.ts +++ b/web/app/components/workflow/hooks/use-workflow-comment.ts @@ -1,10 +1,10 @@ import type { UserProfile, WorkflowCommentDetail, WorkflowCommentList } from '@/service/workflow-comment' -import { useParams } from 'next/navigation' import { useCallback, useEffect, useRef } from 'react' import { useReactFlow } from 'reactflow' import { collaborationManager } from '@/app/components/workflow/collaboration' import { useAppContext } from '@/context/app-context' import { useGlobalPublicStore } from '@/context/global-public-context' +import { useParams } from '@/next/navigation' import { createWorkflowComment, createWorkflowCommentReply, deleteWorkflowComment, deleteWorkflowCommentReply, fetchWorkflowComment, fetchWorkflowComments, resolveWorkflowComment, updateWorkflowComment, updateWorkflowCommentReply } from '@/service/workflow-comment' import { useStore } from '../store' import { ControlMode } from '../types' diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-finished.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-finished.ts index 5fc8807873..97ea487b3f 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-finished.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-finished.ts @@ -14,7 +14,13 @@ export const useWorkflowFinished = () => { setWorkflowRunningData, } = workflowStore.getState() - const isStringOutput = data.outputs && Object.keys(data.outputs).length === 1 && typeof data.outputs[Object.keys(data.outputs)[0]] === 'string' + const out = data.outputs + const outputKeys = out && typeof out === 'object' && !Array.isArray(out) ? Object.keys(out) : [] + const firstOutputKey = outputKeys[0] + const firstOutputVal = firstOutputKey !== undefined && out && typeof out === 'object' && !Array.isArray(out) + ? (out as Record)[firstOutputKey] + : undefined + const isSingleKeyedString = outputKeys.length === 1 && typeof firstOutputVal === 'string' setWorkflowRunningData(produce(workflowRunningData!, (draft) => { draft.result = { @@ -22,9 +28,13 @@ export const useWorkflowFinished = () => { ...data, files: getFilesInLogs(data.outputs), } as any - if (isStringOutput) { + if (typeof out === 'string') { draft.resultTabActive = true - draft.resultText = data.outputs[Object.keys(data.outputs)[0]] + draft.resultText = out + } + else if (isSingleKeyedString && typeof firstOutputVal === 'string') { + draft.resultTabActive = true + draft.resultText = firstOutputVal } })) }, [workflowStore]) diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-finished.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-finished.ts index 779b73f3ca..42ea6becf2 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-finished.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-finished.ts @@ -47,13 +47,17 @@ export const useWorkflowNodeFinished = () => { currentNode.data._runningBranchId = ErrorHandleTypeEnum.failBranch } else { + const rawOutputs = data.outputs + const outputRecord = rawOutputs && typeof rawOutputs === 'object' && !Array.isArray(rawOutputs) + ? rawOutputs as Record + : undefined if (data.node_type === BlockEnum.IfElse) - currentNode.data._runningBranchId = data?.outputs?.selected_case_id + currentNode.data._runningBranchId = outputRecord?.selected_case_id if (data.node_type === BlockEnum.QuestionClassifier) - currentNode.data._runningBranchId = data?.outputs?.class_id + currentNode.data._runningBranchId = outputRecord?.class_id if (data.node_type === BlockEnum.HumanInput) - currentNode.data._runningBranchId = data?.outputs?.__action_id + currentNode.data._runningBranchId = outputRecord?.__action_id } }) setNodes(newNodes) diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index d8e5aa4caa..b02e16e373 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -7,6 +7,7 @@ import type { } from 'reactflow' import type { CursorPosition, OnlineUser } from './collaboration/types' import type { Shape as HooksStoreShape } from './hooks-store' +import type { InteractionModeType } from './interaction-mode' import type { WorkflowSliceShape } from './store/workflow/workflow-slice' import type { ConversationVariable, @@ -14,7 +15,7 @@ import type { EnvironmentVariable, Node, } from './types' -import type { VarInInspect } from '@/types/workflow' +import type { VarInInspect, WorkflowDraftFeatures } from '@/types/workflow' import { useEventListener, } from 'ahooks' @@ -41,10 +42,18 @@ import ReactFlow, { useReactFlow, useStoreApi, } from 'reactflow' -import Toast from '@/app/components/base/toast' +import { + AlertDialog, + AlertDialogActions, + AlertDialogCancelButton, + AlertDialogConfirmButton, + AlertDialogContent, + AlertDialogDescription, + AlertDialogTitle, +} from '@/app/components/base/ui/alert-dialog' +import { toast } from '@/app/components/base/ui/toast' import { IS_DEV } from '@/config' import { useEventEmitterContextContext } from '@/context/event-emitter' -import dynamic from '@/next/dynamic' import { useAllBuiltInTools, useAllCustomTools, @@ -86,6 +95,7 @@ import { import { HooksStoreContextProvider, useHooksStore } from './hooks-store' import { useWorkflowComment } from './hooks/use-workflow-comment' import { useWorkflowSearch } from './hooks/use-workflow-search' +import { InteractionMode } from './interaction-mode' import NodeContextmenu from './node-contextmenu' import CustomNode from './nodes' import useMatchSchemaType from './nodes/_base/components/variable/use-match-schema-type' @@ -120,10 +130,6 @@ import { WorkflowHistoryProvider } from './workflow-history-store' import 'reactflow/dist/style.css' import './style.css' -const Confirm = dynamic(() => import('@/app/components/base/confirm'), { - ssr: false, -}) - const nodeTypes = { [CUSTOM_NODE]: CustomNode, [CUSTOM_NOTE_NODE]: CustomNoteNode, @@ -137,17 +143,12 @@ const edgeTypes = { [CUSTOM_EDGE]: CustomEdge, } -export enum InteractionMode { - Default = 'default', - Subgraph = 'subgraph', -} - type WorkflowDataUpdatePayload = { nodes: Node[] edges: Edge[] viewport?: Viewport hash?: string - features?: unknown + features?: WorkflowDraftFeatures conversation_variables?: ConversationVariable[] environment_variables?: EnvironmentVariable[] } @@ -160,7 +161,7 @@ export type WorkflowProps = { onWorkflowDataUpdate?: (v: WorkflowDataUpdatePayload) => void allowSelectionWhenReadOnly?: boolean canvasReadOnly?: boolean - interactionMode?: InteractionMode + interactionMode?: InteractionModeType cursors?: Record myUserId?: string | null onlineUsers?: OnlineUser[] @@ -285,10 +286,7 @@ export const Workflow: FC = memo(({ useEffect(() => { return collaborationManager.onHistoryAction((_) => { - Toast.notify({ - type: 'info', - message: t('collaboration.historyAction.generic', { ns: 'workflow' }), - }) + toast.info(t('collaboration.historyAction.generic', { ns: 'workflow' })) }) }, [t]) const { @@ -642,14 +640,39 @@ export const Workflow: FC = memo(({ {!isSubGraph && } {!isSubGraph && } {!isSubGraph && } - {!!showConfirm && ( - setShowConfirm(undefined)} - onConfirm={showConfirm.onConfirm} - title={showConfirm.title} - content={showConfirm.desc} - /> + {showConfirm && ( + { + if (!open) + setShowConfirm(undefined) + }} + > + +
+ + {showConfirm.title} + + {showConfirm.desc && ( + + {showConfirm.desc} + + )} +
+ + + {t('operation.cancel', { ns: 'common' })} + + { + void showConfirm.onConfirm() + }} + > + {t('operation.confirm', { ns: 'common' })} + + +
+
)} {controlMode === ControlMode.Comment && isMouseOverCanvas && ( diff --git a/web/app/components/workflow/interaction-mode.ts b/web/app/components/workflow/interaction-mode.ts new file mode 100644 index 0000000000..d1775e1c98 --- /dev/null +++ b/web/app/components/workflow/interaction-mode.ts @@ -0,0 +1,6 @@ +export const InteractionMode = { + Default: 'default', + Subgraph: 'subgraph', +} as const + +export type InteractionModeType = typeof InteractionMode[keyof typeof InteractionMode] diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx index e45c9dbd95..ade526de51 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx @@ -102,7 +102,7 @@ const FormItem: FC = ({ })() const isBooleanType = type === InputVarType.checkbox - const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(type) + const isArrayLikeType = ([InputVarType.contexts, InputVarType.iterator] as InputVarType[]).includes(type) const isContext = type === InputVarType.contexts const isIterator = type === InputVarType.iterator const isIteratorItemFile = isIterator && payload.isFileItem diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx index b3592cd369..ec4b126651 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx @@ -61,10 +61,12 @@ const Form: FC = ({ onChange(newValues) } }, [valuesRef, onChange, mapKeysWithSameValueSelector]) - const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(inputs[0]?.type) - const isIteratorItemFile = inputs[0]?.type === InputVarType.iterator && inputs[0]?.isFileItem + const firstInputType = inputs[0]?.type + const isArrayLikeType = firstInputType !== undefined + && ([InputVarType.contexts, InputVarType.iterator] as InputVarType[]).includes(firstInputType) + const isIteratorItemFile = firstInputType === InputVarType.iterator && inputs[0]?.isFileItem - const isContext = inputs[0]?.type === InputVarType.contexts + const isContext = firstInputType === InputVarType.contexts const handleAddContext = useCallback(() => { const newValues = produce(values, (draft: any) => { const key = inputs[0].variable diff --git a/web/app/components/workflow/nodes/_base/components/config-vision.tsx b/web/app/components/workflow/nodes/_base/components/config-vision.tsx index 65a179fddd..0d601fd4c7 100644 --- a/web/app/components/workflow/nodes/_base/components/config-vision.tsx +++ b/web/app/components/workflow/nodes/_base/components/config-vision.tsx @@ -40,7 +40,7 @@ const ConfigVision: FC = ({ const { t } = useTranslation() const filterVar = useCallback((payload: Var) => { - return [VarType.file, VarType.arrayFile].includes(payload.type) + return ([VarType.file, VarType.arrayFile] as VarType[]).includes(payload.type) }, []) const handleVisionResolutionChange = useCallback((resolution: Resolution) => { const newConfig = produce(config, (draft) => { diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx index a4bef1fc6b..89c383c9c5 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx @@ -57,8 +57,8 @@ const CodeEditor: FC = ({ value = '', placeholder = '', onChange = noop, - onBlur, - onFocus, + onBlur: _onBlur, + onFocus: _onFocus, title = '', headerRight, language, diff --git a/web/app/components/workflow/nodes/_base/components/file-type-item.tsx b/web/app/components/workflow/nodes/_base/components/file-type-item.tsx index 6851af2874..f93d914398 100644 --- a/web/app/components/workflow/nodes/_base/components/file-type-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/file-type-item.tsx @@ -12,7 +12,12 @@ import { cn } from '@/utils/classnames' import { SupportUploadFileTypes } from '../../../types' type Props = { - type: SupportUploadFileTypes.image | SupportUploadFileTypes.document | SupportUploadFileTypes.audio | SupportUploadFileTypes.video | SupportUploadFileTypes.custom + type: + | typeof SupportUploadFileTypes.image + | typeof SupportUploadFileTypes.document + | typeof SupportUploadFileTypes.audio + | typeof SupportUploadFileTypes.video + | typeof SupportUploadFileTypes.custom selected: boolean onToggle: (type: SupportUploadFileTypes) => void onCustomFileTypesChange?: (customFileTypes: string[]) => void diff --git a/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx b/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx index ae2dbb8e51..828850df94 100644 --- a/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx +++ b/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx @@ -106,7 +106,7 @@ const FileUploadSetting: FC = ({ [SupportUploadFileTypes.document, SupportUploadFileTypes.image, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => ( @@ -179,7 +179,7 @@ const FileUploadSetting: FC = ({ [SupportUploadFileTypes.document, SupportUploadFileTypes.image, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => ( diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index b8401fc4ca..a41c70def9 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { NestedNodeConfig, ResourceVarInputs } from '../types' -import type { CredentialFormSchema, FormOption } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { FormOption as BaseSelectFormOption } from '@/app/components/base/form/types' +import type { CredentialFormSchema, FormOption, FormShowOnObject, TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Event, Tool } from '@/app/components/tools/types' import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' @@ -34,18 +35,44 @@ import { VarKindType } from '../types' import FormInputBoolean from './form-input-boolean' import FormInputTypeSwitch from './form-input-type-switch' +type SelectOptionRow = FormOption | BaseSelectFormOption + +function credentialInputMatchesShowOn(value: ResourceVarInputs, showOnItem: FormShowOnObject): boolean { + const entry = value[showOnItem.variable] + const comparable = entry !== undefined && entry !== null && typeof entry === 'object' && 'value' in entry + ? (entry as { value: unknown }).value + : entry + return comparable === showOnItem.value +} + +function selectOptionDisplayLabel(opt: SelectOptionRow, language: string): string { + const { label } = opt + if (typeof label === 'string') + return label + if (label && typeof label === 'object') + return (label as Record)[language] || (label as { en_US?: string }).en_US || opt.value + return opt.value +} + +type CredentialFormSchemaRuntime = CredentialFormSchema & { + _type?: FormTypeEnum + multiple?: boolean + options?: FormOption[] + placeholder?: TypeWithI18N +} + type Props = { readOnly: boolean nodeId: string schema: CredentialFormSchema value: ResourceVarInputs - onChange: (value: any) => void + onChange: (value: ResourceVarInputs) => void inPanel?: boolean currentTool?: Tool | Event currentProvider?: ToolWithProvider | TriggerWithProvider showManageInputField?: boolean onManageInputField?: () => void - extraParams?: Record + extraParams?: Record providerType?: string disableVariableInsertion?: boolean } @@ -59,7 +86,7 @@ type VariableReferenceFieldsProps = { varInput: ResourceVarInputs[string] targetVarType: string filterVar?: (payload: Var, selector: ValueSelector) => boolean - onValueChange: (newValue: any) => void + onValueChange: (newValue: unknown) => void onVariableSelectorChange: (newValue: ValueSelector | string) => void showManageInputField?: boolean onManageInputField?: () => void @@ -156,7 +183,7 @@ const FormInputItem: FC = ({ const hooksStore = useContext(HooksStoreContext) const workflowStore = useContext(WorkflowContext) const canUseWorkflowHooks = !!hooksStore && !!workflowStore - const [toolsOptions, setToolsOptions] = useState(null) + const [toolsOptions, setToolsOptions] = useState(null) const [isLoadingToolsOptions, setIsLoadingToolsOptions] = useState(false) const { @@ -165,10 +192,10 @@ const FormInputItem: FC = ({ type, _type, default: defaultValue, - options, + options = [], multiple, scope, - } = schema as any + } = schema as CredentialFormSchemaRuntime const varInput = value[variable] const isString = type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput const isNumber = type === FormTypeEnum.textNumber @@ -215,17 +242,17 @@ const FormInputItem: FC = ({ const getFilterVar = () => { if (isNumber) - return (varPayload: any) => varPayload.type === VarType.number + return (varPayload: Var) => varPayload.type === VarType.number else if (isString) - return (varPayload: any) => [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + return (varPayload: Var) => ([VarType.string, VarType.number, VarType.secret] as VarType[]).includes(varPayload.type) else if (isFile) - return (varPayload: any) => [VarType.file, VarType.arrayFile].includes(varPayload.type) + return (varPayload: Var) => ([VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) else if (isBoolean) - return (varPayload: any) => varPayload.type === VarType.boolean + return (varPayload: Var) => varPayload.type === VarType.boolean else if (isObject) - return (varPayload: any) => varPayload.type === VarType.object + return (varPayload: Var) => varPayload.type === VarType.object else if (isArray) - return (varPayload: any) => [VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject, VarType.arrayMessage].includes(varPayload.type) + return (varPayload: Var) => ([VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject, VarType.arrayMessage] as VarType[]).includes(varPayload.type) return undefined } @@ -320,17 +347,17 @@ const FormInputItem: FC = ({ } } - const handleValueChange = (newValue: any, newType?: VarKindType, nestedNodeConfig?: NestedNodeConfig | null) => { - const normalizedValue = isNumber ? Number.parseFloat(newValue) : newValue + const handleValueChange = (newValue: unknown, newType?: VarKindType, nestedNodeConfig?: NestedNodeConfig | null) => { + const normalizedValue = isNumber ? Number.parseFloat(String(newValue)) : newValue const assemblePlaceholder = nodeId && variable ? `{{#${nodeId}_ext_${variable}.result#}}` : '' const isAssembleValue = typeof normalizedValue === 'string' && assemblePlaceholder && normalizedValue.includes(assemblePlaceholder) - const resolvedType = isAssembleValue + const resolvedType: VarKindType = isAssembleValue ? VarKindType.nested_node - : newType ?? (varInput?.type === VarKindType.nested_node ? VarKindType.nested_node : getVarKindType()) + : newType ?? (varInput?.type === VarKindType.nested_node ? VarKindType.nested_node : getVarKindType() ?? VarKindType.constant) const resolvedNestedNodeConfig = resolvedType === VarKindType.nested_node ? (nestedNodeConfig ?? varInput?.nested_node_config ?? { extractor_node_id: nodeId && variable ? `${nodeId}_ext_${variable}` : '', @@ -351,25 +378,26 @@ const FormInputItem: FC = ({ }) } - const getSelectedLabels = (selectedValues: any[]) => { - if (!selectedValues || selectedValues.length === 0) + const getSelectedLabels = (selectedValues: unknown) => { + if (!Array.isArray(selectedValues) || selectedValues.length === 0) return '' - const optionsList = isDynamicSelect ? (dynamicOptions || options || []) : (options || []) - const selectedOptions = optionsList.filter((opt: any) => - selectedValues.includes(opt.value), + const values = selectedValues as string[] + const optionsList: SelectOptionRow[] = isDynamicSelect ? (dynamicOptions || options || []) : (options || []) + const selectedOptions = optionsList.filter((opt: SelectOptionRow) => + values.includes(opt.value), ) if (selectedOptions.length <= 2) { return selectedOptions - .map((opt: any) => opt.label?.[language] || opt.label?.en_US || opt.value) + .map((opt: SelectOptionRow) => selectOptionDisplayLabel(opt, language)) .join(', ') } return `${selectedOptions.length} selected` } - const handleAppOrModelSelect = (newValue: any) => { + const handleAppOrModelSelect = (newValue: unknown) => { onChange({ ...value, [variable]: { @@ -391,9 +419,9 @@ const FormInputItem: FC = ({ } const availableCheckboxOptions = useMemo(() => ( - (options || []).filter((option: { show_on?: Array<{ variable: string, value: any }> }) => { + (options || []).filter((option: { show_on?: FormShowOnObject[] }) => { if (option.show_on?.length) - return option.show_on.every(showOnItem => value[showOnItem.variable]?.value === showOnItem.value || value[showOnItem.variable] === showOnItem.value) + return option.show_on.every(showOnItem => credentialInputMatchesShowOn(value, showOnItem)) return true }) ), [options, value]) @@ -496,19 +524,19 @@ const FormInputItem: FC = ({ wrapperClassName="h-8 grow" disabled={readOnly} defaultValue={varInput?.value} - items={options.filter((option: { show_on: any[] }) => { + items={options.filter((option: FormOption) => { if (option.show_on.length) - return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) + return option.show_on.every(showOnItem => credentialInputMatchesShowOn(value, showOnItem)) return true - }).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => ({ + }).map((option: FormOption) => ({ value: option.value, - name: option.label[language] || option.label.en_US, + name: selectOptionDisplayLabel(option, language), icon: option.icon, }))} onSelect={item => handleValueChange(item.value as string)} placeholder={placeholder?.[language] || placeholder?.en_US} - renderOption={options.some((opt: any) => opt.icon) + renderOption={options.some((opt: FormOption) => opt.icon) ? ({ item }) => (
{item.icon && ( @@ -540,11 +568,11 @@ const FormInputItem: FC = ({ - {options.filter((option: { show_on: any[] }) => { + {options.filter((option: FormOption) => { if (option.show_on?.length) - return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) + return option.show_on.every(showOnItem => credentialInputMatchesShowOn(value, showOnItem)) return true - }).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => ( + }).map((option: FormOption) => ( = ({ )} - {option.label[language] || option.label.en_US} + {selectOptionDisplayLabel(option, language)}
{selected && ( @@ -579,14 +607,14 @@ const FormInputItem: FC = ({ wrapperClassName="h-8 grow" disabled={readOnly || isLoadingOptions} defaultValue={varInput?.value} - items={(dynamicOptions || options || []).filter((option: { show_on?: any[] }) => { + items={(dynamicOptions || options || []).filter((option: SelectOptionRow) => { if (option.show_on?.length) - return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) + return option.show_on.every(showOnItem => credentialInputMatchesShowOn(value, showOnItem)) return true - }).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => ({ + }).map((option: SelectOptionRow) => ({ value: option.value, - name: option.label[language] || option.label.en_US, + name: selectOptionDisplayLabel(option, language), icon: option.icon, }))} onSelect={item => handleValueChange(item.value as string)} @@ -632,11 +660,11 @@ const FormInputItem: FC = ({
- {(dynamicOptions || options || []).filter((option: { show_on?: any[] }) => { + {(dynamicOptions || options || []).filter((option: SelectOptionRow) => { if (option.show_on?.length) - return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) + return option.show_on.every(showOnItem => credentialInputMatchesShowOn(value, showOnItem)) return true - }).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => ( + }).map((option: SelectOptionRow) => ( = ({ )} - {option.label[language] || option.label.en_US} + {selectOptionDisplayLabel(option, language)}
{selected && ( @@ -670,7 +698,14 @@ const FormInputItem: FC = ({
{ + const v = varInput?.value + if (v === undefined || v === null) + return undefined + if (typeof v === 'string' || typeof v === 'object') + return v as string | object + return undefined + })()} isExpand isInNode language={CodeLanguage.json} diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 2157a8c631..69a2accb43 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -162,7 +162,7 @@ const Item: FC = ({ }) => { const isStructureOutput = itemData.type === VarType.object && (itemData.children as StructuredOutput)?.schema?.properties const isFile = itemData.type === VarType.file && !isStructureOutput - const isObj = ([VarType.object, VarType.file].includes(itemData.type) && itemData.children && (itemData.children as Var[]).length > 0) + const isObj = (([VarType.object, VarType.file] as VarType[]).includes(itemData.type) && itemData.children && (itemData.children as Var[]).length > 0) const isSys = itemData.variable.startsWith('sys.') const isEnv = itemData.variable.startsWith('env.') const isChatVar = itemData.variable.startsWith('conversation.') diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index d0f5c43e24..f6992d73f6 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -326,7 +326,7 @@ const BasePanel: FC = ({ }, [pendingSingleRun, id, handleSingleRun, handleStop, setPendingSingleRun]) const logParams = useLogs() - const passedLogParams = useMemo(() => [BlockEnum.Tool, BlockEnum.Agent, BlockEnum.Iteration, BlockEnum.Loop].includes(data.type) ? logParams : {}, [data.type, logParams]) + const passedLogParams = useMemo(() => ([BlockEnum.Tool, BlockEnum.Agent, BlockEnum.Iteration, BlockEnum.Loop] as BlockEnum[]).includes(data.type) ? logParams : {}, [data.type, logParams]) const storeBuildInTools = useStore(s => s.buildInTools) const { data: buildInTools } = useAllBuiltInTools() diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx index 31a7e3b9fd..b793e7fad9 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx @@ -46,7 +46,8 @@ const LastRun: FC = ({ const [pageHasHide, setPageHasHide] = useState(false) const [pageShowed, setPageShowed] = useState(false) - const hidePageOneStepRunFinished = [NodeRunningStatus.Succeeded, NodeRunningStatus.Failed].includes(hidePageOneStepFinishedStatus!) + const hidePageOneStepRunFinished = hidePageOneStepFinishedStatus != null + && ([NodeRunningStatus.Succeeded, NodeRunningStatus.Failed] as NodeRunningStatus[]).includes(hidePageOneStepFinishedStatus) const canRunLastRun = !isRunAfterSingleRun || isOneStepRunSucceed || isOneStepRunFailed || (pageHasHide && hidePageOneStepRunFinished) const { data: lastRunResult, isFetching, error } = useLastRun(configsMap?.flowType || FlowType.appFlow, configsMap?.flowId || '', nodeId, canRunLastRun) const isRunning = useMemo(() => { @@ -55,7 +56,7 @@ const LastRun: FC = ({ if (!isRunAfterSingleRun) return isFetching - return [NodeRunningStatus.Running, NodeRunningStatus.NotStart].includes(oneStepRunRunningStatus!) + return ([NodeRunningStatus.Running, NodeRunningStatus.NotStart] as NodeRunningStatus[]).includes(oneStepRunRunningStatus!) }, [isFetching, isPaused, isRunAfterSingleRun, oneStepRunRunningStatus]) const noLastRun = (error as any)?.status === 404 @@ -87,7 +88,7 @@ const LastRun: FC = ({ }, [isOneStepRunSucceed, isOneStepRunFailed, oneStepRunRunningStatus]) useEffect(() => { - if ([NodeRunningStatus.Succeeded, NodeRunningStatus.Failed].includes(oneStepRunRunningStatus!)) + if (([NodeRunningStatus.Succeeded, NodeRunningStatus.Failed] as NodeRunningStatus[]).includes(oneStepRunRunningStatus!)) setHidePageOneStepFinishedStatus(oneStepRunRunningStatus!) }, [oneStepRunRunningStatus]) diff --git a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts index 2d85f0ea07..2d958f1533 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts @@ -128,7 +128,7 @@ const varTypeToInputVarType = (type: VarType, { return InputVarType.number if (type === VarType.boolean) return InputVarType.checkbox - if ([VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject].includes(type)) + if (([VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject] as VarType[]).includes(type)) return InputVarType.json if (type === VarType.file) return InputVarType.singleFile diff --git a/web/app/components/workflow/nodes/agent/use-config.ts b/web/app/components/workflow/nodes/agent/use-config.ts index ce8765d833..49311f4d6d 100644 --- a/web/app/components/workflow/nodes/agent/use-config.ts +++ b/web/app/components/workflow/nodes/agent/use-config.ts @@ -157,7 +157,7 @@ const useConfig = (id: string, payload: AgentNodeType) => { // vars const filterMemoryPromptVar = useCallback((varPayload: Var) => { - return [ + return ([ VarKindType.arrayObject, VarKindType.array, VarKindType.number, @@ -167,7 +167,7 @@ const useConfig = (id: string, payload: AgentNodeType) => { VarKindType.arrayNumber, VarKindType.file, VarKindType.arrayFile, - ].includes(varPayload.type) + ] as VarKindType[]).includes(varPayload.type) }, []) const { diff --git a/web/app/components/workflow/nodes/code/use-config.ts b/web/app/components/workflow/nodes/code/use-config.ts index 17b661326f..1335aac2dc 100644 --- a/web/app/components/workflow/nodes/code/use-config.ts +++ b/web/app/components/workflow/nodes/code/use-config.ts @@ -197,7 +197,7 @@ const useConfig = (id: string, payload: CodeNodeType) => { }) const filterVar = useCallback((varPayload: Var) => { - return [VarType.string, VarType.number, VarType.boolean, VarType.secret, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.arrayBoolean, VarType.file, VarType.arrayFile].includes(varPayload.type) + return ([VarType.string, VarType.number, VarType.boolean, VarType.secret, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.arrayBoolean, VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) const handleCodeAndVarsChange = useCallback((code: string, inputVariables: Variable[], outputVariables: OutputVar) => { diff --git a/web/app/components/workflow/nodes/command/default.ts b/web/app/components/workflow/nodes/command/default.ts index e80db6fa62..f5c7c8633f 100644 --- a/web/app/components/workflow/nodes/command/default.ts +++ b/web/app/components/workflow/nodes/command/default.ts @@ -18,12 +18,13 @@ const nodeDefault: NodeDefault = { working_directory: '', command: '', }, - checkValid(payload: CommandNodeType, t: any) { + checkValid(payload: CommandNodeType, t: Parameters['checkValid']>[1]) { let errorMessages = '' const { command } = payload + const translate = t as (key: string, options?: Record) => string if (!errorMessages && !command) - errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.command`, { ns: 'workflow' }) }) + errorMessages = translate(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: translate(`${i18nPrefix}.fields.command`, { ns: 'workflow' }) }) return { isValid: !errorMessages, diff --git a/web/app/components/workflow/nodes/http/components/api-input.tsx b/web/app/components/workflow/nodes/http/components/api-input.tsx index 6c161c2590..5eda0069e6 100644 --- a/web/app/components/workflow/nodes/http/components/api-input.tsx +++ b/web/app/components/workflow/nodes/http/components/api-input.tsx @@ -43,7 +43,7 @@ const ApiInput: FC = ({ const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, filterVar: (varPayload: Var) => { - return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + return ([VarType.string, VarType.number, VarType.secret] as VarType[]).includes(varPayload.type) }, }) diff --git a/web/app/components/workflow/nodes/http/components/authorization/index.tsx b/web/app/components/workflow/nodes/http/components/authorization/index.tsx index 7866d90a91..444ff38469 100644 --- a/web/app/components/workflow/nodes/http/components/authorization/index.tsx +++ b/web/app/components/workflow/nodes/http/components/authorization/index.tsx @@ -51,7 +51,7 @@ const Authorization: FC = ({ const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, filterVar: (varPayload: Var) => { - return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + return ([VarType.string, VarType.number, VarType.secret] as VarType[]).includes(varPayload.type) }, }) diff --git a/web/app/components/workflow/nodes/http/components/edit-body/index.tsx b/web/app/components/workflow/nodes/http/components/edit-body/index.tsx index 90c230b6bb..4ab30cadc8 100644 --- a/web/app/components/workflow/nodes/http/components/edit-body/index.tsx +++ b/web/app/components/workflow/nodes/http/components/edit-body/index.tsx @@ -58,7 +58,7 @@ const EditBody: FC = ({ const { availableVars, availableNodes } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, filterVar: (varPayload: Var) => { - return [VarType.string, VarType.number, VarType.secret, VarType.arrayNumber, VarType.arrayString].includes(varPayload.type) + return ([VarType.string, VarType.number, VarType.secret, VarType.arrayNumber, VarType.arrayString] as VarType[]).includes(varPayload.type) }, }) @@ -100,7 +100,7 @@ const EditBody: FC = ({ }, [onChange, payload]) const filterOnlyFileVariable = (varPayload: Var) => { - return [VarType.file, VarType.arrayFile].includes(varPayload.type) + return ([VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) } const handleBodyValueChange = useCallback((value: string) => { diff --git a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx index a6914f0eb7..76b1126b71 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx @@ -45,9 +45,10 @@ const InputItem: FC = ({ const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, filterVar: (varPayload: Var) => { - const supportVarTypes = [VarType.string, VarType.number, VarType.secret] - if (isSupportFile) + const supportVarTypes: VarType[] = [VarType.string, VarType.number, VarType.secret] + if (isSupportFile) { supportVarTypes.push(VarType.file, VarType.arrayFile) + } return supportVarTypes.includes(varPayload.type) }, diff --git a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx index c653448b1f..f65061c78f 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx @@ -58,7 +58,7 @@ const KeyValueItem: FC = ({ }, [onChange, payload]) const filterOnlyFileVariable = (varPayload: Var) => { - return [VarType.file, VarType.arrayFile].includes(varPayload.type) + return ([VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) } return ( diff --git a/web/app/components/workflow/nodes/http/use-config.ts b/web/app/components/workflow/nodes/http/use-config.ts index fe8c8ac236..a8153b9adf 100644 --- a/web/app/components/workflow/nodes/http/use-config.ts +++ b/web/app/components/workflow/nodes/http/use-config.ts @@ -121,7 +121,7 @@ const useConfig = (id: string, payload: HttpNodeType) => { }, [inputs, setInputs]) const filterVar = useCallback((varPayload: Var) => { - return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + return ([VarType.string, VarType.number, VarType.secret] as VarType[]).includes(varPayload.type) }, []) // curl import panel diff --git a/web/app/components/workflow/nodes/human-input/panel.tsx b/web/app/components/workflow/nodes/human-input/panel.tsx index 7f46a561a1..7e91027abd 100644 --- a/web/app/components/workflow/nodes/human-input/panel.tsx +++ b/web/app/components/workflow/nodes/human-input/panel.tsx @@ -59,7 +59,7 @@ const Panel: FC> = ({ const { availableVars, availableNodesWithParent } = useAvailableVarList(id, { onlyLeafNodeVar: false, filterVar: (varPayload: Var) => { - return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + return ([VarType.string, VarType.number, VarType.secret] as VarType[]).includes(varPayload.type) }, }) diff --git a/web/app/components/workflow/nodes/iteration/use-config.ts b/web/app/components/workflow/nodes/iteration/use-config.ts index 79df409474..fb62a6fee7 100644 --- a/web/app/components/workflow/nodes/iteration/use-config.ts +++ b/web/app/components/workflow/nodes/iteration/use-config.ts @@ -32,7 +32,7 @@ const useConfig = (id: string, payload: IterationNodeType) => { const { inputs, setInputs } = useNodeCrud(id, payload) const filterInputVar = useCallback((varPayload: Var) => { - return [VarType.array, VarType.arrayString, VarType.arrayBoolean, VarType.arrayNumber, VarType.arrayObject, VarType.arrayFile].includes(varPayload.type) + return ([VarType.array, VarType.arrayString, VarType.arrayBoolean, VarType.arrayNumber, VarType.arrayObject, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) const handleInputChange = useCallback((input: ValueSelector | string, _varKindType: VarKindType, varInfo?: Var) => { diff --git a/web/app/components/workflow/nodes/knowledge-base/__tests__/panel.spec.tsx b/web/app/components/workflow/nodes/knowledge-base/__tests__/panel.spec.tsx index 0a15845445..c759269740 100644 --- a/web/app/components/workflow/nodes/knowledge-base/__tests__/panel.spec.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/__tests__/panel.spec.tsx @@ -131,7 +131,7 @@ const panelProps: PanelProps = { runInputData: {}, runInputDataRef: { current: {} }, setRunInputData: vi.fn(), - runResult: undefined, + runResult: null, } describe('KnowledgeBasePanel', () => { diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts b/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts index 8939c6a7c2..d4b8c9ff39 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts @@ -48,7 +48,7 @@ const useSingleRunFormParams = ({ }, [runInputDataRef, setRunInputData]) const filterFileVar = useCallback((varPayload: Var) => { - return [VarType.file, VarType.arrayFile].includes(varPayload.type) + return ([VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) // Get all variables from previous nodes that are file or array of file diff --git a/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx b/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx index 0f5df70f5a..cec5e68ed6 100644 --- a/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx @@ -28,7 +28,7 @@ const ExtractInput: FC = ({ const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, filterVar: (varPayload: Var) => { - return [VarType.number].includes(varPayload.type) + return ([VarType.number] as VarType[]).includes(varPayload.type) }, }) diff --git a/web/app/components/workflow/nodes/list-operator/use-config.ts b/web/app/components/workflow/nodes/list-operator/use-config.ts index 72f92bfea4..6ac657a350 100644 --- a/web/app/components/workflow/nodes/list-operator/use-config.ts +++ b/web/app/components/workflow/nodes/list-operator/use-config.ts @@ -76,7 +76,7 @@ const useConfig = (id: string, payload: ListFilterNodeType) => { return [(itemVarType || VarType.string).substring(0, 1).toUpperCase(), (itemVarType || VarType.string).substring(1)].join('') }, [inputs.variable, itemVarType]) - const hasSubVariable = [VarType.arrayFile].includes(varType) + const hasSubVariable = ([VarType.arrayFile] as VarType[]).includes(varType) const handleVarChanges = useCallback((variable: ValueSelector | string) => { const newInputs = produce(inputs, (draft) => { @@ -99,7 +99,7 @@ const useConfig = (id: string, payload: ListFilterNodeType) => { const filterVar = useCallback((varPayload: Var) => { // Don't know the item struct of VarType.arrayObject, so not support it - return [VarType.arrayNumber, VarType.arrayString, VarType.arrayBoolean, VarType.arrayFile].includes(varPayload.type) + return ([VarType.arrayNumber, VarType.arrayString, VarType.arrayBoolean, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) const handleFilterEnabledChange = useCallback((enabled: boolean) => { diff --git a/web/app/components/workflow/nodes/llm/components/computer-use-config.tsx b/web/app/components/workflow/nodes/llm/components/computer-use-config.tsx index eba61c11c2..3e3d657435 100644 --- a/web/app/components/workflow/nodes/llm/components/computer-use-config.tsx +++ b/web/app/components/workflow/nodes/llm/components/computer-use-config.tsx @@ -4,7 +4,8 @@ import type { ToolSetting } from '../types' import * as React from 'react' import { useTranslation } from 'react-i18next' import Switch from '@/app/components/base/switch' -import Tooltip from '@/app/components/base/tooltip' + +import Tooltip from '@/app/components/base/tooltip-plus' import FieldCollapse from '@/app/components/workflow/nodes/_base/components/collapse/field-collapse' import Split from '@/app/components/workflow/nodes/_base/components/split' import ReferenceToolConfig from './reference-tool-config' diff --git a/web/app/components/workflow/nodes/llm/components/config-context-item.tsx b/web/app/components/workflow/nodes/llm/components/config-context-item.tsx index d662712e45..eaff634017 100644 --- a/web/app/components/workflow/nodes/llm/components/config-context-item.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-context-item.tsx @@ -9,7 +9,7 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' +} from '@/app/components/base/portal-to-follow-elem-plus' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' import VariableLabelInSelect from '@/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-select' import { BlockEnum } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx index ea7b3a9515..218b4c087e 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx @@ -13,7 +13,7 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' +} from '@/app/components/base/portal-to-follow-elem-plus' import AddButton from '@/app/components/workflow/nodes/_base/components/add-button' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' diff --git a/web/app/components/workflow/nodes/llm/components/tools/index.tsx b/web/app/components/workflow/nodes/llm/components/tools/index.tsx index 83fefdaf8a..6d666763b2 100644 --- a/web/app/components/workflow/nodes/llm/components/tools/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/tools/index.tsx @@ -1,7 +1,8 @@ import type { ToolValue } from '@/app/components/workflow/block-selector/types' import { memo } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' + +import Tooltip from '@/app/components/base/tooltip-plus' import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector' import { BoxGroup } from '@/app/components/workflow/nodes/_base/components/layout' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/llm/components/tools/max-iterations.tsx b/web/app/components/workflow/nodes/llm/components/tools/max-iterations.tsx index 2354f5fcf8..fb423a0309 100644 --- a/web/app/components/workflow/nodes/llm/components/tools/max-iterations.tsx +++ b/web/app/components/workflow/nodes/llm/components/tools/max-iterations.tsx @@ -1,5 +1,6 @@ import { memo } from 'react' -import Tooltip from '@/app/components/base/tooltip' + +import Tooltip from '@/app/components/base/tooltip-plus' import { NumberField, NumberFieldControls, diff --git a/web/app/components/workflow/nodes/llm/types.ts b/web/app/components/workflow/nodes/llm/types.ts index 5190792d79..7ef67da0ad 100644 --- a/web/app/components/workflow/nodes/llm/types.ts +++ b/web/app/components/workflow/nodes/llm/types.ts @@ -8,9 +8,9 @@ export type Tool = { tool_name: string plugin_unique_identifier?: string credential_id?: string - parameters?: Record - settings?: Record - extra?: Record + parameters?: Record + settings?: Record + extra?: Record } export type ToolSetting = { diff --git a/web/app/components/workflow/nodes/llm/use-config.ts b/web/app/components/workflow/nodes/llm/use-config.ts index 94c8a67903..543d79f50f 100644 --- a/web/app/components/workflow/nodes/llm/use-config.ts +++ b/web/app/components/workflow/nodes/llm/use-config.ts @@ -357,15 +357,15 @@ const useConfig = (id: string, payload: LLMNodeType) => { }, [setInputs, deleteNodeInspectorVars, id]) const filterInputVar = useCallback((varPayload: Var) => { - return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type) + return ([VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) const filterJinja2InputVar = useCallback((varPayload: Var) => { - return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.arrayBoolean, VarType.arrayObject, VarType.object, VarType.array, VarType.boolean].includes(varPayload.type) + return ([VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.arrayBoolean, VarType.arrayObject, VarType.object, VarType.array, VarType.boolean] as VarType[]).includes(varPayload.type) }, []) const filterMemoryPromptVar = useCallback((varPayload: Var) => { - return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type) + return ([VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) // reasoning format diff --git a/web/app/components/workflow/nodes/llm/use-node-skills.ts b/web/app/components/workflow/nodes/llm/use-node-skills.ts index 3a87138539..cbb656786a 100644 --- a/web/app/components/workflow/nodes/llm/use-node-skills.ts +++ b/web/app/components/workflow/nodes/llm/use-node-skills.ts @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query' import { useMemo } from 'react' -import { useStoreApi } from 'reactflow' +import { useStore as useReactFlowStore, useStoreApi } from 'reactflow' import { useStore as useAppStore } from '@/app/components/app/store' import { consoleClient, consoleQuery } from '@/service/client' @@ -21,6 +21,7 @@ type UseNodeSkillsParams = { export function useNodeSkills({ nodeId, promptTemplateKey, enabled = true }: UseNodeSkillsParams) { const appId = useAppStore(s => s.appDetail?.id) const store = useStoreApi() + const nodeData = useReactFlowStore(s => s.getNodes().find(n => n.id === nodeId)?.data) const isQueryEnabled = enabled && !!appId && !!nodeId const queryKey = useMemo(() => { @@ -33,8 +34,10 @@ export function useNodeSkills({ nodeId, promptTemplateKey, enabled = true }: Use }), nodeId, promptTemplateKey, + nodeData, + store, ] - }, [appId, nodeId, promptTemplateKey]) + }, [appId, nodeId, promptTemplateKey, nodeData, store]) const { data, isLoading } = useQuery({ queryKey, diff --git a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts index e0c4c97fad..e8fab7029e 100644 --- a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts @@ -73,7 +73,7 @@ const useSingleRunFormParams = ({ })() const filterMemoryPromptVar = useCallback((varPayload: Var) => { - return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type) + return ([VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) const { diff --git a/web/app/components/workflow/nodes/loop/use-config.ts b/web/app/components/workflow/nodes/loop/use-config.ts index 95a3108c08..78739e12eb 100644 --- a/web/app/components/workflow/nodes/loop/use-config.ts +++ b/web/app/components/workflow/nodes/loop/use-config.ts @@ -51,7 +51,7 @@ const useConfig = (id: string, payload: LoopNodeType) => { }, [setInputs]) const filterInputVar = useCallback((varPayload: Var) => { - return [VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject, VarType.arrayFile].includes(varPayload.type) + return ([VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) // output diff --git a/web/app/components/workflow/nodes/parameter-extractor/use-config.ts b/web/app/components/workflow/nodes/parameter-extractor/use-config.ts index 71e94e95db..a36a1c4af4 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/use-config.ts +++ b/web/app/components/workflow/nodes/parameter-extractor/use-config.ts @@ -48,7 +48,7 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => { }, [doSetInputs, defaultRolePrefix]) const filterVar = useCallback((varPayload: Var) => { - return [VarType.string].includes(varPayload.type) + return ([VarType.string] as VarType[]).includes(varPayload.type) }, []) const handleInputVarChange = useCallback((newInputVar: ValueSelector | string) => { @@ -170,7 +170,7 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => { const isSupportFunctionCall = supportFunctionCall(currModel?.features) const filterInputVar = useCallback((varPayload: Var) => { - return [VarType.number, VarType.string].includes(varPayload.type) + return ([VarType.number, VarType.string] as VarType[]).includes(varPayload.type) }, []) const { diff --git a/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts b/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts index 52a4462153..96ea4858a8 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts @@ -72,7 +72,7 @@ const useSingleRunFormParams = ({ }, [runInputDataRef, setRunInputData]) const filterVisionInputVar = useCallback((varPayload: Var) => { - return [VarType.file, VarType.arrayFile].includes(varPayload.type) + return ([VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) const { availableVars: availableVisionVars, diff --git a/web/app/components/workflow/nodes/question-classifier/use-config.ts b/web/app/components/workflow/nodes/question-classifier/use-config.ts index 5a46897de5..cdb9e9b7e0 100644 --- a/web/app/components/workflow/nodes/question-classifier/use-config.ts +++ b/web/app/components/workflow/nodes/question-classifier/use-config.ts @@ -122,11 +122,11 @@ const useConfig = (id: string, payload: QuestionClassifierNodeType) => { }, [inputs, setInputs]) const filterInputVar = useCallback((varPayload: Var) => { - return [VarType.number, VarType.string].includes(varPayload.type) + return ([VarType.number, VarType.string] as VarType[]).includes(varPayload.type) }, []) const filterVisionInputVar = useCallback((varPayload: Var) => { - return [VarType.file, VarType.arrayFile].includes(varPayload.type) + return ([VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) const { diff --git a/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts b/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts index 9b4b05367e..87eff5c37a 100644 --- a/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts @@ -71,7 +71,7 @@ const useSingleRunFormParams = ({ }, [runInputDataRef, setRunInputData]) const filterVisionInputVar = useCallback((varPayload: Var) => { - return [VarType.file, VarType.arrayFile].includes(varPayload.type) + return ([VarType.file, VarType.arrayFile] as VarType[]).includes(varPayload.type) }, []) const { availableVars: availableVisionVars, diff --git a/web/app/components/workflow/nodes/sub-graph-start/index.tsx b/web/app/components/workflow/nodes/sub-graph-start/index.tsx index 5970dfd1f0..132aa71040 100644 --- a/web/app/components/workflow/nodes/sub-graph-start/index.tsx +++ b/web/app/components/workflow/nodes/sub-graph-start/index.tsx @@ -4,7 +4,8 @@ import { memo } from 'react' import { useTranslation } from 'react-i18next' import { AssembleVariablesAlt } from '@/app/components/base/icons/src/vender/line/general' import { Agent } from '@/app/components/base/icons/src/vender/workflow' -import Tooltip from '@/app/components/base/tooltip' + +import Tooltip from '@/app/components/base/tooltip-plus' import { NodeSourceHandle } from '@/app/components/workflow/nodes/_base/components/node-handle' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/template-transform/use-config.ts b/web/app/components/workflow/nodes/template-transform/use-config.ts index ed62ea2fa0..d2f3e59437 100644 --- a/web/app/components/workflow/nodes/template-transform/use-config.ts +++ b/web/app/components/workflow/nodes/template-transform/use-config.ts @@ -75,7 +75,7 @@ const useConfig = (id: string, payload: TemplateTransformNodeType) => { }, [setInputs]) const filterVar = useCallback((varPayload: Var) => { - return [VarType.string, VarType.number, VarType.boolean, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayBoolean, VarType.arrayObject].includes(varPayload.type) + return ([VarType.string, VarType.number, VarType.boolean, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayBoolean, VarType.arrayObject] as VarType[]).includes(varPayload.type) }, []) return { diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/right-panel.tsx b/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/right-panel.tsx index 5d471254ea..f29c8b6265 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/right-panel.tsx +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/components/right-panel.tsx @@ -9,7 +9,7 @@ import Button from '@/app/components/base/button' import { CopyFeedbackNew } from '@/app/components/base/copy-feedback' import { CodeAssistant } from '@/app/components/base/icons/src/vender/line/general' import Loading from '@/app/components/base/loading' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem-plus' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-generate.ts b/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-generate.ts index ce0960e95c..46b0e21557 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-generate.ts +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/hooks/use-context-generate.ts @@ -16,7 +16,7 @@ import { useSessionStorageState } from 'ahooks' import useBoolean from 'ahooks/lib/useBoolean' import { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' @@ -394,10 +394,7 @@ const useContextGenerate = ({ if (response.error) { shouldMarkFetched = false - Toast.notify({ - type: 'error', - message: t('modal.errors.networkError', { ns: 'pluginTrigger' }), - }) + toast.error(t('modal.errors.networkError', { ns: 'pluginTrigger' })) setSuggestedQuestions([]) return } @@ -411,10 +408,7 @@ const useContextGenerate = ({ return } shouldMarkFetched = false - Toast.notify({ - type: 'error', - message: t('modal.errors.networkError', { ns: 'pluginTrigger' }), - }) + toast.error(t('modal.errors.networkError', { ns: 'pluginTrigger' })) setSuggestedQuestions([]) } finally { @@ -477,10 +471,7 @@ const useContextGenerate = ({ }) if (response.error) { - Toast.notify({ - type: 'error', - message: response.error, - }) + toast.error(response.error) return } diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx b/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx index 2b7f7ce070..72b52b5560 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx @@ -4,7 +4,8 @@ import type { Node, NodeOutPutVar } from '@/app/components/workflow/types' import type { ContextGenerateResponse } from '@/service/debug' import * as React from 'react' import { forwardRef, useCallback, useImperativeHandle, useMemo } from 'react' -import Modal from '@/app/components/base/modal' +import { useTranslation } from 'react-i18next' +import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' import { useHooksStore } from '@/app/components/workflow/hooks-store' import { useNodeDataUpdate } from '@/app/components/workflow/hooks/use-node-data-update' import { useStore, useWorkflowStore } from '@/app/components/workflow/store' @@ -63,6 +64,7 @@ const ContextGenerateModal = forwardRef(({ availableNodes, onOpenInternalViewAndRun, }, ref) => { + const { t } = useTranslation('workflow') const configsMap = useHooksStore(s => s.configsMap) const nodes = useStore(s => s.nodes) const workflowStore = useWorkflowStore() @@ -217,57 +219,67 @@ const ContextGenerateModal = forwardRef(({ const canApply = !!current return ( - { + if (!open) + handleCloseModal() + }} > -
- - applyToNode(true)} - canRun={canRun} - canApply={canApply} - isRunning={isRunning} - onClose={handleCloseModal} - /> -
-
+ + + + {t('nodes.tool.contextGenerate.title')} + +
+ + applyToNode(true)} + canRun={canRun} + canApply={canApply} + isRunning={isRunning} + onClose={handleCloseModal} + /> +
+
+ ) }) diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx index 2c8e8cd21d..240122b8a5 100644 --- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx +++ b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx @@ -50,7 +50,7 @@ const InputVarList: FC = ({ const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, filterVar: (varPayload: Var) => { - return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + return ([VarType.string, VarType.number, VarType.secret] as VarType[]).includes(varPayload.type) }, }) const paramType = (type: string) => { diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts index 58bcd5900d..fae57bd297 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts @@ -10,7 +10,7 @@ import type { Node as WorkflowNode, } from '@/app/components/workflow/types' import { useCallback, useMemo } from 'react' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { NULL_STRATEGY } from '@/app/components/workflow/nodes/_base/constants' import { Type } from '@/app/components/workflow/nodes/llm/types' import { BlockEnum, EditionType, isPromptMessageContext, PromptRole, VarType } from '@/app/components/workflow/types' @@ -445,10 +445,7 @@ export function useMixedVariableExtractor({ }) } catch (error) { - Toast.notify({ - type: 'error', - message: error as string, - }) + toast.error(error instanceof Error ? error.message : String(error)) } }, [applyNestedNodeGraphData, configsMap?.flowId, configsMap?.flowType, paramKey, resolveNestedNodeParameterSchema, toolNodeId]) diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx index 417e354314..2bca3e0e6f 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx @@ -84,14 +84,18 @@ const ChatVariableModal = ({ return objectPlaceholder }, [type]) const getObjectValue = useCallback(() => { - if (!chatVar || Object.keys(chatVar.value).length === 0) + const raw = chatVar?.value + if (!chatVar || raw === null || typeof raw !== 'object' || Array.isArray(raw) || Object.keys(raw).length === 0) return [DEFAULT_OBJECT_VALUE] - return Object.keys(chatVar.value).map((key) => { + return Object.keys(raw).map((key) => { + const v = raw[key] + const isStr = typeof v === 'string' + const isNum = typeof v === 'number' return { key, - type: typeof chatVar.value[key] === 'string' ? ChatVarType.String : ChatVarType.Number, - value: chatVar.value[key], + type: isStr ? ChatVarType.String : ChatVarType.Number, + value: isStr || isNum ? v : undefined, } }) }, [chatVar]) diff --git a/web/app/components/workflow/panel/comments-panel/index.tsx b/web/app/components/workflow/panel/comments-panel/index.tsx index d97c7f0dbc..2065a8dddd 100644 --- a/web/app/components/workflow/panel/comments-panel/index.tsx +++ b/web/app/components/workflow/panel/comments-panel/index.tsx @@ -1,6 +1,5 @@ import type { WorkflowCommentList } from '@/service/workflow-comment' import { RiCheckboxCircleFill, RiCheckboxCircleLine, RiCheckLine, RiCloseLine, RiFilter3Line } from '@remixicon/react' -import { useParams } from 'next/navigation' import { memo, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' @@ -12,6 +11,7 @@ import { useStore } from '@/app/components/workflow/store' import { ControlMode } from '@/app/components/workflow/types' import { useAppContext } from '@/context/app-context' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' +import { useParams } from '@/next/navigation' import { resolveWorkflowComment } from '@/service/workflow-comment' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx b/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx index b35bd86cbd..6cfff7ad7b 100644 --- a/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx @@ -64,7 +64,7 @@ const ConversationVariableModal = ({ const [isCopied, setIsCopied] = React.useState(false) const handleCopy = useCallback(() => { - copy(currentVar.value) + copy(typeof currentVar.value === 'string' ? currentVar.value : JSON.stringify(currentVar.value)) setIsCopied(true) setTimeout(() => { setIsCopied(false) diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks/create-llm-trace-builder.ts b/web/app/components/workflow/panel/debug-and-preview/hooks/create-llm-trace-builder.ts index 5e3e238326..a8578fb91d 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks/create-llm-trace-builder.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks/create-llm-trace-builder.ts @@ -84,9 +84,12 @@ export function createLLMTraceBuilder() { if (chunkType === 'tool_call') { const lastModel = trace.findLast(item => item.type === 'model') if (lastModel) { - if (!lastModel.output.tool_calls) - lastModel.output.tool_calls = [] - lastModel.output.tool_calls.push({ + const modelOut = lastModel.output as Record & { + tool_calls?: { id: string, name: string, arguments: string }[] + } + if (!modelOut.tool_calls) + modelOut.tool_calls = [] + modelOut.tool_calls.push({ id: meta.tool_call_id || '', name: meta.tool_name || '', arguments: meta.tool_arguments || '', diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks/use-chat-message-sender.ts b/web/app/components/workflow/panel/debug-and-preview/hooks/use-chat-message-sender.ts index 10a4798de4..a50e52c31c 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks/use-chat-message-sender.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks/use-chat-message-sender.ts @@ -26,7 +26,7 @@ import { getProcessedFiles, getProcessedFilesFromResponse, } from '@/app/components/base/file-uploader/utils' -import { useToastContext } from '@/app/components/base/toast/context' +import { toast } from '@/app/components/base/ui/toast' import { sseGet, } from '@/service/base' @@ -90,7 +90,6 @@ export function useChatMessageSender({ updateCurrentQAOnTree, }: UseChatMessageSenderParams) { const { t } = useTranslation() - const { notify } = useToastContext() const { handleRun } = useWorkflowRun() const workflowStore = useWorkflowStore() @@ -132,7 +131,7 @@ export function useChatMessageSender({ { onGetSuggestedQuestions }: SendCallback, ) => { if (workflowStore.getState().isResponding) { - notify({ type: 'info', message: t('errorMessage.waitForResponse', { ns: 'appDebug' }) }) + toast.info(t('errorMessage.waitForResponse', { ns: 'appDebug' })) return false } @@ -559,7 +558,6 @@ export function useChatMessageSender({ return true }, [ workflowStore, - notify, t, setSuggestedQuestionsAbortController, setWorkflowEventsAbortController, diff --git a/web/app/components/workflow/panel/env-panel/env-item.tsx b/web/app/components/workflow/panel/env-panel/env-item.tsx index 11e60c6592..8211f47861 100644 --- a/web/app/components/workflow/panel/env-panel/env-item.tsx +++ b/web/app/components/workflow/panel/env-panel/env-item.tsx @@ -47,7 +47,7 @@ const EnvItem = ({
-
{env.value_type === 'secret' ? envSecrets[env.id] : env.value}
+
{env.value_type === 'secret' ? envSecrets[env.id] : String(env.value ?? '')}
{env.description && ( <> diff --git a/web/app/components/workflow/panel/env-panel/index.tsx b/web/app/components/workflow/panel/env-panel/index.tsx index 5fbc323628..0e869df728 100644 --- a/web/app/components/workflow/panel/env-panel/index.tsx +++ b/web/app/components/workflow/panel/env-panel/index.tsx @@ -120,7 +120,7 @@ const EnvPanel = () => { if (env.value_type === 'secret') { setEnvSecrets({ ...envSecrets, - [env.id]: formatSecret(env.value), + [env.id]: formatSecret(typeof env.value === 'string' ? env.value : JSON.stringify(env.value)), }) } newList = [env, ...envList] @@ -158,7 +158,7 @@ const EnvPanel = () => { newEnv = env setEnvSecrets({ ...envSecrets, - [env.id]: formatSecret(env.value), + [env.id]: formatSecret(typeof env.value === 'string' ? env.value : JSON.stringify(env.value)), }) } else { @@ -171,7 +171,7 @@ const EnvPanel = () => { newEnv = env setEnvSecrets({ ...envSecrets, - [env.id]: formatSecret(env.value), + [env.id]: formatSecret(typeof env.value === 'string' ? env.value : JSON.stringify(env.value)), }) } } diff --git a/web/app/components/workflow/panel/version-history-panel/__tests__/index.spec.tsx b/web/app/components/workflow/panel/version-history-panel/__tests__/index.spec.tsx index 3d73d5a61c..3d89b99be8 100644 --- a/web/app/components/workflow/panel/version-history-panel/__tests__/index.spec.tsx +++ b/web/app/components/workflow/panel/version-history-panel/__tests__/index.spec.tsx @@ -6,8 +6,7 @@ import { VersionHistoryContextMenuOptions, WorkflowVersion } from '../../../type const mockHandleRestoreFromPublishedWorkflow = vi.fn() const mockHandleLoadBackupDraft = vi.fn() -const mockHandleRefreshWorkflowDraft = vi.fn() -const mockRestoreWorkflow = vi.fn() +const mockRequestRestore = vi.fn() const mockSetCurrentVersion = vi.fn() const mockSetShowWorkflowVersionHistoryPanel = vi.fn() const mockWorkflowStoreSetState = vi.fn() @@ -60,7 +59,6 @@ vi.mock('@/service/use-workflow', () => ({ useDeleteWorkflow: () => ({ mutateAsync: vi.fn() }), useInvalidAllLastRun: () => vi.fn(), useResetWorkflowVersionHistory: () => vi.fn(), - useRestoreWorkflow: () => ({ mutateAsync: mockRestoreWorkflow }), useUpdateWorkflow: () => ({ mutateAsync: vi.fn() }), useWorkflowVersionHistory: () => ({ data: { @@ -89,7 +87,7 @@ vi.mock('@/service/use-workflow', () => ({ vi.mock('../../../hooks', () => ({ useDSL: () => ({ handleExportDSL: vi.fn() }), - useWorkflowRefreshDraft: () => ({ handleRefreshWorkflowDraft: mockHandleRefreshWorkflowDraft }), + useLeaderRestore: () => ({ requestRestore: mockRequestRestore }), useWorkflowRun: () => ({ handleRestoreFromPublishedWorkflow: mockHandleRestoreFromPublishedWorkflow, handleLoadBackupDraft: mockHandleLoadBackupDraft, @@ -174,6 +172,7 @@ vi.mock('../version-history-item', () => ({ describe('VersionHistoryPanel', () => { beforeEach(() => { vi.clearAllMocks() + mockRequestRestore.mockReset() mockCurrentVersion = null }) @@ -211,7 +210,7 @@ describe('VersionHistoryPanel', () => { }) }) - it('should set current version before confirming restore from context menu', async () => { + it('should request restore with the published version when confirming from context menu', async () => { const { VersionHistoryPanel } = await import('../index') render( @@ -227,19 +226,21 @@ describe('VersionHistoryPanel', () => { fireEvent.click(screen.getByText('confirm restore')) await waitFor(() => { - expect(mockSetCurrentVersion).toHaveBeenCalledWith(expect.objectContaining({ - id: 'published-version-id', - })) - expect(mockRestoreWorkflow).toHaveBeenCalledWith('/apps/app-1/workflows/published-version-id/restore') + expect(mockRequestRestore).toHaveBeenCalledWith( + expect.objectContaining({ versionId: 'published-version-id' }), + expect.any(Object), + ) expect(mockWorkflowStoreSetState).toHaveBeenCalledWith({ isRestoring: false }) expect(mockWorkflowStoreSetState).toHaveBeenCalledWith({ backupDraft: undefined }) - expect(mockHandleRefreshWorkflowDraft).toHaveBeenCalled() }) }) it('should keep restore mode backup state when restore request fails', async () => { const { VersionHistoryPanel } = await import('../index') - mockRestoreWorkflow.mockRejectedValueOnce(new Error('restore failed')) + mockRequestRestore.mockImplementation((_data, callbacks) => { + callbacks?.onError?.() + callbacks?.onSettled?.() + }) mockCurrentVersion = createVersionHistory({ id: 'draft-version-id', version: WorkflowVersion.Draft, @@ -258,12 +259,9 @@ describe('VersionHistoryPanel', () => { fireEvent.click(screen.getByText('confirm restore')) await waitFor(() => { - expect(mockRestoreWorkflow).toHaveBeenCalledWith('/apps/app-1/workflows/published-version-id/restore') + expect(mockRequestRestore).toHaveBeenCalled() }) - expect(mockWorkflowStoreSetState).not.toHaveBeenCalledWith({ isRestoring: false }) - expect(mockWorkflowStoreSetState).not.toHaveBeenCalledWith({ backupDraft: undefined }) expect(mockSetCurrentVersion).not.toHaveBeenCalled() - expect(mockHandleRefreshWorkflowDraft).not.toHaveBeenCalled() }) }) diff --git a/web/app/components/workflow/panel/version-history-panel/index.spec.tsx b/web/app/components/workflow/panel/version-history-panel/index.spec.tsx index 38836aa410..46e717edaf 100644 --- a/web/app/components/workflow/panel/version-history-panel/index.spec.tsx +++ b/web/app/components/workflow/panel/version-history-panel/index.spec.tsx @@ -5,6 +5,12 @@ const mockHandleRestoreFromPublishedWorkflow = vi.fn() const mockHandleLoadBackupDraft = vi.fn() const mockSetCurrentVersion = vi.fn() +type VersionHistoryMockState = { + setShowWorkflowVersionHistoryPanel: ReturnType + currentVersion: null + setCurrentVersion: typeof mockSetCurrentVersion +} + vi.mock('@/context/app-context', () => ({ useSelector: () => ({ id: 'test-user-id' }), })) @@ -88,8 +94,8 @@ vi.mock('../../hooks-store', () => ({ })) vi.mock('../../store', () => ({ - useStore: (selector: (state: any) => any) => { - const state = { + useStore: (selector: (state: VersionHistoryMockState) => T) => { + const state: VersionHistoryMockState = { setShowWorkflowVersionHistoryPanel: vi.fn(), currentVersion: null, setCurrentVersion: mockSetCurrentVersion, diff --git a/web/app/components/workflow/panel/version-history-panel/index.tsx b/web/app/components/workflow/panel/version-history-panel/index.tsx index 173f311ed2..d5837552d9 100644 --- a/web/app/components/workflow/panel/version-history-panel/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/index.tsx @@ -10,8 +10,8 @@ import Divider from '@/app/components/base/divider' import { useFeaturesStore } from '@/app/components/base/features/hooks' import { toast } from '@/app/components/base/ui/toast' import { useSelector as useAppContextSelector } from '@/context/app-context' -import { useDeleteWorkflow, useInvalidAllLastRun, useResetWorkflowVersionHistory, useRestoreWorkflow, useUpdateWorkflow, useWorkflowVersionHistory } from '@/service/use-workflow' -import { useDSL, useLeaderRestore, useWorkflowRefreshDraft, useWorkflowRun } from '../../hooks' +import { useDeleteWorkflow, useInvalidAllLastRun, useResetWorkflowVersionHistory, useUpdateWorkflow, useWorkflowVersionHistory } from '@/service/use-workflow' +import { useDSL, useLeaderRestore, useWorkflowRun } from '../../hooks' import { useHooksStore } from '../../hooks-store' import { useStore, useWorkflowStore } from '../../store' import { VersionHistoryContextMenuOptions, WorkflowVersion, WorkflowVersionFilterOptions } from '../../types' @@ -35,11 +35,11 @@ export type VersionHistoryPanelProps = { export const VersionHistoryPanel = ({ getVersionListUrl, deleteVersionUrl, - restoreVersionUrl, + restoreVersionUrl: _restoreVersionUrl, updateVersionUrl, latestVersionId, }: VersionHistoryPanelProps) => { - const [filterValue, setFilterValue] = useState(WorkflowVersionFilterOptions.all) + const [filterValue, setFilterValue] = useState(WorkflowVersionFilterOptions.all) const [isOnlyShowNamedVersions, setIsOnlyShowNamedVersions] = useState(false) const [operatedItem, setOperatedItem] = useState() const [restoreConfirmOpen, setRestoreConfirmOpen] = useState(false) @@ -49,7 +49,6 @@ export const VersionHistoryPanel = ({ const { handleRestoreFromPublishedWorkflow, handleLoadBackupDraft } = useWorkflowRun() const { requestRestore } = useLeaderRestore() const featuresStore = useFeaturesStore() - const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() const { handleExportDSL } = useDSL() const setShowWorkflowVersionHistoryPanel = useStore(s => s.setShowWorkflowVersionHistoryPanel) const currentVersion = useStore(s => s.currentVersion) @@ -146,7 +145,6 @@ export const VersionHistoryPanel = ({ }, []) const resetWorkflowVersionHistory = useResetWorkflowVersionHistory() - const { mutateAsync: restoreWorkflow } = useRestoreWorkflow() const handleRestore = useCallback(async (item: VersionHistory) => { setShowWorkflowVersionHistoryPanel(false) diff --git a/web/app/components/workflow/panel/workflow-preview.tsx b/web/app/components/workflow/panel/workflow-preview.tsx index b24bbcff77..aac32286bf 100644 --- a/web/app/components/workflow/panel/workflow-preview.tsx +++ b/web/app/components/workflow/panel/workflow-preview.tsx @@ -10,7 +10,7 @@ import ActionButton from '@/app/components/base/action-button' import Button from '@/app/components/base/button' import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows' import Loading from '@/app/components/base/loading' -import Tooltip from '@/app/components/base/tooltip' +import Tooltip from '@/app/components/base/tooltip-plus' import { toast } from '@/app/components/base/ui/toast' import { submitHumanInputForm } from '@/service/workflow' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/run/loop-log/loop-result-panel.tsx b/web/app/components/workflow/run/loop-log/loop-result-panel.tsx index 299ae9647d..d37b166474 100644 --- a/web/app/components/workflow/run/loop-log/loop-result-panel.tsx +++ b/web/app/components/workflow/run/loop-log/loop-result-panel.tsx @@ -136,7 +136,7 @@ const LoopResultPanel: FC = ({ title={
{t('nodes.loop.loopVariables', { ns: 'workflow' }).toLocaleUpperCase()}
} language={CodeLanguage.json} height={112} - value={loopVariableMap[index]} + value={loopVariableMap[String(index)] as string | object} isJSONStringifyBeauty />
diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index d5c8d2ec2b..dfda405345 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -136,7 +136,12 @@ const NodePanel: FC = ({ )} /> )} - + {nodeInfo.title}
@@ -256,7 +261,7 @@ const NodePanel: FC = ({ readOnly title={
{inputsTitle}
} language={CodeLanguage.json} - value={nodeInfo.inputs} + value={nodeInfo.inputs as string | object} isJSONStringifyBeauty footer={nodeInfo.inputs_truncated && } /> @@ -268,7 +273,7 @@ const NodePanel: FC = ({ readOnly title={
{processDataTitle}
} language={CodeLanguage.json} - value={nodeInfo.process_data} + value={nodeInfo.process_data as string | object} isJSONStringifyBeauty />
@@ -279,7 +284,7 @@ const NodePanel: FC = ({ readOnly title={
{outputTitle}
} language={CodeLanguage.json} - value={nodeInfo.outputs} + value={nodeInfo.outputs as string | object} isJSONStringifyBeauty tip={} footer={nodeInfo.outputs_truncated && } diff --git a/web/app/components/workflow/run/utils/format-log/agent/index.ts b/web/app/components/workflow/run/utils/format-log/agent/index.ts index 19acd8c120..bebfd12268 100644 --- a/web/app/components/workflow/run/utils/format-log/agent/index.ts +++ b/web/app/components/workflow/run/utils/format-log/agent/index.ts @@ -2,7 +2,7 @@ import type { AgentLogItem, AgentLogItemWithChildren, NodeTracing } from '@/type import { cloneDeep } from 'es-toolkit/object' import { BlockEnum } from '@/app/components/workflow/types' -const supportedAgentLogNodes = [BlockEnum.Agent, BlockEnum.Tool] +const supportedAgentLogNodes: BlockEnum[] = [BlockEnum.Agent, BlockEnum.Tool] const remove = (node: AgentLogItemWithChildren, removeId: string) => { let { children } = node diff --git a/web/app/components/workflow/selection-contextmenu.tsx b/web/app/components/workflow/selection-contextmenu.tsx index 5b0c68fe5d..a260f9dd30 100644 --- a/web/app/components/workflow/selection-contextmenu.tsx +++ b/web/app/components/workflow/selection-contextmenu.tsx @@ -20,7 +20,7 @@ import { import { useTranslation } from 'react-i18next' import { useStore as useReactFlowStore } from 'reactflow' import { shallow } from 'zustand/shallow' -import Tooltip from '@/app/components/base/tooltip' +import Tooltip from '@/app/components/base/tooltip-plus' import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow' import { useNodesInteractions, useNodesReadOnly, useNodesSyncDraft } from './hooks' import { useSelectionInteractions } from './hooks/use-selection-interactions' diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-block.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-block.tsx index c8216141f1..8e4a52accd 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-block.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-block.tsx @@ -11,7 +11,7 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' +} from '@/app/components/base/portal-to-follow-elem-plus' import { useBasicTypeaheadTriggerMatch } from '@/app/components/base/prompt-editor/hooks' import { $splitNodeContainingQuery } from '@/app/components/base/prompt-editor/utils' import { FilePickerPanel } from './file-picker-panel' diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-upload-modal.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-upload-modal.tsx index 83307cf842..92aa0d9ea4 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-upload-modal.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-upload-modal.tsx @@ -8,13 +8,13 @@ import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' -import Modal from '@/app/components/base/modal' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import Toast from '@/app/components/base/toast' +} from '@/app/components/base/portal-to-follow-elem-plus' +import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' +import { toast } from '@/app/components/base/ui/toast' import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card' import { ROOT_ID } from '@/app/components/workflow/skill/constants' import TreeGuideLines from '@/app/components/workflow/skill/file-tree/tree/tree-guide-lines' @@ -261,187 +261,190 @@ const FilePickerUploadModal = ({ onClose() } catch { - Toast.notify({ - type: 'error', - message: t('skillSidebar.menu.createError'), - }) + toast.error(t('skillSidebar.menu.createError')) } }, [appId, canCreate, effectiveUploadFolderId, emitTreeUpdate, onClose, t, trimmedFileName, uploadFile]) const modeLabel = t('skillEditor.uploadIn') return ( - { + if (!open) + handleClose() + }} + disablePointerDismissal={isBusy} > -
-
-
- setMode('create')} - selected={mode === 'create'} - disabled={isBusy} - /> - setMode('upload')} - selected={mode === 'upload'} - disabled={isBusy} - /> -
-
-
{modeLabel}
- - - - - -
{ - e.preventDefault() - e.stopPropagation() - }} - > - - key={folderPickerVersion} - data={uploadInTreeData} - idAccessor="id" - childrenAccessor="children" - width="100%" - className="pb-1" - height={240} - rowHeight={24} - indent={20} - overscanCount={5} - openByDefault={false} - initialOpenState={folderPickerOpenState} - disableDrag - disableDrop - > - {(props: NodeRendererProps) => ( - - )} - -
-
-
-
- {mode === 'create' && ( -
-
{t('skillSidebar.fileNamePlaceholder')}
- setFileName(e.target.value)} - placeholder={t('skillSidebar.fileNamePlaceholder') || ''} + + {!isBusy && } + + {t('skillEditor.addFiles')} + +
+
+
+ setMode('create')} + selected={mode === 'create'} + disabled={isBusy} + /> + setMode('upload')} + selected={mode === 'upload'} disabled={isBusy} - onKeyDown={(e) => { - if (e.key === 'Enter') - void handleCreateFile() - }} />
- )} - - {mode === 'upload' && ( -
{ - if (!isBusy && appId) - fileInputRef.current?.click() - }} - onDragOver={(e) => { - e.preventDefault() - if (!isBusy) - setIsDragOver(true) - }} - onDragLeave={(e) => { - e.preventDefault() - setIsDragOver(false) - }} - onDrop={handleDrop} - onKeyDown={(e) => { - if ((e.key === 'Enter' || e.key === ' ') && !isBusy && appId) - fileInputRef.current?.click() - }} - > -
- + + ) } diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx index cadaaacd50..89e4b66d10 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx @@ -13,9 +13,9 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' +} from '@/app/components/base/portal-to-follow-elem-plus' import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks' -import Tooltip from '@/app/components/base/tooltip' +import Tooltip from '@/app/components/base/tooltip-plus' import { START_TAB_ID } from '@/app/components/workflow/skill/constants' import { useSkillAssetNodeMap } from '@/app/components/workflow/skill/hooks/file-tree/data/use-skill-asset-tree' import { getFileIconType } from '@/app/components/workflow/skill/utils/file-utils' diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/file-preview-panel.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/file-preview-panel.tsx index 2b7f153b08..4af7f46175 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/file-preview-panel.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/file-preview-panel.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import FileTypeIcon from '@/app/components/base/file-uploader/file-type-icon' import Loading from '@/app/components/base/loading' -import Tooltip from '@/app/components/base/tooltip' +import Tooltip from '@/app/components/base/tooltip-plus' import SkillEditor from '@/app/components/workflow/skill/editor/skill-editor' import { useFileTypeInfo } from '@/app/components/workflow/skill/hooks/use-file-type-info' import { getFileIconType } from '@/app/components/workflow/skill/utils/file-utils' diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/commands.ts b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/commands.ts new file mode 100644 index 0000000000..ca9226c984 --- /dev/null +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/commands.ts @@ -0,0 +1,5 @@ +import type { ToolBlockPayload } from './node' +import { createCommand } from 'lexical' + +export const INSERT_TOOL_BLOCK_COMMAND = createCommand('INSERT_TOOL_BLOCK_COMMAND') +export const DELETE_TOOL_BLOCK_COMMAND = createCommand('DELETE_TOOL_BLOCK_COMMAND') diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx index bd0effd451..76655e0e0c 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx @@ -4,7 +4,6 @@ import type { ToolValue } from '@/app/components/workflow/block-selector/types' import type { ToolWithProvider } from '@/app/components/workflow/types' import type { AppAssetTreeView } from '@/types/app-asset' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' -import Link from 'next/link' import * as React from 'react' import { useEffect, useMemo, useState } from 'react' import { createPortal } from 'react-dom' @@ -12,9 +11,11 @@ import { Trans, useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import AppIcon from '@/app/components/base/app-icon' import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general' +// eslint-disable-next-line no-restricted-imports import Modal from '@/app/components/base/modal' import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks' -import Tooltip from '@/app/components/base/tooltip' + +import Tooltip from '@/app/components/base/tooltip-plus' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import ToolAuthorizationSection from '@/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section' import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance' @@ -27,6 +28,7 @@ import ToolSettingsSection from '@/app/components/workflow/skill/editor/skill-ed import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' +import Link from '@/next/link' import { useAllBuiltInTools, useAllCustomTools, @@ -37,7 +39,7 @@ import { Theme } from '@/types/app' import { canFindTool } from '@/utils' import { cn } from '@/utils/classnames' import { basePath } from '@/utils/var' -import { DELETE_TOOL_BLOCK_COMMAND } from './index' +import { DELETE_TOOL_BLOCK_COMMAND } from './commands' import { useToolBlockContext } from './tool-block-context' import ToolHeader from './tool-header' diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/index.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/index.tsx index e3bebc9735..3ff5ea6fc3 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/index.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/index.tsx @@ -4,14 +4,11 @@ import { mergeRegister } from '@lexical/utils' import { $insertNodes, COMMAND_PRIORITY_EDITOR, - createCommand, } from 'lexical' import { memo, useEffect } from 'react' +import { DELETE_TOOL_BLOCK_COMMAND, INSERT_TOOL_BLOCK_COMMAND } from './commands' import { $createToolBlockNode, ToolBlockNode } from './node' -export const INSERT_TOOL_BLOCK_COMMAND = createCommand('INSERT_TOOL_BLOCK_COMMAND') -export const DELETE_TOOL_BLOCK_COMMAND = createCommand('DELETE_TOOL_BLOCK_COMMAND') - const ToolBlock = memo(() => { const [editor] = useLexicalComposerContext() diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx index 06b61e2f37..bab3275f3d 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx @@ -4,17 +4,18 @@ import type { ToolParameter } from '@/app/components/tools/types' import type { ToolValue } from '@/app/components/workflow/block-selector/types' import type { ToolWithProvider } from '@/app/components/workflow/types' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' -import Link from 'next/link' import * as React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { createPortal } from 'react-dom' import { Trans, useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import AppIcon from '@/app/components/base/app-icon' +// eslint-disable-next-line no-restricted-imports import Modal from '@/app/components/base/modal' import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks' import Switch from '@/app/components/base/switch' -import Tooltip from '@/app/components/base/tooltip' + +import Tooltip from '@/app/components/base/tooltip-plus' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import ToolAuthorizationSection from '@/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section' import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance' @@ -27,6 +28,7 @@ import ToolSettingsSection from '@/app/components/workflow/skill/editor/skill-ed import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' +import Link from '@/next/link' import { useAllBuiltInTools, useAllCustomTools, @@ -37,7 +39,7 @@ import { Theme } from '@/types/app' import { canFindTool } from '@/utils' import { cn } from '@/utils/classnames' import { basePath } from '@/utils/var' -import { DELETE_TOOL_BLOCK_COMMAND } from './index' +import { DELETE_TOOL_BLOCK_COMMAND } from './commands' import { useToolBlockContext } from './tool-block-context' import ToolHeader from './tool-header' diff --git a/web/app/components/workflow/skill/file-tree/tree/menu-item.tsx b/web/app/components/workflow/skill/file-tree/tree/menu-item.tsx index 10ddc625f9..21b16a33c2 100644 --- a/web/app/components/workflow/skill/file-tree/tree/menu-item.tsx +++ b/web/app/components/workflow/skill/file-tree/tree/menu-item.tsx @@ -3,7 +3,7 @@ import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' import * as React from 'react' -import Tooltip from '@/app/components/base/tooltip' +import Tooltip from '@/app/components/base/tooltip-plus' import ShortcutsName from '@/app/components/workflow/shortcuts-name' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/skill/file-tree/tree/node-menu.tsx b/web/app/components/workflow/skill/file-tree/tree/node-menu.tsx index 651dc9e2ed..51572da4fc 100644 --- a/web/app/components/workflow/skill/file-tree/tree/node-menu.tsx +++ b/web/app/components/workflow/skill/file-tree/tree/node-menu.tsx @@ -3,15 +3,23 @@ import type { NodeApi, TreeApi } from 'react-arborist' import type { NodeMenuType } from '../../constants' import type { TreeNodeData } from '../../type' -import dynamic from 'next/dynamic' import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import Confirm from '@/app/components/base/confirm' import { FileAdd, FolderAdd } from '@/app/components/base/icons/src/vender/line/files' import { UploadCloud02 } from '@/app/components/base/icons/src/vender/line/general' import { Download02 } from '@/app/components/base/icons/src/vender/solid/general' +import { + AlertDialog, + AlertDialogActions, + AlertDialogCancelButton, + AlertDialogConfirmButton, + AlertDialogContent, + AlertDialogDescription, + AlertDialogTitle, +} from '@/app/components/base/ui/alert-dialog' import { useStore, useWorkflowStore } from '@/app/components/workflow/store' +import dynamic from '@/next/dynamic' import { cn } from '@/utils/classnames' import { NODE_MENU_TYPE } from '../../constants' import { useFileOperations } from '../../hooks/file-tree/operations/use-file-operations' @@ -214,15 +222,37 @@ const NodeMenu = ({ )} - + { + if (!open) + handleDeleteCancel() + }} + > + +
+ + {deleteConfirmTitle} + + + {deleteConfirmContent} + +
+ + + {t('operation.cancel', { ns: 'common' })} + + { + void handleDeleteConfirm() + }} + > + {t('operation.confirm', { ns: 'common' })} + + +
+
setIsImportModalOpen(false)} diff --git a/web/app/components/workflow/skill/file-tree/tree/tree-context-menu.spec.tsx b/web/app/components/workflow/skill/file-tree/tree/tree-context-menu.spec.tsx index 66b4dc2fce..188f08f598 100644 --- a/web/app/components/workflow/skill/file-tree/tree/tree-context-menu.spec.tsx +++ b/web/app/components/workflow/skill/file-tree/tree/tree-context-menu.spec.tsx @@ -41,7 +41,7 @@ vi.mock('@floating-ui/react', () => ({ ), })) -vi.mock('@/app/components/base/portal-to-follow-elem/use-context-menu-floating', () => ({ +vi.mock('@/app/components/base/portal-to-follow-elem-plus/use-context-menu-floating', () => ({ useContextMenuFloating: (options: FloatingOptions) => { mocks.floatingOptions = options return { diff --git a/web/app/components/workflow/skill/file-tree/tree/tree-context-menu.tsx b/web/app/components/workflow/skill/file-tree/tree/tree-context-menu.tsx index f3a8a33dd0..cf00b1afa9 100644 --- a/web/app/components/workflow/skill/file-tree/tree/tree-context-menu.tsx +++ b/web/app/components/workflow/skill/file-tree/tree/tree-context-menu.tsx @@ -5,7 +5,7 @@ import type { TreeNodeData } from '../../type' import { FloatingPortal } from '@floating-ui/react' import * as React from 'react' import { useCallback, useMemo } from 'react' -import { useContextMenuFloating } from '@/app/components/base/portal-to-follow-elem/use-context-menu-floating' +import { useContextMenuFloating } from '@/app/components/base/portal-to-follow-elem-plus/use-context-menu-floating' import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { getMenuNodeId, getNodeMenuType } from '../../utils/tree-utils' import NodeMenu from './node-menu' diff --git a/web/app/components/workflow/skill/file-tree/tree/tree-node.tsx b/web/app/components/workflow/skill/file-tree/tree/tree-node.tsx index 3ccc155d4b..10ac2810ce 100644 --- a/web/app/components/workflow/skill/file-tree/tree/tree-node.tsx +++ b/web/app/components/workflow/skill/file-tree/tree/tree-node.tsx @@ -9,7 +9,7 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' +} from '@/app/components/base/portal-to-follow-elem-plus' import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { cn } from '@/utils/classnames' import { useFolderFileDrop } from '../../hooks/file-tree/dnd/use-folder-file-drop' diff --git a/web/app/components/workflow/skill/hooks/file-tree/dnd/use-file-drop.ts b/web/app/components/workflow/skill/hooks/file-tree/dnd/use-file-drop.ts index 7f38f48054..af58b0d492 100644 --- a/web/app/components/workflow/skill/hooks/file-tree/dnd/use-file-drop.ts +++ b/web/app/components/workflow/skill/hooks/file-tree/dnd/use-file-drop.ts @@ -6,7 +6,7 @@ import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useWorkflowStore } from '@/app/components/workflow/store' import { useUploadFileWithPresignedUrl } from '@/service/use-app-asset' import { ROOT_ID } from '../../../constants' @@ -65,10 +65,7 @@ export function useFileDrop() { const entry = item.webkitGetAsEntry?.() // Skip directories - they have isDirectory = true if (entry?.isDirectory) { - Toast.notify({ - type: 'error', - message: t('skillSidebar.menu.folderDropNotSupported'), - }) + toast.error(t('skillSidebar.menu.folderDropNotSupported')) continue } const file = item.getAsFile() @@ -93,16 +90,10 @@ export function useFileDrop() { ) emitTreeUpdate() - Toast.notify({ - type: 'success', - message: t('skillSidebar.menu.filesUploaded', { count: files.length }), - }) + toast.success(t('skillSidebar.menu.filesUploaded', { count: files.length })) } catch { - Toast.notify({ - type: 'error', - message: t('skillSidebar.menu.uploadError'), - }) + toast.error(t('skillSidebar.menu.uploadError')) } }, [appId, uploadFile, t, storeApi, emitTreeUpdate]) diff --git a/web/app/components/workflow/skill/hooks/file-tree/interaction/use-inline-create-node.ts b/web/app/components/workflow/skill/hooks/file-tree/interaction/use-inline-create-node.ts index 57bed049b1..5c2fb1ea6a 100644 --- a/web/app/components/workflow/skill/hooks/file-tree/interaction/use-inline-create-node.ts +++ b/web/app/components/workflow/skill/hooks/file-tree/interaction/use-inline-create-node.ts @@ -5,7 +5,7 @@ import type { TreeNodeData } from '../../../type' import { useCallback, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { useCreateAppAssetFolder, @@ -74,10 +74,7 @@ export function useInlineCreateNode({ }, }) emitTreeUpdate() - Toast.notify({ - type: 'success', - message: t('skillSidebar.menu.folderCreated'), - }) + toast.success(t('skillSidebar.menu.folderCreated')) } else { const emptyBlob = new Blob([''], { type: 'text/plain' }) @@ -91,17 +88,11 @@ export function useInlineCreateNode({ const extension = getFileExtension(trimmedName, createdFile.extension) if (isTextLikeFile(extension)) storeApi.getState().openTab(createdFile.id, { pinned: true, autoFocusEditor: true }) - Toast.notify({ - type: 'success', - message: t('skillSidebar.menu.fileCreated'), - }) + toast.success(t('skillSidebar.menu.fileCreated')) } } catch { - Toast.notify({ - type: 'error', - message: t('skillSidebar.menu.createError'), - }) + toast.error(t('skillSidebar.menu.createError')) } finally { storeApi.getState().clearCreateNode() @@ -115,15 +106,9 @@ export function useInlineCreateNode({ payload: { name }, }).then(() => { emitTreeUpdate() - Toast.notify({ - type: 'success', - message: t('skillSidebar.menu.renamed'), - }) + toast.success(t('skillSidebar.menu.renamed')) }).catch(() => { - Toast.notify({ - type: 'error', - message: t('skillSidebar.menu.renameError'), - }) + toast.error(t('skillSidebar.menu.renameError')) }) }, [ appId, diff --git a/web/app/components/workflow/skill/hooks/file-tree/operations/use-download-operation.ts b/web/app/components/workflow/skill/hooks/file-tree/operations/use-download-operation.ts index 1d96c5bf2a..5bd0718054 100644 --- a/web/app/components/workflow/skill/hooks/file-tree/operations/use-download-operation.ts +++ b/web/app/components/workflow/skill/hooks/file-tree/operations/use-download-operation.ts @@ -2,7 +2,7 @@ import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { consoleClient } from '@/service/client' import { downloadUrl } from '@/utils/download' @@ -37,10 +37,7 @@ export function useDownloadOperation({ downloadUrl({ url: download_url, fileName }) } catch { - Toast.notify({ - type: 'error', - message: t('skillSidebar.menu.downloadError'), - }) + toast.error(t('skillSidebar.menu.downloadError')) } finally { setIsDownloading(false) diff --git a/web/app/components/workflow/skill/hooks/file-tree/operations/use-modify-operations.ts b/web/app/components/workflow/skill/hooks/file-tree/operations/use-modify-operations.ts index c735804b71..5ffd6926b5 100644 --- a/web/app/components/workflow/skill/hooks/file-tree/operations/use-modify-operations.ts +++ b/web/app/components/workflow/skill/hooks/file-tree/operations/use-modify-operations.ts @@ -9,7 +9,7 @@ import type { SkillEditorSliceShape } from '@/app/components/workflow/store/work import type { AppAssetTreeResponse } from '@/types/app-asset' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useDeleteAppAssetNode } from '@/service/use-app-asset' import { getAllDescendantFileIds } from '../../../utils/tree-utils' import { useSkillTreeUpdateEmitter } from '../data/use-skill-tree-collaboration' @@ -74,20 +74,18 @@ export function useModifyOperations({ storeApi.getState().clearDraftContent(nodeId) } - Toast.notify({ - type: 'success', - message: isFolder + toast.success( + isFolder ? t('skillSidebar.menu.deleted') : t('skillSidebar.menu.fileDeleted'), - }) + ) } catch { - Toast.notify({ - type: 'error', - message: isFolder + toast.error( + isFolder ? t('skillSidebar.menu.deleteError') : t('skillSidebar.menu.fileDeleteError'), - }) + ) } finally { setShowDeleteConfirm(false) diff --git a/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-move.ts b/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-move.ts index 106b64c784..2354d070f6 100644 --- a/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-move.ts +++ b/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-move.ts @@ -6,7 +6,7 @@ import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useMoveAppAssetNode } from '@/service/use-app-asset' import { toApiParentId } from '../../../utils/tree-utils' import { useSkillTreeUpdateEmitter } from '../data/use-skill-tree-collaboration' @@ -28,16 +28,10 @@ export function useNodeMove() { }) emitTreeUpdate() - Toast.notify({ - type: 'success', - message: t('skillSidebar.menu.moved'), - }) + toast.success(t('skillSidebar.menu.moved')) } catch { - Toast.notify({ - type: 'error', - message: t('skillSidebar.menu.moveError'), - }) + toast.error(t('skillSidebar.menu.moveError')) } }, [appId, moveNodeAsync, t, emitTreeUpdate]) diff --git a/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-reorder.ts b/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-reorder.ts index d152ed0443..ad366fdea4 100644 --- a/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-reorder.ts +++ b/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-reorder.ts @@ -5,7 +5,7 @@ import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useReorderAppAssetNode } from '@/service/use-app-asset' import { useSkillTreeUpdateEmitter } from '../data/use-skill-tree-collaboration' @@ -25,16 +25,10 @@ export function useNodeReorder() { }) emitTreeUpdate() - Toast.notify({ - type: 'success', - message: t('skillSidebar.menu.moved'), - }) + toast.success(t('skillSidebar.menu.moved')) } catch { - Toast.notify({ - type: 'error', - message: t('skillSidebar.menu.moveError'), - }) + toast.error(t('skillSidebar.menu.moveError')) } }, [appId, reorderNodeAsync, t, emitTreeUpdate]) diff --git a/web/app/components/workflow/skill/hooks/file-tree/operations/use-paste-operation.ts b/web/app/components/workflow/skill/hooks/file-tree/operations/use-paste-operation.ts index c6ac8e1140..3e6bb855ac 100644 --- a/web/app/components/workflow/skill/hooks/file-tree/operations/use-paste-operation.ts +++ b/web/app/components/workflow/skill/hooks/file-tree/operations/use-paste-operation.ts @@ -7,7 +7,7 @@ import type { AppAssetTreeResponse } from '@/types/app-asset' import { useCallback, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useWorkflowStore } from '@/app/components/workflow/store' import { useMoveAppAssetNode } from '@/service/use-app-asset' import { findNodeById, getTargetFolderIdFromSelection, toApiParentId } from '../../../utils/tree-utils' @@ -65,10 +65,7 @@ export function usePasteOperation({ }) if (isMovingToSelf) { - Toast.notify({ - type: 'error', - message: t('skillSidebar.menu.cannotMoveToSelf'), - }) + toast.error(t('skillSidebar.menu.cannotMoveToSelf')) return } @@ -88,16 +85,10 @@ export function usePasteOperation({ storeApi.getState().clearClipboard() emitTreeUpdate() - Toast.notify({ - type: 'success', - message: t('skillSidebar.menu.moved'), - }) + toast.success(t('skillSidebar.menu.moved')) } catch { - Toast.notify({ - type: 'error', - message: t('skillSidebar.menu.moveError'), - }) + toast.error(t('skillSidebar.menu.moveError')) } finally { isPastingRef.current = false diff --git a/web/app/components/workflow/skill/hooks/use-skill-save-manager.tsx b/web/app/components/workflow/skill/hooks/use-skill-save-manager.tsx index 60b67bffd4..cdf4efe3e4 100644 --- a/web/app/components/workflow/skill/hooks/use-skill-save-manager.tsx +++ b/web/app/components/workflow/skill/hooks/use-skill-save-manager.tsx @@ -5,7 +5,7 @@ import isDeepEqual from 'fast-deep-equal' import * as React from 'react' import { useCallback, useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useWorkflowStore } from '@/app/components/workflow/store' import { extractToolConfigIds } from '@/app/components/workflow/utils' import { useGlobalPublicStore } from '@/context/global-public-context' @@ -287,10 +287,10 @@ export const SkillSaveProvider = ({ const errorMessage = result.error instanceof Error ? result.error.message : String(result.error) - Toast.notify({ type: 'error', message: errorMessage }) + toast.error(errorMessage) } else if (result.saved) { - Toast.notify({ type: 'success', message: t('api.saved', { ns: 'common' }) }) + toast.success(t('api.saved', { ns: 'common' })) } }) } diff --git a/web/app/components/workflow/skill/skill-body/panels/file-content-panel.tsx b/web/app/components/workflow/skill/skill-body/panels/file-content-panel.tsx index e09ae23c02..ad655fd98b 100644 --- a/web/app/components/workflow/skill/skill-body/panels/file-content-panel.tsx +++ b/web/app/components/workflow/skill/skill-body/panels/file-content-panel.tsx @@ -5,7 +5,6 @@ import type { SkillFileDataMode } from '../../hooks/use-skill-file-data' import type { AppAssetTreeView } from '@/types/app-asset' import { loader } from '@monaco-editor/react' import isDeepEqual from 'fast-deep-equal' -import dynamic from 'next/dynamic' import * as React from 'react' import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -13,6 +12,7 @@ import { useStore as useAppStore } from '@/app/components/app/store' import Loading from '@/app/components/base/loading' import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import useTheme from '@/hooks/use-theme' +import dynamic from '@/next/dynamic' import { Theme } from '@/types/app' import { basePath } from '@/utils/var' import { useSkillCodeCollaboration } from '../../../collaboration/skills/use-skill-code-collaboration' diff --git a/web/app/components/workflow/skill/skill-body/sidebar-search-add.tsx b/web/app/components/workflow/skill/skill-body/sidebar-search-add.tsx index fc4bbb79c2..d176ca73a5 100644 --- a/web/app/components/workflow/skill/skill-body/sidebar-search-add.tsx +++ b/web/app/components/workflow/skill/skill-body/sidebar-search-add.tsx @@ -1,6 +1,5 @@ 'use client' -import dynamic from 'next/dynamic' import * as React from 'react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -11,9 +10,10 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' +} from '@/app/components/base/portal-to-follow-elem-plus' import SearchInput from '@/app/components/base/search-input' import { useStore, useWorkflowStore } from '@/app/components/workflow/store' +import dynamic from '@/next/dynamic' import { ROOT_ID } from '../constants' import MenuItem from '../file-tree/tree/menu-item' import { useSkillAssetTreeData } from '../hooks/file-tree/data/use-skill-asset-tree' diff --git a/web/app/components/workflow/skill/skill-body/tabs/file-tabs.tsx b/web/app/components/workflow/skill/skill-body/tabs/file-tabs.tsx index 4829d78755..956fabf612 100644 --- a/web/app/components/workflow/skill/skill-body/tabs/file-tabs.tsx +++ b/web/app/components/workflow/skill/skill-body/tabs/file-tabs.tsx @@ -3,7 +3,15 @@ import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import Confirm from '@/app/components/base/confirm' +import { + AlertDialog, + AlertDialogActions, + AlertDialogCancelButton, + AlertDialogConfirmButton, + AlertDialogContent, + AlertDialogDescription, + AlertDialogTitle, +} from '@/app/components/base/ui/alert-dialog' import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { cn } from '@/utils/classnames' import { getArtifactPath, isArtifactTab, START_TAB_ID } from '../../constants' @@ -66,6 +74,11 @@ const FileTabs = () => { setPendingCloseId(null) }, []) + const handleUnsavedDialogOpenChange = useCallback((open: boolean) => { + if (!open) + handleCancelClose() + }, [handleCancelClose]) + return ( <>
{ ) })}
- + + +
+ + {t('skillSidebar.unsavedChanges.title')} + + + {t('skillSidebar.unsavedChanges.content')} + +
+ + + {t('operation.cancel', { ns: 'common' })} + + + {t('skillSidebar.unsavedChanges.confirmClose')} + + +
+
) } diff --git a/web/app/components/workflow/skill/start-tab/create-blank-skill-modal.tsx b/web/app/components/workflow/skill/start-tab/create-blank-skill-modal.tsx index 5c5663553a..1e011a6d4d 100644 --- a/web/app/components/workflow/skill/start-tab/create-blank-skill-modal.tsx +++ b/web/app/components/workflow/skill/start-tab/create-blank-skill-modal.tsx @@ -1,13 +1,13 @@ 'use client' import type { BatchUploadNodeInput } from '@/types/app-asset' -import { memo, useCallback, useRef, useState } from 'react' +import { memo, useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' -import Modal from '@/app/components/base/modal' -import Toast from '@/app/components/base/toast' +import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' +import { toast } from '@/app/components/base/ui/toast' import { useWorkflowStore } from '@/app/components/workflow/store' import { useBatchUpload } from '@/service/use-app-asset' import { useExistingSkillNames } from '../hooks/file-tree/data/use-skill-asset-tree' @@ -48,6 +48,11 @@ const CreateBlankSkillModal = ({ isOpen, onClose }: CreateBlankSkillModalProps) const inputRef = useRef(null) + useEffect(() => { + if (isOpen) + queueMicrotask(() => inputRef.current?.focus()) + }, [isOpen]) + const trimmedName = skillName.trim() const isDuplicate = !!trimmedName && (existingNames?.has(trimmedName) ?? false) const canCreate = !!trimmedName && !isDuplicate && !isCreating @@ -98,12 +103,12 @@ const CreateBlankSkillModal = ({ isOpen, onClose }: CreateBlankSkillModalProps) if (skillMdId) storeApi.getState().openTab(skillMdId, { pinned: true }) - Toast.notify({ type: 'success', message: t('skill.startTab.createSuccess', { ns: 'workflow', name: trimmedName }) }) + toast.success(t('skill.startTab.createSuccess', { ns: 'workflow', name: trimmedName })) onClose() } catch { storeApi.getState().setUploadStatus('partial_error') - Toast.notify({ type: 'error', message: t('skill.startTab.createError', { ns: 'workflow' }) }) + toast.error(t('skill.startTab.createError', { ns: 'workflow' })) } finally { setIsCreating(false) @@ -112,53 +117,59 @@ const CreateBlankSkillModal = ({ isOpen, onClose }: CreateBlankSkillModalProps) }, [canCreate, appId, trimmedName, storeApi, onClose, t]) return ( - { + if (!open) + handleClose() + }} + disablePointerDismissal={isCreating} > -
- - setSkillName(e.target.value)} - placeholder={t('skill.startTab.createModal.namePlaceholder', { ns: 'workflow' }) || ''} - destructive={isDuplicate} - disabled={isCreating} - onKeyDown={(e) => { - if (e.key === 'Enter' && canCreate) - handleCreate() - }} - /> - {isDuplicate && ( -

- {t('skill.startTab.createModal.nameDuplicate', { ns: 'workflow' })} -

- )} -
-
- - -
-
+ + {!isCreating && } + + {t('skill.startTab.createModal.title', { ns: 'workflow' })} + +
+ + setSkillName(e.target.value)} + placeholder={t('skill.startTab.createModal.namePlaceholder', { ns: 'workflow' }) || ''} + destructive={isDuplicate} + disabled={isCreating} + onKeyDown={(e) => { + if (e.key === 'Enter' && canCreate) + handleCreate() + }} + /> + {isDuplicate && ( +

+ {t('skill.startTab.createModal.nameDuplicate', { ns: 'workflow' })} +

+ )} +
+
+ + +
+
+ ) } diff --git a/web/app/components/workflow/skill/start-tab/create-import-section.tsx b/web/app/components/workflow/skill/start-tab/create-import-section.tsx index 0dbb03d23f..a4478d0e0c 100644 --- a/web/app/components/workflow/skill/start-tab/create-import-section.tsx +++ b/web/app/components/workflow/skill/start-tab/create-import-section.tsx @@ -1,8 +1,8 @@ 'use client' -import dynamic from 'next/dynamic' import { memo, useState } from 'react' import { useTranslation } from 'react-i18next' +import dynamic from '@/next/dynamic' import ActionCard from './action-card' import CreateBlankSkillModal from './create-blank-skill-modal' diff --git a/web/app/components/workflow/skill/start-tab/import-skill-modal.tsx b/web/app/components/workflow/skill/start-tab/import-skill-modal.tsx index 380a2a04c5..4775319378 100644 --- a/web/app/components/workflow/skill/start-tab/import-skill-modal.tsx +++ b/web/app/components/workflow/skill/start-tab/import-skill-modal.tsx @@ -5,8 +5,8 @@ import { memo, useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' -import Modal from '@/app/components/base/modal' -import Toast from '@/app/components/base/toast' +import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog' +import { toast } from '@/app/components/base/ui/toast' import { useWorkflowStore } from '@/app/components/workflow/store' import { useBatchUpload } from '@/service/use-app-asset' import { useExistingSkillNames } from '../hooks/file-tree/data/use-skill-asset-tree' @@ -71,7 +71,7 @@ const ImportSkillModal = ({ isOpen, onClose }: ImportSkillModalProps) => { const validateAndSetFile = useCallback((file: File) => { if (!file.name.toLowerCase().endsWith('.zip')) { - Toast.notify({ type: 'error', message: t(`${PREFIX}.invalidFileType`, { ns: NS }) }) + toast.error(t(`${PREFIX}.invalidFileType`, { ns: NS })) return } setSelectedFile(file) @@ -115,7 +115,7 @@ const ImportSkillModal = ({ isOpen, onClose }: ImportSkillModalProps) => { const extracted = await extractAndValidateZip(zipData) if (existingNames?.has(extracted.rootFolderName)) { - Toast.notify({ type: 'error', message: t(`${PREFIX}.nameDuplicate`, { ns: NS }) }) + toast.error(t(`${PREFIX}.nameDuplicate`, { ns: NS })) setIsImporting(false) storeApi.getState().setUploadStatus('partial_error') return @@ -143,17 +143,17 @@ const ImportSkillModal = ({ isOpen, onClose }: ImportSkillModalProps) => { if (skillMd?.id) storeApi.getState().openTab(skillMd.id, { pinned: true }) - Toast.notify({ type: 'success', message: t(`${PREFIX}.importSuccess`, { ns: NS, name: extracted.rootFolderName }) }) + toast.success(t(`${PREFIX}.importSuccess`, { ns: NS, name: extracted.rootFolderName })) onClose() } catch (error) { storeApi.getState().setUploadStatus('partial_error') if (error instanceof ZipValidationError) { const i18nKey = ZIP_ERROR_I18N_KEYS[error.code as keyof typeof ZIP_ERROR_I18N_KEYS] - Toast.notify({ type: 'error', message: i18nKey ? t(i18nKey, { ns: NS }) : error.message }) + toast.error(i18nKey ? t(i18nKey, { ns: NS }) : error.message) } else { - Toast.notify({ type: 'error', message: t(`${PREFIX}.errorInvalidZip`, { ns: NS }) }) + toast.error(t(`${PREFIX}.errorInvalidZip`, { ns: NS })) } } finally { @@ -163,73 +163,80 @@ const ImportSkillModal = ({ isOpen, onClose }: ImportSkillModalProps) => { }, [selectedFile, appId, storeApi, existingNames, t, onClose]) return ( - { + if (!open) + handleClose() + }} + disablePointerDismissal={isImporting} > -
- {!selectedFile - ? ( -
fileInputRef.current?.click()} - > - -

- {t(`${PREFIX}.dropHint`, { ns: NS })} - {' '} - - {t(`${PREFIX}.browseFiles`, { ns: NS })} - -

-
- ) - : ( -
-
- {selectedFile.name} - {formatFileSize(selectedFile.size)} -
- -
- )} - -
-
- - -
-
+ +

+ {t(`${PREFIX}.dropHint`, { ns: NS })} + {' '} + + {t(`${PREFIX}.browseFiles`, { ns: NS })} + +

+
+ ) + : ( +
+
+ {selectedFile.name} + {formatFileSize(selectedFile.size)} +
+ +
+ )} + +
+
+ + +
+
+ ) } diff --git a/web/app/components/workflow/skill/viewer/read-only-file-preview.tsx b/web/app/components/workflow/skill/viewer/read-only-file-preview.tsx index 2856e5ef85..ec8be066e6 100644 --- a/web/app/components/workflow/skill/viewer/read-only-file-preview.tsx +++ b/web/app/components/workflow/skill/viewer/read-only-file-preview.tsx @@ -1,8 +1,8 @@ 'use client' -import dynamic from 'next/dynamic' import * as React from 'react' import Loading from '@/app/components/base/loading' +import dynamic from '@/next/dynamic' import { useFetchTextContent } from '../hooks/use-fetch-text-content' import { useFileTypeInfo } from '../hooks/use-file-type-info' import { getFileLanguage } from '../utils/file-utils' diff --git a/web/app/components/workflow/skill/viewer/sqlite-file-preview/table-selector.tsx b/web/app/components/workflow/skill/viewer/sqlite-file-preview/table-selector.tsx index 2202f1be05..4fd7df1c62 100644 --- a/web/app/components/workflow/skill/viewer/sqlite-file-preview/table-selector.tsx +++ b/web/app/components/workflow/skill/viewer/sqlite-file-preview/table-selector.tsx @@ -7,7 +7,7 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' +} from '@/app/components/base/portal-to-follow-elem-plus' import { cn } from '@/utils/classnames' type TableSelectorProps = { diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 18ff026662..c8b922791f 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -1,4 +1,4 @@ -/* eslint-disable ts/no-explicit-any */ +/* eslint-disable ts/no-redeclare -- const objects + matching type aliases replace enums under erasableSyntaxOnly */ import type { Edge as ReactFlowEdge, Node as ReactFlowNode, @@ -26,51 +26,58 @@ import type { PanelProps, } from '@/types/workflow' -export enum BlockEnum { - Start = 'start', - End = 'end', - Answer = 'answer', - LLM = 'llm', - KnowledgeRetrieval = 'knowledge-retrieval', - QuestionClassifier = 'question-classifier', - IfElse = 'if-else', - Code = 'code', - TemplateTransform = 'template-transform', - HttpRequest = 'http-request', - VariableAssigner = 'variable-assigner', - VariableAggregator = 'variable-aggregator', - Tool = 'tool', - ParameterExtractor = 'parameter-extractor', - Iteration = 'iteration', - DocExtractor = 'document-extractor', - ListFilter = 'list-operator', - IterationStart = 'iteration-start', - Assigner = 'assigner', // is now named as VariableAssigner - Agent = 'agent', - Loop = 'loop', - LoopStart = 'loop-start', - LoopEnd = 'loop-end', - HumanInput = 'human-input', - DataSource = 'datasource', - DataSourceEmpty = 'datasource-empty', - KnowledgeBase = 'knowledge-index', - TriggerSchedule = 'trigger-schedule', - TriggerWebhook = 'trigger-webhook', - TriggerPlugin = 'trigger-plugin', - Command = 'command', - FileUpload = 'file-upload', -} +export type JsonPrimitive = string | number | boolean | null +export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue } -export enum ControlMode { - Pointer = 'pointer', - Hand = 'hand', - Comment = 'comment', -} -export enum ErrorHandleMode { - Terminated = 'terminated', - ContinueOnError = 'continue-on-error', - RemoveAbnormalOutput = 'remove-abnormal-output', -} +export const BlockEnum = { + Start: 'start', + End: 'end', + Answer: 'answer', + LLM: 'llm', + KnowledgeRetrieval: 'knowledge-retrieval', + QuestionClassifier: 'question-classifier', + IfElse: 'if-else', + Code: 'code', + TemplateTransform: 'template-transform', + HttpRequest: 'http-request', + VariableAssigner: 'variable-assigner', + VariableAggregator: 'variable-aggregator', + Tool: 'tool', + ParameterExtractor: 'parameter-extractor', + Iteration: 'iteration', + DocExtractor: 'document-extractor', + ListFilter: 'list-operator', + IterationStart: 'iteration-start', + Assigner: 'assigner', // is now named as VariableAssigner + Agent: 'agent', + Loop: 'loop', + LoopStart: 'loop-start', + LoopEnd: 'loop-end', + HumanInput: 'human-input', + DataSource: 'datasource', + DataSourceEmpty: 'datasource-empty', + KnowledgeBase: 'knowledge-index', + TriggerSchedule: 'trigger-schedule', + TriggerWebhook: 'trigger-webhook', + TriggerPlugin: 'trigger-plugin', + Command: 'command', + FileUpload: 'file-upload', +} as const +export type BlockEnum = typeof BlockEnum[keyof typeof BlockEnum] + +export const ControlMode = { + Pointer: 'pointer', + Hand: 'hand', + Comment: 'comment', +} as const +export type ControlMode = typeof ControlMode[keyof typeof ControlMode] + +export const ErrorHandleMode = { + Terminated: 'terminated', + ContinueOnError: 'continue-on-error', + RemoveAbnormalOutput: 'remove-abnormal-output', +} as const +export type ErrorHandleMode = typeof ErrorHandleMode[keyof typeof ErrorHandleMode] export type Branch = { id: string name: string @@ -175,7 +182,7 @@ export type Variable = { export type EnvironmentVariable = { id: string name: string - value: any + value: JsonValue value_type: 'string' | 'number' | 'secret' description: string } @@ -184,7 +191,7 @@ export type ConversationVariable = { id: string name: string value_type: ChatVarType - value: any + value: JsonValue description: string } @@ -199,22 +206,23 @@ export type VariableWithValue = { value: string } -export enum InputVarType { - textInput = 'text-input', - paragraph = 'paragraph', - select = 'select', - number = 'number', - url = 'url', - files = 'files', - json = 'json', // obj, array - jsonObject = 'json_object', // only object support define json schema - contexts = 'contexts', // knowledge retrieval - iterator = 'iterator', // iteration input - singleFile = 'file', - multiFiles = 'file-list', - loop = 'loop', // loop input - checkbox = 'checkbox', -} +export const InputVarType = { + textInput: 'text-input', + paragraph: 'paragraph', + select: 'select', + number: 'number', + url: 'url', + files: 'files', + json: 'json', // obj, array + jsonObject: 'json_object', // only object support define json schema + contexts: 'contexts', // knowledge retrieval + iterator: 'iterator', // iteration input + singleFile: 'file', + multiFiles: 'file-list', + loop: 'loop', // loop input + checkbox: 'checkbox', +} as const +export type InputVarType = typeof InputVarType[keyof typeof InputVarType] export type InputVar = { type: InputVarType @@ -236,26 +244,28 @@ export type InputVar = { getVarValueFromDependent?: boolean hide?: boolean isFileItem?: boolean - json_schema?: string | Record // for jsonObject type + json_schema?: string | Record // for jsonObject type } & Partial export type ModelConfig = { provider: string name: string mode: string - completion_params: Record + completion_params: Record } -export enum PromptRole { - system = 'system', - user = 'user', - assistant = 'assistant', -} +export const PromptRole = { + system: 'system', + user: 'user', + assistant: 'assistant', +} as const +export type PromptRole = typeof PromptRole[keyof typeof PromptRole] -export enum EditionType { - basic = 'basic', - jinja2 = 'jinja2', -} +export const EditionType = { + basic: 'basic', + jinja2: 'jinja2', +} as const +export type EditionType = typeof EditionType[keyof typeof EditionType] export type PromptItem = { id?: string @@ -264,14 +274,14 @@ export type PromptItem = { edition_type?: EditionType jinja2_text?: string skill?: boolean - metadata?: Record + metadata?: Record } export type PromptMessageContext = { id?: string $context: ValueSelector skill?: boolean - metadata?: Record + metadata?: Record } export type PromptTemplateItem = PromptItem | PromptMessageContext @@ -280,10 +290,11 @@ export const isPromptMessageContext = (item: PromptTemplateItem): item is Prompt return '$context' in item } -export enum MemoryRole { - user = 'user', - assistant = 'assistant', -} +export const MemoryRole = { + user: 'user', + assistant: 'assistant', +} as const +export type MemoryRole = typeof MemoryRole[keyof typeof MemoryRole] export type RolePrefix = { user: string @@ -299,29 +310,31 @@ export type Memory = { query_prompt_template: string } -export enum VarType { - string = 'string', - number = 'number', - integer = 'integer', - secret = 'secret', - boolean = 'boolean', - object = 'object', - file = 'file', - array = 'array', - arrayString = 'array[string]', - arrayNumber = 'array[number]', - arrayObject = 'array[object]', - arrayBoolean = 'array[boolean]', - arrayFile = 'array[file]', - arrayMessage = 'array[message]', - any = 'any', - arrayAny = 'array[any]', -} +export const VarType = { + string: 'string', + number: 'number', + integer: 'integer', + secret: 'secret', + boolean: 'boolean', + object: 'object', + file: 'file', + array: 'array', + arrayString: 'array[string]', + arrayNumber: 'array[number]', + arrayObject: 'array[object]', + arrayBoolean: 'array[boolean]', + arrayFile: 'array[file]', + arrayMessage: 'array[message]', + any: 'any', + arrayAny: 'array[any]', +} as const +export type VarType = typeof VarType[keyof typeof VarType] -export enum ValueType { - variable = 'variable', - constant = 'constant', -} +export const ValueType = { + variable: 'variable', + constant: 'constant', +} as const +export type ValueType = typeof ValueType[keyof typeof ValueType] export type VarSchemaContainer = { schema?: StructuredOutput['schema'] | Record | string @@ -357,6 +370,7 @@ export type NodeOutPutVar = { // allow node default validators with narrower payload types to be stored in shared collections. type CheckValidFn = { + // eslint-disable-next-line ts/no-explicit-any -- bivariant node validators; implementations use narrower `t` / moreData shapes bivarianceHack: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean, errorMessage?: string } }['bivarianceHack'] @@ -389,7 +403,7 @@ export type NodeDefaultBase = { isTypeFixed?: boolean } defaultValue: Partial - defaultRunInputData?: Record + defaultRunInputData?: Record checkValid: CheckValidFn getOutputVars?: GetOutputVarsFn } @@ -402,32 +416,35 @@ export type NodeDefault = Omit void -export enum WorkflowRunningStatus { - Waiting = 'waiting', - Running = 'running', - Succeeded = 'succeeded', - Failed = 'failed', - Stopped = 'stopped', - Paused = 'paused', -} +export const WorkflowRunningStatus = { + Waiting: 'waiting', + Running: 'running', + Succeeded: 'succeeded', + Failed: 'failed', + Stopped: 'stopped', + Paused: 'paused', +} as const +export type WorkflowRunningStatus = typeof WorkflowRunningStatus[keyof typeof WorkflowRunningStatus] -export enum WorkflowVersion { - Draft = 'draft', - Latest = 'latest', -} +export const WorkflowVersion = { + Draft: 'draft', + Latest: 'latest', +} as const +export type WorkflowVersion = typeof WorkflowVersion[keyof typeof WorkflowVersion] -export enum NodeRunningStatus { - NotStart = 'not-start', - Waiting = 'waiting', - Listening = 'listening', - Running = 'running', - Succeeded = 'succeeded', - Failed = 'failed', - Exception = 'exception', - Retry = 'retry', - Stopped = 'stopped', - Paused = 'paused', -} +export const NodeRunningStatus = { + NotStart: 'not-start', + Waiting: 'waiting', + Listening: 'listening', + Running: 'running', + Succeeded: 'succeeded', + Failed: 'failed', + Exception: 'exception', + Retry: 'retry', + Stopped: 'stopped', + Paused: 'paused', +} as const +export type NodeRunningStatus = typeof NodeRunningStatus[keyof typeof NodeRunningStatus] export type OnNodeAdd = ( newNodePayload: { @@ -501,10 +518,11 @@ export type HistoryWorkflowData = { finished_at?: number } -export enum ChangeType { - changeVarName = 'changeVarName', - remove = 'remove', -} +export const ChangeType = { + changeVarName: 'changeVarName', + remove: 'remove', +} as const +export type ChangeType = typeof ChangeType[keyof typeof ChangeType] export type MoreInfo = { type: ChangeType @@ -525,13 +543,14 @@ export type RAGRecommendedPlugins = { uninstalled_recommended_plugins: Plugin[] } -export enum SupportUploadFileTypes { - image = 'image', - document = 'document', - audio = 'audio', - video = 'video', - custom = 'custom', -} +export const SupportUploadFileTypes = { + image: 'image', + document: 'document', + audio: 'audio', + video: 'video', + custom: 'custom', +} as const +export type SupportUploadFileTypes = typeof SupportUploadFileTypes[keyof typeof SupportUploadFileTypes] export type UploadFileSetting = { allowed_file_upload_methods: TransferMethod[] @@ -547,34 +566,35 @@ export type VisionSetting = { detail: Resolution } -export enum WorkflowVersionFilterOptions { - all = 'all', - onlyYours = 'onlyYours', -} +export const WorkflowVersionFilterOptions = { + all: 'all', + onlyYours: 'onlyYours', +} as const +export type WorkflowVersionFilterOptions = typeof WorkflowVersionFilterOptions[keyof typeof WorkflowVersionFilterOptions] -export enum VersionHistoryContextMenuOptions { - restore = 'restore', - edit = 'edit', - delete = 'delete', - exportDSL = 'exportDSL', - copyId = 'copyId', -} +export const VersionHistoryContextMenuOptions = { + restore: 'restore', + edit: 'edit', + delete: 'delete', + exportDSL: 'exportDSL', + copyId: 'copyId', +} as const +export type VersionHistoryContextMenuOptions = typeof VersionHistoryContextMenuOptions[keyof typeof VersionHistoryContextMenuOptions] export type ChildNodeTypeCount = { [key: string]: number } -export const TRIGGER_NODE_TYPES = [ +export const TRIGGER_NODE_TYPES: BlockEnum[] = [ BlockEnum.TriggerSchedule, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin, -] as const +] -// Type-safe trigger node type extracted from TRIGGER_NODE_TYPES array -export type TriggerNodeType = typeof TRIGGER_NODE_TYPES[number] +export type TriggerNodeType = typeof BlockEnum.TriggerSchedule | typeof BlockEnum.TriggerWebhook | typeof BlockEnum.TriggerPlugin export function isTriggerNode(nodeType: BlockEnum): boolean { - return TRIGGER_NODE_TYPES.includes(nodeType as any) + return TRIGGER_NODE_TYPES.includes(nodeType) } export type Block = { @@ -584,7 +604,8 @@ export type Block = { description?: string } -export enum ViewType { - graph = 'graph', - skill = 'skill', -} +export const ViewType = { + graph: 'graph', + skill: 'skill', +} as const +export type ViewType = typeof ViewType[keyof typeof ViewType] diff --git a/web/app/components/workflow/update-dsl-modal.tsx b/web/app/components/workflow/update-dsl-modal.tsx index b51cb9ae83..e2d2b1902b 100644 --- a/web/app/components/workflow/update-dsl-modal.tsx +++ b/web/app/components/workflow/update-dsl-modal.tsx @@ -23,7 +23,6 @@ import Uploader from '@/app/components/app/create-from-dsl-modal/uploader' import { useStore as useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' -import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' import { ToastContext } from '@/app/components/base/toast/context' import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' import { useEventEmitterContextContext } from '@/context/event-emitter' @@ -39,10 +38,7 @@ import { fetchWorkflowDraft } from '@/service/workflow' import { AppModeEnum } from '@/types/app' import { collaborationManager } from './collaboration/core/collaboration-manager' import { WORKFLOW_DATA_UPDATE } from './constants' -import { - BlockEnum, - SupportUploadFileTypes, -} from './types' +import { BlockEnum } from './types' import { initialEdges, initialNodes, @@ -99,31 +95,7 @@ const UpdateDSLModal = ({ } = await fetchWorkflowDraft(`/apps/${app_id}/workflows/draft`) const { nodes, edges, viewport } = graph - const newFeatures = { - file: { - image: { - enabled: !!features.file_upload?.image?.enabled, - number_limits: features.file_upload?.image?.number_limits || 3, - transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], - }, - enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled), - allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image], - allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`), - allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], - number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3, - }, - opening: { - enabled: !!features.opening_statement, - opening_statement: features.opening_statement, - suggested_questions: features.suggested_questions, - }, - suggested: features.suggested_questions_after_answer || { enabled: false }, - speech2text: features.speech_to_text || { enabled: false }, - text2speech: features.text_to_speech || { enabled: false }, - citation: features.retriever_resource || { enabled: false }, - moderation: features.sensitive_word_avoidance || { enabled: false }, - sandbox: features.sandbox || { enabled: false }, - } + const draftFeatures = features ?? {} eventEmitter?.emit({ type: WORKFLOW_DATA_UPDATE, @@ -131,7 +103,7 @@ const UpdateDSLModal = ({ nodes: initialNodes(nodes, edges), edges: initialEdges(edges, nodes), viewport, - features: newFeatures, + features: draftFeatures, hash, conversation_variables: conversation_variables || [], environment_variables: environment_variables || [], @@ -143,7 +115,7 @@ const UpdateDSLModal = ({ try { const data = yamlLoad(content) as any const nodes = data?.workflow?.graph?.nodes ?? [] - const invalidNodes = appDetail?.mode === AppModeEnum.ADVANCED_CHAT + const invalidNodes: BlockEnum[] = appDetail?.mode === AppModeEnum.ADVANCED_CHAT ? [ BlockEnum.End, BlockEnum.TriggerWebhook, @@ -152,7 +124,8 @@ const UpdateDSLModal = ({ ] : [BlockEnum.Answer] const hasInvalidNode = nodes.some((node: Node) => { - return invalidNodes.includes(node?.data?.type) + const nodeType = node?.data?.type + return nodeType !== undefined && invalidNodes.includes(nodeType) }) if (hasInvalidNode) { notify({ type: 'error', message: t('common.importFailure', { ns: 'workflow' }) }) diff --git a/web/app/components/workflow/utils/node.ts b/web/app/components/workflow/utils/node.ts index 72f559835c..e16da33180 100644 --- a/web/app/components/workflow/utils/node.ts +++ b/web/app/components/workflow/utils/node.ts @@ -76,7 +76,7 @@ export const mergeNodeDefaultData = ({ data, position, id, zIndex, type, ...rest }: Omit, 'id'> & { id?: string }): { +export function generateNewNode>({ data, position, id, zIndex, type, ...rest }: Omit, 'id'> & { id?: string }): { newNode: Node newIterationStartNode?: Node newLoopStartNode?: Node diff --git a/web/app/components/workflow/variable-inspect/right.tsx b/web/app/components/workflow/variable-inspect/right.tsx index c3838af074..7aab66f41b 100644 --- a/web/app/components/workflow/variable-inspect/right.tsx +++ b/web/app/components/workflow/variable-inspect/right.tsx @@ -168,7 +168,9 @@ const Right: FC = ({ {currentVar && ( <> {currentNodeType - && [VarInInspectType.environment, VarInInspectType.conversation, VarInInspectType.system].includes(currentNodeType as VarInInspectType) && ( + && ([VarInInspectType.environment, VarInInspectType.conversation, VarInInspectType.system] as const).includes( + currentNodeType as typeof VarInInspectType.environment | typeof VarInInspectType.conversation | typeof VarInInspectType.system, + ) && ( { - if (value.value_type === VarType.file) - return value.value ? getProcessedFilesFromResponse([value.value]) : [] - if (value.value_type === VarType.arrayFile || (value.type === VarInInspectType.system && isSysFiles)) - return value.value && value.value.length > 0 ? getProcessedFilesFromResponse(value.value) : [] + if (value.value_type === VarType.file) { + const v = value.value + return v != null ? getProcessedFilesFromResponse([v as unknown as FileResponse]) : [] + } + if (value.value_type === VarType.arrayFile || (value.type === VarInInspectType.system && isSysFiles)) { + const v = value.value + return Array.isArray(v) && v.length > 0 ? getProcessedFilesFromResponse(v as unknown as FileResponse[]) : [] + } return [] } @@ -89,8 +93,8 @@ const ValueContent = ({ const textValue = showTextEditor ? ( currentVar.value_type === VarType.number - ? JSON.stringify(currentVar.value) - : (currentVar.value || '') + ? JSON.stringify(currentVar.value ?? '') + : (typeof currentVar.value === 'string' || typeof currentVar.value === 'number' ? currentVar.value : String(currentVar.value ?? '')) ) : '' const jsonValue = showJSONEditor @@ -261,7 +265,7 @@ const ValueContent = ({ { showBoolArrayEditor && (
- {currentVar.value.map((v: boolean, i: number) => ( + {(currentVar.value as boolean[]).map((v: boolean, i: number) => ( ({ diff --git a/web/app/page.tsx b/web/app/page.tsx index 33f67bfd07..26874a89f1 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -1,4 +1,4 @@ -import { redirect } from 'next/navigation' +import { redirect } from '@/next/navigation' type HomePageProps = { searchParams: Promise> diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index 3c619b5493..6ed3f0d13a 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -152,7 +152,11 @@ export default antfu( }, { name: 'dify/base-ui-primitives', - files: ['app/components/base/ui/**/*.tsx', 'app/components/base/avatar/**/*.tsx'], + files: [ + 'app/components/base/ui/**/*.tsx', + 'app/components/base/avatar/**/*.tsx', + 'app/components/base/portal-to-follow-elem-plus/**/*.tsx', + ], rules: { 'react-refresh/only-export-components': 'off', }, diff --git a/web/eslint.constants.mjs b/web/eslint.constants.mjs index 9992d94f36..741b430af0 100644 --- a/web/eslint.constants.mjs +++ b/web/eslint.constants.mjs @@ -92,6 +92,8 @@ export const OVERLAY_RESTRICTED_IMPORT_PATTERNS = [ ] export const OVERLAY_MIGRATION_LEGACY_BASE_FILES = [ + 'app/components/base/portal-to-follow-elem-plus/index.tsx', + 'app/components/base/portal-to-follow-elem-plus/use-context-menu-floating.ts', 'app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx', 'app/components/base/chat/chat-with-history/header/operation.tsx', 'app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown.tsx', diff --git a/web/next/navigation.ts b/web/next/navigation.ts index ec7c112645..7147fb2f5d 100644 --- a/web/next/navigation.ts +++ b/web/next/navigation.ts @@ -1,4 +1,5 @@ export { + redirect, useParams, usePathname, useRouter, diff --git a/web/service/workflow-payload.ts b/web/service/workflow-payload.ts index 5e2cdebdb3..cea1fc5a89 100644 --- a/web/service/workflow-payload.ts +++ b/web/service/workflow-payload.ts @@ -40,7 +40,7 @@ const sanitizeTriggerPluginNode = (node: Node): Node + text_to_speech?: Record + retriever_resource?: Record + sensitive_word_avoidance?: Record + annotation_reply?: Record +} + export type AgentLogItem = { node_execution_id: string message_id: string @@ -57,14 +86,14 @@ export type LLMGenerationItem = { toolIcon?: string | IconObject toolIconDark?: string | IconObject toolArguments?: string - toolOutput?: Record | string + toolOutput?: Record | string toolFiles?: string[] toolError?: string toolDuration?: number modelName?: string modelProvider?: string - modelOutput?: Record | string + modelOutput?: Record | string modelDuration?: number modelIcon?: string | IconObject modelIconDark?: string | IconObject @@ -98,7 +127,7 @@ export type LLMLogItem = { export type LLMTraceItem = { type: 'model' | 'tool' duration: number - output: Record + output: Record provider?: string name: string icon?: string | IconObject @@ -117,11 +146,11 @@ export type NodeTracing = { loop_id?: string node_type: BlockEnum title: string - inputs: any + inputs: JsonValue inputs_truncated: boolean - process_data: any + process_data: JsonValue process_data_truncated: boolean - outputs?: Record + outputs?: Record | string outputs_truncated: boolean outputs_full_content?: { download_url: string @@ -151,7 +180,7 @@ export type NodeTracing = { agent_strategy?: string icon?: string } - loop_variable_map?: Record + loop_variable_map?: Record llm_trace?: LLMTraceItem[] } metadata: { @@ -169,7 +198,7 @@ export type NodeTracing = { iterDurationMap?: IterationDurationMap loopDurationMap?: LoopDurationMap finished_at: number - extras?: any + extras?: JsonValue expand?: boolean // for UI details?: NodeTracing[][] // iteration or loop detail retryDetail?: NodeTracing[] // retry detail @@ -195,7 +224,7 @@ export type FetchWorkflowDraftResponse = { edges: Edge[] viewport?: Viewport } - features?: any + features?: WorkflowDraftFeatures created_at: number created_by: { id: string @@ -277,9 +306,9 @@ export type WorkflowPausedResponse = { workflow_run_id: string event: string data: { - outputs: any // todo: remove any + outputs: Record | null paused_nodes: string[] - reasons: any[] // todo: remove any + reasons: JsonValue[] workflow_run_id: string } } @@ -292,7 +321,7 @@ export type WorkflowFinishedResponse = { id: string workflow_id: string status: string - outputs: any + outputs: Record | string | null error: string elapsed_time: number total_tokens: number @@ -485,7 +514,7 @@ export type WorkflowRunHistory = { } inputs: Record status: WorkflowRunningStatus - outputs: Record + outputs: Record error?: string elapsed_time: number total_tokens: number @@ -508,7 +537,7 @@ export type ChatRunHistoryResponse = { export type NodesDefaultConfigsResponse = { type: string - config: any + config: unknown }[] export type ConversationVariableResponse = { @@ -521,7 +550,7 @@ export type ConversationVariableResponse = { export type IterationDurationMap = Record export type LoopDurationMap = Record -export type LoopVariableMap = Record +export type LoopVariableMap = Record export type WorkflowConfigResponse = { parallel_depth_limit: number @@ -546,21 +575,24 @@ export type PanelExposedType = { export type PanelProps = { getInputVars: (textList: string[]) => InputVar[] toVarInputs: (variables: Variable[]) => InputVar[] - runInputData: Record - runInputDataRef: RefObject> - setRunInputData: (data: Record) => void - runResult: any + runInputData: Record + runInputDataRef: RefObject> + setRunInputData: (data: Record) => void + runResult: NodeTracing | null } export type NodeRunResult = NodeTracing // Var Inspect -export enum VarInInspectType { - conversation = 'conversation', - environment = 'env', - node = 'node', - system = 'sys', -} +/* eslint-disable ts/no-redeclare -- const + type share names (erasable enum replacement) */ +export const VarInInspectType = { + conversation: 'conversation', + environment: 'env', + node: 'node', + system: 'sys', +} as const +export type VarInInspectType = typeof VarInInspectType[keyof typeof VarInInspectType] +/* eslint-enable ts/no-redeclare */ export type FullContent = { size_bytes: number @@ -580,7 +612,7 @@ export type VarInInspect = { description: string selector: ValueSelector // can get node id from selector[0] value_type: VarType - value: any + value?: JsonValue edited: boolean visible: boolean is_truncated: boolean diff --git a/web/utils/var.ts b/web/utils/var.ts index efad8794eb..c2c3f2b0f6 100644 --- a/web/utils/var.ts +++ b/web/utils/var.ts @@ -30,7 +30,7 @@ export const getNewVar = (key: string, type: string) => { } } -export const getNewVarInWorkflow = (key: string, type = InputVarType.textInput): InputVar => { +export const getNewVarInWorkflow = (key: string, type: InputVarType = InputVarType.textInput): InputVar => { const { ...rest } = VAR_ITEM_TEMPLATE_IN_WORKFLOW if (type !== InputVarType.textInput) { return {