mirror of https://github.com/langgenius/dify.git
feat: memory
This commit is contained in:
parent
38d27100ac
commit
10f5270320
|
|
@ -341,7 +341,7 @@ const Editor: FC<Props> = ({
|
|||
</div>
|
||||
</div>
|
||||
</Wrap>
|
||||
{isMemorySupported && <MemoryCreateButton instanceId={instanceId} hideTrigger />}
|
||||
{isMemorySupported && <MemoryCreateButton nodeId={nodeId || ''} instanceId={instanceId} hideTrigger />}
|
||||
</>
|
||||
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import ActionButton from '@/app/components/base/action-button'
|
|||
import { useMemoryVariables } from './hooks/use-memory-variables'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { Memory as MemoryIcon } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import VariableModal from '@/app/components/workflow/panel/chat-variable-panel/components/variable-modal'
|
||||
|
||||
type BlockMemoryProps = {
|
||||
id: string
|
||||
|
|
@ -20,17 +21,18 @@ type BlockMemoryProps = {
|
|||
}
|
||||
const BlockMemory = ({ id }: BlockMemoryProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { memoryVariablesInUsed } = useMemoryVariables(id)
|
||||
const {
|
||||
memoryVariablesInUsed,
|
||||
editMemoryVariable,
|
||||
handleSetEditMemoryVariable,
|
||||
handleEdit,
|
||||
} = useMemoryVariables(id)
|
||||
const [showConfirm, setShowConfirm] = useState<{
|
||||
title: string
|
||||
desc: string
|
||||
onConfirm: () => void
|
||||
} | undefined>(undefined)
|
||||
|
||||
const handleEdit = (blockId: string) => {
|
||||
console.log('edit', blockId)
|
||||
}
|
||||
|
||||
const handleDelete = (blockId: string) => {
|
||||
setShowConfirm({
|
||||
title: t('workflow.nodes.common.memory.block.deleteConfirmTitle'),
|
||||
|
|
@ -67,7 +69,7 @@ const BlockMemory = ({ id }: BlockMemoryProps) => {
|
|||
<ActionButton
|
||||
className='hidden shrink-0 group-hover:block'
|
||||
size='m'
|
||||
onClick={() => handleEdit(memoryVariable.id)}
|
||||
onClick={() => handleSetEditMemoryVariable(memoryVariable.id)}
|
||||
>
|
||||
<RiEditLine className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
|
|
@ -93,6 +95,16 @@ const BlockMemory = ({ id }: BlockMemoryProps) => {
|
|||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!editMemoryVariable && (
|
||||
<VariableModal
|
||||
chatVar={editMemoryVariable}
|
||||
onClose={() => handleSetEditMemoryVariable(undefined)}
|
||||
onSave={handleEdit}
|
||||
nodeScopeMemoryVariable={{ nodeId: id }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
import { useMemo } from 'react'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
import type { MemoryVariable } from '@/app/components/workflow/types'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
|
||||
|
||||
export const useMemoryVariables = (nodeId: string) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const memoryVariables = useStore(s => s.memoryVariables)
|
||||
const [editMemoryVariable, setEditMemoryVariable] = useState<MemoryVariable | undefined>(undefined)
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const memoryVariablesInUsed = useMemo(() => {
|
||||
return memoryVariables.filter(variable => variable.node_id === nodeId)
|
||||
|
|
@ -11,8 +23,36 @@ export const useMemoryVariables = (nodeId: string) => {
|
|||
const handleDelete = (blockId: string) => {
|
||||
console.log('delete', blockId)
|
||||
}
|
||||
|
||||
const handleSave = useCallback((newMemoryVar: MemoryVariable) => {
|
||||
const { memoryVariables, setMemoryVariables } = workflowStore.getState()
|
||||
const newList = [newMemoryVar, ...memoryVariables]
|
||||
setMemoryVariables(newList)
|
||||
handleSyncWorkflowDraft()
|
||||
}, [handleSyncWorkflowDraft, workflowStore])
|
||||
|
||||
const handleSetEditMemoryVariable = (memoryVariableId?: string) => {
|
||||
if (!memoryVariableId) {
|
||||
setEditMemoryVariable(undefined)
|
||||
return
|
||||
}
|
||||
const memoryVariable = memoryVariables.find(variable => variable.id === memoryVariableId)
|
||||
setEditMemoryVariable(memoryVariable)
|
||||
}
|
||||
|
||||
const handleEdit = (memoryVariable: MemoryVariable) => {
|
||||
const { memoryVariables, setMemoryVariables } = workflowStore.getState()
|
||||
const newList = memoryVariables.map(variable => variable.id === memoryVariable.id ? memoryVariable : variable)
|
||||
setMemoryVariables(newList)
|
||||
handleSyncWorkflowDraft()
|
||||
}
|
||||
|
||||
return {
|
||||
memoryVariablesInUsed,
|
||||
handleDelete,
|
||||
handleSave,
|
||||
handleSetEditMemoryVariable,
|
||||
handleEdit,
|
||||
editMemoryVariable,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import Collapse from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||
import type {
|
||||
Node,
|
||||
|
|
@ -17,6 +19,8 @@ import { useMemory } from './hooks/use-memory'
|
|||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import { MemoryMode } from '@/app/components/workflow/types'
|
||||
import BlockMemory from './block-memory'
|
||||
import { ActionButton } from '@/app/components/base/action-button'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type MemoryProps = Pick<Node, 'id' | 'data'> & {
|
||||
readonly?: boolean
|
||||
|
|
@ -38,6 +42,14 @@ const MemorySystem = ({
|
|||
handleUpdateMemory,
|
||||
} = useMemory(id, data as LLMNodeType)
|
||||
|
||||
const renderTrigger = useCallback((open?: boolean) => {
|
||||
return (
|
||||
<ActionButton className={cn('shrink-0', open && 'bg-state-base-hover')}>
|
||||
<RiAddLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className=''>
|
||||
|
|
@ -64,7 +76,10 @@ const MemorySystem = ({
|
|||
<>
|
||||
<Divider type='vertical' className='!ml-1.5 !mr-1 h-3 !w-px bg-divider-regular' />
|
||||
<div onClick={e => e.stopPropagation()}>
|
||||
<MemoryCreateButton />
|
||||
<MemoryCreateButton
|
||||
nodeId={id}
|
||||
renderTrigger={renderTrigger}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import VariableModal from '@/app/components/workflow/panel/chat-variable-panel/components/variable-modal'
|
||||
import type { OffsetOptions, Placement } from '@floating-ui/react'
|
||||
import {
|
||||
|
|
@ -7,52 +6,36 @@ import {
|
|||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import type { MemoryVariable } from '@/app/components/workflow/types'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
|
||||
import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { MEMORY_VAR_CREATED_BY_MODAL_BY_EVENT_EMITTER, MEMORY_VAR_MODAL_SHOW_BY_EVENT_EMITTER } from '@/app/components/workflow/nodes/_base/components/prompt/type'
|
||||
import { useMemoryVariables } from './hooks/use-memory-variables'
|
||||
|
||||
type Props = {
|
||||
placement?: Placement
|
||||
offset?: number | OffsetOptions
|
||||
hideTrigger?: boolean
|
||||
instanceId?: string
|
||||
nodeId: string
|
||||
renderTrigger?: (open?: boolean) => React.ReactNode
|
||||
}
|
||||
|
||||
const MemoryCreateButton = ({
|
||||
placement,
|
||||
offset,
|
||||
hideTrigger,
|
||||
instanceId,
|
||||
nodeId,
|
||||
renderTrigger = () => <div></div>,
|
||||
}: Props) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [open, setOpen] = useState(false)
|
||||
const varList = useStore(s => s.memoryVariables) as MemoryVariable[]
|
||||
const updateMemoryVarList = useStore(s => s.setMemoryVariables)
|
||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const {
|
||||
invalidateConversationVarValues,
|
||||
} = useInspectVarsCrud()
|
||||
|
||||
const handleVarChanged = useCallback(() => {
|
||||
doSyncWorkflowDraft(false, {
|
||||
onSuccess() {
|
||||
invalidateConversationVarValues()
|
||||
},
|
||||
})
|
||||
}, [doSyncWorkflowDraft, invalidateConversationVarValues])
|
||||
const { handleSave: handleSaveMemoryVariables } = useMemoryVariables(nodeId)
|
||||
|
||||
const handleSave = useCallback((newMemoryVar: MemoryVariable) => {
|
||||
const newList = [newMemoryVar, ...varList]
|
||||
updateMemoryVarList(newList)
|
||||
handleVarChanged()
|
||||
setOpen(false)
|
||||
handleSaveMemoryVariables(newMemoryVar)
|
||||
if (instanceId)
|
||||
eventEmitter?.emit({ type: MEMORY_VAR_CREATED_BY_MODAL_BY_EVENT_EMITTER, instanceId, variable: ['memory', newMemoryVar.name] } as any)
|
||||
}, [varList, updateMemoryVarList, handleVarChanged, setOpen, eventEmitter, instanceId])
|
||||
}, [handleSaveMemoryVariables, eventEmitter, instanceId])
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === MEMORY_VAR_MODAL_SHOW_BY_EVENT_EMITTER && v.instanceId === instanceId)
|
||||
|
|
@ -68,12 +51,7 @@ const MemoryCreateButton = ({
|
|||
offset={offset}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
{hideTrigger && <div></div>}
|
||||
{!hideTrigger && (
|
||||
<ActionButton className='shrink-0'>
|
||||
<RiAddLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)}
|
||||
{renderTrigger?.(open)}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
<VariableModal
|
||||
|
|
@ -81,6 +59,7 @@ const MemoryCreateButton = ({
|
|||
onClose={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
nodeScopeMemoryVariable={nodeId ? { nodeId } : undefined}
|
||||
/>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
|
|
|
|||
|
|
@ -19,15 +19,20 @@ import VariableForm from '@/app/components/base/form/form-scenarios/variable'
|
|||
import { useForm } from '../hooks'
|
||||
|
||||
export type ModalPropsType = {
|
||||
className?: string
|
||||
chatVar?: ConversationVariable | MemoryVariable
|
||||
onClose: () => void
|
||||
onSave: (chatVar: ConversationVariable | MemoryVariable) => void
|
||||
nodeScopeMemoryVariable?: {
|
||||
nodeId: string
|
||||
}
|
||||
}
|
||||
|
||||
const ChatVariableModal = ({
|
||||
chatVar,
|
||||
onClose,
|
||||
onSave,
|
||||
nodeScopeMemoryVariable,
|
||||
}: ModalPropsType) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
|
|
@ -36,7 +41,7 @@ const ChatVariableModal = ({
|
|||
const {
|
||||
formSchemas,
|
||||
defaultValues,
|
||||
} = useForm(chatVar)
|
||||
} = useForm(chatVar, nodeScopeMemoryVariable)
|
||||
const formRef = useRef<FormRefObject>(null)
|
||||
const form = useTanstackForm({
|
||||
defaultValues,
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ import {
|
|||
} from './use-memory-schema'
|
||||
import type { ConversationVariable, MemoryVariable } from '@/app/components/workflow/types'
|
||||
|
||||
export const useForm = (chatVar?: ConversationVariable | MemoryVariable) => {
|
||||
export const useForm = (chatVar?: ConversationVariable | MemoryVariable, nodeScopeMemoryVariable?: { nodeId: string }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const typeSchema = useTypeSchema()
|
||||
const valueSchema = useValueSchema()
|
||||
const editInJSONSchema = useEditInJSONSchema()
|
||||
const memorySchema = useMemorySchema()
|
||||
const memoryDefaultValues = useMemoryDefaultValues()
|
||||
const memorySchema = useMemorySchema(nodeScopeMemoryVariable)
|
||||
const memoryDefaultValues = useMemoryDefaultValues(nodeScopeMemoryVariable)
|
||||
|
||||
const formSchemas = useMemo(() => {
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -3,9 +3,56 @@ import { FormTypeEnum } from '@/app/components/base/form/types'
|
|||
import type { FormSchema } from '@/app/components/base/form/types'
|
||||
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||
|
||||
export const useMemorySchema = () => {
|
||||
export const useMemorySchema = (nodeScopeMemoryVariable?: { nodeId: string }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const scopeSchema = [
|
||||
{
|
||||
name: 'scope',
|
||||
label: t('workflow.chatVariable.modal.scope'),
|
||||
type: FormTypeEnum.radio,
|
||||
default: 'app',
|
||||
fieldClassName: 'flex justify-between',
|
||||
inputClassName: 'w-[102px]',
|
||||
options: [
|
||||
{
|
||||
label: 'App',
|
||||
value: 'app',
|
||||
},
|
||||
{
|
||||
label: 'Node',
|
||||
value: 'node',
|
||||
},
|
||||
],
|
||||
show_on: [
|
||||
{
|
||||
variable: 'value_type',
|
||||
value: [ChatVarType.Memory],
|
||||
},
|
||||
],
|
||||
selfFormProps: {
|
||||
withTopDivider: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'node_id',
|
||||
label: 'Node',
|
||||
type: FormTypeEnum.nodeSelector,
|
||||
fieldClassName: 'flex justify-between',
|
||||
default: '',
|
||||
show_on: [
|
||||
{
|
||||
variable: 'value_type',
|
||||
value: [ChatVarType.Memory],
|
||||
},
|
||||
{
|
||||
variable: 'scope',
|
||||
value: 'node',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'template',
|
||||
|
|
@ -90,50 +137,7 @@ export const useMemorySchema = () => {
|
|||
inputWrapperClassName: 'w-[102px]',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'scope',
|
||||
label: t('workflow.chatVariable.modal.scope'),
|
||||
type: FormTypeEnum.radio,
|
||||
default: 'app',
|
||||
fieldClassName: 'flex justify-between',
|
||||
inputClassName: 'w-[102px]',
|
||||
options: [
|
||||
{
|
||||
label: 'App',
|
||||
value: 'app',
|
||||
},
|
||||
{
|
||||
label: 'Node',
|
||||
value: 'node',
|
||||
},
|
||||
],
|
||||
show_on: [
|
||||
{
|
||||
variable: 'value_type',
|
||||
value: [ChatVarType.Memory],
|
||||
},
|
||||
],
|
||||
selfFormProps: {
|
||||
withTopDivider: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'node_id',
|
||||
label: 'Node',
|
||||
type: FormTypeEnum.nodeSelector,
|
||||
fieldClassName: 'flex justify-between',
|
||||
default: '',
|
||||
show_on: [
|
||||
{
|
||||
variable: 'value_type',
|
||||
value: [ChatVarType.Memory],
|
||||
},
|
||||
{
|
||||
variable: 'scope',
|
||||
value: 'node',
|
||||
},
|
||||
],
|
||||
},
|
||||
...(!nodeScopeMemoryVariable ? scopeSchema : []),
|
||||
{
|
||||
name: 'term',
|
||||
label: t('workflow.chatVariable.modal.term'),
|
||||
|
|
@ -238,14 +242,15 @@ export const useMemorySchema = () => {
|
|||
] as FormSchema[]
|
||||
}
|
||||
|
||||
export const useMemoryDefaultValues = () => {
|
||||
export const useMemoryDefaultValues = (nodeScopeMemoryVariable?: { nodeId: string }) => {
|
||||
return {
|
||||
template: '',
|
||||
instruction: '',
|
||||
strategy: 'on_turns',
|
||||
update_turns: 500,
|
||||
preserved_turns: 10,
|
||||
scope: 'app',
|
||||
scope: nodeScopeMemoryVariable ? 'node' : 'app',
|
||||
node_id: nodeScopeMemoryVariable?.nodeId || '',
|
||||
term: 'session',
|
||||
more: false,
|
||||
model: '',
|
||||
|
|
|
|||
Loading…
Reference in New Issue