From ccfc8c6f15b9837abf303925344b7f7f493ade91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Wed, 8 Apr 2026 21:29:07 +0800 Subject: [PATCH] chore: align prompt editor var checks with use-check-list checks (#34715) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../hitl-input-block/__tests__/index.spec.tsx | 5 +- .../__tests__/variable-block.spec.tsx | 5 +- .../hitl-input-block-replacement-block.tsx | 2 +- .../plugins/hitl-input-block/index.tsx | 14 ++- .../hitl-input-block/variable-block.tsx | 6 +- .../__tests__/component.spec.tsx | 100 ++++++++++++++---- .../__tests__/index.spec.tsx | 6 +- .../__tests__/node.spec.tsx | 65 ++++++------ ...-variable-block-replacement-block.spec.tsx | 9 +- .../workflow-variable-block/component.tsx | 51 ++++----- .../plugins/workflow-variable-block/index.tsx | 25 +++-- .../plugins/workflow-variable-block/node.tsx | 81 ++++++++------ ...kflow-variable-block-replacement-block.tsx | 18 ++-- web/eslint-suppressions.json | 9 +- 14 files changed, 237 insertions(+), 159 deletions(-) diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/index.spec.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/index.spec.tsx index b5f38cdd1b..62b867d155 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/index.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/index.spec.tsx @@ -120,7 +120,10 @@ describe('HITLInputBlock', () => { }) await waitFor(() => { - expect(onWorkflowMapUpdate).toHaveBeenCalledWith(workflowNodesMap) + expect(onWorkflowMapUpdate).toHaveBeenCalledWith({ + workflowNodesMap, + availableVariables: [], + }) }) }) }) diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/variable-block.spec.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/variable-block.spec.tsx index c848d08c5c..db3e474b60 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/variable-block.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/variable-block.spec.tsx @@ -148,7 +148,10 @@ describe('HITLInputVariableBlockComponent', () => { editor!.update(() => { $getRoot().selectEnd() }) - handled = editor!.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, createWorkflowNodesMap()) + handled = editor!.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, { + workflowNodesMap: createWorkflowNodesMap(), + availableVariables: [], + }) }) expect(handled).toBe(true) diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/hitl-input-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/hitl-input-block-replacement-block.tsx index cd1515c57d..0da99b9155 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/hitl-input-block-replacement-block.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/hitl-input-block-replacement-block.tsx @@ -22,7 +22,7 @@ const HITLInputReplacementBlock = ({ onFormInputsChange, onFormInputItemRename, onFormInputItemRemove, - workflowNodesMap, + workflowNodesMap = {}, getVarType, variables, readonly, diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/index.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/index.tsx index 2c10fdbd5a..1b2af39ebe 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/index.tsx @@ -14,6 +14,7 @@ import { useEffect, } from 'react' import { CustomTextNode } from '../custom-text/node' +import { UPDATE_WORKFLOW_NODES_MAP as WORKFLOW_UPDATE_WORKFLOW_NODES_MAP } from '../workflow-variable-block' import { $createHITLInputNode, HITLInputNode, @@ -21,11 +22,13 @@ import { export const INSERT_HITL_INPUT_BLOCK_COMMAND = createCommand('INSERT_HITL_INPUT_BLOCK_COMMAND') export const DELETE_HITL_INPUT_BLOCK_COMMAND = createCommand('DELETE_HITL_INPUT_BLOCK_COMMAND') -export const UPDATE_WORKFLOW_NODES_MAP = createCommand('UPDATE_WORKFLOW_NODES_MAP') +export const UPDATE_WORKFLOW_NODES_MAP = WORKFLOW_UPDATE_WORKFLOW_NODES_MAP + const HITLInputBlock = memo(({ onInsert, onDelete, - workflowNodesMap, + workflowNodesMap = {}, + variables: workflowAvailableVariables, getVarType, readonly, }: HITLInputBlockType) => { @@ -33,9 +36,12 @@ const HITLInputBlock = memo(({ useEffect(() => { editor.update(() => { - editor.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, workflowNodesMap) + editor.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, { + workflowNodesMap: workflowNodesMap || {}, + availableVariables: workflowAvailableVariables || [], + }) }) - }, [editor, workflowNodesMap]) + }, [editor, workflowNodesMap, workflowAvailableVariables]) useEffect(() => { if (!editor.hasNodes([HITLInputNode])) diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/variable-block.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/variable-block.tsx index b1374b994f..a466d64eff 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/variable-block.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/variable-block.tsx @@ -1,3 +1,4 @@ +import type { UpdateWorkflowNodesMapPayload } from '../workflow-variable-block' import type { WorkflowNodesMap } from '../workflow-variable-block/node' import type { ValueSelector, Var } from '@/app/components/workflow/types' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' @@ -98,9 +99,8 @@ const HITLInputVariableBlockComponent = ({ return mergeRegister( editor.registerCommand( UPDATE_WORKFLOW_NODES_MAP, - (workflowNodesMap: WorkflowNodesMap) => { - setLocalWorkflowNodesMap(workflowNodesMap) - + (payload: UpdateWorkflowNodesMapPayload) => { + setLocalWorkflowNodesMap(payload.workflowNodesMap) return true }, COMMAND_PRIORITY_EDITOR, diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/component.spec.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/component.spec.tsx index ff064f2a99..a6cb70ddb6 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/component.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/component.spec.tsx @@ -1,4 +1,5 @@ import type { LexicalEditor } from 'lexical' +import type { UpdateWorkflowNodesMapPayload } from '../index' import type { ValueSelector, Var } from '@/app/components/workflow/types' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { mergeRegister } from '@lexical/utils' @@ -216,7 +217,7 @@ describe('WorkflowVariableBlockComponent', () => { }) }) - it('should mark env variable invalid when not found in environmentVariables', () => { + it('should treat env variable as valid regardless of environmentVariables contents', () => { const environmentVariables: Var[] = [{ variable: 'env.valid_key', type: VarType.string }] render( @@ -229,7 +230,7 @@ describe('WorkflowVariableBlockComponent', () => { ) expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({ - errorMsg: expect.any(String), + errorMsg: undefined, })) }) @@ -281,7 +282,7 @@ describe('WorkflowVariableBlockComponent', () => { })) }) - it('should evaluate env fallback selector tokens when classifier is forced', () => { + it('should mark forced env branch invalid when selector prefix is missing', () => { mockForcedVariableKind.value = 'env' const environmentVariables: Var[] = [{ variable: '.', type: VarType.string }] @@ -295,7 +296,7 @@ describe('WorkflowVariableBlockComponent', () => { ) expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({ - errorMsg: undefined, + errorMsg: expect.any(String), })) }) @@ -330,7 +331,7 @@ describe('WorkflowVariableBlockComponent', () => { })) }) - it('should mark conversation variable invalid when not found in conversationVariables', () => { + it('should treat conversation variable as valid regardless of conversationVariables contents', () => { const conversationVariables: Var[] = [{ variable: 'conversation.other', type: VarType.string }] render( @@ -343,7 +344,7 @@ describe('WorkflowVariableBlockComponent', () => { ) expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({ - errorMsg: expect.any(String), + errorMsg: undefined, })) }) @@ -364,7 +365,7 @@ describe('WorkflowVariableBlockComponent', () => { })) }) - it('should evaluate conversation fallback selector tokens when classifier is forced', () => { + it('should mark forced conversation branch invalid when selector prefix is missing', () => { mockForcedVariableKind.value = 'conversation' const conversationVariables: Var[] = [{ variable: '.', type: VarType.string }] @@ -378,7 +379,7 @@ describe('WorkflowVariableBlockComponent', () => { ) expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({ - errorMsg: undefined, + errorMsg: expect.any(String), })) }) @@ -427,7 +428,7 @@ describe('WorkflowVariableBlockComponent', () => { })) }) - it('should mark rag variable invalid when not found in ragVariables', () => { + it('should treat rag variable as valid regardless of ragVariables contents', () => { const ragVariables: Var[] = [{ variable: 'rag.shared.other', type: VarType.string }] render( @@ -440,7 +441,7 @@ describe('WorkflowVariableBlockComponent', () => { ) expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({ - errorMsg: expect.any(String), + errorMsg: undefined, })) }) @@ -461,7 +462,7 @@ describe('WorkflowVariableBlockComponent', () => { })) }) - it('should evaluate rag fallback selector tokens when classifier is forced', () => { + it('should mark forced rag branch invalid when selector prefix is missing', () => { mockForcedVariableKind.value = 'rag' const ragVariables: Var[] = [{ variable: '..', type: VarType.string }] @@ -475,7 +476,7 @@ describe('WorkflowVariableBlockComponent', () => { ) expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({ - errorMsg: undefined, + errorMsg: expect.any(String), })) }) @@ -488,20 +489,81 @@ describe('WorkflowVariableBlockComponent', () => { />, ) - const updateHandler = mockRegisterCommand.mock.calls[0][1] as (map: Record) => boolean + const updateHandler = mockRegisterCommand.mock.calls[0][1] as (payload: UpdateWorkflowNodesMapPayload) => boolean let result = false act(() => { result = updateHandler({ - 'node-1': { - title: 'Updated', - type: BlockEnum.LLM, - width: 100, - height: 50, - position: { x: 0, y: 0 }, + workflowNodesMap: { + 'node-1': { + title: 'Updated', + type: BlockEnum.LLM, + width: 100, + height: 50, + position: { x: 0, y: 0 }, + }, }, + availableVariables: [], }) }) expect(result).toBe(true) }) + + it('should mark non-special variable invalid when source key is missing in availableVariables', () => { + render( + , + ) + + expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({ + errorMsg: expect.any(String), + })) + }) + + it('should keep non-special variable valid when source key exists in availableVariables', () => { + render( + , + ) + + expect(mockVarLabel).toHaveBeenCalledWith(expect.objectContaining({ + errorMsg: undefined, + })) + }) }) diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/index.spec.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/index.spec.tsx index 1591dc44f9..00b5b66660 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/index.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/index.spec.tsx @@ -105,7 +105,10 @@ describe('WorkflowVariableBlock', () => { ) expect(mockUpdate).toHaveBeenCalled() - expect(mockDispatchCommand).toHaveBeenCalledWith(UPDATE_WORKFLOW_NODES_MAP, workflowNodesMap) + expect(mockDispatchCommand).toHaveBeenCalledWith(UPDATE_WORKFLOW_NODES_MAP, { + workflowNodesMap, + availableVariables: [], + }) }) it('should throw when WorkflowVariableBlockNode is not registered', () => { @@ -137,6 +140,7 @@ describe('WorkflowVariableBlock', () => { ['node-1', 'answer'], workflowNodesMap, getVarType, + [], ) expect($insertNodes).toHaveBeenCalledWith([{ id: 'workflow-node' }]) expect(onInsert).toHaveBeenCalledTimes(1) diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/node.spec.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/node.spec.tsx index 8d7a1cc33d..4154cd2fd9 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/node.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/node.spec.tsx @@ -1,5 +1,5 @@ import type { Klass, LexicalEditor, LexicalNode } from 'lexical' -import type { Var } from '@/app/components/workflow/types' +import type { NodeOutPutVar } from '@/app/components/workflow/types' import { createEditor } from 'lexical' import { Type } from '@/app/components/workflow/nodes/llm/types' import { BlockEnum, VarType } from '@/app/components/workflow/types' @@ -57,45 +57,43 @@ describe('WorkflowVariableBlockNode', () => { it('should decorate with component props from node state', () => { runInEditor(() => { const getVarType = vi.fn(() => Type.number) - const environmentVariables: Var[] = [{ variable: 'env.key', type: VarType.string }] - const conversationVariables: Var[] = [{ variable: 'conversation.topic', type: VarType.string }] - const ragVariables: Var[] = [{ variable: 'rag.shared.answer', type: VarType.string }] + const availableVariables: NodeOutPutVar[] = [{ + nodeId: 'node-1', + title: 'Node A', + vars: [{ variable: 'answer', type: VarType.string }], + }] const node = new WorkflowVariableBlockNode( ['node-1', 'answer'], { 'node-1': { title: 'A', type: BlockEnum.LLM } }, getVarType, 'decorator-key', - environmentVariables, - conversationVariables, - ragVariables, + availableVariables, ) const decorated = node.decorate() expect(decorated.props.nodeKey).toBe('decorator-key') expect(decorated.props.variables).toEqual(['node-1', 'answer']) expect(decorated.props.workflowNodesMap).toEqual({ 'node-1': { title: 'A', type: BlockEnum.LLM } }) - expect(decorated.props.environmentVariables).toEqual(environmentVariables) - expect(decorated.props.conversationVariables).toEqual(conversationVariables) - expect(decorated.props.ragVariables).toEqual(ragVariables) + expect(decorated.props.availableVariables).toEqual(availableVariables) }) }) - it('should export and import json with full payload', () => { + it('should export and import json with available variables payload', () => { runInEditor(() => { const getVarType = vi.fn(() => Type.string) - const environmentVariables: Var[] = [{ variable: 'env.key', type: VarType.string }] - const conversationVariables: Var[] = [{ variable: 'conversation.topic', type: VarType.string }] - const ragVariables: Var[] = [{ variable: 'rag.shared.answer', type: VarType.string }] + const availableVariables: NodeOutPutVar[] = [{ + nodeId: 'node-1', + title: 'Node A', + vars: [{ variable: 'answer', type: VarType.string }], + }] const node = new WorkflowVariableBlockNode( ['node-1', 'answer'], { 'node-1': { title: 'A', type: BlockEnum.LLM } }, getVarType, undefined, - environmentVariables, - conversationVariables, - ragVariables, + availableVariables, ) expect(node.exportJSON()).toEqual({ @@ -104,9 +102,7 @@ describe('WorkflowVariableBlockNode', () => { variables: ['node-1', 'answer'], workflowNodesMap: { 'node-1': { title: 'A', type: BlockEnum.LLM } }, getVarType, - environmentVariables, - conversationVariables, - ragVariables, + availableVariables, }) const imported = WorkflowVariableBlockNode.importJSON({ @@ -115,48 +111,51 @@ describe('WorkflowVariableBlockNode', () => { variables: ['node-2', 'result'], workflowNodesMap: { 'node-2': { title: 'B', type: BlockEnum.Tool } }, getVarType, - environmentVariables, - conversationVariables, - ragVariables, + availableVariables, }) expect(imported).toBeInstanceOf(WorkflowVariableBlockNode) expect(imported.getVariables()).toEqual(['node-2', 'result']) expect(imported.getWorkflowNodesMap()).toEqual({ 'node-2': { title: 'B', type: BlockEnum.Tool } }) + expect(imported.getAvailableVariables()).toEqual(availableVariables) }) }) it('should return getters and text content in expected format', () => { runInEditor(() => { const getVarType = vi.fn(() => Type.string) - const environmentVariables: Var[] = [{ variable: 'env.key', type: VarType.string }] - const conversationVariables: Var[] = [{ variable: 'conversation.topic', type: VarType.string }] - const ragVariables: Var[] = [{ variable: 'rag.shared.answer', type: VarType.string }] + const availableVariables: NodeOutPutVar[] = [{ + nodeId: 'node-1', + title: 'Node A', + vars: [{ variable: 'answer', type: VarType.string }], + }] const node = new WorkflowVariableBlockNode( ['node-1', 'answer'], { 'node-1': { title: 'A', type: BlockEnum.LLM } }, getVarType, undefined, - environmentVariables, - conversationVariables, - ragVariables, + availableVariables, ) expect(node.getVariables()).toEqual(['node-1', 'answer']) expect(node.getWorkflowNodesMap()).toEqual({ 'node-1': { title: 'A', type: BlockEnum.LLM } }) expect(node.getVarType()).toBe(getVarType) - expect(node.getEnvironmentVariables()).toEqual(environmentVariables) - expect(node.getConversationVariables()).toEqual(conversationVariables) - expect(node.getRagVariables()).toEqual(ragVariables) + expect(node.getAvailableVariables()).toEqual(availableVariables) expect(node.getTextContent()).toBe('{{#node-1.answer#}}') }) }) it('should create node helper and type guard checks', () => { runInEditor(() => { - const node = $createWorkflowVariableBlockNode(['node-1', 'answer'], {}, undefined) + const availableVariables: NodeOutPutVar[] = [{ + nodeId: 'node-1', + title: 'Node A', + vars: [{ variable: 'answer', type: VarType.string }], + }] + const node = $createWorkflowVariableBlockNode(['node-1', 'answer'], {}, undefined, availableVariables) expect(node).toBeInstanceOf(WorkflowVariableBlockNode) + expect(node.getAvailableVariables()).toEqual(availableVariables) expect($isWorkflowVariableBlockNode(node)).toBe(true) expect($isWorkflowVariableBlockNode(null)).toBe(false) expect($isWorkflowVariableBlockNode(undefined)).toBe(false) diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/workflow-variable-block-replacement-block.spec.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/workflow-variable-block-replacement-block.spec.tsx index b9cb1faa37..9dcc37ec35 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/workflow-variable-block-replacement-block.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/__tests__/workflow-variable-block-replacement-block.spec.tsx @@ -183,12 +183,7 @@ describe('WorkflowVariableBlockReplacementBlock', () => { ['node-1', 'output'], workflowNodesMap, getVarType, - variables[0].vars, - variables[1].vars, - [ - { variable: 'ragVarA', type: VarType.string, isRagVariable: true }, - { variable: 'rag.shared.answer', type: VarType.string, isRagVariable: true }, - ], + variables, ) expect($applyNodeReplacement).toHaveBeenCalledWith({ type: 'workflow-node' }) expect(created).toEqual({ type: 'workflow-node' }) @@ -214,8 +209,6 @@ describe('WorkflowVariableBlockReplacementBlock', () => { workflowNodesMap, undefined, [], - [], - undefined, ) }) }) diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx index 2b46d1a378..bf91d25834 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx @@ -1,5 +1,8 @@ +import type { + UpdateWorkflowNodesMapPayload, +} from './index' import type { WorkflowNodesMap } from './node' -import type { ValueSelector, Var } from '@/app/components/workflow/types' +import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { mergeRegister } from '@lexical/utils' import { @@ -15,7 +18,7 @@ import { import { useTranslation } from 'react-i18next' import { useReactFlow, useStoreApi } from 'reactflow' import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' -import { isConversationVar, isENV, isGlobalVar, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { isRagVariableVar, isSpecialVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import VarFullPathPanel from '@/app/components/workflow/nodes/_base/components/variable/var-full-path-panel' import { VariableLabelInEditor, @@ -34,6 +37,7 @@ type WorkflowVariableBlockComponentProps = { nodeKey: string variables: string[] workflowNodesMap: WorkflowNodesMap + availableVariables?: NodeOutPutVar[] environmentVariables?: Var[] conversationVariables?: Var[] ragVariables?: Var[] @@ -47,10 +51,8 @@ const WorkflowVariableBlockComponent = ({ nodeKey, variables, workflowNodesMap = {}, + availableVariables, getVarType, - environmentVariables, - conversationVariables, - ragVariables, }: WorkflowVariableBlockComponentProps) => { const { t } = useTranslation() const [editor] = useLexicalComposerContext() @@ -66,36 +68,25 @@ const WorkflowVariableBlockComponent = ({ } )() const [localWorkflowNodesMap, setLocalWorkflowNodesMap] = useState(workflowNodesMap) + const [localAvailableVariables, setLocalAvailableVariables] = useState(availableVariables || []) const node = localWorkflowNodesMap![variables[isRagVar ? 1 : 0]] const isException = isExceptionVariable(varName, node?.type) const sourceNodeId = variables[isRagVar ? 1 : 0] const isLlmModelInstalled = useLlmModelPluginInstalled(sourceNodeId, localWorkflowNodesMap) const variableValid = useMemo(() => { - let variableValid = true - const isEnv = isENV(variables) - const isChatVar = isConversationVar(variables) - const isGlobal = isGlobalVar(variables) - if (isGlobal) + if (isSpecialVar(variables[0] ?? '')) return true - if (isEnv) { - if (environmentVariables) - variableValid = environmentVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`) - } - else if (isChatVar) { - if (conversationVariables) - variableValid = conversationVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`) - } - else if (isRagVar) { - if (ragVariables) - variableValid = ragVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}.${variables?.[2] ?? ''}`) - } - else { - variableValid = !!node - } - return variableValid - }, [variables, node, environmentVariables, conversationVariables, isRagVar, ragVariables]) + if (!variables[1]) + return false + + const sourceNode = localAvailableVariables.find(v => v.nodeId === variables[0]) + if (!sourceNode) + return false + + return sourceNode.vars.some(v => v.variable === variables[1]) + }, [localAvailableVariables, variables]) const reactflow = useReactFlow() const store = useStoreApi() @@ -107,9 +98,9 @@ const WorkflowVariableBlockComponent = ({ return mergeRegister( editor.registerCommand( UPDATE_WORKFLOW_NODES_MAP, - (workflowNodesMap: WorkflowNodesMap) => { - setLocalWorkflowNodesMap(workflowNodesMap) - + (payload: UpdateWorkflowNodesMapPayload) => { + setLocalWorkflowNodesMap(payload.workflowNodesMap) + setLocalAvailableVariables(payload.availableVariables) return true }, COMMAND_PRIORITY_EDITOR, diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/index.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/index.tsx index dfbd238dbf..ab79630f80 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/index.tsx @@ -17,9 +17,14 @@ import { export const INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND = createCommand('INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND') export const DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND = createCommand('DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND') -export const UPDATE_WORKFLOW_NODES_MAP = createCommand('UPDATE_WORKFLOW_NODES_MAP') +export type UpdateWorkflowNodesMapPayload = { + workflowNodesMap: NonNullable + availableVariables: NonNullable +} +export const UPDATE_WORKFLOW_NODES_MAP = createCommand('UPDATE_WORKFLOW_NODES_MAP') const WorkflowVariableBlock = memo(({ - workflowNodesMap, + workflowNodesMap = {}, + variables: workflowAvailableVariables, onInsert, onDelete, getVarType, @@ -28,9 +33,12 @@ const WorkflowVariableBlock = memo(({ useEffect(() => { editor.update(() => { - editor.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, workflowNodesMap) + editor.dispatchCommand(UPDATE_WORKFLOW_NODES_MAP, { + workflowNodesMap: workflowNodesMap || {}, + availableVariables: workflowAvailableVariables || [], + }) }) - }, [editor, workflowNodesMap]) + }, [editor, workflowNodesMap, workflowAvailableVariables]) useEffect(() => { if (!editor.hasNodes([WorkflowVariableBlockNode])) @@ -40,7 +48,12 @@ const WorkflowVariableBlock = memo(({ editor.registerCommand( INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, (variables: string[]) => { - const workflowVariableBlockNode = $createWorkflowVariableBlockNode(variables, workflowNodesMap, getVarType) + const workflowVariableBlockNode = $createWorkflowVariableBlockNode( + variables, + workflowNodesMap, + getVarType, + workflowAvailableVariables || [], + ) $insertNodes([workflowVariableBlockNode]) if (onInsert) @@ -61,7 +74,7 @@ const WorkflowVariableBlock = memo(({ COMMAND_PRIORITY_EDITOR, ), ) - }, [editor, onInsert, onDelete, workflowNodesMap, getVarType]) + }, [editor, onInsert, onDelete, workflowNodesMap, getVarType, workflowAvailableVariables]) return null }) diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/node.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/node.tsx index 743937d8a6..2d13627b20 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/node.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/node.tsx @@ -1,49 +1,55 @@ import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical' import type { GetVarType, WorkflowVariableBlockType } from '../../types' -import type { Var } from '@/app/components/workflow/types' +import type { NodeOutPutVar } from '@/app/components/workflow/types' import { DecoratorNode } from 'lexical' import WorkflowVariableBlockComponent from './component' -export type WorkflowNodesMap = WorkflowVariableBlockType['workflowNodesMap'] +export type WorkflowNodesMap = NonNullable type SerializedNode = SerializedLexicalNode & { variables: string[] workflowNodesMap: WorkflowNodesMap getVarType?: GetVarType - environmentVariables?: Var[] - conversationVariables?: Var[] - ragVariables?: Var[] + availableVariables?: NodeOutPutVar[] } export class WorkflowVariableBlockNode extends DecoratorNode { __variables: string[] __workflowNodesMap: WorkflowNodesMap __getVarType?: GetVarType - __environmentVariables?: Var[] - __conversationVariables?: Var[] - __ragVariables?: Var[] + __availableVariables?: NodeOutPutVar[] static getType(): string { return 'workflow-variable-block' } static clone(node: WorkflowVariableBlockNode): WorkflowVariableBlockNode { - return new WorkflowVariableBlockNode(node.__variables, node.__workflowNodesMap, node.__getVarType, node.__key, node.__environmentVariables, node.__conversationVariables, node.__ragVariables) + return new WorkflowVariableBlockNode( + node.__variables, + node.__workflowNodesMap, + node.__getVarType, + node.__key, + node.__availableVariables, + ) } isInline(): boolean { return true } - constructor(variables: string[], workflowNodesMap: WorkflowNodesMap, getVarType: any, key?: NodeKey, environmentVariables?: Var[], conversationVariables?: Var[], ragVariables?: Var[]) { + constructor( + variables: string[], + workflowNodesMap: WorkflowNodesMap, + getVarType: any, + key?: NodeKey, + availableVariables?: NodeOutPutVar[], + ) { super(key) this.__variables = variables this.__workflowNodesMap = workflowNodesMap this.__getVarType = getVarType - this.__environmentVariables = environmentVariables - this.__conversationVariables = conversationVariables - this.__ragVariables = ragVariables + this.__availableVariables = availableVariables } createDOM(): HTMLElement { @@ -63,30 +69,34 @@ export class WorkflowVariableBlockNode extends DecoratorNode variables={this.__variables} workflowNodesMap={this.__workflowNodesMap} getVarType={this.__getVarType!} - environmentVariables={this.__environmentVariables} - conversationVariables={this.__conversationVariables} - ragVariables={this.__ragVariables} + availableVariables={this.__availableVariables} /> ) } static importJSON(serializedNode: SerializedNode): WorkflowVariableBlockNode { - const node = $createWorkflowVariableBlockNode(serializedNode.variables, serializedNode.workflowNodesMap, serializedNode.getVarType, serializedNode.environmentVariables, serializedNode.conversationVariables, serializedNode.ragVariables) + const node = $createWorkflowVariableBlockNode( + serializedNode.variables, + serializedNode.workflowNodesMap, + serializedNode.getVarType, + serializedNode.availableVariables, + ) return node } exportJSON(): SerializedNode { - return { + const json: SerializedNode = { type: 'workflow-variable-block', version: 1, variables: this.getVariables(), workflowNodesMap: this.getWorkflowNodesMap(), getVarType: this.getVarType(), - environmentVariables: this.getEnvironmentVariables(), - conversationVariables: this.getConversationVariables(), - ragVariables: this.getRagVariables(), } + if (this.getAvailableVariables()) + json.availableVariables = this.getAvailableVariables() + + return json } getVariables(): string[] { @@ -104,27 +114,28 @@ export class WorkflowVariableBlockNode extends DecoratorNode return self.__getVarType } - getEnvironmentVariables(): any { + getAvailableVariables(): NodeOutPutVar[] | undefined { const self = this.getLatest() - return self.__environmentVariables - } - - getConversationVariables(): any { - const self = this.getLatest() - return self.__conversationVariables - } - - getRagVariables(): any { - const self = this.getLatest() - return self.__ragVariables + return self.__availableVariables } getTextContent(): string { return `{{#${this.getVariables().join('.')}#}}` } } -export function $createWorkflowVariableBlockNode(variables: string[], workflowNodesMap: WorkflowNodesMap, getVarType?: GetVarType, environmentVariables?: Var[], conversationVariables?: Var[], ragVariables?: Var[]): WorkflowVariableBlockNode { - return new WorkflowVariableBlockNode(variables, workflowNodesMap, getVarType, undefined, environmentVariables, conversationVariables, ragVariables) +export function $createWorkflowVariableBlockNode( + variables: string[], + workflowNodesMap: WorkflowNodesMap, + getVarType?: GetVarType, + availableVariables?: NodeOutPutVar[], +): WorkflowVariableBlockNode { + return new WorkflowVariableBlockNode( + variables, + workflowNodesMap, + getVarType, + undefined, + availableVariables, + ) } export function $isWorkflowVariableBlockNode( diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx index 573c97f465..e3c947d786 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx @@ -15,19 +15,12 @@ import { WorkflowVariableBlockNode } from './index' import { $createWorkflowVariableBlockNode } from './node' const WorkflowVariableBlockReplacementBlock = ({ - workflowNodesMap, + workflowNodesMap = {}, getVarType, onInsert, variables, }: WorkflowVariableBlockType) => { const [editor] = useLexicalComposerContext() - const ragVariables = variables?.reduce((acc, curr) => { - if (curr.nodeId === 'rag') - acc.push(...curr.vars) - else - acc.push(...curr.vars.filter(v => v.isRagVariable)) - return acc - }, []) useEffect(() => { if (!editor.hasNodes([WorkflowVariableBlockNode])) @@ -39,8 +32,13 @@ const WorkflowVariableBlockReplacementBlock = ({ onInsert() const nodePathString = textNode.getTextContent().slice(3, -3) - return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap, getVarType, variables?.find(o => o.nodeId === 'env')?.vars || [], variables?.find(o => o.nodeId === 'conversation')?.vars || [], ragVariables)) - }, [onInsert, workflowNodesMap, getVarType, variables, ragVariables]) + return $applyNodeReplacement($createWorkflowVariableBlockNode( + nodePathString.split('.'), + workflowNodesMap, + getVarType, + variables || [], + )) + }, [onInsert, workflowNodesMap, getVarType, variables]) const getMatch = useCallback((text: string) => { const matchArr = REGEX.exec(text) diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 9828aa5858..722eb8a7e4 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -3385,7 +3385,7 @@ "count": 3 }, "react-refresh/only-export-components": { - "count": 3 + "count": 2 } }, "app/components/base/prompt-editor/plugins/hitl-input-block/input-field.tsx": { @@ -3485,12 +3485,7 @@ }, "app/components/base/prompt-editor/plugins/workflow-variable-block/node.tsx": { "ts/no-explicit-any": { - "count": 5 - } - }, - "app/components/base/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx": { - "ts/no-explicit-any": { - "count": 1 + "count": 2 } }, "app/components/base/prompt-log-modal/card.tsx": {