This commit is contained in:
zxhlyh 2025-11-05 18:28:17 +08:00
parent 9d310fed92
commit 7cef0fff89
22 changed files with 104 additions and 129 deletions

View File

@ -80,9 +80,6 @@ import {
UPDATE_HISTORY_EVENT_EMITTER, UPDATE_HISTORY_EVENT_EMITTER,
} from './constants' } from './constants'
import { useEventEmitterContextContext } from '@/context/event-emitter' import { useEventEmitterContextContext } from '@/context/event-emitter'
import type {
MemoryVariable,
} from '@/app/components/workflow/types'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
export type PromptEditorProps = { export type PromptEditorProps = {
@ -109,8 +106,6 @@ export type PromptEditorProps = {
lastRunBlock?: LastRunBlockType lastRunBlock?: LastRunBlockType
isSupportFileVar?: boolean isSupportFileVar?: boolean
isMemorySupported?: boolean isMemorySupported?: boolean
memoryVarInNode?: MemoryVariable[]
memoryVarInApp?: MemoryVariable[]
} }
const PromptEditor: FC<PromptEditorProps> = ({ const PromptEditor: FC<PromptEditorProps> = ({
@ -137,8 +132,6 @@ const PromptEditor: FC<PromptEditorProps> = ({
lastRunBlock, lastRunBlock,
isSupportFileVar, isSupportFileVar,
isMemorySupported, isMemorySupported,
memoryVarInNode = [],
memoryVarInApp = [],
}) => { }) => {
const { eventEmitter } = useEventEmitterContextContext() const { eventEmitter } = useEventEmitterContextContext()
const initialConfig = { const initialConfig = {
@ -209,11 +202,10 @@ const PromptEditor: FC<PromptEditorProps> = ({
} }
ErrorBoundary={LexicalErrorBoundary} ErrorBoundary={LexicalErrorBoundary}
/> />
{isMemorySupported && ( {isMemorySupported && workflowVariableBlock?.show && (
<MemoryPopupPlugin <MemoryPopupPlugin
instanceId={instanceId} instanceId={instanceId}
memoryVarInNode={memoryVarInNode} memoryVariables={workflowVariableBlock?.variables?.find(v => v.nodeId === 'memory_block')?.vars || []}
memoryVarInApp={memoryVarInApp}
/> />
)} )}
<ComponentPickerBlock <ComponentPickerBlock

View File

@ -29,7 +29,7 @@ import { MEMORY_POPUP_SHOW_BY_EVENT_EMITTER, MEMORY_VAR_CREATED_BY_MODAL_BY_EVEN
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import VariableIcon from '@/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon' import VariableIcon from '@/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon'
import type { import type {
MemoryVariable, Var,
} from '@/app/components/workflow/types' } from '@/app/components/workflow/types'
import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block' import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block'
@ -39,16 +39,14 @@ export type MemoryPopupProps = {
className?: string className?: string
container?: Element | null container?: Element | null
instanceId?: string instanceId?: string
memoryVarInNode: MemoryVariable[] memoryVariables: Var[]
memoryVarInApp: MemoryVariable[]
} }
export default function MemoryPopupPlugin({ export default function MemoryPopupPlugin({
className, className,
container, container,
instanceId, instanceId,
memoryVarInNode, memoryVariables,
memoryVarInApp,
}: MemoryPopupProps) { }: MemoryPopupProps) {
const { t } = useTranslation() const { t } = useTranslation()
const [editor] = useLexicalComposerContext() const [editor] = useLexicalComposerContext()
@ -61,6 +59,8 @@ export default function MemoryPopupPlugin({
const containerEl = useMemo(() => container ?? (typeof document !== 'undefined' ? document.body : null), [container]) const containerEl = useMemo(() => container ?? (typeof document !== 'undefined' ? document.body : null), [container])
const useContainer = !!containerEl && containerEl !== document.body const useContainer = !!containerEl && containerEl !== document.body
const memoryVarInNode = memoryVariables.filter(memoryVariable => memoryVariable.memoryVariableNodeId)
const memoryVarInApp = memoryVariables.filter(memoryVariable => !memoryVariable.memoryVariableNodeId)
const { refs, floatingStyles, isPositioned } = useFloating({ const { refs, floatingStyles, isPositioned } = useFloating({
placement: 'bottom-start', placement: 'bottom-start',
@ -213,15 +213,15 @@ export default function MemoryPopupPlugin({
<div className='p-1'> <div className='p-1'>
{memoryVarInNode.map(variable => ( {memoryVarInNode.map(variable => (
<div <div
key={variable.id} key={variable.variable}
className='flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover' className='flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover'
onClick={() => handleSelectVariable(['memory_block', variable.id])} onClick={() => handleSelectVariable(['memory_block', variable.variable])}
> >
<VariableIcon <VariableIcon
variables={['memory_block', variable.id]} variables={['memory_block', '']}
className='text-util-colors-teal-teal-700' className='text-util-colors-teal-teal-700'
/> />
<div title={variable.name} className='system-sm-medium shrink-0 truncate text-text-secondary'>{variable.name}</div> <div title={variable.memoryVariableName} className='system-sm-medium shrink-0 truncate text-text-secondary'>{variable.memoryVariableName}</div>
</div> </div>
))} ))}
</div> </div>
@ -237,15 +237,15 @@ export default function MemoryPopupPlugin({
<div className='p-1'> <div className='p-1'>
{memoryVarInApp.map(variable => ( {memoryVarInApp.map(variable => (
<div <div
key={variable.id} key={variable.variable}
className='flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover' className='flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover'
onClick={() => handleSelectVariable(['memory_block', variable.id])} onClick={() => handleSelectVariable(['memory_block', variable.variable])}
> >
<VariableIcon <VariableIcon
variables={['memory_block', variable.id]} variables={['memory_block', '']}
className='text-util-colors-teal-teal-700' className='text-util-colors-teal-teal-700'
/> />
<div title={variable.name} className='system-sm-medium shrink-0 truncate text-text-secondary'>{variable.name}</div> <div title={variable.variable} className='system-sm-medium shrink-0 truncate text-text-secondary'>{variable.memoryVariableName}</div>
</div> </div>
))} ))}
</div> </div>

View File

@ -13,6 +13,7 @@ export type SerializedNode = SerializedLexicalNode & {
getVarType?: GetVarType getVarType?: GetVarType
environmentVariables?: Var[] environmentVariables?: Var[]
conversationVariables?: Var[] conversationVariables?: Var[]
memoryVariables?: Var[]
ragVariables?: Var[] ragVariables?: Var[]
} }
@ -98,6 +99,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
getVarType: this.getVarType(), getVarType: this.getVarType(),
environmentVariables: this.getEnvironmentVariables(), environmentVariables: this.getEnvironmentVariables(),
conversationVariables: this.getConversationVariables(), conversationVariables: this.getConversationVariables(),
memoryVariables: this.getMemoryVariables(),
ragVariables: this.getRagVariables(), ragVariables: this.getRagVariables(),
} }
} }

View File

@ -28,6 +28,7 @@ const WorkflowVariableBlockReplacementBlock = ({
acc.push(...curr.vars.filter(v => v.isRagVariable)) acc.push(...curr.vars.filter(v => v.isRagVariable))
return acc return acc
}, []) }, [])
const memoryVariables = variables?.find(variable => variable.nodeId === 'memory_block')?.vars || []
useEffect(() => { useEffect(() => {
if (!editor.hasNodes([WorkflowVariableBlockNode])) if (!editor.hasNodes([WorkflowVariableBlockNode]))
@ -39,7 +40,7 @@ const WorkflowVariableBlockReplacementBlock = ({
onInsert() onInsert()
const nodePathString = textNode.getTextContent().slice(3, -3) 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)) return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap, getVarType, variables?.find(o => o.nodeId === 'env')?.vars || [], variables?.find(o => o.nodeId === 'conversation')?.vars || [], memoryVariables, ragVariables))
}, [onInsert, workflowNodesMap, getVarType, variables]) }, [onInsert, workflowNodesMap, getVarType, variables])
const getMatch = useCallback((text: string) => { const getMatch = useCallback((text: string) => {

View File

@ -51,7 +51,7 @@ export const useSetWorkflowVarsWithValue = ({
const { getNodes } = store.getState() const { getNodes } = store.getState()
const nodeArr = getNodes() const nodeArr = getNodes()
const allNodesOutputVars = toNodeOutputVars(nodeArr, false, () => true, [], [], [], passedInAllPluginInfoList || allPluginInfoList, passedInSchemaTypeDefinitions || schemaTypeDefinitions) const allNodesOutputVars = toNodeOutputVars(nodeArr, false, () => true, [], [], [], [], passedInAllPluginInfoList || allPluginInfoList, passedInSchemaTypeDefinitions || schemaTypeDefinitions)
const nodesKeyValue: Record<string, Node> = {} const nodesKeyValue: Record<string, Node> = {}
nodeArr.forEach((node) => { nodeArr.forEach((node) => {

View File

@ -132,7 +132,7 @@ export const useInspectVarsCrudCommon = ({
mcpTools: mcpTools || [], mcpTools: mcpTools || [],
dataSourceList: dataSourceList || [], dataSourceList: dataSourceList || [],
} }
const currentNodeOutputVars = toNodeOutputVars([currentNode], false, () => true, [], [], [], allPluginInfoList, schemaTypeDefinitions) const currentNodeOutputVars = toNodeOutputVars([currentNode], false, () => true, [], [], [], [], allPluginInfoList, schemaTypeDefinitions)
const vars = await fetchNodeInspectVars(flowType, flowId, nodeId) const vars = await fetchNodeInspectVars(flowType, flowId, nodeId)
const varsWithSchemaType = vars.map((varItem) => { const varsWithSchemaType = vars.map((varItem) => {
const schemaType = currentNodeOutputVars[0]?.vars.find(v => v.variable === varItem.name)?.schemaType || '' const schemaType = currentNodeOutputVars[0]?.vars.find(v => v.variable === varItem.name)?.schemaType || ''

View File

@ -37,7 +37,6 @@ export const useWorkflowVariables = () => {
hideEnv, hideEnv,
hideChatVar, hideChatVar,
conversationVariablesFirst, conversationVariablesFirst,
memoryVarSortFn,
}: { }: {
parentNode?: Node | null parentNode?: Node | null
beforeNodes: Node[] beforeNodes: Node[]
@ -46,11 +45,11 @@ export const useWorkflowVariables = () => {
hideEnv?: boolean hideEnv?: boolean
hideChatVar?: boolean hideChatVar?: boolean
conversationVariablesFirst?: boolean conversationVariablesFirst?: boolean
memoryVarSortFn?: (a: string, b: string) => number
}): NodeOutPutVar[] => { }): NodeOutPutVar[] => {
const { const {
conversationVariables, conversationVariables,
environmentVariables, environmentVariables,
memoryVariables,
ragPipelineVariables, ragPipelineVariables,
dataSourceList, dataSourceList,
} = workflowStore.getState() } = workflowStore.getState()
@ -61,6 +60,7 @@ export const useWorkflowVariables = () => {
isChatMode, isChatMode,
environmentVariables: hideEnv ? [] : environmentVariables, environmentVariables: hideEnv ? [] : environmentVariables,
conversationVariables: (isChatMode && !hideChatVar) ? conversationVariables : [], conversationVariables: (isChatMode && !hideChatVar) ? conversationVariables : [],
memoryVariables: isChatMode ? memoryVariables : [],
ragVariables: ragPipelineVariables, ragVariables: ragPipelineVariables,
filterVar, filterVar,
allPluginInfoList: { allPluginInfoList: {
@ -72,7 +72,6 @@ export const useWorkflowVariables = () => {
}, },
schemaTypeDefinitions, schemaTypeDefinitions,
conversationVariablesFirst, conversationVariablesFirst,
memoryVarSortFn,
}) })
}, [t, workflowStore, schemaTypeDefinitions, buildInTools, customTools, workflowTools, mcpTools]) }, [t, workflowStore, schemaTypeDefinitions, buildInTools, customTools, workflowTools, mcpTools])

View File

@ -38,9 +38,6 @@ import { useStore } from '@/app/components/workflow/store'
import { useWorkflowVariableType } from '@/app/components/workflow/hooks' import { useWorkflowVariableType } from '@/app/components/workflow/hooks'
import AddMemoryButton from './add-memory-button' import AddMemoryButton from './add-memory-button'
import { MEMORY_POPUP_SHOW_BY_EVENT_EMITTER } from './type' import { MEMORY_POPUP_SHOW_BY_EVENT_EMITTER } from './type'
import type {
MemoryVariable,
} from '@/app/components/workflow/types'
import MemoryCreateButton from '@/app/components/workflow/nodes/llm/components/memory-system/memory-create-button' import MemoryCreateButton from '@/app/components/workflow/nodes/llm/components/memory-system/memory-create-button'
type Props = { type Props = {
@ -86,8 +83,6 @@ type Props = {
titleClassName?: string titleClassName?: string
required?: boolean required?: boolean
isMemorySupported?: boolean isMemorySupported?: boolean
memoryVarInNode?: MemoryVariable[]
memoryVarInApp?: MemoryVariable[]
} }
const Editor: FC<Props> = ({ const Editor: FC<Props> = ({
@ -128,8 +123,6 @@ const Editor: FC<Props> = ({
editorContainerClassName, editorContainerClassName,
required, required,
isMemorySupported, isMemorySupported,
memoryVarInNode = [],
memoryVarInApp = [],
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { eventEmitter } = useEventEmitterContextContext() const { eventEmitter } = useEventEmitterContextContext()
@ -311,8 +304,6 @@ const Editor: FC<Props> = ({
editable={!readOnly} editable={!readOnly}
isSupportFileVar={isSupportFileVar} isSupportFileVar={isSupportFileVar}
isMemorySupported isMemorySupported
memoryVarInNode={memoryVarInNode}
memoryVarInApp={memoryVarInApp}
/> />
{/* to patch Editor not support dynamic change editable status */} {/* to patch Editor not support dynamic change editable status */}
{readOnly && <div className='absolute inset-0 z-10'></div>} {readOnly && <div className='absolute inset-0 z-10'></div>}

View File

@ -39,7 +39,7 @@ import type {
import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types' import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types'
import type { Field as StructField } from '@/app/components/workflow/nodes/llm/types' import type { Field as StructField } from '@/app/components/workflow/nodes/llm/types'
import type { RAGPipelineVariable } from '@/models/pipeline' import type { RAGPipelineVariable } from '@/models/pipeline'
import type { MemoryVariable } from '@/app/components/workflow/types'
import { import {
AGENT_OUTPUT_STRUCT, AGENT_OUTPUT_STRUCT,
FILE_STRUCT, FILE_STRUCT,
@ -82,7 +82,7 @@ export const isRagVariableVar = (valueSelector: ValueSelector) => {
} }
export const isSpecialVar = (prefix: string): boolean => { export const isSpecialVar = (prefix: string): boolean => {
return ['sys', 'env', 'conversation', 'rag'].includes(prefix) return ['sys', 'env', 'conversation', 'memory_block', 'rag'].includes(prefix)
} }
export const hasValidChildren = (children: any): boolean => { export const hasValidChildren = (children: any): boolean => {
@ -313,7 +313,6 @@ const formatItem = (
allPluginInfoList: Record<string, ToolWithProvider[]>, allPluginInfoList: Record<string, ToolWithProvider[]>,
ragVars?: Var[], ragVars?: Var[],
schemaTypeDefinitions: SchemaTypeDefinition[] = [], schemaTypeDefinitions: SchemaTypeDefinition[] = [],
memoryVarSortFn?: (a: string, b: string) => number,
): NodeOutPutVar => { ): NodeOutPutVar => {
const { id, data } = item const { id, data } = item
@ -629,15 +628,25 @@ const formatItem = (
} }
case 'conversation': { case 'conversation': {
if (memoryVarSortFn) res.vars = [
data.chatVarList.sort(memoryVarSortFn) ...data.memoryVarList.map((memoryVar: MemoryVariable) => {
res.vars = data.chatVarList.map((chatVar: ConversationVariable) => { return {
return { variable: `memory_block.${memoryVar.node_id ? `${memoryVar.node_id}_` : ''}${memoryVar.id}`,
variable: `conversation.${chatVar.name}`, type: 'memory_block',
type: chatVar.value_type, description: '',
description: chatVar.description, isMemoryVariable: true,
} memoryVariableName: memoryVar.name,
}) as Var[] memoryVariableNodeId: memoryVar.node_id,
}
}) as Var[],
...data.chatVarList.map((chatVar: ConversationVariable) => {
return {
variable: `conversation.${chatVar.name}`,
type: chatVar.value_type,
description: chatVar.description,
}
}) as Var[],
]
break break
} }
@ -680,11 +689,13 @@ const formatItem = (
(() => { (() => {
const variableArr = v.variable.split('.') const variableArr = v.variable.split('.')
const [first] = variableArr const [first] = variableArr
if (isSpecialVar(first)) return variableArr if (isSpecialVar(first)) return variableArr
return [...selector, ...variableArr] return [...selector, ...variableArr]
})(), })(),
) )
if (isCurrentMatched) return true if (isCurrentMatched) return true
const isFile = v.type === VarType.file const isFile = v.type === VarType.file
@ -759,11 +770,11 @@ export const toNodeOutputVars = (
filterVar = (_payload: Var, _selector: ValueSelector) => true, filterVar = (_payload: Var, _selector: ValueSelector) => true,
environmentVariables: EnvironmentVariable[] = [], environmentVariables: EnvironmentVariable[] = [],
conversationVariables: ConversationVariable[] = [], conversationVariables: ConversationVariable[] = [],
memoryVariables: MemoryVariable[] = [],
ragVariables: RAGPipelineVariable[] = [], ragVariables: RAGPipelineVariable[] = [],
allPluginInfoList: Record<string, ToolWithProvider[]>, allPluginInfoList: Record<string, ToolWithProvider[]>,
schemaTypeDefinitions?: SchemaTypeDefinition[], schemaTypeDefinitions?: SchemaTypeDefinition[],
conversationVariablesFirst: boolean = false, conversationVariablesFirst: boolean = false,
memoryVarSortFn?: (a: string, b: string) => number,
): NodeOutPutVar[] => { ): NodeOutPutVar[] => {
// ENV_NODE data format // ENV_NODE data format
const ENV_NODE = { const ENV_NODE = {
@ -780,6 +791,7 @@ export const toNodeOutputVars = (
data: { data: {
title: 'CONVERSATION', title: 'CONVERSATION',
type: 'conversation', type: 'conversation',
memoryVarList: memoryVariables,
chatVarList: conversationVariables, chatVarList: conversationVariables,
}, },
} }
@ -802,6 +814,8 @@ export const toNodeOutputVars = (
if (b.data.type === 'env') return -1 if (b.data.type === 'env') return -1
if (a.data.type === 'conversation') return 1 if (a.data.type === 'conversation') return 1
if (b.data.type === 'conversation') return -1 if (b.data.type === 'conversation') return -1
if (a.data.type === 'memory_block') return 1
if (b.data.type === 'memory_block') return -1
// sort nodes by x position // sort nodes by x position
return (b.position?.x || 0) - (a.position?.x || 0) return (b.position?.x || 0) - (a.position?.x || 0)
}) })
@ -851,7 +865,6 @@ export const toNodeOutputVars = (
} as Var), } as Var),
), ),
schemaTypeDefinitions, schemaTypeDefinitions,
memoryVarSortFn,
), ),
isStartNode: node.data.type === BlockEnum.Start, isStartNode: node.data.type === BlockEnum.Start,
} }
@ -979,6 +992,7 @@ export const getVarType = ({
isConstant, isConstant,
environmentVariables = [], environmentVariables = [],
conversationVariables = [], conversationVariables = [],
memoryVariables = [],
ragVariables = [], ragVariables = [],
allPluginInfoList, allPluginInfoList,
schemaTypeDefinitions, schemaTypeDefinitions,
@ -993,6 +1007,7 @@ export const getVarType = ({
isConstant?: boolean; isConstant?: boolean;
environmentVariables?: EnvironmentVariable[]; environmentVariables?: EnvironmentVariable[];
conversationVariables?: ConversationVariable[]; conversationVariables?: ConversationVariable[];
memoryVariables?: MemoryVariable[];
ragVariables?: RAGPipelineVariable[]; ragVariables?: RAGPipelineVariable[];
allPluginInfoList: Record<string, ToolWithProvider[]>; allPluginInfoList: Record<string, ToolWithProvider[]>;
schemaTypeDefinitions?: SchemaTypeDefinition[]; schemaTypeDefinitions?: SchemaTypeDefinition[];
@ -1006,6 +1021,7 @@ export const getVarType = ({
undefined, undefined,
environmentVariables, environmentVariables,
conversationVariables, conversationVariables,
memoryVariables,
ragVariables, ragVariables,
allPluginInfoList, allPluginInfoList,
schemaTypeDefinitions, schemaTypeDefinitions,
@ -1132,12 +1148,12 @@ export const toNodeAvailableVars = ({
isChatMode, isChatMode,
environmentVariables, environmentVariables,
conversationVariables, conversationVariables,
memoryVariables,
ragVariables, ragVariables,
filterVar, filterVar,
allPluginInfoList, allPluginInfoList,
schemaTypeDefinitions, schemaTypeDefinitions,
conversationVariablesFirst, conversationVariablesFirst,
memoryVarSortFn,
}: { }: {
parentNode?: Node | null; parentNode?: Node | null;
t?: any; t?: any;
@ -1148,13 +1164,14 @@ export const toNodeAvailableVars = ({
environmentVariables?: EnvironmentVariable[]; environmentVariables?: EnvironmentVariable[];
// chat var // chat var
conversationVariables?: ConversationVariable[]; conversationVariables?: ConversationVariable[];
// memory variables
memoryVariables?: MemoryVariable[];
// rag variables // rag variables
ragVariables?: RAGPipelineVariable[]; ragVariables?: RAGPipelineVariable[];
filterVar: (payload: Var, selector: ValueSelector) => boolean; filterVar: (payload: Var, selector: ValueSelector) => boolean;
allPluginInfoList: Record<string, ToolWithProvider[]>; allPluginInfoList: Record<string, ToolWithProvider[]>;
schemaTypeDefinitions?: SchemaTypeDefinition[]; schemaTypeDefinitions?: SchemaTypeDefinition[];
conversationVariablesFirst?: boolean conversationVariablesFirst?: boolean
memoryVarSortFn?: (a: string, b: string) => number
}): NodeOutPutVar[] => { }): NodeOutPutVar[] => {
const beforeNodesOutputVars = toNodeOutputVars( const beforeNodesOutputVars = toNodeOutputVars(
beforeNodes, beforeNodes,
@ -1162,11 +1179,11 @@ export const toNodeAvailableVars = ({
filterVar, filterVar,
environmentVariables, environmentVariables,
conversationVariables, conversationVariables,
memoryVariables,
ragVariables, ragVariables,
allPluginInfoList, allPluginInfoList,
schemaTypeDefinitions, schemaTypeDefinitions,
conversationVariablesFirst, conversationVariablesFirst,
memoryVarSortFn,
) )
const isInIteration = parentNode?.data.type === BlockEnum.Iteration const isInIteration = parentNode?.data.type === BlockEnum.Iteration
if (isInIteration) { if (isInIteration) {
@ -1179,6 +1196,7 @@ export const toNodeAvailableVars = ({
isChatMode, isChatMode,
environmentVariables, environmentVariables,
conversationVariables, conversationVariables,
memoryVariables,
allPluginInfoList, allPluginInfoList,
schemaTypeDefinitions, schemaTypeDefinitions,
}) })

View File

@ -64,6 +64,7 @@ const Item: FC<ItemProps> = ({
const isSys = itemData.variable.startsWith('sys.') const isSys = itemData.variable.startsWith('sys.')
const isEnv = itemData.variable.startsWith('env.') const isEnv = itemData.variable.startsWith('env.')
const isChatVar = itemData.variable.startsWith('conversation.') const isChatVar = itemData.variable.startsWith('conversation.')
const isMemoryVar = itemData.variable.startsWith('memory_block')
const isRagVariable = itemData.isRagVariable const isRagVariable = itemData.isRagVariable
const flatVarIcon = useMemo(() => { const flatVarIcon = useMemo(() => {
if (!isFlat) if (!isFlat)
@ -147,7 +148,7 @@ const Item: FC<ItemProps> = ({
if (isFlat) { if (isFlat) {
onChange([itemData.variable], itemData) onChange([itemData.variable], itemData)
} }
else if (isSys || isEnv || isChatVar || isRagVariable) { // system variable | environment variable | conversation variable else if (isSys || isEnv || isChatVar || isMemoryVar || isRagVariable) { // system variable | environment variable | conversation variable
onChange([...objPath, ...itemData.variable.split('.')], itemData) onChange([...objPath, ...itemData.variable.split('.')], itemData)
} }
else { else {
@ -157,10 +158,16 @@ const Item: FC<ItemProps> = ({
const variableCategory = useMemo(() => { const variableCategory = useMemo(() => {
if (isEnv) return 'environment' if (isEnv) return 'environment'
if (isChatVar) return 'conversation' if (isChatVar) return 'conversation'
if (isMemoryVar) return 'memory_block'
if (isLoopVar) return 'loop' if (isLoopVar) return 'loop'
if (isRagVariable) return 'rag' if (isRagVariable) return 'rag'
return 'system' return 'system'
}, [isEnv, isChatVar, isSys, isLoopVar, isRagVariable]) }, [isEnv, isChatVar, isMemoryVar, isSys, isLoopVar, isRagVariable])
const variableType = useMemo(() => {
if (itemData.type === 'memory_block')
return 'memory'
return itemData.type
}, [itemData.type])
return ( return (
<PortalToFollowElem <PortalToFollowElem
open={open} open={open}
@ -187,7 +194,7 @@ const Item: FC<ItemProps> = ({
/>} />}
{isFlat && flatVarIcon} {isFlat && flatVarIcon}
{!isEnv && !isChatVar && !isRagVariable && ( {!isEnv && !isChatVar && !isMemoryVar && !isRagVariable && (
<div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{varName}</div> <div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{varName}</div>
)} )}
{isEnv && ( {isEnv && (
@ -196,11 +203,15 @@ const Item: FC<ItemProps> = ({
{isChatVar && ( {isChatVar && (
<div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('conversation.', '')}</div> <div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('conversation.', '')}</div>
)} )}
{isMemoryVar && (
<div title={itemData.memoryVariableName} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.memoryVariableName}</div>
)
}
{isRagVariable && ( {isRagVariable && (
<div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.split('.').slice(-1)[0]}</div> <div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.split('.').slice(-1)[0]}</div>
)} )}
</div> </div>
<div className='ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary'>{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : itemData.type}</div> <div className='ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary'>{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : variableType}</div>
{ {
(isObj || isStructureOutput) && ( (isObj || isStructureOutput) && (
<ChevronRight className={cn('ml-0.5 h-3 w-3 text-text-quaternary', isHovering && 'text-text-tertiary')} /> <ChevronRight className={cn('ml-0.5 h-3 w-3 text-text-quaternary', isHovering && 'text-text-tertiary')} />

View File

@ -16,7 +16,6 @@ type Params = {
filterVar: (payload: Var, selector: ValueSelector) => boolean filterVar: (payload: Var, selector: ValueSelector) => boolean
passedInAvailableNodes?: Node[] passedInAvailableNodes?: Node[]
conversationVariablesFirst?: boolean conversationVariablesFirst?: boolean
memoryVarSortFn?: (a: string, b: string) => number
} }
// TODO: loop type? // TODO: loop type?
@ -27,7 +26,6 @@ const useAvailableVarList = (nodeId: string, {
hideChatVar, hideChatVar,
passedInAvailableNodes, passedInAvailableNodes,
conversationVariablesFirst, conversationVariablesFirst,
memoryVarSortFn,
}: Params = { }: Params = {
onlyLeafNodeVar: false, onlyLeafNodeVar: false,
filterVar: () => true, filterVar: () => true,
@ -67,7 +65,7 @@ const useAvailableVarList = (nodeId: string, {
}) })
} }
} }
const availableVars = [...getNodeAvailableVars({ const nodeAvailableVars = getNodeAvailableVars({
parentNode: iterationNode, parentNode: iterationNode,
beforeNodes: availableNodes, beforeNodes: availableNodes,
isChatMode, isChatMode,
@ -75,7 +73,22 @@ const useAvailableVarList = (nodeId: string, {
hideEnv, hideEnv,
hideChatVar, hideChatVar,
conversationVariablesFirst, conversationVariablesFirst,
memoryVarSortFn, })
const availableVars = [...nodeAvailableVars.map((availableVar) => {
if (availableVar.nodeId === 'conversation') {
return {
...availableVar,
vars: availableVar.vars.filter((v) => {
if (!v.memoryVariableNodeId)
return true
return v.memoryVariableNodeId === nodeId
}),
}
}
return availableVar
}), ...dataSourceRagVars] }), ...dataSourceRagVars]
return { return {

View File

@ -131,6 +131,7 @@ const useOneStepRun = <T>({
const { t } = useTranslation() const { t } = useTranslation()
const { getBeforeNodesInSameBranch, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() as any const { getBeforeNodesInSameBranch, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() as any
const conversationVariables = useStore(s => s.conversationVariables) const conversationVariables = useStore(s => s.conversationVariables)
const memoryVariables = useStore(s => s.memoryVariables)
const isChatMode = useIsChatMode() const isChatMode = useIsChatMode()
const isIteration = data.type === BlockEnum.Iteration const isIteration = data.type === BlockEnum.Iteration
const isLoop = data.type === BlockEnum.Loop const isLoop = data.type === BlockEnum.Loop
@ -159,7 +160,7 @@ const useOneStepRun = <T>({
dataSourceList: dataSourceList || [], dataSourceList: dataSourceList || [],
} }
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables, [], allPluginInfoList, schemaTypeDefinitions) const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables, memoryVariables, [], allPluginInfoList, schemaTypeDefinitions)
const targetVar = allOutputVars.find(item => isSystem ? !!item.isStartNode : item.nodeId === valueSelector[0]) const targetVar = allOutputVars.find(item => isSystem ? !!item.isStartNode : item.nodeId === valueSelector[0])
if (!targetVar) if (!targetVar)
return undefined return undefined

View File

@ -58,7 +58,7 @@ const useConfig = (id: string, payload: IterationNodeType) => {
mcpTools: mcpTools || [], mcpTools: mcpTools || [],
dataSourceList: dataSourceList || [], dataSourceList: dataSourceList || [],
} }
const childrenNodeVars = toNodeOutputVars(iterationChildrenNodes, isChatMode, undefined, [], [], [], allPluginInfoList) const childrenNodeVars = toNodeOutputVars(iterationChildrenNodes, isChatMode, undefined, [], [], [], [], allPluginInfoList)
const handleOutputVarChange = useCallback((output: ValueSelector | string, _varKindType: VarKindType, varInfo?: Var) => { const handleOutputVarChange = useCallback((output: ValueSelector | string, _varKindType: VarKindType, varInfo?: Var) => {
if (isEqual(inputs.output_selector, output as ValueSelector)) if (isEqual(inputs.output_selector, output as ValueSelector))

View File

@ -9,9 +9,6 @@ import Editor from '@/app/components/workflow/nodes/_base/components/prompt/edit
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector' import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import { PromptRole } from '@/models/debug' import { PromptRole } from '@/models/debug'
import type {
MemoryVariable,
} from '@/app/components/workflow/types'
const i18nPrefix = 'workflow.nodes.llm' const i18nPrefix = 'workflow.nodes.llm'
@ -42,8 +39,6 @@ type Props = {
varList: Variable[] varList: Variable[]
handleAddVariable: (payload: any) => void handleAddVariable: (payload: any) => void
modelConfig?: ModelConfig modelConfig?: ModelConfig
memoryVarInNode?: MemoryVariable[]
memoryVarInApp?: MemoryVariable[]
} }
const roleOptions = [ const roleOptions = [
@ -86,8 +81,6 @@ const ConfigPromptItem: FC<Props> = ({
varList, varList,
handleAddVariable, handleAddVariable,
modelConfig, modelConfig,
memoryVarInNode = [],
memoryVarInApp = [],
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
@ -164,8 +157,6 @@ const ConfigPromptItem: FC<Props> = ({
</> </>
} }
isMemorySupported isMemorySupported
memoryVarInNode={memoryVarInNode}
memoryVarInApp={memoryVarInApp}
/> />
) )
} }

View File

@ -14,9 +14,6 @@ import cn from '@/utils/classnames'
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
import AddButton from '@/app/components/workflow/nodes/_base/components/add-button' import AddButton from '@/app/components/workflow/nodes/_base/components/add-button'
import { DragHandle } from '@/app/components/base/icons/src/vender/line/others' import { DragHandle } from '@/app/components/base/icons/src/vender/line/others'
import type {
MemoryVariable,
} from '@/app/components/workflow/types'
const i18nPrefix = 'workflow.nodes.llm' const i18nPrefix = 'workflow.nodes.llm'
@ -37,9 +34,6 @@ type Props = {
varList?: Variable[] varList?: Variable[]
handleAddVariable: (payload: any) => void handleAddVariable: (payload: any) => void
modelConfig: ModelConfig modelConfig: ModelConfig
memoryVarSortFn?: (a: string, b: string) => number
memoryVarInNode?: MemoryVariable[]
memoryVarInApp?: MemoryVariable[]
} }
const ConfigPrompt: FC<Props> = ({ const ConfigPrompt: FC<Props> = ({
@ -55,9 +49,6 @@ const ConfigPrompt: FC<Props> = ({
varList = [], varList = [],
handleAddVariable, handleAddVariable,
modelConfig, modelConfig,
memoryVarSortFn,
memoryVarInNode,
memoryVarInApp,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
@ -83,7 +74,6 @@ const ConfigPrompt: FC<Props> = ({
onlyLeafNodeVar: false, onlyLeafNodeVar: false,
filterVar, filterVar,
conversationVariablesFirst: true, conversationVariablesFirst: true,
memoryVarSortFn,
}) })
const handleChatModePromptChange = useCallback((index: number) => { const handleChatModePromptChange = useCallback((index: number) => {
@ -215,8 +205,6 @@ const ConfigPrompt: FC<Props> = ({
varList={varList} varList={varList}
handleAddVariable={handleAddVariable} handleAddVariable={handleAddVariable}
modelConfig={modelConfig} modelConfig={modelConfig}
memoryVarInNode={memoryVarInNode}
memoryVarInApp={memoryVarInApp}
/> />
</div> </div>
) )

View File

@ -64,9 +64,6 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
handleStructureOutputChange, handleStructureOutputChange,
filterJinja2InputVar, filterJinja2InputVar,
handleReasoningFormatChange, handleReasoningFormatChange,
memoryVarSortFn,
memoryVarInNode,
memoryVarInApp,
} = useConfig(id, data) } = useConfig(id, data)
const { memoryType } = useMemory(id, data) const { memoryType } = useMemory(id, data)
@ -158,9 +155,6 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
varList={inputs.prompt_config?.jinja2_variables || []} varList={inputs.prompt_config?.jinja2_variables || []}
handleAddVariable={handleAddVariable} handleAddVariable={handleAddVariable}
modelConfig={model} modelConfig={model}
memoryVarSortFn={memoryVarSortFn}
memoryVarInNode={memoryVarInNode}
memoryVarInApp={memoryVarInApp}
/> />
)} )}

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import produce from 'immer' import produce from 'immer'
import { EditionType, VarType } from '../../types' import { EditionType, VarType } from '../../types'
import type { Memory, PromptItem, ValueSelector, Var, Variable } from '../../types' import type { Memory, PromptItem, ValueSelector, Var, Variable } from '../../types'
@ -22,7 +22,6 @@ import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars
const useConfig = (id: string, payload: LLMNodeType) => { const useConfig = (id: string, payload: LLMNodeType) => {
const { nodesReadOnly: readOnly } = useNodesReadOnly() const { nodesReadOnly: readOnly } = useNodesReadOnly()
const isChatMode = useIsChatMode() const isChatMode = useIsChatMode()
const memoryVariables = useStore(s => s.memoryVariables)
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 [defaultRolePrefix, setDefaultRolePrefix] = useState<{ user: string; assistant: string }>({ user: '', assistant: '' })
@ -308,11 +307,11 @@ const useConfig = (id: string, payload: LLMNodeType) => {
}, [setInputs, deleteNodeInspectorVars, id]) }, [setInputs, deleteNodeInspectorVars, id])
const filterInputVar = useCallback((varPayload: Var) => { 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, VarType.memory].includes(varPayload.type)
}, []) }, [])
const filterJinja2InputVar = useCallback((varPayload: Var) => { 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, VarType.memory].includes(varPayload.type)
}, []) }, [])
const filterMemoryPromptVar = useCallback((varPayload: Var) => { const filterMemoryPromptVar = useCallback((varPayload: Var) => {
@ -335,29 +334,6 @@ const useConfig = (id: string, payload: LLMNodeType) => {
filterVar: filterMemoryPromptVar, 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])
const memoryVarInNode = useMemo(() => {
const idsInNode = inputs.memory?.block_id || []
return memoryVariables
.filter(varItem => idsInNode.includes(varItem.id))
}, [inputs.memory?.block_id, memoryVariables])
const memoryVarInApp = useMemo(() => {
const idsInApp = inputs.memory?.block_id || []
return memoryVariables
.filter(varItem => !idsInApp.includes(varItem.id))
}, [inputs.memory?.block_id, memoryVariables])
return { return {
readOnly, readOnly,
isChatMode, isChatMode,
@ -391,9 +367,6 @@ const useConfig = (id: string, payload: LLMNodeType) => {
handleStructureOutputEnableChange, handleStructureOutputEnableChange,
filterJinja2InputVar, filterJinja2InputVar,
handleReasoningFormatChange, handleReasoningFormatChange,
memoryVarSortFn,
memoryVarInNode,
memoryVarInApp,
} }
} }

View File

@ -38,7 +38,7 @@ const useConfig = (id: string, payload: LoopNodeType) => {
const { nodesReadOnly: readOnly } = useNodesReadOnly() const { nodesReadOnly: readOnly } = useNodesReadOnly()
const isChatMode = useIsChatMode() const isChatMode = useIsChatMode()
const conversationVariables = useStore(s => s.conversationVariables) const conversationVariables = useStore(s => s.conversationVariables)
const memoryVariables = useStore(s => s.memoryVariables)
const { inputs, setInputs } = useNodeCrud<LoopNodeType>(id, payload) const { inputs, setInputs } = useNodeCrud<LoopNodeType>(id, payload)
const inputsRef = useRef(inputs) const inputsRef = useRef(inputs)
const handleInputsChange = useCallback((newInputs: LoopNodeType) => { const handleInputsChange = useCallback((newInputs: LoopNodeType) => {
@ -65,7 +65,7 @@ const useConfig = (id: string, payload: LoopNodeType) => {
mcpTools: mcpTools || [], mcpTools: mcpTools || [],
dataSourceList: dataSourceList || [], dataSourceList: dataSourceList || [],
} }
const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode, undefined, [], conversationVariables, [], allPluginInfoList) const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode, undefined, [], conversationVariables, memoryVariables, [], allPluginInfoList)
const { const {
getIsVarFileAttribute, getIsVarFileAttribute,

View File

@ -1,7 +1,6 @@
import React, { useCallback, useRef } from 'react' import React, { useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { v4 as uuid4 } from 'uuid'
import { import {
useForm as useTanstackForm, useForm as useTanstackForm,
useStore as useTanstackStore, useStore as useTanstackStore,
@ -84,7 +83,7 @@ const ChatVariableModal = ({
return notify({ type: 'error', message: 'object key can not be empty' }) return notify({ type: 'error', message: 'object key can not be empty' })
onSave({ onSave({
id: chatVar ? chatVar.id : uuid4(), id: chatVar ? chatVar.id : `${Date.now()}`,
name, name,
value_type, value_type,
value: editInJSON ? JSON.parse(value) : value, value: editInJSON ? JSON.parse(value) : value,

View File

@ -44,7 +44,7 @@ export const useTypeSchema = () => {
label: t('workflow.chatVariable.modal.type'), label: t('workflow.chatVariable.modal.type'),
type: 'select', type: 'select',
options: typeList.map(type => ({ options: typeList.map(type => ({
label: type, label: type === ChatVarType.Memory ? 'memory' : type,
value: type, value: type,
})), })),
onChange: handleTypeChange, onChange: handleTypeChange,

View File

@ -7,7 +7,7 @@ export enum ChatVarType {
ArrayNumber = 'array[number]', ArrayNumber = 'array[number]',
ArrayBoolean = 'array[boolean]', ArrayBoolean = 'array[boolean]',
ArrayObject = 'array[object]', ArrayObject = 'array[object]',
Memory = 'memory', Memory = 'memory_block',
} }
export type ObjectValueItem = { export type ObjectValueItem = {

View File

@ -312,7 +312,7 @@ export enum VarType {
arrayFile = 'array[file]', arrayFile = 'array[file]',
any = 'any', any = 'any',
arrayAny = 'array[any]', arrayAny = 'array[any]',
memory = 'memory', memory = 'memory_block',
} }
export enum ValueType { export enum ValueType {
@ -334,6 +334,8 @@ export type Var = {
nodeId?: string nodeId?: string
isRagVariable?: boolean isRagVariable?: boolean
schemaType?: string schemaType?: string
memoryVariableName?: string
memoryVariableNodeId?: string
} }
export type NodeOutPutVar = { export type NodeOutPutVar = {