From 8cb2cffbf776ee409036baa143dee6ad52722fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Fri, 5 Jun 2026 10:14:23 +0800 Subject: [PATCH] feat: improve output node (#35511) --- .../__tests__/use-available-blocks.spec.ts | 10 ++-- .../hooks/__tests__/use-checklist.spec.ts | 35 ++++++++++++++ .../workflow/hooks/use-available-blocks.ts | 4 +- .../workflow/hooks/use-checklist.ts | 47 ++++++++++++++++++ .../__tests__/use-workflow-finished.spec.ts | 5 +- .../__tests__/use-workflow-text-chunk.spec.ts | 48 +++++++++++++++++++ .../use-workflow-started.ts | 1 + .../use-workflow-text-chunk.ts | 13 ++++- .../use-workflow-text-replace.ts | 1 + .../workflow/store/workflow/workflow-slice.ts | 1 + web/i18n/ar-TN/workflow.json | 1 + web/i18n/de-DE/workflow.json | 1 + web/i18n/en-US/workflow.json | 1 + web/i18n/es-ES/workflow.json | 1 + web/i18n/fa-IR/workflow.json | 1 + web/i18n/fr-FR/workflow.json | 1 + web/i18n/hi-IN/workflow.json | 1 + web/i18n/id-ID/workflow.json | 1 + web/i18n/it-IT/workflow.json | 1 + web/i18n/ja-JP/workflow.json | 1 + web/i18n/ko-KR/workflow.json | 1 + web/i18n/nl-NL/workflow.json | 3 +- web/i18n/pl-PL/workflow.json | 1 + web/i18n/pt-BR/workflow.json | 1 + web/i18n/ro-RO/workflow.json | 1 + web/i18n/ru-RU/workflow.json | 1 + web/i18n/sl-SI/workflow.json | 1 + web/i18n/th-TH/workflow.json | 1 + web/i18n/tr-TR/workflow.json | 1 + web/i18n/uk-UA/workflow.json | 1 + web/i18n/vi-VN/workflow.json | 1 + web/i18n/zh-Hans/workflow.json | 1 + web/i18n/zh-Hant/workflow.json | 1 + web/types/workflow.ts | 1 + 34 files changed, 182 insertions(+), 9 deletions(-) diff --git a/web/app/components/workflow/hooks/__tests__/use-available-blocks.spec.ts b/web/app/components/workflow/hooks/__tests__/use-available-blocks.spec.ts index c89ba9ce96..0989b08cbd 100644 --- a/web/app/components/workflow/hooks/__tests__/use-available-blocks.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-available-blocks.spec.ts @@ -81,9 +81,10 @@ describe('useAvailableBlocks', () => { expect(result.current.availableNextBlocks).toEqual([]) }) - it('should return empty array for End node', () => { + it('should return available nodes for End node', () => { const { result } = renderWorkflowHook(() => useAvailableBlocks(BlockEnum.End), { hooksStoreProps }) - expect(result.current.availableNextBlocks).toEqual([]) + expect(result.current.availableNextBlocks.length).toBeGreaterThan(0) + expect(result.current.availableNextBlocks).toContain(BlockEnum.Code) }) it('should return empty array for LoopEnd node', () => { @@ -143,10 +144,11 @@ describe('useAvailableBlocks', () => { expect(blocks.availablePrevBlocks).toEqual([]) }) - it('should return empty nextBlocks for End/LoopEnd/KnowledgeBase', () => { + it('should return empty nextBlocks for LoopEnd/KnowledgeBase and available nodes for End', () => { const { result } = renderWorkflowHook(() => useAvailableBlocks(BlockEnum.LLM), { hooksStoreProps }) - expect(result.current.getAvailableBlocks(BlockEnum.End).availableNextBlocks).toEqual([]) + expect(result.current.getAvailableBlocks(BlockEnum.End).availableNextBlocks.length).toBeGreaterThan(0) + expect(result.current.getAvailableBlocks(BlockEnum.End).availableNextBlocks).toContain(BlockEnum.Code) expect(result.current.getAvailableBlocks(BlockEnum.LoopEnd).availableNextBlocks).toEqual([]) expect(result.current.getAvailableBlocks(BlockEnum.KnowledgeBase).availableNextBlocks).toEqual([]) }) diff --git a/web/app/components/workflow/hooks/__tests__/use-checklist.spec.ts b/web/app/components/workflow/hooks/__tests__/use-checklist.spec.ts index f6d2774611..d583b95f79 100644 --- a/web/app/components/workflow/hooks/__tests__/use-checklist.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-checklist.spec.ts @@ -372,6 +372,41 @@ describe('useChecklist', () => { ]) }) + it('should detect duplicate output variables across end nodes', () => { + const startNode = createNode({ id: 'start', data: { type: BlockEnum.Start, title: 'Start' } }) + const firstEndNode = createNode({ + id: 'end-1', + data: { + type: BlockEnum.End, + title: 'Output 1', + outputs: [{ variable: 'workflow_id', value_selector: ['sys', 'workflow_id'] }], + }, + }) + const secondEndNode = createNode({ + id: 'end-2', + data: { + type: BlockEnum.End, + title: 'Output 2', + outputs: [{ variable: 'workflow_id', value_selector: ['sys', 'workflow_id'] }], + }, + }) + + const edges = [ + createEdge({ source: 'start', target: 'end-1' }), + createEdge({ source: 'start', target: 'end-2' }), + ] + + const { result } = renderWorkflowHook( + () => useChecklist([startNode, firstEndNode, secondEndNode], edges), + ) + + const firstWarning = result.current.find((item: ChecklistItem) => item.id === 'end-1') + const secondWarning = result.current.find((item: ChecklistItem) => item.id === 'end-2') + + expect(firstWarning?.errorMessages.some(message => message.includes('duplicateOutputVariable'))).toBe(true) + expect(secondWarning?.errorMessages.some(message => message.includes('duplicateOutputVariable'))).toBe(true) + }) + it('should sync checklist items to the workflow store without render phase update warnings', async () => { const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) try { diff --git a/web/app/components/workflow/hooks/use-available-blocks.ts b/web/app/components/workflow/hooks/use-available-blocks.ts index 3d92142b10..888e126474 100644 --- a/web/app/components/workflow/hooks/use-available-blocks.ts +++ b/web/app/components/workflow/hooks/use-available-blocks.ts @@ -30,7 +30,7 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, inContainer?: boolean) return availableNodesType }, [availableNodesType, nodeType]) const availableNextBlocks = useMemo(() => { - if (!nodeType || nodeType === BlockEnum.End || nodeType === BlockEnum.LoopEnd || nodeType === BlockEnum.KnowledgeBase) + if (!nodeType || nodeType === BlockEnum.LoopEnd || nodeType === BlockEnum.KnowledgeBase) return [] return availableNodesType @@ -42,7 +42,7 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, inContainer?: boolean) availablePrevBlocks = [] let availableNextBlocks = availableNodesType - if (!nodeType || nodeType === BlockEnum.End || nodeType === BlockEnum.LoopEnd || nodeType === BlockEnum.KnowledgeBase) + if (!nodeType || nodeType === BlockEnum.LoopEnd || nodeType === BlockEnum.KnowledgeBase) availableNextBlocks = [] return { diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index ae9cc0a4a2..ddba1edd5a 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -91,6 +91,43 @@ const START_NODE_TYPES: BlockEnum[] = [ BlockEnum.TriggerPlugin, ] +const getDuplicateEndOutputMessages = ( + nodes: Node[], + t: ReturnType['t'], +) => { + const variableOccurrences = new Map() + + nodes.forEach((node) => { + if (node.type !== CUSTOM_NODE || node.data.type !== BlockEnum.End) + return + + const outputs = ((node.data as { outputs?: Array<{ variable?: string }> }).outputs) || [] + outputs.forEach((output) => { + const variable = output.variable?.trim() + if (!variable) + return + + const occurrences = variableOccurrences.get(variable) || [] + occurrences.push(node.id) + variableOccurrences.set(variable, occurrences) + }) + }) + + const nodeMessages = new Map() + variableOccurrences.forEach((nodeIds, variable) => { + if (nodeIds.length <= 1) + return + + Array.from(new Set(nodeIds)).forEach((nodeId) => { + const messages = nodeMessages.get(nodeId) || [] + messages.push(t('errorMsg.duplicateOutputVariable', { ns: 'workflow', variable })) + nodeMessages.set(nodeId, messages) + }) + }) + + return nodeMessages +} + export const useChecklist = (nodes: Node[], edges: Edge[]) => { const { t } = useTranslation() const language = useGetLanguage() @@ -179,6 +216,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { const needWarningNodes = useMemo(() => { const list: ChecklistItem[] = [] const filteredNodes = nodes.filter(node => node.type === CUSTOM_NODE) + const duplicateEndOutputMessages = getDuplicateEndOutputMessages(filteredNodes, t) const { validNodes } = getValidTreeNodes(filteredNodes, edges) const installedPluginIds = new Set(modelProviders.map(p => extractPluginId(p.provider))) @@ -253,6 +291,8 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { } if (hasInvalidVar) errorMessages.push(t('errorMsg.invalidVariable', { ns: 'workflow' })) + + errorMessages.push(...(duplicateEndOutputMessages.get(node!.id) || [])) } const isStartNodeMeta = nodesExtraData?.[node!.data.type as BlockEnum]?.metaData.isStart ?? false @@ -386,6 +426,7 @@ export const useChecklistBeforePublish = () => { } = workflowStore.getState() const nodes = getNodes() const filteredNodes = nodes.filter(node => node.type === CUSTOM_NODE) + const duplicateEndOutputMessages = getDuplicateEndOutputMessages(filteredNodes, t) const { validNodes, maxDepth } = getValidTreeNodes(filteredNodes, edges) if (maxDepth > MAX_TREE_DEPTH) { @@ -500,6 +541,12 @@ export const useChecklistBeforePublish = () => { return false } + const duplicateOutputMessages = duplicateEndOutputMessages.get(node!.id) || [] + if (duplicateOutputMessages.length > 0) { + toast.error(`[${node!.data.title}] ${duplicateOutputMessages[0]}`) + return false + } + const availableVars = map[node!.id]!.availableVars for (const variable of usedVars) { diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-finished.spec.ts b/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-finished.spec.ts index 910b64ed18..eb0a4a97c6 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-finished.spec.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-finished.spec.ts @@ -27,6 +27,9 @@ describe('useWorkflowFinished', () => { data: { status: 'succeeded', outputs: { a: 'hello', b: 'world' } }, } as WorkflowFinishedResponse) - expect(store.getState().workflowRunningData!.resultTabActive).toBeFalsy() + const state = store.getState().workflowRunningData! + expect(state.result.status).toBe('succeeded') + expect(state.resultTabActive).toBe(false) + expect(state.resultText).toBe('') }) }) diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-text-chunk.spec.ts b/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-text-chunk.spec.ts index fcf36fe596..6af90592ef 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-text-chunk.spec.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/__tests__/use-workflow-text-chunk.spec.ts @@ -16,4 +16,52 @@ describe('useWorkflowTextChunk', () => { expect(state.resultText).toBe('Hello World') expect(state.resultTabActive).toBe(true) }) + + it('inserts a line break when text chunks switch to a different output selector', () => { + const { result, store } = renderWorkflowHook(() => useWorkflowTextChunk(), { + initialStoreState: { + workflowRunningData: baseRunningData({ resultText: 'Hello', resultTextSelectorKey: 'end.answer' }), + }, + }) + + result.current.handleWorkflowTextChunk({ + data: { text: '42', from_variable_selector: ['end', 'count'] }, + } as TextChunkResponse) + + const state = store.getState().workflowRunningData! + expect(state.resultText).toBe('Hello\n42') + expect(state.resultTextSelectorKey).toBe('end.count') + }) + + it('does not add an extra line break when the incoming chunk already starts with one', () => { + const { result, store } = renderWorkflowHook(() => useWorkflowTextChunk(), { + initialStoreState: { + workflowRunningData: baseRunningData({ resultText: 'Hello', resultTextSelectorKey: 'end.answer' }), + }, + }) + + result.current.handleWorkflowTextChunk({ + data: { text: '\n42', from_variable_selector: ['end', 'count'] }, + } as TextChunkResponse) + + const state = store.getState().workflowRunningData! + expect(state.resultText).toBe('Hello\n42') + expect(state.resultTextSelectorKey).toBe('end.count') + }) + + it('does not insert a line break when text chunks stay on the same output selector', () => { + const { result, store } = renderWorkflowHook(() => useWorkflowTextChunk(), { + initialStoreState: { + workflowRunningData: baseRunningData({ resultText: 'Hello', resultTextSelectorKey: 'end.answer' }), + }, + }) + + result.current.handleWorkflowTextChunk({ + data: { text: ' world', from_variable_selector: ['end', 'answer'] }, + } as TextChunkResponse) + + const state = store.getState().workflowRunningData! + expect(state.resultText).toBe('Hello world') + expect(state.resultTextSelectorKey).toBe('end.answer') + }) }) diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-started.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-started.ts index fa9109c460..a100768444 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-started.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-started.ts @@ -40,6 +40,7 @@ export const useWorkflowStarted = () => { status: WorkflowRunningStatus.Running, } draft.resultText = '' + draft.resultTextSelectorKey = undefined })) const nodes = getNodes() const newNodes = produce(nodes, (draft) => { diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-chunk.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-chunk.ts index dfca7f61a6..21e46d6d1e 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-chunk.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-chunk.ts @@ -7,15 +7,26 @@ export const useWorkflowTextChunk = () => { const workflowStore = useWorkflowStore() const handleWorkflowTextChunk = useCallback((params: TextChunkResponse) => { - const { data: { text } } = params + const { data: { text, from_variable_selector } } = params const { workflowRunningData, setWorkflowRunningData, } = workflowStore.getState() + const nextSelectorKey = from_variable_selector?.join('.') setWorkflowRunningData(produce(workflowRunningData!, (draft) => { draft.resultTabActive = true + const shouldInsertLineBreak = nextSelectorKey + && draft.resultText + && draft.resultTextSelectorKey + && draft.resultTextSelectorKey !== nextSelectorKey + && !draft.resultText.endsWith('\n') + && !text.startsWith('\n') + if (shouldInsertLineBreak) + draft.resultText += '\n' draft.resultText += text + if (nextSelectorKey) + draft.resultTextSelectorKey = nextSelectorKey })) }, [workflowStore]) diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-replace.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-replace.ts index 74bb0a7ad3..aa5696a6f1 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-replace.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-replace.ts @@ -14,6 +14,7 @@ export const useWorkflowTextReplace = () => { } = workflowStore.getState() setWorkflowRunningData(produce(workflowRunningData!, (draft) => { draft.resultText = text + draft.resultTextSelectorKey = undefined })) }, [workflowStore]) diff --git a/web/app/components/workflow/store/workflow/workflow-slice.ts b/web/app/components/workflow/store/workflow/workflow-slice.ts index 33c60deb16..b3464cd102 100644 --- a/web/app/components/workflow/store/workflow/workflow-slice.ts +++ b/web/app/components/workflow/store/workflow/workflow-slice.ts @@ -10,6 +10,7 @@ import type { FileUploadConfigResponse } from '@/models/common' type PreviewRunningData = WorkflowRunningData & { resultTabActive?: boolean resultText?: string + resultTextSelectorKey?: string // human input form schema or data cached when node is in 'Paused' status extraContentAndFormData?: Record } diff --git a/web/i18n/ar-TN/workflow.json b/web/i18n/ar-TN/workflow.json index d63ef946ab..29c0e5a485 100644 --- a/web/i18n/ar-TN/workflow.json +++ b/web/i18n/ar-TN/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "الرجاء إضافة عقدة البداية أولاً قبل {{operation}}", "errorMsg.authRequired": "الترخيص مطلوب", "errorMsg.configureModel": "قم بتكوين نموذج", + "errorMsg.duplicateOutputVariable": "متغير عقدة الإخراج \"{{variable}}\" مكرر. يجب أن تكون أسماء متغيرات عقدة الإخراج فريدة.", "errorMsg.fieldRequired": "{{field}} مطلوب", "errorMsg.fields.code": "الكود", "errorMsg.fields.model": "النموذج", diff --git a/web/i18n/de-DE/workflow.json b/web/i18n/de-DE/workflow.json index f28df9302e..8a033fcd2a 100644 --- a/web/i18n/de-DE/workflow.json +++ b/web/i18n/de-DE/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Bitte füge zuerst einen Startknoten hinzu, bevor du {{operation}}.", "errorMsg.authRequired": "Autorisierung ist erforderlich", "errorMsg.configureModel": "Modell konfigurieren", + "errorMsg.duplicateOutputVariable": "Doppelte Variable des Output-Knotens \"{{variable}}\". Variablennamen von Output-Knoten müssen eindeutig sein.", "errorMsg.fieldRequired": "{{field}} ist erforderlich", "errorMsg.fields.code": "Code", "errorMsg.fields.model": "Modell", diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index 45263f4a3b..569a87b1b8 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Please add a start node first before {{operation}}", "errorMsg.authRequired": "Authorization is required", "errorMsg.configureModel": "Configure a model", + "errorMsg.duplicateOutputVariable": "Duplicate Output node variable \"{{variable}}\". Output node variable names must be unique.", "errorMsg.fieldRequired": "{{field}} is required", "errorMsg.fields.code": "Code", "errorMsg.fields.model": "Model", diff --git a/web/i18n/es-ES/workflow.json b/web/i18n/es-ES/workflow.json index 0773d2b280..5272bd1269 100644 --- a/web/i18n/es-ES/workflow.json +++ b/web/i18n/es-ES/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Por favor, agregue primero un nodo de inicio antes de {{operation}}", "errorMsg.authRequired": "Se requiere autorización", "errorMsg.configureModel": "Configura un modelo", + "errorMsg.duplicateOutputVariable": "Variable duplicada del nodo de salida \"{{variable}}\". Los nombres de las variables del nodo de salida deben ser únicos.", "errorMsg.fieldRequired": "Se requiere {{field}}", "errorMsg.fields.code": "Código", "errorMsg.fields.model": "Modelo", diff --git a/web/i18n/fa-IR/workflow.json b/web/i18n/fa-IR/workflow.json index 8a7a7de637..f0129b5572 100644 --- a/web/i18n/fa-IR/workflow.json +++ b/web/i18n/fa-IR/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "لطفاً قبل از {{operation}} ابتدا یک گره شروع اضافه کنید", "errorMsg.authRequired": "احراز هویت الزامی است", "errorMsg.configureModel": "یک مدل پیکربندی کنید", + "errorMsg.duplicateOutputVariable": "متغیر گره خروجی «{{variable}}» تکراری است. نام متغیرهای گره خروجی باید یکتا باشند.", "errorMsg.fieldRequired": "{{field}} الزامی است", "errorMsg.fields.code": "کد", "errorMsg.fields.model": "مدل", diff --git a/web/i18n/fr-FR/workflow.json b/web/i18n/fr-FR/workflow.json index 439e42df1a..aa236a5639 100644 --- a/web/i18n/fr-FR/workflow.json +++ b/web/i18n/fr-FR/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Veuillez d'abord ajouter un nœud de départ avant {{operation}}", "errorMsg.authRequired": "Autorisation requise", "errorMsg.configureModel": "Configurez un modèle", + "errorMsg.duplicateOutputVariable": "Variable du nœud de sortie en double \"{{variable}}\". Les noms des variables du nœud de sortie doivent être uniques.", "errorMsg.fieldRequired": "{{field}} est requis", "errorMsg.fields.code": "Code", "errorMsg.fields.model": "Modèle", diff --git a/web/i18n/hi-IN/workflow.json b/web/i18n/hi-IN/workflow.json index 474fb2c0ca..cd5f120086 100644 --- a/web/i18n/hi-IN/workflow.json +++ b/web/i18n/hi-IN/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "कृपया {{operation}} से पहले एक प्रारंभ नोड जोड़ें", "errorMsg.authRequired": "प्राधिकरण आवश्यक है", "errorMsg.configureModel": "एक मॉडल कॉन्फ़िगर करें", + "errorMsg.duplicateOutputVariable": "आउटपुट नोड वेरिएबल \"{{variable}}\" डुप्लिकेट है। आउटपुट नोड वेरिएबल के नाम यूनिक होने चाहिए।", "errorMsg.fieldRequired": "{{field}} आवश्यक है", "errorMsg.fields.code": "कोड", "errorMsg.fields.model": "मॉडल", diff --git a/web/i18n/id-ID/workflow.json b/web/i18n/id-ID/workflow.json index f10afa7755..065a89ef81 100644 --- a/web/i18n/id-ID/workflow.json +++ b/web/i18n/id-ID/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Silakan tambahkan node awal terlebih dahulu sebelum {{operation}}", "errorMsg.authRequired": "Otorisasi diperlukan", "errorMsg.configureModel": "Konfigurasikan model", + "errorMsg.duplicateOutputVariable": "Variabel node output duplikat \"{{variable}}\". Nama variabel node output harus unik.", "errorMsg.fieldRequired": "{{field}} wajib diisi", "errorMsg.fields.code": "Kode", "errorMsg.fields.model": "Model", diff --git a/web/i18n/it-IT/workflow.json b/web/i18n/it-IT/workflow.json index 61b4dd633b..5d7cd0e4e2 100644 --- a/web/i18n/it-IT/workflow.json +++ b/web/i18n/it-IT/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Per favore aggiungi prima un nodo iniziale prima di {{operation}}", "errorMsg.authRequired": "È richiesta l'autorizzazione", "errorMsg.configureModel": "Configura un modello", + "errorMsg.duplicateOutputVariable": "Variabile del nodo di output duplicata \"{{variable}}\". I nomi delle variabili del nodo di output devono essere univoci.", "errorMsg.fieldRequired": "{{field}} è richiesto", "errorMsg.fields.code": "Codice", "errorMsg.fields.model": "Modello", diff --git a/web/i18n/ja-JP/workflow.json b/web/i18n/ja-JP/workflow.json index 0b8d7bc354..ee07f4ded1 100644 --- a/web/i18n/ja-JP/workflow.json +++ b/web/i18n/ja-JP/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "{{operation}}前に開始ノードを追加してください", "errorMsg.authRequired": "認証が必要です", "errorMsg.configureModel": "モデルを設定してください", + "errorMsg.duplicateOutputVariable": "出力ノード変数「{{variable}}」が重複しています。出力ノード変数名は一意である必要があります。", "errorMsg.fieldRequired": "{{field}} は必須です", "errorMsg.fields.code": "コード", "errorMsg.fields.model": "モデル", diff --git a/web/i18n/ko-KR/workflow.json b/web/i18n/ko-KR/workflow.json index e428c475a5..f9e0f544d2 100644 --- a/web/i18n/ko-KR/workflow.json +++ b/web/i18n/ko-KR/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "{{operation}} 전에 먼저 시작 노드를 추가해 주세요", "errorMsg.authRequired": "인증이 필요합니다", "errorMsg.configureModel": "모델을 구성하세요", + "errorMsg.duplicateOutputVariable": "출력 노드 변수 \"{{variable}}\"가 중복되었습니다. 출력 노드 변수 이름은 고유해야 합니다.", "errorMsg.fieldRequired": "{{field}}가 필요합니다", "errorMsg.fields.code": "코드", "errorMsg.fields.model": "모델", diff --git a/web/i18n/nl-NL/workflow.json b/web/i18n/nl-NL/workflow.json index 2daa7fb437..00d0e64160 100644 --- a/web/i18n/nl-NL/workflow.json +++ b/web/i18n/nl-NL/workflow.json @@ -130,7 +130,7 @@ "comments.filter.all": "Alles", "comments.filter.onlyYourThreads": "Alleen jouw threads", "comments.filter.showResolved": "Opgeloste tonen", - "comments.loading": "Laden\u2026", + "comments.loading": "Laden…", "comments.noComments": "Nog geen reacties", "comments.panelTitle": "Reactie", "comments.placeholder.add": "Voeg een reactie toe", @@ -344,6 +344,7 @@ "error.startNodeRequired": "Please add a start node first before {{operation}}", "errorMsg.authRequired": "Authorization is required", "errorMsg.configureModel": "Configureer een model", + "errorMsg.duplicateOutputVariable": "Dubbele variabele van de outputnode \"{{variable}}\". Namen van outputnode-variabelen moeten uniek zijn.", "errorMsg.fieldRequired": "{{field}} is required", "errorMsg.fields.code": "Code", "errorMsg.fields.model": "Model", diff --git a/web/i18n/pl-PL/workflow.json b/web/i18n/pl-PL/workflow.json index 31f0469634..d9c8299bb3 100644 --- a/web/i18n/pl-PL/workflow.json +++ b/web/i18n/pl-PL/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Najpierw dodaj węzeł początkowy przed {{operation}}", "errorMsg.authRequired": "Wymagana autoryzacja", "errorMsg.configureModel": "Skonfiguruj model", + "errorMsg.duplicateOutputVariable": "Zduplikowana zmienna węzła wyjściowego \"{{variable}}\". Nazwy zmiennych węzła wyjściowego muszą być unikalne.", "errorMsg.fieldRequired": "{{field}} jest wymagane", "errorMsg.fields.code": "Kod", "errorMsg.fields.model": "Model", diff --git a/web/i18n/pt-BR/workflow.json b/web/i18n/pt-BR/workflow.json index c577b86e05..51523cdf59 100644 --- a/web/i18n/pt-BR/workflow.json +++ b/web/i18n/pt-BR/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Por favor, adicione um nó inicial antes de {{operation}}", "errorMsg.authRequired": "Autorização é necessária", "errorMsg.configureModel": "Configure um modelo", + "errorMsg.duplicateOutputVariable": "Variável duplicada do nó de saída \"{{variable}}\". Os nomes das variáveis do nó de saída devem ser únicos.", "errorMsg.fieldRequired": "{{field}} é obrigatório", "errorMsg.fields.code": "Código", "errorMsg.fields.model": "Modelo", diff --git a/web/i18n/ro-RO/workflow.json b/web/i18n/ro-RO/workflow.json index 10d6ca98d1..5b375ee4af 100644 --- a/web/i18n/ro-RO/workflow.json +++ b/web/i18n/ro-RO/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Vă rugăm să adăugați mai întâi un nod de pornire înainte de {{operation}}", "errorMsg.authRequired": "Autorizarea este necesară", "errorMsg.configureModel": "Configurați un model", + "errorMsg.duplicateOutputVariable": "Variabilă duplicată a nodului de ieșire „{{variable}}”. Numele variabilelor nodului de ieșire trebuie să fie unice.", "errorMsg.fieldRequired": "{{field}} este obligatoriu", "errorMsg.fields.code": "Cod", "errorMsg.fields.model": "Model", diff --git a/web/i18n/ru-RU/workflow.json b/web/i18n/ru-RU/workflow.json index fedb86151d..af84a1cb30 100644 --- a/web/i18n/ru-RU/workflow.json +++ b/web/i18n/ru-RU/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Пожалуйста, сначала добавьте начальный узел перед {{operation}}", "errorMsg.authRequired": "Требуется авторизация", "errorMsg.configureModel": "Настройте модель", + "errorMsg.duplicateOutputVariable": "Повторяющаяся переменная узла вывода \"{{variable}}\". Имена переменных узла вывода должны быть уникальными.", "errorMsg.fieldRequired": "{{field}} обязательно для заполнения", "errorMsg.fields.code": "Код", "errorMsg.fields.model": "Модель", diff --git a/web/i18n/sl-SI/workflow.json b/web/i18n/sl-SI/workflow.json index eb7017c46a..0b0b1ca349 100644 --- a/web/i18n/sl-SI/workflow.json +++ b/web/i18n/sl-SI/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Prosimo, najprej dodajte začetni vozel pred {{operation}}", "errorMsg.authRequired": "Zahtevana je avtorizacija", "errorMsg.configureModel": "Konfiguriraj model", + "errorMsg.duplicateOutputVariable": "Podvojena spremenljivka izhodnega vozlišča \"{{variable}}\". Imena spremenljivk izhodnega vozlišča morajo biti enolična.", "errorMsg.fieldRequired": "{{field}} je obvezno", "errorMsg.fields.code": "Koda", "errorMsg.fields.model": "Model", diff --git a/web/i18n/th-TH/workflow.json b/web/i18n/th-TH/workflow.json index d99ba37a4a..fa17229b35 100644 --- a/web/i18n/th-TH/workflow.json +++ b/web/i18n/th-TH/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "โปรดเพิ่มโหนดเริ่มต้นก่อน {{operation}}", "errorMsg.authRequired": "ต้องได้รับอนุญาต", "errorMsg.configureModel": "กำหนดค่าโมเดล", + "errorMsg.duplicateOutputVariable": "ตัวแปรของโหนดเอาต์พุต \"{{variable}}\" ซ้ำกัน ชื่อตัวแปรของโหนดเอาต์พุตต้องไม่ซ้ำกัน", "errorMsg.fieldRequired": "{{field}} เป็นสิ่งจําเป็น", "errorMsg.fields.code": "รหัส", "errorMsg.fields.model": "แบบ", diff --git a/web/i18n/tr-TR/workflow.json b/web/i18n/tr-TR/workflow.json index 6bfc7de148..c6fca70942 100644 --- a/web/i18n/tr-TR/workflow.json +++ b/web/i18n/tr-TR/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Lütfen {{operation}} işleminden önce önce bir başlangıç düğümü ekleyin", "errorMsg.authRequired": "Yetkilendirme gereklidir", "errorMsg.configureModel": "Bir model yapılandırın", + "errorMsg.duplicateOutputVariable": "Yinelenen çıktı düğümü değişkeni \"{{variable}}\". Çıktı düğümü değişken adları benzersiz olmalıdır.", "errorMsg.fieldRequired": "{{field}} gereklidir", "errorMsg.fields.code": "Kod", "errorMsg.fields.model": "Model", diff --git a/web/i18n/uk-UA/workflow.json b/web/i18n/uk-UA/workflow.json index cbbf4f082d..106b98bf05 100644 --- a/web/i18n/uk-UA/workflow.json +++ b/web/i18n/uk-UA/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Будь ласка, спершу додайте стартовий вузол перед {{operation}}", "errorMsg.authRequired": "Потрібна авторизація", "errorMsg.configureModel": "Налаштуйте модель", + "errorMsg.duplicateOutputVariable": "Дубльована змінна вузла виводу \"{{variable}}\". Назви змінних вузла виводу мають бути унікальними.", "errorMsg.fieldRequired": "{{field}} є обов'язковим", "errorMsg.fields.code": "Код", "errorMsg.fields.model": "Модель", diff --git a/web/i18n/vi-VN/workflow.json b/web/i18n/vi-VN/workflow.json index 716a48fc70..ffff18bd29 100644 --- a/web/i18n/vi-VN/workflow.json +++ b/web/i18n/vi-VN/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "Vui lòng thêm một nút bắt đầu trước {{operation}}", "errorMsg.authRequired": "Yêu cầu xác thực", "errorMsg.configureModel": "Cấu hình mô hình", + "errorMsg.duplicateOutputVariable": "Biến của nút đầu ra \"{{variable}}\" bị trùng lặp. Tên biến của nút đầu ra phải là duy nhất.", "errorMsg.fieldRequired": "{{field}} là bắt buộc", "errorMsg.fields.code": "Mã", "errorMsg.fields.model": "Mô hình", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index 46193cd492..9f41549b51 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "请先添加开始节点,然后再{{operation}}", "errorMsg.authRequired": "请先授权", "errorMsg.configureModel": "请配置模型", + "errorMsg.duplicateOutputVariable": "输出节点变量“{{variable}}”重复。输出节点变量名必须唯一。", "errorMsg.fieldRequired": "{{field}} 不能为空", "errorMsg.fields.code": "代码", "errorMsg.fields.model": "模型", diff --git a/web/i18n/zh-Hant/workflow.json b/web/i18n/zh-Hant/workflow.json index 0b5437bf66..8823e133d3 100644 --- a/web/i18n/zh-Hant/workflow.json +++ b/web/i18n/zh-Hant/workflow.json @@ -344,6 +344,7 @@ "error.startNodeRequired": "請先新增一個起始節點,再執行 {{operation}}", "errorMsg.authRequired": "請先授權", "errorMsg.configureModel": "請配置模型", + "errorMsg.duplicateOutputVariable": "輸出節點變數「{{variable}}」重複。輸出節點變數名稱必須唯一。", "errorMsg.fieldRequired": "{{field}} 不能為空", "errorMsg.fields.code": "程式碼", "errorMsg.fields.model": "模型", diff --git a/web/types/workflow.ts b/web/types/workflow.ts index 66297ba5c2..6bbd36a51f 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -305,6 +305,7 @@ export type TextChunkResponse = { event: string data: { text: string + from_variable_selector?: string[] } }