feat: memory

This commit is contained in:
zxhlyh 2025-10-24 10:39:27 +08:00
parent 6b2d460023
commit 7e2d16a518
18 changed files with 84 additions and 116 deletions

View File

@ -212,10 +212,13 @@ export default function MemoryPopupPlugin({
</div>
<div className='p-1'>
{memoryVarInNode.map(variable => (
<div key={variable.id} className='flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover' onClick={() => handleSelectVariable(['conversation', variable.name])}>
<div
key={variable.id}
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])}
>
<VariableIcon
isMemoryVariable
variables={['conversation', variable.name]}
variables={['memory_block', variable.id]}
className='text-util-colors-teal-teal-700'
/>
<div title={variable.name} className='system-sm-medium shrink-0 truncate text-text-secondary'>{variable.name}</div>
@ -233,10 +236,13 @@ export default function MemoryPopupPlugin({
</div>
<div className='p-1'>
{memoryVarInApp.map(variable => (
<div key={variable.id} className='flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover' onClick={() => handleSelectVariable(['conversation', variable.name])}>
<div
key={variable.id}
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])}
>
<VariableIcon
isMemoryVariable
variables={['conversation', variable.name]}
variables={['memory_block', variable.id]}
className='text-util-colors-teal-teal-700'
/>
<div title={variable.name} className='system-sm-medium shrink-0 truncate text-text-secondary'>{variable.name}</div>

View File

@ -18,7 +18,7 @@ import {
DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND,
UPDATE_WORKFLOW_NODES_MAP,
} from './index'
import { isConversationVar, isENV, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { isConversationVar as isConversationVariable, isENV, isMemoryVariable, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import Tooltip from '@/app/components/base/tooltip'
import { isExceptionVariable } from '@/app/components/workflow/utils'
import VarFullPathPanel from '@/app/components/workflow/nodes/_base/components/variable/var-full-path-panel'
@ -34,6 +34,7 @@ type WorkflowVariableBlockComponentProps = {
workflowNodesMap: WorkflowNodesMap
environmentVariables?: Var[]
conversationVariables?: Var[]
memoryVariables?: Var[]
ragVariables?: Var[]
getVarType?: (payload: {
nodeId: string,
@ -48,6 +49,7 @@ const WorkflowVariableBlockComponent = ({
getVarType,
environmentVariables,
conversationVariables,
memoryVariables,
ragVariables,
}: WorkflowVariableBlockComponentProps) => {
const { t } = useTranslation()
@ -66,18 +68,23 @@ const WorkflowVariableBlockComponent = ({
const [localWorkflowNodesMap, setLocalWorkflowNodesMap] = useState<WorkflowNodesMap>(workflowNodesMap)
const node = localWorkflowNodesMap![variables[isRagVar ? 1 : 0]]
const isEnv = isENV(variables)
// const isChatVar = isConversationVar(variables) && conversationVariables?.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}` && v.type !== 'memory')
const isMemoryVar = isConversationVar(variables) && conversationVariables?.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}` && v.type === 'memory')
const isConversationVar = isConversationVariable(variables)
const isMemoryVar = isMemoryVariable(variables)
const isException = isExceptionVariable(varName, node?.type)
let variableValid = true
if (isEnv) {
if (environmentVariables)
variableValid = environmentVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
}
else if (isConversationVar(variables)) {
else if (isConversationVar) {
if (conversationVariables)
variableValid = conversationVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
}
else if (isMemoryVar) {
if (memoryVariables)
variableValid = memoryVariables.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] ?? ''}`)
@ -136,7 +143,6 @@ const WorkflowVariableBlockComponent = ({
handleVariableJump()
}}
isExceptionVariable={isException}
isMemoryVariable={isMemoryVar}
errorMsg={!variableValid ? t('workflow.errorMsg.invalidVariable') : undefined}
isSelected={isSelected}
ref={ref}

View File

@ -22,6 +22,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
__getVarType?: GetVarType
__environmentVariables?: Var[]
__conversationVariables?: Var[]
__memoryVariables?: Var[]
__ragVariables?: Var[]
static getType(): string {
@ -36,7 +37,16 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
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,
environmentVariables?: Var[],
conversationVariables?: Var[],
memoryVariables?: Var[],
ragVariables?: Var[],
) {
super(key)
this.__variables = variables
@ -44,6 +54,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
this.__getVarType = getVarType
this.__environmentVariables = environmentVariables
this.__conversationVariables = conversationVariables
this.__memoryVariables = memoryVariables
this.__ragVariables = ragVariables
}
@ -66,6 +77,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
getVarType={this.__getVarType!}
environmentVariables={this.__environmentVariables}
conversationVariables={this.__conversationVariables}
memoryVariables={this.__memoryVariables}
ragVariables={this.__ragVariables}
/>
)
@ -115,6 +127,11 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
return self.__conversationVariables
}
getMemoryVariables(): any {
const self = this.getLatest()
return self.__memoryVariables
}
getRagVariables(): any {
const self = this.getLatest()
return self.__ragVariables
@ -124,8 +141,8 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
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, environmentVariables?: Var[], conversationVariables?: Var[], memoryVariables?: Var[], ragVariables?: Var[]): WorkflowVariableBlockNode {
return new WorkflowVariableBlockNode(variables, workflowNodesMap, getVarType, undefined, environmentVariables, conversationVariables, memoryVariables, ragVariables)
}
export function $isWorkflowVariableBlockNode(

View File

@ -53,7 +53,7 @@ const WorkflowMain = ({
}
if (memory_blocks) {
const { setMemoryVariables } = workflowStore.getState()
setMemoryVariables(formatMemoryVariables(memory_blocks, nodes))
setMemoryVariables(formatMemoryVariables(memory_blocks))
}
}, [featuresStore, workflowStore, formatMemoryVariables])

View File

@ -85,7 +85,7 @@ export const useNodesSyncDraft = () => {
},
environment_variables: environmentVariables,
conversation_variables: conversationVariables,
memory_blocks: memoryVariables.map(({ node, value_type, more, model, ...rest }) => {
memory_blocks: memoryVariables.map(({ value_type, more, model, ...rest }) => {
return {
...rest,
model: model ? {

View File

@ -55,7 +55,7 @@ export const useWorkflowInit = () => {
}, {} as Record<string, string>),
environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [],
conversationVariables: res.conversation_variables || [],
memoryVariables: formatMemoryVariables((res.memory_blocks || []), res.graph.nodes),
memoryVariables: formatMemoryVariables((res.memory_blocks || [])),
})
setSyncWorkflowDraftHash(res.hash)
setIsLoading(false)

View File

@ -30,7 +30,7 @@ export const useWorkflowRefreshDraft = () => {
}, {} as Record<string, string>))
setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [])
setConversationVariables(response.conversation_variables || [])
setMemoryVariables(formatMemoryVariables((response.memory_blocks || []), response.graph.nodes))
setMemoryVariables(formatMemoryVariables((response.memory_blocks || [])))
}).finally(() => setIsSyncingWorkflowDraft(false))
}, [handleUpdateWorkflowCanvas, workflowStore, formatMemoryVariables])

View File

@ -1,82 +1,29 @@
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import produce from 'immer'
import {
useStore,
useWorkflowStore,
} from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
import type { MemoryVariable, Node } from '@/app/components/workflow/types'
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
import type { MemoryVariable } from '@/app/components/workflow/types'
export const useMemoryVariable = () => {
const workflowStore = useWorkflowStore()
const setMemoryVariables = useStore(s => s.setMemoryVariables)
const store = useStoreApi()
const handleAddMemoryVariableToNode = useCallback((nodeId: string, memoryVariableId: string) => {
const { getNodes, setNodes } = store.getState()
const nodes = getNodes()
const newNodes = produce(nodes, (draft) => {
const currentNode = draft.find(n => n.id === nodeId)
if (currentNode) {
currentNode.data.memory = {
...(currentNode.data.memory || {}),
block_id: [...(currentNode.data.memory?.block_id || []), memoryVariableId],
}
}
})
setNodes(newNodes)
}, [store])
const handleDeleteMemoryVariableFromNode = useCallback((nodeId: string, memoryVariableId: string) => {
const { getNodes, setNodes } = store.getState()
const nodes = getNodes()
const newNodes = produce(nodes, (draft) => {
const currentNode = draft.find(n => n.id === nodeId)
if (currentNode) {
currentNode.data.memory = {
...(currentNode.data.memory || {}),
block_id: currentNode.data.memory?.block_id?.filter((id: string) => id !== memoryVariableId) || [],
}
}
})
setNodes(newNodes)
}, [store])
const handleAddMemoryVariable = useCallback((memoryVariable: MemoryVariable) => {
const { memoryVariables } = workflowStore.getState()
setMemoryVariables([memoryVariable, ...memoryVariables])
if (memoryVariable.node)
handleAddMemoryVariableToNode(memoryVariable.node, memoryVariable.id)
}, [setMemoryVariables, workflowStore, handleAddMemoryVariableToNode])
}, [setMemoryVariables, workflowStore])
const handleUpdateMemoryVariable = useCallback((memoryVariable: MemoryVariable) => {
const { memoryVariables } = workflowStore.getState()
const oldMemoryVariable = memoryVariables.find(v => v.id === memoryVariable.id)
setMemoryVariables(memoryVariables.map(v => v.id === memoryVariable.id ? memoryVariable : v))
if (oldMemoryVariable && !oldMemoryVariable?.node && memoryVariable.node) {
handleAddMemoryVariableToNode(memoryVariable.node, memoryVariable.id)
}
else if (oldMemoryVariable && oldMemoryVariable.node && !memoryVariable.node) {
handleDeleteMemoryVariableFromNode(oldMemoryVariable.node, memoryVariable.id)
}
else if (oldMemoryVariable && oldMemoryVariable.node && memoryVariable.node && memoryVariable.node !== oldMemoryVariable.node) {
handleDeleteMemoryVariableFromNode(oldMemoryVariable.node, memoryVariable.id)
handleAddMemoryVariableToNode(memoryVariable.node, memoryVariable.id)
}
}, [setMemoryVariables, workflowStore, handleAddMemoryVariableToNode, handleDeleteMemoryVariableFromNode])
}, [setMemoryVariables, workflowStore])
const handleDeleteMemoryVariable = useCallback((memoryVariable: MemoryVariable) => {
const { memoryVariables } = workflowStore.getState()
setMemoryVariables(memoryVariables.filter(v => v.id !== memoryVariable.id))
if (memoryVariable.node)
handleDeleteMemoryVariableFromNode(memoryVariable.node, memoryVariable.id)
}, [setMemoryVariables, workflowStore, handleDeleteMemoryVariableFromNode])
}, [setMemoryVariables, workflowStore])
return {
handleAddMemoryVariable,
@ -86,32 +33,8 @@ export const useMemoryVariable = () => {
}
export const useFormatMemoryVariables = () => {
const formatMemoryVariables = useCallback((memoryVariables: MemoryVariable[], nodes: Node[]) => {
let clonedMemoryVariables = [...memoryVariables]
const nodeScopeMemoryVariablesIds = clonedMemoryVariables.filter(v => v.scope === 'node').map(v => v.id)
const nodeScopeMemoryVariablesMap = nodeScopeMemoryVariablesIds.reduce((acc, id) => {
acc[id] = id
return acc
}, {} as Record<string, string>)
if (!!nodeScopeMemoryVariablesIds.length) {
const llmNodes = nodes.filter(n => n.data.type === BlockEnum.LLM)
clonedMemoryVariables = clonedMemoryVariables.map((v) => {
if (nodeScopeMemoryVariablesMap[v.id]) {
const node = llmNodes.find(n => ((n.data as LLMNodeType).memory?.block_id || []).includes(v.id))
return {
...v,
node: node?.id,
}
}
return v
})
}
return clonedMemoryVariables.map((v) => {
const formatMemoryVariables = useCallback((memoryVariables: MemoryVariable[]) => {
return memoryVariables.map((v) => {
return {
...v,
value_type: ChatVarType.Memory,

View File

@ -72,6 +72,10 @@ export const isConversationVar = (valueSelector: ValueSelector) => {
return valueSelector[0] === 'conversation'
}
export const isMemoryVariable = (valueSelector: ValueSelector) => {
return valueSelector[0] === 'memory_block'
}
export const isRagVariableVar = (valueSelector: ValueSelector) => {
if (!valueSelector) return false
return valueSelector[0] === 'rag'

View File

@ -7,15 +7,13 @@ export type VariableIconProps = {
className?: string
variables?: string[]
variableCategory?: VarInInspectType | string
isMemoryVariable?: boolean
}
const VariableIcon = ({
className,
variables = [],
variableCategory,
isMemoryVariable,
}: VariableIconProps) => {
const VarIcon = useVarIcon(variables, variableCategory, isMemoryVariable)
const VarIcon = useVarIcon(variables, variableCategory)
return VarIcon && (
<VarIcon

View File

@ -6,12 +6,13 @@ import { InputField } from '@/app/components/base/icons/src/vender/pipeline'
import {
isConversationVar,
isENV,
isMemoryVariable,
isRagVariableVar,
isSystemVar,
} from '../utils'
import { VarInInspectType } from '@/types/workflow'
export const useVarIcon = (variables: string[], variableCategory?: VarInInspectType | string, isMemoryVariable?: boolean) => {
export const useVarIcon = (variables: string[], variableCategory?: VarInInspectType | string) => {
if (variableCategory === 'loop')
return Loop
@ -21,7 +22,7 @@ export const useVarIcon = (variables: string[], variableCategory?: VarInInspectT
if (isENV(variables) || variableCategory === VarInInspectType.environment || variableCategory === 'environment')
return Env
if (isMemoryVariable)
if (isMemoryVariable(variables) || variableCategory === VarInInspectType.memory || variableCategory === 'memory_block')
return Memory
if (isConversationVar(variables) || variableCategory === VarInInspectType.conversation || variableCategory === 'conversation')
@ -44,6 +45,9 @@ export const useVarColor = (variables: string[], isExceptionVariable?: boolean,
if (isConversationVar(variables) || variableCategory === VarInInspectType.conversation || variableCategory === 'conversation')
return 'text-util-colors-teal-teal-700'
if (isMemoryVariable(variables) || variableCategory === VarInInspectType.memory || variableCategory === 'memory_block')
return 'text-util-colors-teal-teal-700'
return 'text-text-accent'
}, [variables, isExceptionVariable, variableCategory])
}
@ -92,6 +96,15 @@ export const useVarBgColorInEditor = (variables: string[], hasError?: boolean) =
}
}
if (isMemoryVariable(variables)) {
return {
hoverBorderColor: 'hover:border-util-colors-teal-teal-100',
hoverBgColor: 'hover:bg-util-colors-teal-teal-50',
selectedBorderColor: 'border-util-colors-teal-teal-600',
selectedBgColor: 'bg-util-colors-teal-teal-50',
}
}
return {
hoverBorderColor: 'hover:border-state-accent-alt',
hoverBgColor: 'hover:bg-state-accent-hover',

View File

@ -15,12 +15,12 @@ import Confirm from '@/app/components/base/confirm'
import { Memory as MemoryIcon } from '@/app/components/base/icons/src/vender/line/others'
type BlockMemoryProps = {
id: string
payload: Memory
}
const BlockMemory = ({ payload }: BlockMemoryProps) => {
const BlockMemory = ({ id }: BlockMemoryProps) => {
const { t } = useTranslation()
const { block_id } = payload
const { memoryVariablesInUsed } = useMemoryVariables(block_id || [])
const { memoryVariablesInUsed } = useMemoryVariables(id)
const [showConfirm, setShowConfirm] = useState<{
title: string
desc: string

View File

@ -1,12 +1,12 @@
import { useMemo } from 'react'
import { useStore } from '@/app/components/workflow/store'
export const useMemoryVariables = (blockIds: string[]) => {
export const useMemoryVariables = (nodeId: string) => {
const memoryVariables = useStore(s => s.memoryVariables)
const memoryVariablesInUsed = useMemo(() => {
return memoryVariables.filter(variable => blockIds.includes(variable.id))
}, [memoryVariables, blockIds])
return memoryVariables.filter(variable => variable.node_id === nodeId)
}, [memoryVariables, nodeId])
const handleDelete = (blockId: string) => {
console.log('delete', blockId)

View File

@ -92,7 +92,7 @@ const MemorySystem = ({
}
{
memoryType === MemoryMode.block && !collapsed && (
<BlockMemory payload={memory as Memory} />
<BlockMemory id={id} payload={memory as Memory} />
)
}
</>

View File

@ -118,7 +118,7 @@ export const useMemorySchema = () => {
},
},
{
name: 'node',
name: 'node_id',
label: 'Node',
type: FormTypeEnum.nodeSelector,
fieldClassName: 'flex justify-between',

View File

@ -176,10 +176,10 @@ export type MemoryVariable = {
strategy?: string
update_turns?: number
scope?: string
node_id?: string
term?: string
end_user_editable?: boolean
value_type: ChatVarType
node?: string
more?: boolean
}

View File

@ -130,7 +130,7 @@ const UpdateDSLModal = ({
hash,
conversation_variables: conversation_variables || [],
environment_variables: environment_variables || [],
memory_blocks: formatMemoryVariables(memory_blocks || [], formattedNodes),
memory_blocks: formatMemoryVariables(memory_blocks || []),
},
} as any)
}, [eventEmitter, formatMemoryVariables])

View File

@ -395,6 +395,7 @@ export enum VarInInspectType {
environment = 'env',
node = 'node',
system = 'sys',
memory = 'memory_block',
}
export type FullContent = {