feat(web): llm node add memory config

This commit is contained in:
zhaoying
2026-01-14 16:50:20 +08:00
parent a6e7565919
commit 617ff706bc
8 changed files with 142 additions and 41 deletions

View File

@@ -1,6 +1,5 @@
import React, { useState, useImperativeHandle, forwardRef, useRef } from 'react';
import { Button, Input, Space, Typography, Tooltip, message, List } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { useState, useImperativeHandle, forwardRef, useRef } from 'react';
import { Button, Space, List } from 'antd';
import { useTranslation } from 'react-i18next';
import type { ChatVariable, AddChatVariableRef } from '../../types';
import type { ChatVariableModalRef } from './types'

View File

@@ -131,7 +131,7 @@ const EditableTable: React.FC<EditableTableProps> = ({
const AddButton = ({ block = false }: { block?: boolean }) => (
<Button
type={block ? "dashed" : "text"}
icon={<PlusOutlined />}
icon={block ? undefined : <PlusOutlined />}
onClick={() => add(createNewRow())}
size="small"
block={block}

View File

@@ -0,0 +1,69 @@
import { type FC } from "react";
import { useTranslation } from 'react-i18next'
import { Form, Row, Col, Divider, Switch, Slider } from 'antd'
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import MessageEditor from '../MessageEditor'
const MemoryConfig: FC<{ options: Suggestion[]; parentName: string; }> = ({
options,
parentName
}) => {
const { t } = useTranslation()
const form = Form.useFormInstance();
const values = Form.useWatch([], form) || {}
console.log('MemoryConfig', values)
const handleChangeEnable = (value: boolean) => {
if (value) {
form.setFieldsValue({
memory: {
...form.getFieldValue(parentName),
enable_window: false,
window_size: 20,
messages: "{{sys.message}}"
}
})
}
}
return (
<>
{values?.memory?.enable && <>
<div className="rb:flex rb:items-center rb:justify-between rb:py-1.5 rb:px-2 rb:bg-[#F6F8FC] rb:rounded-md rb:mb-2">
{t('workflow.config.llm.memory')}
<span>{t('workflow.config.llm.inner')}</span>
</div>
<Form.Item layout="horizontal" name={[parentName, 'messages']}>
<MessageEditor
title="USER"
isArray={false}
parentName={[parentName, 'messages']}
options={options}
/>
</Form.Item>
<Divider />
</>}
<Form.Item layout="horizontal" name={[parentName, 'enable']} label={t('workflow.config.llm.memory')}>
<Switch onChange={handleChangeEnable} />
</Form.Item>
{values?.memory?.enable && <>
<Row className="rb:mb-3">
<Col span={10}>
<Form.Item layout="horizontal" name={[parentName, 'enable_window']} noStyle>
<Switch />
</Form.Item>
<span className="rb:ml-2">{t('workflow.config.llm.enable_window')}</span>
</Col>
<Col span={14}>
<Form.Item layout="horizontal" name={[parentName, 'window_size']} noStyle>
<Slider min={1} max={100} step={1} className="rb:my-0!" disabled={!values?.memory?.enable_window} />
</Form.Item>
</Col>
</Row>
</>}
</>
);
};
export default MemoryConfig;

View File

@@ -127,7 +127,7 @@ const MessageEditor: FC<MessageEditor> = ({
</Space>
);
})}
<Form.Item>
<Form.Item noStyle>
<Button type="dashed" onClick={() => handleAdd(add)} block>
+{t('workflow.addMessage')}
</Button>

View File

@@ -22,6 +22,7 @@ import ConditionList from './ConditionList'
import CycleVarsList from './CycleVarsList'
import AssignmentList from './AssignmentList'
import ToolConfig from './ToolConfig'
import MemoryConfig from './MemoryConfig'
// import { calculateVariableList } from './utils/variableListCalculator'
interface PropertiesProps {
@@ -1230,6 +1231,20 @@ const Properties: FC<PropertiesProps> = ({
</Form.Item>
)
}
if (config.type === 'memoryConfig') {
return (
<Form.Item
key={key}
name={key}
noStyle
>
<MemoryConfig
parentName={key}
options={getFilteredVariableList('llm')}
/>
</Form.Item>
)
}
return (
<Form.Item

View File

@@ -135,6 +135,14 @@ export const nodeLibrary: NodeLibrary[] = [
readonly: true
},
]
},
memory: {
type: 'memoryConfig',
defaultValue: {
enable: false,
enable_window: false,
window_size: 20
}
}
}
},
@@ -750,10 +758,6 @@ export const outputVariable: { [key: string]: OutputVariable } = {
{ name: "body", type: "string" },
{ name: "status_code", type: "number" },
],
error: [
{ name: "error_message", type: "string" },
{ name: "error_type", type: "string" },
]
},
'tool': {
default: [

View File

@@ -6,7 +6,7 @@ import { Graph, Node, MiniMap, Snapline, Clipboard, Keyboard, type Edge } from '
import { register } from '@antv/x6-react-shape';
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs } from '../constant';
import type { WorkflowConfig, NodeProperties } from '../types';
import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types';
import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application'
import type { PortMetadata } from '@antv/x6/lib/model/port';
@@ -35,6 +35,8 @@ export interface UseWorkflowGraphReturn {
copyEvent: () => boolean | void;
parseEvent: () => boolean | void;
handleSave: (flag?: boolean) => Promise<unknown>;
chatVariables: ChatVariable[];
setChatVariables: React.Dispatch<React.SetStateAction<ChatVariable[]>>;
}
export const edge_color = '#155EEF';
@@ -54,6 +56,7 @@ export const useWorkflowGraph = ({
const [canRedo, setCanRedo] = useState(false);
const [isHandMode, setIsHandMode] = useState(false);
const [config, setConfig] = useState<WorkflowConfig | null>(null);
const [chatVariables, setChatVariables] = useState<ChatVariable[]>([])
useEffect(() => {
getConfig()
@@ -63,16 +66,15 @@ export const useWorkflowGraph = ({
getWorkflowConfig(id)
.then(res => {
const { variables, ...rest } = res as WorkflowConfig
setConfig({
...rest,
variables: variables.map(v => {
const { default: _, ...cleanV } = v
return {
...cleanV,
defaultValue: v.default ?? ''
}
})
const initChatVariables = variables.map(v => {
const { default: _, ...cleanV } = v
return {
...cleanV,
defaultValue: v.default ?? ''
}
})
setChatVariables(initChatVariables)
setConfig({ ...rest, variables: initChatVariables })
})
}
@@ -94,7 +96,17 @@ export const useWorkflowGraph = ({
if (nodeLibraryConfig?.config) {
Object.keys(nodeLibraryConfig.config).forEach(key => {
if (key === 'knowledge_retrieval' && nodeLibraryConfig.config && nodeLibraryConfig.config[key]) {
if (key === 'memory' && nodeLibraryConfig.config && nodeLibraryConfig.config[key]) {
const { memory, messages } = config as any;
if (memory?.enable && messages && messages.length > 0) {
const lastMessage = messages[messages.length - 1]
nodeLibraryConfig.config[key].defaultValue = {
...memory,
messages: lastMessage.content
}
nodeLibraryConfig.config.messages.defaultValue.splice(-1, 1)
}
} else if (key === 'knowledge_retrieval' && nodeLibraryConfig.config && nodeLibraryConfig.config[key]) {
const { query, ...rest } = config
nodeLibraryConfig.config[key].defaultValue = {
...rest
@@ -917,13 +929,13 @@ export const useWorkflowGraph = ({
const params = {
...config,
variables: config.variables.map(v => {
const { defaultValue, ...cleanV } = v
return {
...cleanV,
default: defaultValue ?? ''
}
}),
variables: chatVariables.map(v => {
const { defaultValue, ...cleanV } = v
return {
...cleanV,
default: defaultValue ?? ''
}
}),
nodes: nodes.map((node: Node) => {
const data = node.getData();
const position = node.getPosition();
@@ -931,7 +943,15 @@ export const useWorkflowGraph = ({
if (data.config) {
Object.keys(data.config).forEach(key => {
if (data.config[key] && 'defaultValue' in data.config[key] && key === 'group_variables') {
if (key === 'memory' && data.config[key] && 'defaultValue' in data.config[key]) {
const { messages, ...rest } = data.config[key].defaultValue
let memoryMessage = { role: 'USER', content: data.config[key].defaultValue.messages }
itemConfig = {
...itemConfig,
messages: rest.enable ? [...itemConfig.messages, memoryMessage] : itemConfig.messages,
memory: { ...rest },
}
} else if (data.config[key] && 'defaultValue' in data.config[key] && key === 'group_variables') {
let group_variables = data.config.group.defaultValue ? {} : data.config[key].defaultValue
if (data.config.group.defaultValue) {
data.config[key].defaultValue.map((vo: any) => {
@@ -1077,5 +1097,7 @@ export const useWorkflowGraph = ({
copyEvent,
parseEvent,
handleSave,
chatVariables,
setChatVariables
};
};

View File

@@ -8,7 +8,7 @@ import PortClickHandler from './components/PortClickHandler';
import { useWorkflowGraph } from './hooks/useWorkflowGraph';
import type { WorkflowRef } from '@/views/ApplicationConfig/types'
import Chat from './components/Chat/Chat';
import type { ChatRef, AddChatVariableRef, ChatVariable } from './types'
import type { ChatRef, AddChatVariableRef } from './types'
import arrowIcon from '@/assets/images/workflow/arrow.png'
import AddChatVariable from './components/AddChatVariable';
@@ -21,7 +21,6 @@ const Workflow = forwardRef<WorkflowRef>((_props, ref) => {
// 使用自定义Hook初始化工作流图
const {
config,
setConfig,
graphRef,
selectedNode,
setSelectedNode,
@@ -38,6 +37,8 @@ const Workflow = forwardRef<WorkflowRef>((_props, ref) => {
copyEvent,
parseEvent,
handleSave,
chatVariables,
setChatVariables
} = useWorkflowGraph({ containerRef, miniMapRef });
const onDragOver = (event: React.DragEvent) => {
@@ -52,15 +53,6 @@ const Workflow = forwardRef<WorkflowRef>((_props, ref) => {
const addVariable = () => {
addChatVariableRef.current?.handleOpen()
}
const handleUpdateChatVariable = (variables: ChatVariable[]) => {
setConfig(prev => {
if (!prev) return null
return {
...prev,
variables
}
})
}
useImperativeHandle(ref, () => ({
handleSave,
@@ -125,8 +117,8 @@ const Workflow = forwardRef<WorkflowRef>((_props, ref) => {
<AddChatVariable
ref={addChatVariableRef}
variables={config?.variables}
onChange={handleUpdateChatVariable}
variables={chatVariables}
onChange={setChatVariables}
/>
</div>
);