diff --git a/web/app/components/workflow/hooks/use-workflow-variables.ts b/web/app/components/workflow/hooks/use-workflow-variables.ts index 8422a7fd0d..0a0dc073ab 100644 --- a/web/app/components/workflow/hooks/use-workflow-variables.ts +++ b/web/app/components/workflow/hooks/use-workflow-variables.ts @@ -31,6 +31,8 @@ export const useWorkflowVariables = () => { filterVar, hideEnv, hideChatVar, + conversationVariablesFirst, + memoryVarSortFn, }: { parentNode?: Node | null beforeNodes: Node[] @@ -38,6 +40,8 @@ export const useWorkflowVariables = () => { filterVar: (payload: Var, selector: ValueSelector) => boolean hideEnv?: boolean hideChatVar?: boolean + conversationVariablesFirst?: boolean + memoryVarSortFn?: (a: string, b: string) => number }): NodeOutPutVar[] => { const { conversationVariables, @@ -61,6 +65,8 @@ export const useWorkflowVariables = () => { dataSourceList: dataSourceList ?? [], }, schemaTypeDefinitions, + conversationVariablesFirst, + memoryVarSortFn, }) }, [t, workflowStore, schemaTypeDefinitions, buildInTools]) diff --git a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx index 181b38b75c..3462a95b1d 100644 --- a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx @@ -36,6 +36,8 @@ import Switch from '@/app/components/base/switch' import { Jinja } from '@/app/components/base/icons/src/vender/workflow' import { useStore } from '@/app/components/workflow/store' import { useWorkflowVariableType } from '@/app/components/workflow/hooks' +import Button from '@/app/components/base/button' +import { Memory } from '@/app/components/base/icons/src/vender/line/others' type Props = { className?: string @@ -75,10 +77,11 @@ type Props = { titleTooltip?: ReactNode inputClassName?: string editorContainerClassName?: string - placeholder?: string + placeholder?: string | React.JSX.Element placeholderClassName?: string titleClassName?: string required?: boolean + isMemorySupported?: boolean } const Editor: FC = ({ @@ -118,6 +121,7 @@ const Editor: FC = ({ titleClassName, editorContainerClassName, required, + isMemorySupported, }) => { const { t } = useTranslation() const { eventEmitter } = useEventEmitterContextContext() @@ -233,68 +237,78 @@ const Editor: FC = ({ {/* Min: 80 Max: 560. Header: 24 */} -
+
{!(isSupportJinja && editionType === EditionType.jinja2) ? ( -
- { - acc[node.id] = { - title: node.data.title, - type: node.data.type, - width: node.width, - height: node.height, - position: node.position, - } - if (node.data.type === BlockEnum.Start) { - acc.sys = { - title: t('workflow.blocks.start'), - type: BlockEnum.Start, + <> +
+ { + acc[node.id] = { + title: node.data.title, + type: node.data.type, + width: node.width, + height: node.height, + position: node.position, } - } - return acc - }, {} as any), - showManageInputField: !!pipelineId, - onManageInputField: () => setShowInputFieldPanel?.(true), - }} - onChange={onChange} - onBlur={setBlur} - onFocus={setFocus} - editable={!readOnly} - isSupportFileVar={isSupportFileVar} - /> - {/* to patch Editor not support dynamic change editable status */} - {readOnly &&
} -
+ if (node.data.type === BlockEnum.Start) { + acc.sys = { + title: t('workflow.blocks.start'), + type: BlockEnum.Start, + } + } + return acc + }, {} as any), + showManageInputField: !!pipelineId, + onManageInputField: () => setShowInputFieldPanel?.(true), + }} + onChange={onChange} + onBlur={setBlur} + onFocus={setFocus} + editable={!readOnly} + isSupportFileVar={isSupportFileVar} + /> + {/* to patch Editor not support dynamic change editable status */} + {readOnly &&
} +
+ {isMemorySupported && isFocus && ( +
+ +
+ )} + ) : (
diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index d3621d5050..24d991ba43 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -313,6 +313,7 @@ const formatItem = ( allPluginInfoList: Record, ragVars?: Var[], schemaTypeDefinitions: SchemaTypeDefinition[] = [], + memoryVarSortFn?: (a: string, b: string) => number, ): NodeOutPutVar => { const { id, data } = item @@ -630,6 +631,8 @@ const formatItem = ( } case 'conversation': { + if (memoryVarSortFn) + data.chatVarList.sort(memoryVarSortFn) res.vars = data.chatVarList.map((chatVar: ConversationVariable) => { return { variable: `conversation.${chatVar.name}`, @@ -759,6 +762,8 @@ export const toNodeOutputVars = ( ragVariables: RAGPipelineVariable[] = [], allPluginInfoList: Record, schemaTypeDefinitions?: SchemaTypeDefinition[], + conversationVariablesFirst: boolean = false, + memoryVarSortFn?: (a: string, b: string) => number, ): NodeOutPutVar[] => { // ENV_NODE data format const ENV_NODE = { @@ -801,44 +806,56 @@ export const toNodeOutputVars = ( return (b.position?.x || 0) - (a.position?.x || 0) }) - const res = [ - ...sortedNodes.filter(node => - SUPPORT_OUTPUT_VARS_NODE.includes(node?.data?.type), - ), - ...(environmentVariables.length > 0 ? [ENV_NODE] : []), - ...(isChatMode && conversationVariables.length > 0 ? [CHAT_VAR_NODE] : []), - ...(RAG_PIPELINE_NODE.data.ragVariables.length > 0 - ? [RAG_PIPELINE_NODE] - : []), - ] - .map((node) => { - let ragVariablesInDataSource: RAGPipelineVariable[] = [] - if (node.data.type === BlockEnum.DataSource) { - ragVariablesInDataSource = ragVariables.filter( - ragVariable => ragVariable.belong_to_node_id === node.id, - ) - } - return { - ...formatItem( - node, - isChatMode, - filterVar, - allPluginInfoList, - ragVariablesInDataSource.map( - (ragVariable: RAGPipelineVariable) => - ({ - variable: `rag.${node.id}.${ragVariable.variable}`, - type: inputVarTypeToVarType(ragVariable.type as any), - description: ragVariable.label, - isRagVariable: true, - } as Var), - ), - schemaTypeDefinitions, + let nodeList = [] + if (conversationVariablesFirst) { + nodeList = [ + ...((isChatMode && conversationVariables.length > 0) ? [CHAT_VAR_NODE] : []), + ...(environmentVariables.length > 0 ? [ENV_NODE] : []), + ...sortedNodes.filter(node => SUPPORT_OUTPUT_VARS_NODE.includes(node?.data?.type)), + ...(RAG_PIPELINE_NODE.data.ragVariables.length > 0 + ? [RAG_PIPELINE_NODE] + : []), + ] + } + else { + nodeList = [ + ...sortedNodes.filter(node => SUPPORT_OUTPUT_VARS_NODE.includes(node?.data?.type)), + ...(environmentVariables.length > 0 ? [ENV_NODE] : []), + ...((isChatMode && conversationVariables.length > 0) ? [CHAT_VAR_NODE] : []), + ...(RAG_PIPELINE_NODE.data.ragVariables.length > 0 + ? [RAG_PIPELINE_NODE] + : []), + ] + } + + const res = nodeList.map((node) => { + let ragVariablesInDataSource: RAGPipelineVariable[] = [] + if (node.data.type === BlockEnum.DataSource) { + ragVariablesInDataSource = ragVariables.filter( + ragVariable => ragVariable.belong_to_node_id === node.id, + ) + } + return { + ...formatItem( + node, + isChatMode, + filterVar, + allPluginInfoList, + ragVariablesInDataSource.map( + (ragVariable: RAGPipelineVariable) => + ({ + variable: `rag.${node.id}.${ragVariable.variable}`, + type: inputVarTypeToVarType(ragVariable.type as any), + description: ragVariable.label, + isRagVariable: true, + } as Var), ), - isStartNode: node.data.type === BlockEnum.Start, - } - }) - .filter(item => item.vars.length > 0) + schemaTypeDefinitions, + memoryVarSortFn, + ), + isStartNode: node.data.type === BlockEnum.Start, + } + }).filter(item => item.vars.length > 0) return res } @@ -1119,6 +1136,8 @@ export const toNodeAvailableVars = ({ filterVar, allPluginInfoList, schemaTypeDefinitions, + conversationVariablesFirst, + memoryVarSortFn, }: { parentNode?: Node | null; t?: any; @@ -1134,6 +1153,8 @@ export const toNodeAvailableVars = ({ filterVar: (payload: Var, selector: ValueSelector) => boolean; allPluginInfoList: Record; schemaTypeDefinitions?: SchemaTypeDefinition[]; + conversationVariablesFirst?: boolean + memoryVarSortFn?: (a: string, b: string) => number }): NodeOutPutVar[] => { const beforeNodesOutputVars = toNodeOutputVars( beforeNodes, @@ -1144,6 +1165,8 @@ export const toNodeAvailableVars = ({ ragVariables, allPluginInfoList, schemaTypeDefinitions, + conversationVariablesFirst, + memoryVarSortFn, ) const isInIteration = parentNode?.data.type === BlockEnum.Iteration if (isInIteration) { diff --git a/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts b/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts index 2ce9dfe809..f0847e5eba 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts @@ -15,6 +15,8 @@ type Params = { hideChatVar?: boolean filterVar: (payload: Var, selector: ValueSelector) => boolean passedInAvailableNodes?: Node[] + conversationVariablesFirst?: boolean + memoryVarSortFn?: (a: string, b: string) => number } // TODO: loop type? @@ -24,6 +26,8 @@ const useAvailableVarList = (nodeId: string, { hideEnv, hideChatVar, passedInAvailableNodes, + conversationVariablesFirst, + memoryVarSortFn, }: Params = { onlyLeafNodeVar: false, filterVar: () => true, @@ -70,6 +74,8 @@ const useAvailableVarList = (nodeId: string, { filterVar, hideEnv, hideChatVar, + conversationVariablesFirst, + memoryVarSortFn, }), ...dataSourceRagVars] return { diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx index 88ba95f76d..01157ce9ad 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx @@ -146,6 +146,17 @@ const ConfigPromptItem: FC = ({ varList={varList} handleAddVariable={handleAddVariable} isSupportFileVar + placeholder={ + <> +
+ {t(`${i18nPrefix}.promptEditorPlaceholder1`)} +
+
+ {t(`${i18nPrefix}.promptEditorPlaceholder2`)} +
+ + } + isMemorySupported /> ) } 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 fee762bd25..120c5619e6 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx @@ -34,6 +34,7 @@ type Props = { varList?: Variable[] handleAddVariable: (payload: any) => void modelConfig: ModelConfig + memoryVarSortFn?: (a: string, b: string) => number } const ConfigPrompt: FC = ({ @@ -49,6 +50,7 @@ const ConfigPrompt: FC = ({ varList = [], handleAddVariable, modelConfig, + memoryVarSortFn, }) => { const { t } = useTranslation() const workflowStore = useWorkflowStore() @@ -73,6 +75,8 @@ const ConfigPrompt: FC = ({ } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, filterVar, + conversationVariablesFirst: true, + memoryVarSortFn, }) const handleChatModePromptChange = useCallback((index: number) => { diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index c6227d664d..117328897d 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -62,6 +62,7 @@ const Panel: FC> = ({ handleStructureOutputChange, filterJinja2InputVar, handleReasoningFormatChange, + memoryVarSortFn, } = useConfig(id, data) const model = inputs.model @@ -152,6 +153,7 @@ const Panel: FC> = ({ varList={inputs.prompt_config?.jinja2_variables || []} handleAddVariable={handleAddVariable} modelConfig={model} + memoryVarSortFn={memoryVarSortFn} /> )} diff --git a/web/app/components/workflow/nodes/llm/use-config.ts b/web/app/components/workflow/nodes/llm/use-config.ts index 45635be3f2..ca444994fd 100644 --- a/web/app/components/workflow/nodes/llm/use-config.ts +++ b/web/app/components/workflow/nodes/llm/use-config.ts @@ -23,7 +23,7 @@ const useConfig = (id: string, payload: LLMNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() const isChatMode = useIsChatMode() - const defaultConfig = useStore(s => s.nodesDefaultConfigs)[payload.type] + const defaultConfig = useStore(s => s.nodesDefaultConfigs)?.[payload.type] const [defaultRolePrefix, setDefaultRolePrefix] = useState<{ user: string; assistant: string }>({ user: '', assistant: '' }) const { inputs, setInputs: doSetInputs } = useNodeCrud(id, payload) const inputRef = useRef(inputs) @@ -331,6 +331,17 @@ const useConfig = (id: string, payload: LLMNodeType) => { filterVar: filterMemoryPromptVar, }) + const memoryVarSortFn = useCallback((a: string, b: string) => { + const idsInNode = inputs.memory?.block_id || [] + const aInNode = idsInNode.includes(a) + const bInNode = idsInNode.includes(b) + + if (aInNode && !bInNode) return -1 + if (!aInNode && bInNode) return 1 + + return a.localeCompare(b) + }, [inputs.memory?.block_id]) + return { readOnly, isChatMode, @@ -364,6 +375,7 @@ const useConfig = (id: string, payload: LLMNodeType) => { handleStructureOutputEnableChange, filterJinja2InputVar, handleReasoningFormatChange, + memoryVarSortFn, } } diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 1b971b1777..eb379111e8 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -522,6 +522,11 @@ const translation = { saveSchema: 'Please finish editing the current field before saving the schema', }, }, + promptEditorPlaceholder1: 'Click here to edit Prompt', + promptEditorPlaceholder2: 'Type \'/\' to insert variable', + memory: { + addButton: 'Add Memory', + }, }, knowledgeRetrieval: { queryVariable: 'Query Variable', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 67280d74d5..f1a0331dac 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -522,6 +522,11 @@ const translation = { tagged: '保持思考标签', separated: '分开思考标签', }, + promptEditorPlaceholder1: '点击此处编辑提示词', + promptEditorPlaceholder2: '输入 \'/\' 插入变量', + memory: { + addButton: '添加记忆', + }, }, knowledgeRetrieval: { queryVariable: '查询变量',