select memory var in popup

This commit is contained in:
JzoNg 2025-09-23 16:07:06 +08:00
parent 7e9375ce7e
commit 57624979e4
9 changed files with 147 additions and 18 deletions

View File

@ -80,6 +80,9 @@ import {
UPDATE_HISTORY_EVENT_EMITTER,
} from './constants'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import type {
ConversationVariable,
} from '@/app/components/workflow/types'
import cn from '@/utils/classnames'
export type PromptEditorProps = {
@ -106,6 +109,8 @@ export type PromptEditorProps = {
lastRunBlock?: LastRunBlockType
isSupportFileVar?: boolean
isMemorySupported?: boolean
memoryVarInNode?: ConversationVariable[]
memoryVarInApp?: ConversationVariable[]
}
const PromptEditor: FC<PromptEditorProps> = ({
@ -132,6 +137,8 @@ const PromptEditor: FC<PromptEditorProps> = ({
lastRunBlock,
isSupportFileVar,
isMemorySupported,
memoryVarInNode = [],
memoryVarInApp = [],
}) => {
const { eventEmitter } = useEventEmitterContextContext()
const initialConfig = {
@ -203,7 +210,11 @@ const PromptEditor: FC<PromptEditorProps> = ({
ErrorBoundary={LexicalErrorBoundary}
/>
{isMemorySupported && (
<MemoryPopupPlugin instanceId={instanceId} />
<MemoryPopupPlugin
instanceId={instanceId}
memoryVarInNode={memoryVarInNode}
memoryVarInApp={memoryVarInApp}
/>
)}
<ComponentPickerBlock
triggerString='/'

View File

@ -6,6 +6,7 @@ import {
useState,
} from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import {
autoUpdate,
flip,
@ -21,6 +22,12 @@ import {
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { MEMORY_POPUP_SHOW_BY_EVENT_EMITTER } from '@/app/components/workflow/nodes/_base/components/prompt/add-memory-button'
import Divider from '@/app/components/base/divider'
import VariableIcon from '@/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon'
import type {
ConversationVariable,
} from '@/app/components/workflow/types'
import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block'
import cn from '@/utils/classnames'
@ -28,13 +35,18 @@ export type MemoryPopupProps = {
className?: string
container?: Element | null
instanceId?: string
memoryVarInNode: ConversationVariable[]
memoryVarInApp: ConversationVariable[]
}
export default function MemoryPopupPlugin({
className,
container,
instanceId,
memoryVarInNode,
memoryVarInApp,
}: MemoryPopupProps) {
const { t } = useTranslation()
const [editor] = useLexicalComposerContext()
const { eventEmitter } = useEventEmitterContextContext()
@ -124,6 +136,11 @@ export default function MemoryPopupPlugin({
openPortal()
})
const handleSelectVariable = useCallback((variable: string[]) => {
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variable)
closePortal()
}, [editor, closePortal])
useEffect(() => {
return editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => {
@ -155,22 +172,68 @@ export default function MemoryPopupPlugin({
return null
return createPortal(
<div
ref={(node) => {
portalRef.current = node
refs.setFloating(node)
}}
className={cn(
useContainer ? '' : 'z-[999999]',
'absolute rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm',
className,
)}
style={{
...floatingStyles,
visibility: isPositioned ? 'visible' : 'hidden',
}}
>
Memory Popup
<div className='h-0 w-0'>
<div
ref={(node) => {
portalRef.current = node
refs.setFloating(node)
}}
className={cn(
useContainer ? '' : 'z-[999999]',
'absolute rounded-xl shadow-lg backdrop-blur-sm',
className,
)}
style={{
...floatingStyles,
visibility: isPositioned ? 'visible' : 'hidden',
}}
>
<div className='w-[261px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur'>
{memoryVarInNode.length > 0 && (
<>
<div className='flex items-center gap-1 pb-1 pt-2.5'>
<Divider className='!h-px !w-3 bg-divider-subtle' />
<div className='system-2xs-medium-uppercase shrink-0 text-text-tertiary'>{t('workflow.nodes.llm.memory.currentNodeLabel')}</div>
<Divider className='!h-px grow bg-divider-subtle' />
</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])}>
<VariableIcon
isMemoryVariable
variables={['conversation', variable.name]}
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>
))}
</div>
</>
)}
{memoryVarInApp.length > 0 && (
<>
<div className='flex items-center gap-1 pb-1 pt-2.5'>
<Divider className='!h-px !w-3 bg-divider-subtle' />
<div className='system-2xs-medium-uppercase shrink-0 text-text-tertiary'>{t('workflow.nodes.llm.memory.conversationScopeLabel')}</div>
<Divider className='!h-px grow bg-divider-subtle' />
</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])}>
<VariableIcon
isMemoryVariable
variables={['conversation', variable.name]}
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>
))}
</div>
</>
)}
<div>{t('workflow.nodes.llm.memory.createButton')}</div>
</div>
</div>
</div>,
containerEl,
)

View File

@ -37,6 +37,9 @@ 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 AddMemoryButton, { MEMORY_POPUP_SHOW_BY_EVENT_EMITTER } from './add-memory-button'
import type {
ConversationVariable,
} from '@/app/components/workflow/types'
type Props = {
className?: string
@ -81,6 +84,8 @@ type Props = {
titleClassName?: string
required?: boolean
isMemorySupported?: boolean
memoryVarInNode?: ConversationVariable[]
memoryVarInApp?: ConversationVariable[]
}
const Editor: FC<Props> = ({
@ -121,6 +126,8 @@ const Editor: FC<Props> = ({
editorContainerClassName,
required,
isMemorySupported,
memoryVarInNode = [],
memoryVarInApp = [],
}) => {
const { t } = useTranslation()
const { eventEmitter } = useEventEmitterContextContext()
@ -301,6 +308,8 @@ const Editor: FC<Props> = ({
editable={!readOnly}
isSupportFileVar={isSupportFileVar}
isMemorySupported
memoryVarInNode={memoryVarInNode}
memoryVarInApp={memoryVarInApp}
/>
{/* to patch Editor not support dynamic change editable status */}
{readOnly && <div className='absolute inset-0 z-10'></div>}

View File

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

View File

@ -14,6 +14,9 @@ import cn from '@/utils/classnames'
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
import AddButton from '@/app/components/workflow/nodes/_base/components/add-button'
import { DragHandle } from '@/app/components/base/icons/src/vender/line/others'
import type {
ConversationVariable,
} from '@/app/components/workflow/types'
const i18nPrefix = 'workflow.nodes.llm'
@ -35,6 +38,8 @@ type Props = {
handleAddVariable: (payload: any) => void
modelConfig: ModelConfig
memoryVarSortFn?: (a: string, b: string) => number
memoryVarInNode?: ConversationVariable[]
memoryVarInApp?: ConversationVariable[]
}
const ConfigPrompt: FC<Props> = ({
@ -51,6 +56,8 @@ const ConfigPrompt: FC<Props> = ({
handleAddVariable,
modelConfig,
memoryVarSortFn,
memoryVarInNode,
memoryVarInApp,
}) => {
const { t } = useTranslation()
const workflowStore = useWorkflowStore()
@ -208,6 +215,8 @@ const ConfigPrompt: FC<Props> = ({
varList={varList}
handleAddVariable={handleAddVariable}
modelConfig={modelConfig}
memoryVarInNode={memoryVarInNode}
memoryVarInApp={memoryVarInApp}
/>
</div>
)

View File

@ -65,6 +65,8 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
filterJinja2InputVar,
handleReasoningFormatChange,
memoryVarSortFn,
memoryVarInNode,
memoryVarInApp,
} = useConfig(id, data)
const { memoryType } = useMemory(id, data)
@ -157,6 +159,8 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
handleAddVariable={handleAddVariable}
modelConfig={model}
memoryVarSortFn={memoryVarSortFn}
memoryVarInNode={memoryVarInNode}
memoryVarInApp={memoryVarInApp}
/>
)}

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import produce from 'immer'
import { EditionType, VarType } from '../../types'
import type { Memory, PromptItem, ValueSelector, Var, Variable } from '../../types'
@ -18,10 +18,12 @@ import {
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
import { checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
const useConfig = (id: string, payload: LLMNodeType) => {
const { nodesReadOnly: readOnly } = useNodesReadOnly()
const isChatMode = useIsChatMode()
const conversationVariables = useStore(s => s.conversationVariables)
const defaultConfig = useStore(s => s.nodesDefaultConfigs)?.[payload.type]
const [defaultRolePrefix, setDefaultRolePrefix] = useState<{ user: string; assistant: string }>({ user: '', assistant: '' })
@ -342,6 +344,20 @@ const useConfig = (id: string, payload: LLMNodeType) => {
return a.localeCompare(b)
}, [inputs.memory?.block_id])
const memoryVarInNode = useMemo(() => {
const idsInNode = inputs.memory?.block_id || []
return conversationVariables
.filter(varItem => varItem.value_type === ChatVarType.Memory)
.filter(varItem => idsInNode.includes(varItem.id))
}, [inputs.memory?.block_id, conversationVariables])
const memoryVarInApp = useMemo(() => {
const idsInApp = inputs.memory?.block_id || []
return conversationVariables
.filter(varItem => varItem.value_type === ChatVarType.Memory)
.filter(varItem => !idsInApp.includes(varItem.id))
}, [inputs.memory?.block_id, conversationVariables])
return {
readOnly,
isChatMode,
@ -376,6 +392,8 @@ const useConfig = (id: string, payload: LLMNodeType) => {
filterJinja2InputVar,
handleReasoningFormatChange,
memoryVarSortFn,
memoryVarInNode,
memoryVarInApp,
}
}

View File

@ -526,6 +526,9 @@ const translation = {
promptEditorPlaceholder2: 'Type \'/\' to insert variable',
memory: {
addButton: 'Add Memory',
currentNodeLabel: 'Current Node scope',
conversationScopeLabel: 'Conversation scope',
createButton: 'Create memory variable',
},
},
knowledgeRetrieval: {

View File

@ -526,6 +526,9 @@ const translation = {
promptEditorPlaceholder2: '输入 \'/\' 插入变量',
memory: {
addButton: '添加记忆',
currentNodeLabel: '当前节点范围',
conversationScopeLabel: '会话范围',
createButton: '创建记忆变量',
},
},
knowledgeRetrieval: {