diff --git a/api/core/workflow/legacy_system_files.py b/api/core/workflow/legacy_system_files.py index d5118c019a..ecdae0e2b7 100644 --- a/api/core/workflow/legacy_system_files.py +++ b/api/core/workflow/legacy_system_files.py @@ -14,6 +14,7 @@ from dataclasses import dataclass from typing import Any _LEGACY_SYSTEM_NODE_ID = "sys" +_LEGACY_USER_INPUT_NODE_ID = "userinput" _LEGACY_FILES_VARIABLE = "files" _COMPAT_VARIABLE_PREFIX = "sys_files" _COMPAT_VARIABLE_DESCRIPTION = "Compatibility input for deprecated sys.files." @@ -21,7 +22,7 @@ _FILE_LIST_TYPE = "file-list" _DEFAULT_FILE_NUMBER_LIMITS = 3 _DEFAULT_ALLOWED_FILE_UPLOAD_METHODS = ["local_file", "remote_url"] _DEFAULT_ALLOWED_FILE_TYPES = ["image", "document", "audio", "video"] -_SYS_FILES_TEMPLATE_PATTERN = re.compile(r"\{\{#sys\.files#\}\}") +_LEGACY_FILES_TEMPLATE_PATTERN = re.compile(r"\{\{#(?:sys|userinput)\.files#\}\}") @dataclass(frozen=True) @@ -41,7 +42,7 @@ def migrate_legacy_sys_files_graph( *, features: Mapping[str, Any] | None = None, ) -> dict[str, Any]: - """Return a graph where `sys.files` references point to a Start-node file-list variable.""" + """Return a graph where legacy file-system references point to a Start-node file-list variable.""" return migrate_legacy_sys_files_graph_with_result(graph, features=features).graph @@ -199,7 +200,7 @@ def _contains_legacy_sys_files_reference(value: Any) -> bool: return True if isinstance(value, str): - return bool(_SYS_FILES_TEMPLATE_PATTERN.search(value)) + return bool(_LEGACY_FILES_TEMPLATE_PATTERN.search(value)) if isinstance(value, Mapping): return any(_contains_legacy_sys_files_reference(item) for item in value.values()) @@ -215,7 +216,7 @@ def _replace_legacy_sys_files_references(value: Any, *, start_node_id: str, vari return [start_node_id, variable_name] if isinstance(value, str): - return _SYS_FILES_TEMPLATE_PATTERN.sub(f"{{{{#{start_node_id}.{variable_name}#}}}}", value) + return _LEGACY_FILES_TEMPLATE_PATTERN.sub(f"{{{{#{start_node_id}.{variable_name}#}}}}", value) if isinstance(value, Mapping): return { @@ -244,7 +245,7 @@ def _is_legacy_sys_files_selector(value: Any) -> bool: return ( isinstance(value, list) and len(value) == 2 - and value[0] == _LEGACY_SYSTEM_NODE_ID + and value[0] in (_LEGACY_SYSTEM_NODE_ID, _LEGACY_USER_INPUT_NODE_ID) and value[1] == _LEGACY_FILES_VARIABLE ) diff --git a/api/tests/unit_tests/core/workflow/test_legacy_system_files.py b/api/tests/unit_tests/core/workflow/test_legacy_system_files.py index 6269303fa2..fb87599f60 100644 --- a/api/tests/unit_tests/core/workflow/test_legacy_system_files.py +++ b/api/tests/unit_tests/core/workflow/test_legacy_system_files.py @@ -7,9 +7,12 @@ from core.workflow.legacy_system_files import ( ) _LEGACY_NODE_ID = "sys" +_LEGACY_ALIAS_NODE_ID = "userinput" _LEGACY_VARIABLE_NAME = "files" _LEGACY_SELECTOR = [_LEGACY_NODE_ID, _LEGACY_VARIABLE_NAME] _LEGACY_TEMPLATE = "{{#" + ".".join((_LEGACY_NODE_ID, _LEGACY_VARIABLE_NAME)) + "#}}" +_LEGACY_ALIAS_SELECTOR = [_LEGACY_ALIAS_NODE_ID, _LEGACY_VARIABLE_NAME] +_LEGACY_ALIAS_TEMPLATE = "{{#" + ".".join((_LEGACY_ALIAS_NODE_ID, _LEGACY_VARIABLE_NAME)) + "#}}" def test_migrate_legacy_sys_files_graph_ignores_invalid_or_unrelated_graphs(): @@ -53,6 +56,28 @@ def test_migrate_legacy_sys_files_graph_creates_collision_free_file_input_from_f assert result.graph["nodes"][1]["data"]["answer"] == ["start", "sys_files_1"] +def test_migrate_legacy_sys_files_graph_rewrites_userinput_files_alias_to_same_start_input(): + graph = { + "nodes": [ + {"id": "start", "data": {"type": "start", "variables": []}}, + { + "id": "answer", + "data": { + "type": "answer", + "answer": _LEGACY_ALIAS_SELECTOR, + "template": _LEGACY_ALIAS_TEMPLATE, + }, + }, + ], + } + + result = migrate_legacy_sys_files_graph_with_result(graph) + + assert result.changed + assert result.graph["nodes"][1]["data"]["answer"] == ["start", "sys_files"] + assert result.graph["nodes"][1]["data"]["template"] == "{{#start.sys_files#}}" + + def test_resolve_legacy_sys_files_compat_variable_handles_missing_start_variable(): assert resolve_legacy_sys_files_compat_variable({}) is None assert resolve_legacy_sys_files_compat_variable({"nodes": [1, {"data": {"value": _LEGACY_SELECTOR}}]}) is None diff --git a/web/app/components/workflow/nodes/start/__tests__/panel.spec.tsx b/web/app/components/workflow/nodes/start/__tests__/panel.spec.tsx index 7b7f7a096d..e854b3ce2a 100644 --- a/web/app/components/workflow/nodes/start/__tests__/panel.spec.tsx +++ b/web/app/components/workflow/nodes/start/__tests__/panel.spec.tsx @@ -8,6 +8,7 @@ import Panel from '../panel' const mockUseConfig = vi.hoisted(() => vi.fn()) const mockConfigVarModal = vi.hoisted(() => vi.fn()) const mockRemoveEffectVarConfirm = vi.hoisted(() => vi.fn()) +const legacyFilesVariable = ['userinput', 'files'].join('.') vi.mock('../use-config', () => ({ __esModule: true, @@ -90,7 +91,7 @@ describe('StartPanel', () => { render() expect(screen.getByText('userinput.query')).toBeInTheDocument() - expect(screen.getByText('userinput.files')).toBeInTheDocument() + expect(screen.queryByText(legacyFilesVariable)).not.toBeInTheDocument() expect(screen.queryByText('LEGACY')).not.toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: 'common.operation.add workflow.nodes.start.inputField' })) @@ -116,7 +117,8 @@ describe('StartPanel', () => { render() expect(screen.queryByText('userinput.query')).not.toBeInTheDocument() - expect(screen.getByText('LEGACY')).toBeInTheDocument() + expect(screen.queryByText(legacyFilesVariable)).not.toBeInTheDocument() + expect(screen.queryByText('LEGACY')).not.toBeInTheDocument() expect(screen.getByText('remove-confirm')).toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: 'confirm-add-var' })) diff --git a/web/app/components/workflow/nodes/start/panel.tsx b/web/app/components/workflow/nodes/start/panel.tsx index c723ee8931..3e5dcc463c 100644 --- a/web/app/components/workflow/nodes/start/panel.tsx +++ b/web/app/components/workflow/nodes/start/panel.tsx @@ -6,12 +6,19 @@ import { useTranslation } from 'react-i18next' import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal' import Field from '@/app/components/workflow/nodes/_base/components/field' import Split from '@/app/components/workflow/nodes/_base/components/split' +import { InputVarType } from '@/app/components/workflow/types' import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm' import VarItem from './components/var-item' import VarList from './components/var-list' import useConfig from './use-config' const i18nPrefix = 'nodes.start' +const chatQueryInputVar: InputVar = { + variable: 'userinput.query', + label: '', + type: InputVarType.textInput, + required: false, +} const Panel: FC> = ({ id, @@ -67,35 +74,22 @@ const Panel: FC> = ({ /> - { isChatMode && ( - - String - - )} - /> + <> + + + String + + )} + /> + > ) } - - - Array[File] - - )} - /> >