From 8262045b1e02dd38ae546f0981542ee39ee52e12 Mon Sep 17 00:00:00 2001 From: lixinyue11 <94037597+lixinyue11@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:36:24 +0800 Subject: [PATCH 01/47] Fix/memory bug fix (#118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 图谱数据量限制数量去掉 * 图谱数据量限制数量去掉 * 图谱数据量限制数量去掉 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 用户详情优化 --- api/app/services/memory_agent_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/app/services/memory_agent_service.py b/api/app/services/memory_agent_service.py index 2d78d796..e05daf4a 100644 --- a/api/app/services/memory_agent_service.py +++ b/api/app/services/memory_agent_service.py @@ -634,7 +634,7 @@ class MemoryAgentService: retrieved_content.append({query:statements}) if retrieved_content==[]: retrieved_content='' - if '信息不足,无法回答。' != str(final_answer) :#and retrieved_content!=[] + if '信息不足,无法回答。' != str(final_answer) and str(search_switch).strip() != "2":#and retrieved_content!=[] # 使用 upsert 方法 repo.upsert( end_user_id=end_user_id, # 确保这个变量在作用域内 From 7b6619b8de96c8a30d2ab93b6e8ef933d8027839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=90=E5=8A=9B=E9=BD=90?= <162269739+lanceyq@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:43:08 +0800 Subject: [PATCH 02/47] Fix/problems (#116) * [fix]The repair model does not allow null values and does not support relational networks. * [fix]The repair model does not allow null values and does not support relational networks. * [changes]Restore field restrictions --- api/app/services/user_memory_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/app/services/user_memory_service.py b/api/app/services/user_memory_service.py index 8f25f477..9221ab06 100644 --- a/api/app/services/user_memory_service.py +++ b/api/app/services/user_memory_service.py @@ -16,6 +16,7 @@ from app.db import get_db_context from app.repositories.conversation_repository import ConversationRepository from app.repositories.end_user_repository import EndUserRepository from app.repositories.neo4j.neo4j_connector import Neo4jConnector +from app.schemas.memory_episodic_schema import EmotionSubject, EmotionType, type_mapping from app.services.implicit_memory_service import ImplicitMemoryService from app.services.memory_base_service import MemoryBaseService from app.services.memory_config_service import MemoryConfigService From 0d9cdd50397b2a2d5874d6be1204528d9ed74dd5 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 14 Jan 2026 18:51:33 +0800 Subject: [PATCH 03/47] fix(web): update add node --- .../views/Workflow/components/Chat/Chat.tsx | 1 + .../Workflow/components/Nodes/AddNode.tsx | 9 +- .../Workflow/components/Nodes/LoopNode.tsx | 25 +++--- .../Workflow/components/PortClickHandler.tsx | 89 +++++++++++++++++-- .../views/Workflow/hooks/useWorkflowGraph.ts | 15 ++-- 5 files changed, 111 insertions(+), 28 deletions(-) diff --git a/web/src/views/Workflow/components/Chat/Chat.tsx b/web/src/views/Workflow/components/Chat/Chat.tsx index 9b1d3254..c1047251 100644 --- a/web/src/views/Workflow/components/Chat/Chat.tsx +++ b/web/src/views/Workflow/components/Chat/Chat.tsx @@ -55,6 +55,7 @@ const Chat = forwardRef(({ appId setOpen(false) setChatList([]) setVariables([]) + setConversationId(null) } const handleEditVariables = () => { variableConfigModalRef.current?.handleOpen(variables) diff --git a/web/src/views/Workflow/components/Nodes/AddNode.tsx b/web/src/views/Workflow/components/Nodes/AddNode.tsx index b9e602d4..a37da651 100644 --- a/web/src/views/Workflow/components/Nodes/AddNode.tsx +++ b/web/src/views/Workflow/components/Nodes/AddNode.tsx @@ -13,11 +13,12 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { const handleNodeSelect = (selectedNodeType: any) => { const parentBBox = node.getBBox(); const cycleId = data.cycle; + const horizontalSpacing = 20; const id = `${selectedNodeType.type.replace(/-/g, '_') }_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` const newNode = graph.addNode({ ...(graphNodeLibrary[selectedNodeType.type] || graphNodeLibrary.default), - x: parentBBox.x, + x: parentBBox.x + horizontalSpacing, y: parentBBox.y, id, data: { @@ -35,7 +36,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { if (cycleId) { const parentNode = graph.getNodes().find((n: any) => n.getData()?.id === cycleId); if (parentNode) { - parentNode.addChild(newNode); + parentNode.insertChild(newNode); } } @@ -47,7 +48,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { source: { cell: edge.getSourceCellId(), port: edge.getSourcePortId() }, target: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'left')?.id || 'left' }, attrs: edge.getAttrs(), - zIndex: 3 + zIndex: 1, }); }); @@ -58,7 +59,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { source: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'right')?.id || 'right' }, target: { cell: edge.getTargetCellId(), port: targetPortId }, attrs: edge.getAttrs(), - zIndex: 3 + zIndex: 1, }); }); diff --git a/web/src/views/Workflow/components/Nodes/LoopNode.tsx b/web/src/views/Workflow/components/Nodes/LoopNode.tsx index 40b4b8ec..71f4dc44 100644 --- a/web/src/views/Workflow/components/Nodes/LoopNode.tsx +++ b/web/src/views/Workflow/components/Nodes/LoopNode.tsx @@ -11,9 +11,13 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { const { t } = useTranslation() useEffect(() => { - initNodes() - // 检查是否需要添加add-node - checkAndAddAddNode() + // 使用setTimeout确保在所有节点都添加完成后再创建连线 + const timer = setTimeout(() => { + initNodes() + checkAndAddAddNode() + }, 50) + + return () => clearTimeout(timer) }, []) const checkAndAddAddNode = () => { @@ -29,7 +33,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { const addNode = graph.addNode({ ...graphNodeLibrary.addStart, - x: cycleStartBBox.x + 64, + x: cycleStartBBox.x + 84, y: cycleStartBBox.y, data: { type: 'add-node', @@ -40,7 +44,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { }, }); - node.addChild(addNode); + node.insertChild(addNode); // 连接cycle-start和add-node const sourcePorts = cycleStartNode.getPorts(); @@ -48,6 +52,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { const sourcePort = sourcePorts.find((port: any) => port.group === 'right')?.id || 'right'; const targetPort = targetPorts.find((port: any) => port.group === 'left')?.id || 'left'; + // 直接创建连线,不使用异步 graph.addEdge({ source: { cell: cycleStartNode.id, port: sourcePort }, target: { cell: addNode.id, port: targetPort }, @@ -61,7 +66,6 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { }, }, }, - zIndex: 10 }); } } @@ -93,7 +97,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { }); const addNode = graph.addNode({ ...graphNodeLibrary.addStart, - x: centerX + 64, + x: centerX + 84, y: centerY, data: { type: 'add-node', @@ -103,8 +107,8 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { cycle: data.id, }, }); - node.addChild(cycleStartNode) - node.addChild(addNode) + node.insertChild(cycleStartNode) + node.insertChild(addNode) const sourcePorts = cycleStartNode.getPorts() const targetPorts = addNode.getPorts() let sourcePort = sourcePorts.find((port: any) => port.group === 'right')?.id || 'right'; @@ -124,11 +128,10 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { strokeWidth: 1, targetMarker: { name: 'block', - size: 8, + size: 2, }, }, }, - zIndex: 10 } graph.addEdge(edgeConfig) diff --git a/web/src/views/Workflow/components/PortClickHandler.tsx b/web/src/views/Workflow/components/PortClickHandler.tsx index 9b83215a..8342ae6a 100644 --- a/web/src/views/Workflow/components/PortClickHandler.tsx +++ b/web/src/views/Workflow/components/PortClickHandler.tsx @@ -36,11 +36,77 @@ const PortClickHandler: React.FC = ({ graph }) => { if (!sourceNode || !graph) return; const sourceNodeData = sourceNode.getData(); + const sourceNodeType = sourceNodeData?.type; - // 计算新节点位置(在源节点右侧) + // 如果是cycle-start节点,需要处理add-node节点 + let addNodePosition = null; + if (sourceNodeType === 'cycle-start' && sourceNodeData.cycle) { + const cycleId = sourceNodeData.cycle; + const addNodes = graph.getNodes().filter((n: any) => + n.getData()?.type === 'add-node' && n.getData()?.cycle === cycleId + ); + + if (addNodes.length > 0) { + const addNode = addNodes[0]; + addNodePosition = addNode.getBBox(); + addNode.remove(); + } + } + + // 计算新节点位置,避免重叠 const sourceBBox = sourceNode.getBBox(); - const newX = sourceBBox.x + sourceBBox.width + 50; - const newY = sourceBBox.y; + const nodeWidth = graphNodeLibrary[selectedNodeType.type]?.width || 120; + const nodeHeight = graphNodeLibrary[selectedNodeType.type]?.height || 88; + const horizontalSpacing = sourceNodeType === 'cycle-start' ? 40 : 80; + const verticalSpacing = 10; + + // 获取源连接桩的group信息 + const sourcePortInfo = sourceNode.getPorts().find((p: any) => p.id === sourcePort); + const sourcePortGroup = sourcePortInfo?.group || sourcePort; + console.log('sourcePortGroup', sourcePortGroup, sourcePortInfo) + + // 如果有add-node位置,使用该位置,否则计算新位置 + let newX, newY; + if (addNodePosition) { + newX = addNodePosition.x; + newY = addNodePosition.y; + } else { + // 根据连接桩位置决定节点放置方向 + if (sourcePortGroup === 'left') { + // 左侧连接桩,在左侧添加节点 + newX = sourceBBox.x - nodeWidth*2 - horizontalSpacing; + newY = sourceBBox.y; + } else { + // 右侧连接桩,在右侧添加节点 + newX = sourceBBox.x + sourceBBox.width + horizontalSpacing; + newY = sourceBBox.y; + } + + // 检查位置是否与现有节点重叠(只考虑与当前节点相连的节点) + const checkOverlap = (x: number, y: number) => { + // 获取与源节点相连的节点 + const connectedNodes = new Set(); + graph.getConnectedEdges(sourceNode).forEach((edge: any) => { + const sourceId = edge.getSourceCellId(); + const targetId = edge.getTargetCellId(); + if (sourceId !== sourceNode.id) connectedNodes.add(sourceId); + if (targetId !== sourceNode.id) connectedNodes.add(targetId); + }); + + return graph.getNodes().some((node: any) => { + if (node.id === sourceNode.id) return false; + if (!connectedNodes.has(node.id)) return false; // 只考虑相连的节点 + const bbox = node.getBBox(); + return !(x + nodeWidth < bbox.x || x > bbox.x + bbox.width || + y + nodeHeight < bbox.y || y > bbox.y + bbox.height); + }); + }; + + // 如果位置被占用,向下寻找空位 + while (checkOverlap(newX, newY)) { + newY += nodeHeight + verticalSpacing; + } + } // 创建新节点 const id = `${selectedNodeType.type.replace(/-/g, '_')}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` @@ -63,14 +129,22 @@ const PortClickHandler: React.FC = ({ graph }) => { if (sourceNodeData.cycle) { const parentNode = graph.getNodes().find((n: any) => n.getData()?.id === sourceNodeData.cycle); if (parentNode) { - parentNode.addChild(newNode); + parentNode.insertChild(newNode); } } // 创建连线 setTimeout(() => { const targetPorts = newNode.getPorts(); - const targetPort = targetPorts.find((port: any) => port.group === 'left')?.id || 'left'; + let targetPort; + + if (sourcePortGroup === 'left') { + // 从左侧连接桩连出,连接到新节点的右侧 + targetPort = targetPorts.find((port: any) => port.group === 'right')?.id || 'right'; + } else { + // 从右侧连接桩连出,连接到新节点的左侧 + targetPort = targetPorts.find((port: any) => port.group === 'left')?.id || 'left'; + } graph.addEdge({ source: { cell: sourceNode.id, port: sourcePort }, @@ -85,7 +159,7 @@ const PortClickHandler: React.FC = ({ graph }) => { }, }, }, - zIndex: 0 + zIndex: sourceNodeData.cycle && sourceNodeType == 'cycle-start' ? 1 : sourceNodeData.cycle ? 2 : 0 }); // 循环节点内子节点通过连接桩添加时,调整循环节点大小 @@ -108,8 +182,9 @@ const PortClickHandler: React.FC = ({ graph }) => { }, { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }); const padding = 20; + const bottomPadding = 50; const newWidth = Math.max(240, bounds.maxX - bounds.minX + padding * 2); - const newHeight = Math.max(120, bounds.maxY - bounds.minY + padding * 2); + const newHeight = Math.max(120, bounds.maxY - bounds.minY + padding + bottomPadding); parentNode.prop('size', { width: newWidth, height: newHeight }); } diff --git a/web/src/views/Workflow/hooks/useWorkflowGraph.ts b/web/src/views/Workflow/hooks/useWorkflowGraph.ts index c281a76e..f3439571 100644 --- a/web/src/views/Workflow/hooks/useWorkflowGraph.ts +++ b/web/src/views/Workflow/hooks/useWorkflowGraph.ts @@ -235,7 +235,7 @@ export const useWorkflowGraph = ({ if (parentNode) { const addedChild = graphRef.current?.addNode(childNode) if (addedChild) { - parentNode.addChild(addedChild) + parentNode.insertChild(addedChild) } } } @@ -275,6 +275,11 @@ export const useWorkflowGraph = ({ }, 100) } if (edges.length) { + // 计算loop和iteration类型节点的数量 + const loopIterationCount = nodes.filter(node => + node.type === 'loop' || node.type === 'iteration' + ).length; + // 去重处理:对于if-else和question-classifier节点,不同连接桩允许连接到相同节点 const uniqueEdges = edges.filter((edge, index, arr) => { return arr.findIndex(e => { @@ -349,7 +354,7 @@ export const useWorkflowGraph = ({ }, }, }, - zIndex: targetCell.getData()?.cycle ? 3 : 0 + zIndex: loopIterationCount } return edgeConfig @@ -725,7 +730,6 @@ export const useWorkflowGraph = ({ }, }, }, - zIndex: 0, }); }, validateConnection({ sourceCell, targetCell, targetMagnet }) { @@ -762,9 +766,8 @@ export const useWorkflowGraph = ({ }, embedding: { enabled: true, - validate (this, { parent }) { - const parentData = parent.getData() - return parentData.type === 'iteration' || parentData.type === 'loop' + validate (this) { + return false } }, translating: { From 58d82df3273a3aa4081e5c647ddebe059afc4e6f Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 14 Jan 2026 19:00:38 +0800 Subject: [PATCH 04/47] fix(web): conversation page not need login --- web/src/App.tsx | 5 +---- web/src/components/Layout/NoAuthLayout.tsx | 14 ++++++++++++++ web/src/routes/index.tsx | 1 + web/src/routes/routes.json | 7 ++++++- 4 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 web/src/components/Layout/NoAuthLayout.tsx diff --git a/web/src/App.tsx b/web/src/App.tsx index 8e3140d9..1abbc2cc 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -30,15 +30,12 @@ import 'dayjs/plugin/utc' import { cookieUtils } from './utils/request'; - - - function App() { const { t } = useTranslation(); const { locale, language, timeZone } = useI18n() useEffect(() => { const authToken = cookieUtils.get('authToken') - if (!authToken && !window.location.hash.includes('#/login')) { + if (!authToken && !window.location.hash.includes('#/login') && !window.location.hash.includes('#/conversation/')) { window.location.href = `/#/login`; } }, []) diff --git a/web/src/components/Layout/NoAuthLayout.tsx b/web/src/components/Layout/NoAuthLayout.tsx new file mode 100644 index 00000000..a2e6f274 --- /dev/null +++ b/web/src/components/Layout/NoAuthLayout.tsx @@ -0,0 +1,14 @@ +import { Outlet } from 'react-router-dom'; +import { type FC } from 'react'; + +// 基础布局组件,用于展示内容并保留用户信息获取功能 +const NoAuthLayout: FC = () => { + + return ( +
+ +
+ ) +}; + +export default NoAuthLayout; \ No newline at end of file diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index 5c302565..7189dc9c 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -34,6 +34,7 @@ const componentMap: Record>> = AuthSpaceLayout: lazy(() => import('@/components/Layout/AuthSpaceLayout')), BasicLayout: lazy(() => import('@/components/Layout/BasicLayout')), LoginLayout: lazy(() => import('@/components/Layout/LoginLayout')), + NoAuthLayout: lazy(() => import('@/components/Layout/NoAuthLayout')), // 视图组件 Index: lazy(() => import('@/views/Index')), Home: lazy(() => import('@/views/Home')), diff --git a/web/src/routes/routes.json b/web/src/routes/routes.json index db0c1b7d..1c317033 100644 --- a/web/src/routes/routes.json +++ b/web/src/routes/routes.json @@ -42,12 +42,17 @@ "element": "BasicLayout", "children": [ { "path": "/application/config/:id", "element": "ApplicationConfig" }, - { "path": "/conversation/:token", "element": "Conversation" }, { "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" }, { "path": "/statement/:id", "element": "StatementDetail" }, { "path": "/user-memory/detail/:id/:type", "element": "MemoryNodeDetail" } ] }, + { + "element": "NoAuthLayout", + "children": [ + { "path": "/conversation/:token", "element": "Conversation" } + ] + }, { "element": "LoginLayout", "children": [ From d2eb10123b0dccd69b43243d1b1e77e46b6d09d7 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 14 Jan 2026 20:03:15 +0800 Subject: [PATCH 05/47] fix(web): chat variable bugfix --- .../views/Workflow/components/Chat/Chat.tsx | 2 +- .../components/Properties/CaseList/index.tsx | 47 +++++++++---------- .../Properties/VariableEditModal.tsx | 1 + .../Workflow/components/Properties/index.tsx | 4 +- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/web/src/views/Workflow/components/Chat/Chat.tsx b/web/src/views/Workflow/components/Chat/Chat.tsx index c1047251..246c2e4c 100644 --- a/web/src/views/Workflow/components/Chat/Chat.tsx +++ b/web/src/views/Workflow/components/Chat/Chat.tsx @@ -40,7 +40,7 @@ const Chat = forwardRef(({ appId const curVariables = startNodes[0].config.variables?.defaultValue curVariables.forEach((vo: StartVariableItem) => { - if (vo.default) { + if (typeof vo.default !== 'undefined') { vo.value = vo.default } const lastVo = variables.find(item => item.name === vo.name) diff --git a/web/src/views/Workflow/components/Properties/CaseList/index.tsx b/web/src/views/Workflow/components/Properties/CaseList/index.tsx index 0126b76d..c475034e 100644 --- a/web/src/views/Workflow/components/Properties/CaseList/index.tsx +++ b/web/src/views/Workflow/components/Properties/CaseList/index.tsx @@ -55,6 +55,11 @@ const CaseList: FC = ({ const updateNodePorts = (caseCount: number, removedCaseIndex?: number) => { if (!selectedNode || !graphRef?.current) return; + // 获取当前端口数量来判断是添加还是删除操作 + const currentPorts = selectedNode.getPorts().filter((port: any) => port.group === 'right'); + const currentCaseCount = currentPorts.length - 1; // 减去ELSE端口 + const isAddingCase = removedCaseIndex === undefined && caseCount > currentCaseCount; + // 保存现有连线信息(包括左侧端口连线) const existingEdges = graphRef.current.getEdges().filter((edge: any) => edge.getSourceCellId() === selectedNode.id || edge.getTargetCellId() === selectedNode.id @@ -83,14 +88,10 @@ const CaseList: FC = ({ selectedNode.prop('size', { width: 240, height: newHeight }) - // 计算端口间距 - const dy = totalPorts; - // 添加 IF 端口 selectedNode.addPort({ id: 'CASE1', group: 'right', - // args: { dy }, attrs: { text: { text: 'IF', fontSize: 12, fill: '#5B6167' }} }); @@ -99,7 +100,6 @@ const CaseList: FC = ({ selectedNode.addPort({ id: `CASE${i + 1}`, group: 'right', - // args: { dy }, attrs: { text: { text: 'ELIF', fontSize: 12, fill: '#5B6167' }} }); } @@ -108,22 +108,11 @@ const CaseList: FC = ({ selectedNode.addPort({ id: `CASE${caseCount + 1}`, group: 'right', - // args: { dy }, attrs: { text: { text: 'ELSE', fontSize: 12, fill: '#5B6167' }} }); - // 恢复仍然存在的端口连线 + // 恢复连线 setTimeout(() => { - // 计算删除前的总端口数来确定原ELSE端口编号 - const originalCaseCount = removedCaseIndex !== undefined ? caseCount + 1 : caseCount; - const originalElsePortNumber = originalCaseCount + 1; - - // 检查ELSE端口是否有连线 - const elseHasConnection = edgeConnections.some(({ sourcePortId, isIncoming }: any) => { - const caseNumber = parseInt(sourcePortId.match(/CASE(\d+)/)?.[1] || '0'); - return !isIncoming && caseNumber === originalElsePortNumber; - }); - edgeConnections.forEach(({ edge, sourcePortId, targetCellId, targetPortId, sourceCellId, isIncoming }: any) => { // 如果是进入连线(左侧端口),直接恢复 if (isIncoming) { @@ -151,7 +140,7 @@ const CaseList: FC = ({ // 处理右侧端口连线 const originalCaseNumber = parseInt(sourcePortId.match(/CASE(\d+)/)?.[1] || '0'); - // 如果是被删除的端口,只删除该端口的连线 + // 如果是删除操作且是被删除的端口,删除连线 if (removedCaseIndex !== undefined && originalCaseNumber === removedCaseIndex + 1) { graphRef.current?.removeCell(edge); return; @@ -159,12 +148,22 @@ const CaseList: FC = ({ let newPortId = sourcePortId; - // 如果是原来的ELSE端口且有连线,重新映射到新的ELSE端口 - if (originalCaseNumber === originalElsePortNumber && elseHasConnection) { - newPortId = `CASE${caseCount + 1}`; // 新的ELSE端口 - } else if (removedCaseIndex !== undefined && originalCaseNumber > removedCaseIndex + 1) { - // 如果是被删除端口之后的端口,编号向前移动 - newPortId = `CASE${originalCaseNumber - 1}`; + // 如果是删除操作,需要重新映射端口ID + if (removedCaseIndex !== undefined) { + if (originalCaseNumber > removedCaseIndex + 1) { + // 被删除端口之后的端口,编号向前移动 + newPortId = `CASE${originalCaseNumber - 1}`; + } + // ELSE端口始终映射到新的ELSE端口位置 + else if (originalCaseNumber === currentCaseCount + 1) { + newPortId = `CASE${caseCount + 1}`; + } + } else if (isAddingCase) { + // 如果是添加操作,ELSE端口需要重新映射 + if (originalCaseNumber === currentCaseCount + 1) { + newPortId = `CASE${caseCount + 1}`; // 新的ELSE端口 + } + // 新添加的端口不恢复任何连线 } const newPorts = selectedNode.getPorts(); diff --git a/web/src/views/Workflow/components/Properties/VariableEditModal.tsx b/web/src/views/Workflow/components/Properties/VariableEditModal.tsx index db4a38a3..075cd695 100644 --- a/web/src/views/Workflow/components/Properties/VariableEditModal.tsx +++ b/web/src/views/Workflow/components/Properties/VariableEditModal.tsx @@ -110,6 +110,7 @@ const VariableEditModal = forwardRef form.setFieldValue('default', undefined)} labelRender={(props) =>
{props.label} {variableType[props.value as keyof typeof variableType]}
} optionRender={(props) =>
{props.label} {variableType[props.value as keyof typeof variableType]}
} /> diff --git a/web/src/views/Workflow/components/Properties/index.tsx b/web/src/views/Workflow/components/Properties/index.tsx index 8752121e..77c3414f 100644 --- a/web/src/views/Workflow/components/Properties/index.tsx +++ b/web/src/views/Workflow/components/Properties/index.tsx @@ -242,6 +242,7 @@ const Properties: FC = ({ }, [values, selectedNode, form]) const handleAddVariable = () => { + setEditIndex(null) variableModalRef.current?.handleOpen() } const handleEditVariable = (index: number, vo: StartVariableItem) => { @@ -250,6 +251,7 @@ const Properties: FC = ({ } const handleRefreshVariable = (value: StartVariableItem) => { if (!selectedNode) return + if (editIndex !== null) { const defaultValue = selectedNode.data.config.variables.defaultValue ?? [] defaultValue[editIndex] = value @@ -260,7 +262,7 @@ const Properties: FC = ({ } selectedNode?.setData({ ...selectedNode.data}) - setConfigs({ ...selectedNode.data.config}) + setConfigs({ ...selectedNode.data.config }) } const handleDeleteVariable = (index: number, vo: StartVariableItem) => { if (!selectedNode) return From 8d2a3b7c9de904726fc220954cde9b9a159647c3 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 14 Jan 2026 20:27:56 +0800 Subject: [PATCH 06/47] fix(web): markdown ui update --- web/src/components/Markdown/Code.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/Markdown/Code.tsx b/web/src/components/Markdown/Code.tsx index 74c9c89c..de60d0de 100644 --- a/web/src/components/Markdown/Code.tsx +++ b/web/src/components/Markdown/Code.tsx @@ -81,7 +81,7 @@ const Code: FC = (props) => { ) } - return {children} + return {children} } export default Code From 6e5e708a362e7fc71d7f9942c9b79f73d5ff39db Mon Sep 17 00:00:00 2001 From: Eternity <1533512157@qq.com> Date: Wed, 14 Jan 2026 20:52:04 +0800 Subject: [PATCH 07/47] fix(workflow): fix execution record insertion failure in released app --- .../controllers/public_share_controller.py | 7 ++-- api/app/services/app_chat_service.py | 2 +- api/app/utils/app_config_utils.py | 41 ++++++++++--------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/api/app/controllers/public_share_controller.py b/api/app/controllers/public_share_controller.py index 354a58ef..04da05df 100644 --- a/api/app/controllers/public_share_controller.py +++ b/api/app/controllers/public_share_controller.py @@ -466,7 +466,7 @@ async def chat( conversation_id=conversation.id, # 使用已创建的会话 ID user_id=str(new_end_user.id), # 转换为字符串 variables=payload.variables, - config= payload.agent_config, + config=agent_config, web_search=payload.web_search, memory=payload.memory, storage_type=storage_type, @@ -565,11 +565,12 @@ async def chat( config = workflow_config_4_app_release(release) if payload.stream: async def event_generator(): + async for event in app_chat_service.workflow_chat_stream( message=payload.message, conversation_id=conversation.id, # 使用已创建的会话 ID - user_id=new_end_user.id, # 转换为字符串 + user_id=end_user_id, # 转换为字符串 variables=payload.variables, config=config, web_search=payload.web_search, @@ -601,7 +602,7 @@ async def chat( message=payload.message, conversation_id=conversation.id, # 使用已创建的会话 ID - user_id=new_end_user.id, # 转换为字符串 + user_id=end_user_id, # 转换为字符串 variables=payload.variables, config=config, web_search=payload.web_search, diff --git a/api/app/services/app_chat_service.py b/api/app/services/app_chat_service.py index 0065c64b..bc2d6ca3 100644 --- a/api/app/services/app_chat_service.py +++ b/api/app/services/app_chat_service.py @@ -558,7 +558,7 @@ class AppChatService: config: WorkflowConfig, app_id: uuid.UUID, workspace_id: uuid.UUID, - user_id: Optional[str] = None, + user_id: str = None, variables: Optional[Dict[str, Any]] = None, web_search: bool = False, memory: bool = True, diff --git a/api/app/utils/app_config_utils.py b/api/app/utils/app_config_utils.py index 834d22af..e936ffb4 100644 --- a/api/app/utils/app_config_utils.py +++ b/api/app/utils/app_config_utils.py @@ -8,9 +8,11 @@ import uuid from typing import Dict, Any, Optional, Union from datetime import datetime +from app.db import get_db_read from app.models import AppRelease, WorkflowConfig from app.models.agent_app_config_model import AgentConfig from app.models.multi_agent_model import MultiAgentConfig +from app.repositories.workflow_repository import WorkflowConfigRepository def model_parameters_to_dict(model_parameters: Any) -> Optional[Dict[str, Any]]: @@ -24,18 +26,18 @@ def model_parameters_to_dict(model_parameters: Any) -> Optional[Dict[str, Any]]: """ if model_parameters is None: return None - + if isinstance(model_parameters, dict): return model_parameters - + # Pydantic v2 if hasattr(model_parameters, 'model_dump'): return model_parameters.model_dump() - + # Pydantic v1 if hasattr(model_parameters, 'dict'): return model_parameters.dict() - + # 其他情况尝试转换 try: return dict(model_parameters) @@ -54,17 +56,18 @@ def dict_to_model_parameters(data: Optional[Dict[str, Any]]) -> Optional[Any]: """ if data is None: return None - + from app.schemas import ModelParameters - + if isinstance(data, ModelParameters): return data - + if isinstance(data, dict): return ModelParameters(**data) - + return None + class AgentConfigProxy: """Proxy class for AgentConfig (legacy compatibility)""" @@ -78,8 +81,7 @@ class AgentConfigProxy: self.default_model_config_id = release.default_model_config_id -def agent_config_4_app_release(release: AppRelease ) -> AgentConfig: - +def agent_config_4_app_release(release: AppRelease) -> AgentConfig: config_dict = release.config agent_config = AgentConfig( @@ -95,11 +97,10 @@ def agent_config_4_app_release(release: AppRelease ) -> AgentConfig: return agent_config -def multi_agent_config_4_app_release(release: AppRelease ) -> MultiAgentConfig: +def multi_agent_config_4_app_release(release: AppRelease) -> MultiAgentConfig: config_dict = release.config - agent_config = MultiAgentConfig( app_id=release.app_id, default_model_config_id=release.default_model_config_id, @@ -116,24 +117,26 @@ def multi_agent_config_4_app_release(release: AppRelease ) -> MultiAgentConfig: return agent_config -def workflow_config_4_app_release(release: AppRelease ) -> WorkflowConfig: +def workflow_config_4_app_release(release: AppRelease) -> WorkflowConfig: config_dict = release.config - + with get_db_read() as db: + source_config = WorkflowConfigRepository(db).get_by_app_id(release.app_id) + source_config_id = source_config.id config = WorkflowConfig( - id=release.id, + id=source_config_id, app_id=release.app_id, nodes=config_dict.get("nodes", []), edges=config_dict.get("edges", []), variables=config_dict.get("variables", []), execution_config=config_dict.get("execution_config", {}), triggers=config_dict.get("triggers", []) - ) return config + def dict_to_multi_agent_config(config_dict: Dict[str, Any], app_id: Optional[uuid.UUID] = None): """Convert dict to MultiAgentConfig model object @@ -276,7 +279,8 @@ def agent_config_to_dict(agent_config) -> Dict[str, Any]: "id": str(agent_config.id), "app_id": str(agent_config.app_id), "system_prompt": agent_config.system_prompt, - "default_model_config_id": str(agent_config.default_model_config_id) if agent_config.default_model_config_id else None, + "default_model_config_id": str( + agent_config.default_model_config_id) if agent_config.default_model_config_id else None, "model_parameters": agent_config.model_parameters, "knowledge_retrieval": agent_config.knowledge_retrieval, "memory": agent_config.memory, @@ -338,6 +342,3 @@ def workflow_config_to_dict(workflow_config) -> Dict[str, Any]: "created_at": workflow_config.created_at.isoformat() if workflow_config.created_at else None, "updated_at": workflow_config.updated_at.isoformat() if workflow_config.updated_at else None } - - - From 575f0ae3348f3d631cedacce28170f86b45931bb Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 14 Jan 2026 21:18:19 +0800 Subject: [PATCH 08/47] fix(web): memory card support click --- web/src/views/UserMemory/index.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web/src/views/UserMemory/index.tsx b/web/src/views/UserMemory/index.tsx index 064b55be..d8318290 100644 --- a/web/src/views/UserMemory/index.tsx +++ b/web/src/views/UserMemory/index.tsx @@ -41,7 +41,9 @@ export default function UserMemory() { navigate(`/user-memory/${id}`) } } - const handleViewMemoryConfig = () => { + const handleViewMemoryConfig = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); navigate(`/memory`) } @@ -84,8 +86,9 @@ export default function UserMemory() { title={name || '-'} extra={
handleViewDetail(end_user.id)} >
} + className="rb:cursor-pointer" + onClick={() => handleViewDetail(end_user.id)} >
{t('userMemory.capacity')}
@@ -96,7 +99,7 @@ export default function UserMemory() {
{t(`userMemory.${item.type || 'person'}`)}
-
+
{t('userMemory.memory_config_name')}
Date: Wed, 14 Jan 2026 21:34:34 +0800 Subject: [PATCH 09/47] perf(http): change resource not found status code to 400 --- api/app/core/error_codes.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/api/app/core/error_codes.py b/api/app/core/error_codes.py index 23023ca4..cb0084b7 100644 --- a/api/app/core/error_codes.py +++ b/api/app/core/error_codes.py @@ -110,24 +110,24 @@ HTTP_MAPPING = { BizCode.TOKEN_EXPIRED: 401, BizCode.TOKEN_BLACKLISTED: 401, BizCode.FORBIDDEN: 403, - BizCode.TENANT_NOT_FOUND: 404, + BizCode.TENANT_NOT_FOUND: 400, BizCode.WORKSPACE_NO_ACCESS: 403, - BizCode.NOT_FOUND: 404, + BizCode.NOT_FOUND: 400, BizCode.USER_NOT_FOUND: 200, - BizCode.WORKSPACE_NOT_FOUND: 404, - BizCode.MODEL_NOT_FOUND: 404, - BizCode.KNOWLEDGE_NOT_FOUND: 404, - BizCode.DOCUMENT_NOT_FOUND: 404, - BizCode.FILE_NOT_FOUND: 404, - BizCode.APP_NOT_FOUND: 404, - BizCode.RELEASE_NOT_FOUND: 404, + BizCode.WORKSPACE_NOT_FOUND: 400, + BizCode.MODEL_NOT_FOUND: 400, + BizCode.KNOWLEDGE_NOT_FOUND: 400, + BizCode.DOCUMENT_NOT_FOUND: 400, + BizCode.FILE_NOT_FOUND: 400, + BizCode.APP_NOT_FOUND: 400, + BizCode.RELEASE_NOT_FOUND: 400, BizCode.DUPLICATE_NAME: 409, BizCode.RESOURCE_ALREADY_EXISTS: 409, BizCode.VERSION_ALREADY_EXISTS: 409, BizCode.STATE_CONFLICT: 409, BizCode.PUBLISH_FAILED: 500, BizCode.NO_DRAFT_TO_PUBLISH: 400, - BizCode.ROLLBACK_TARGET_NOT_FOUND: 404, + BizCode.ROLLBACK_TARGET_NOT_FOUND: 400, BizCode.APP_TYPE_NOT_SUPPORTED: 400, BizCode.AGENT_CONFIG_MISSING: 400, BizCode.SHARE_DISABLED: 403, From 59f24fb5b4020a0c2a42283f5dbe0d48eb43db80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BF=8A=E7=94=B7?= Date: Thu, 15 Jan 2026 11:55:00 +0800 Subject: [PATCH 10/47] feat(muti agent): non-streaming output sub-nodes do not record the generated content of the conversation --- api/app/services/draft_run_service.py | 5 +++-- api/app/services/multi_agent_orchestrator.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/api/app/services/draft_run_service.py b/api/app/services/draft_run_service.py index 569684d5..50934226 100644 --- a/api/app/services/draft_run_service.py +++ b/api/app/services/draft_run_service.py @@ -245,7 +245,8 @@ class DraftRunService: storage_type: Optional[str] = None, user_rag_memory_id: Optional[str] = None, web_search: bool = True, - memory: bool = True + memory: bool = True, + sub_agent: bool = False ) -> Dict[str, Any]: """执行试运行(使用 LangChain Agent) @@ -435,7 +436,7 @@ class DraftRunService: elapsed_time = time.time() - start_time # 8. 保存会话消息 - if agent_config.memory and agent_config.memory.get("enabled"): + if not sub_agent and agent_config.memory and agent_config.memory.get("enabled"): await self._save_conversation_message( conversation_id=conversation_id, user_message=message, diff --git a/api/app/services/multi_agent_orchestrator.py b/api/app/services/multi_agent_orchestrator.py index b0c7a957..1972f344 100644 --- a/api/app/services/multi_agent_orchestrator.py +++ b/api/app/services/multi_agent_orchestrator.py @@ -1327,7 +1327,8 @@ class MultiAgentOrchestrator: web_search=web_search, memory=memory, storage_type=storage_type, - user_rag_memory_id=user_rag_memory_id + user_rag_memory_id=user_rag_memory_id, + sub_agent=True ) return result From 04eaf35567e0fe896b4431327293dd75f7e84482 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Thu, 15 Jan 2026 12:10:46 +0800 Subject: [PATCH 11/47] fix(web): network colors bugfix --- .../UserMemoryDetail/components/RelationshipNetwork.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx b/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx index d12c3e57..7a927479 100644 --- a/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx +++ b/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx @@ -81,12 +81,14 @@ const RelationshipNetwork:FC = () => { name: displayName, category: categoryIndex >= 0 ? categoryIndex : 0, symbolSize: symbolSize, // 根据连接数调整节点大小 - itemStyle: { - color: colors[categoryIndex % 8] - } }) }) + // 创建节点ID到标签的映射 + const nodeIdToLabel: Record = {} + nodes.forEach(node => { + nodeIdToLabel[node.id] = node.label + }) // 处理边数据 edges.forEach(edge => { curEdges.push({ From ec25efcb756f51977cd8664121c9a7fd42096305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BF=8A=E7=94=B7?= Date: Thu, 15 Jan 2026 12:21:24 +0800 Subject: [PATCH 12/47] fix(mcp tool): bug fix for the sse protocol request header in the mcp tool --- api/app/core/tools/mcp/client.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/app/core/tools/mcp/client.py b/api/app/core/tools/mcp/client.py index e513a147..c082b314 100644 --- a/api/app/core/tools/mcp/client.py +++ b/api/app/core/tools/mcp/client.py @@ -96,10 +96,7 @@ class SimpleMCPClient: """初始化 SSE MCP 会话 - 参考 Dify 实现""" try: # 建立 SSE 连接 - response = await self._session.get( - self.server_url, - headers={"Accept": "text/event-stream"} - ) + response = await self._session.get(self.server_url) if response.status != 200: error_text = await response.text() From a3f053ed02133070814a00c1de46febef266091c Mon Sep 17 00:00:00 2001 From: Eternity <1533512157@qq.com> Date: Thu, 15 Jan 2026 12:33:49 +0800 Subject: [PATCH 13/47] fix(workflow): Fix missing user ID in trial run sessions --- api/app/controllers/app_controller.py | 14 ++++++++++++-- api/app/services/workflow_service.py | 20 ++------------------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/api/app/controllers/app_controller.py b/api/app/controllers/app_controller.py index f55ea5b5..147f24d7 100644 --- a/api/app/controllers/app_controller.py +++ b/api/app/controllers/app_controller.py @@ -11,15 +11,16 @@ from app.core.response_utils import success from app.db import get_db from app.dependencies import get_current_user, cur_workspace_access_guard from app.models import User -from app.models.app_model import AppType, App +from app.models.app_model import AppType from app.repositories import knowledge_repository +from app.repositories.end_user_repository import EndUserRepository from app.schemas import app_schema from app.schemas.response_schema import PageData, PageMeta +from app.schemas.workflow_schema import WorkflowConfig as WorkflowConfigSchema from app.schemas.workflow_schema import WorkflowConfigUpdate from app.services import app_service, workspace_service from app.services.agent_config_helper import enrich_agent_config from app.services.app_service import AppService -from app.schemas.workflow_schema import WorkflowConfig as WorkflowConfigSchema from app.services.workflow_service import WorkflowService, get_workflow_service router = APIRouter(prefix="/apps", tags=["Apps"]) @@ -405,6 +406,15 @@ async def draft_run( # 只读操作,允许访问共享应用 service._validate_app_accessible(app, workspace_id) + if not payload.user_id: + end_user_repo = EndUserRepository(db) + new_end_user = end_user_repo.get_or_create_end_user( + app_id=app_id, + other_id=str(current_user.id), + original_user_id=str(current_user.id) # Save original user_id to other_id + ) + payload.user_id = str(new_end_user.id) + # 处理会话ID(创建或验证) conversation_id = await draft_service._ensure_conversation( conversation_id=payload.conversation_id, diff --git a/api/app/services/workflow_service.py b/api/app/services/workflow_service.py index f9988352..f7fc76a4 100644 --- a/api/app/services/workflow_service.py +++ b/api/app/services/workflow_service.py @@ -483,14 +483,6 @@ class WorkflowService: try: # 更新状态为运行中 self.update_execution_status(execution.execution_id, "running") - with get_db_context() as db: - end_user_repo = EndUserRepository(db) - new_end_user = end_user_repo.get_or_create_end_user( - app_id=app_id, - other_id=payload.user_id, - original_user_id=payload.user_id # Save original user_id to other_id - ) - end_user_id = str(new_end_user.id) executions = self.execution_repo.get_by_conversation_id(conversation_id=conversation_id_uuid) @@ -511,7 +503,7 @@ class WorkflowService: input_data=input_data, execution_id=execution.execution_id, workspace_id=str(workspace_id), - user_id=end_user_id + user_id=payload.user_id ) # 更新执行结果 @@ -638,14 +630,6 @@ class WorkflowService: try: # 更新状态为运行中 self.update_execution_status(execution.execution_id, "running") - with get_db_context() as db: - end_user_repo = EndUserRepository(db) - new_end_user = end_user_repo.get_or_create_end_user( - app_id=app_id, - other_id=payload.user_id, - original_user_id=payload.user_id # Save original user_id to other_id - ) - end_user_id = str(new_end_user.id) executions = self.execution_repo.get_by_conversation_id(conversation_id=conversation_id_uuid) for exec_res in executions: @@ -665,7 +649,7 @@ class WorkflowService: input_data=input_data, execution_id=execution.execution_id, workspace_id=str(workspace_id), - user_id=end_user_id + user_id=payload.user_id ): if event.get("event") == "workflow_end": From 6010e9e4ff6f0695b5820d73501d2de0851b38b1 Mon Sep 17 00:00:00 2001 From: Eternity <1533512157@qq.com> Date: Thu, 15 Jan 2026 13:15:53 +0800 Subject: [PATCH 14/47] fix(workflow): Fix missing user ID in trial run sessions --- api/app/services/workflow_service.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/app/services/workflow_service.py b/api/app/services/workflow_service.py index f7fc76a4..974d5418 100644 --- a/api/app/services/workflow_service.py +++ b/api/app/services/workflow_service.py @@ -13,11 +13,10 @@ from sqlalchemy.orm import Session from app.core.error_codes import BizCode from app.core.exceptions import BusinessException from app.core.workflow.validator import validate_workflow_config -from app.db import get_db, get_db_context +from app.db import get_db +from app.models.conversation_model import Message from app.models.workflow_model import WorkflowConfig, WorkflowExecution from app.repositories.conversation_repository import MessageRepository -from app.models.conversation_model import Message -from app.repositories.end_user_repository import EndUserRepository from app.repositories.workflow_repository import ( WorkflowConfigRepository, WorkflowExecutionRepository, From 2e1744c66b2b5d821d0fcb897f1da3ae3ed3798c Mon Sep 17 00:00:00 2001 From: Eternity <1533512157@qq.com> Date: Thu, 15 Jan 2026 13:18:50 +0800 Subject: [PATCH 15/47] fix(workflow): Fix missing user ID in trial run sessions --- api/app/controllers/app_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/app/controllers/app_controller.py b/api/app/controllers/app_controller.py index 147f24d7..43f177ef 100644 --- a/api/app/controllers/app_controller.py +++ b/api/app/controllers/app_controller.py @@ -406,7 +406,7 @@ async def draft_run( # 只读操作,允许访问共享应用 service._validate_app_accessible(app, workspace_id) - if not payload.user_id: + if payload.user_id is None: end_user_repo = EndUserRepository(db) new_end_user = end_user_repo.get_or_create_end_user( app_id=app_id, From 4c8da850500f2cc3730fba04b403c98621881124 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Thu, 15 Jan 2026 13:38:27 +0800 Subject: [PATCH 16/47] fix(web): Calculation logic adjustment of variableList --- web/src/i18n/en.ts | 3 ++ web/src/i18n/zh.ts | 3 ++ .../Workflow/components/Nodes/AddNode.tsx | 4 +- .../Workflow/components/Nodes/LoopNode.tsx | 13 +++--- .../Workflow/components/PortClickHandler.tsx | 4 +- .../Workflow/components/Properties/index.tsx | 41 ++++++++++++++++--- .../views/Workflow/hooks/useWorkflowGraph.ts | 9 ++-- web/src/views/Workflow/index.tsx | 1 + 8 files changed, 55 insertions(+), 23 deletions(-) diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 2d2d96a2..6923e5fb 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1960,6 +1960,9 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re addMessage: 'Add Message', answerDesc: 'Reply', addNode: 'Add Node', + arrange: 'Arrange', + redo: 'Redo', + undo: 'Undo', }, emotionEngine: { emotionEngineConfig: 'Emotion Engine Configuration', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 53e71c84..92804d3c 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -2057,6 +2057,9 @@ export const zh = { addMessage: '添加消息', answerDesc: '回复', addNode: '添加节点', + arrange: '整理', + redo: '重做', + undo: '撤销', }, emotionEngine: { emotionEngineConfig: '情感引擎配置', diff --git a/web/src/views/Workflow/components/Nodes/AddNode.tsx b/web/src/views/Workflow/components/Nodes/AddNode.tsx index a37da651..d2d1d15c 100644 --- a/web/src/views/Workflow/components/Nodes/AddNode.tsx +++ b/web/src/views/Workflow/components/Nodes/AddNode.tsx @@ -36,7 +36,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { if (cycleId) { const parentNode = graph.getNodes().find((n: any) => n.getData()?.id === cycleId); if (parentNode) { - parentNode.insertChild(newNode); + parentNode.addChild(newNode); } } @@ -48,7 +48,6 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { source: { cell: edge.getSourceCellId(), port: edge.getSourcePortId() }, target: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'left')?.id || 'left' }, attrs: edge.getAttrs(), - zIndex: 1, }); }); @@ -59,7 +58,6 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { source: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'right')?.id || 'right' }, target: { cell: edge.getTargetCellId(), port: targetPortId }, attrs: edge.getAttrs(), - zIndex: 1, }); }); diff --git a/web/src/views/Workflow/components/Nodes/LoopNode.tsx b/web/src/views/Workflow/components/Nodes/LoopNode.tsx index 71f4dc44..26109d58 100644 --- a/web/src/views/Workflow/components/Nodes/LoopNode.tsx +++ b/web/src/views/Workflow/components/Nodes/LoopNode.tsx @@ -18,7 +18,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { }, 50) return () => clearTimeout(timer) - }, []) + }, [graph]) const checkAndAddAddNode = () => { if (!graph) return; @@ -44,15 +44,15 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { }, }); - node.insertChild(addNode); + node.addChild(addNode); // 连接cycle-start和add-node const sourcePorts = cycleStartNode.getPorts(); const targetPorts = addNode.getPorts(); const sourcePort = sourcePorts.find((port: any) => port.group === 'right')?.id || 'right'; const targetPort = targetPorts.find((port: any) => port.group === 'left')?.id || 'left'; - - // 直接创建连线,不使用异步 + + // 然后创建连线 graph.addEdge({ source: { cell: cycleStartNode.id, port: sourcePort }, target: { cell: addNode.id, port: targetPort }, @@ -107,8 +107,8 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { cycle: data.id, }, }); - node.insertChild(cycleStartNode) - node.insertChild(addNode) + node.addChild(cycleStartNode) + node.addChild(addNode) const sourcePorts = cycleStartNode.getPorts() const targetPorts = addNode.getPorts() let sourcePort = sourcePorts.find((port: any) => port.group === 'right')?.id || 'right'; @@ -133,7 +133,6 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { }, }, } - graph.addEdge(edgeConfig) } diff --git a/web/src/views/Workflow/components/PortClickHandler.tsx b/web/src/views/Workflow/components/PortClickHandler.tsx index 8342ae6a..050ed35d 100644 --- a/web/src/views/Workflow/components/PortClickHandler.tsx +++ b/web/src/views/Workflow/components/PortClickHandler.tsx @@ -129,7 +129,7 @@ const PortClickHandler: React.FC = ({ graph }) => { if (sourceNodeData.cycle) { const parentNode = graph.getNodes().find((n: any) => n.getData()?.id === sourceNodeData.cycle); if (parentNode) { - parentNode.insertChild(newNode); + parentNode.addChild(newNode); } } @@ -159,7 +159,7 @@ const PortClickHandler: React.FC = ({ graph }) => { }, }, }, - zIndex: sourceNodeData.cycle && sourceNodeType == 'cycle-start' ? 1 : sourceNodeData.cycle ? 2 : 0 + // zIndex: sourceNodeData.cycle && sourceNodeType == 'cycle-start' ? 1 : sourceNodeData.cycle ? 2 : 0 }); // 循环节点内子节点通过连接桩添加时,调整循环节点大小 diff --git a/web/src/views/Workflow/components/Properties/index.tsx b/web/src/views/Workflow/components/Properties/index.tsx index 77c3414f..f9fbad63 100644 --- a/web/src/views/Workflow/components/Properties/index.tsx +++ b/web/src/views/Workflow/components/Properties/index.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import { Graph, Node } from '@antv/x6'; import { Form, Input, Button, Select, InputNumber, Slider, Space, Divider, App, Switch } from 'antd' -import type { NodeConfig, NodeProperties, StartVariableItem, VariableEditModalRef } from '../../types' +import type { NodeConfig, NodeProperties, StartVariableItem, VariableEditModalRef, ChatVariable } from '../../types' import Empty from '@/components/Empty'; import emptyIcon from '@/assets/images/workflow/empty.png' import CustomSelect from "@/components/CustomSelect"; @@ -34,11 +34,12 @@ interface PropertiesProps { copyEvent: () => void; parseEvent: () => void; config?: any; + chatVariables: ChatVariable[]; } const Properties: FC = ({ selectedNode, graphRef, - config: workflowConfig, + chatVariables }) => { const { t } = useTranslation() const { modal } = App.useApp() @@ -47,6 +48,7 @@ const Properties: FC = ({ const values = Form.useWatch([], form); const variableModalRef = useRef(null) const [editIndex, setEditIndex] = useState(null) + const [graphUpdateTrigger, setGraphUpdateTrigger] = useState(0) const prevMappingNamesRef = useRef([]) const prevTemplateVarsRef = useRef([]) const syncTimeoutRef = useRef(null) @@ -349,11 +351,9 @@ const Properties: FC = ({ const parentPreviousNodeIds = getAllPreviousNodes(parentLoopNode.id); allRelevantNodeIds.push(...parentPreviousNodeIds); } - - // Add conversation variables from global config - const conversationVariables = workflowConfig?.variables || []; + const conversationVariables = chatVariables || []; conversationVariables.forEach((variable: any) => { const key = `CONVERSATION_${variable.name}`; @@ -763,7 +763,36 @@ const Properties: FC = ({ } return variableList; - }, [selectedNode, graphRef, workflowConfig?.variables]); + }, [selectedNode, graphRef, graphUpdateTrigger, chatVariables]); + + // Trigger variableList update when graph edges or nodes change + useEffect(() => { + if (!graphRef?.current) return; + + const graph = graphRef.current; + const handleGraphChange = () => { + console.log('handleGraphChange') + // Force variableList recalculation by updating trigger + setGraphUpdateTrigger(prev => prev + 1); + }; + + // Listen to graph changes + graph.on('edge:added', handleGraphChange); + graph.on('edge:removed', handleGraphChange); + graph.on('edge:changed', handleGraphChange); + graph.on('node:added', handleGraphChange); + graph.on('node:removed', handleGraphChange); + graph.on('node:change:data', handleGraphChange); + + return () => { + graph.off('edge:added', handleGraphChange); + graph.off('edge:removed', handleGraphChange); + graph.off('edge:changed', handleGraphChange); + graph.off('node:added', handleGraphChange); + graph.off('node:removed', handleGraphChange); + graph.off('node:change:data', handleGraphChange); + }; + }, [graphRef]); // Filter out boolean type variables for loop and llm nodes const getFilteredVariableList = (nodeType?: string, key?: string) => { diff --git a/web/src/views/Workflow/hooks/useWorkflowGraph.ts b/web/src/views/Workflow/hooks/useWorkflowGraph.ts index f3439571..87ee80fc 100644 --- a/web/src/views/Workflow/hooks/useWorkflowGraph.ts +++ b/web/src/views/Workflow/hooks/useWorkflowGraph.ts @@ -54,7 +54,7 @@ export const useWorkflowGraph = ({ const historyRef = useRef<{ undoStack: string[], redoStack: string[] }>({ undoStack: [], redoStack: [] }); const [canUndo, setCanUndo] = useState(false); const [canRedo, setCanRedo] = useState(false); - const [isHandMode, setIsHandMode] = useState(false); + const [isHandMode, setIsHandMode] = useState(true); const [config, setConfig] = useState(null); const [chatVariables, setChatVariables] = useState([]) @@ -235,7 +235,7 @@ export const useWorkflowGraph = ({ if (parentNode) { const addedChild = graphRef.current?.addNode(childNode) if (addedChild) { - parentNode.insertChild(addedChild) + parentNode.addChild(addedChild) } } } @@ -354,7 +354,7 @@ export const useWorkflowGraph = ({ }, }, }, - zIndex: loopIterationCount + // zIndex: loopIterationCount } return edgeConfig @@ -694,10 +694,9 @@ export const useWorkflowGraph = ({ thickness: 1, // 网点大小 } }, - panning: false, + panning: isHandMode, mousewheel: { enabled: true, - modifiers: ['ctrl', 'meta'], }, connecting: { // router: 'orth', diff --git a/web/src/views/Workflow/index.tsx b/web/src/views/Workflow/index.tsx index ba17a63a..506fd3c4 100644 --- a/web/src/views/Workflow/index.tsx +++ b/web/src/views/Workflow/index.tsx @@ -107,6 +107,7 @@ const Workflow = forwardRef((_props, ref) => { copyEvent={copyEvent} parseEvent={parseEvent} config={config} + chatVariables={chatVariables} /> Date: Thu, 15 Jan 2026 14:15:34 +0800 Subject: [PATCH 17/47] fix(web): non-loop child nodes support add end node --- web/src/views/Workflow/components/PortClickHandler.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/views/Workflow/components/PortClickHandler.tsx b/web/src/views/Workflow/components/PortClickHandler.tsx index 050ed35d..8d95431c 100644 --- a/web/src/views/Workflow/components/PortClickHandler.tsx +++ b/web/src/views/Workflow/components/PortClickHandler.tsx @@ -234,9 +234,9 @@ const PortClickHandler: React.FC = ({ graph }) => { filteredNodes = category.nodes.filter(nodeType => !['start', 'end', 'loop', 'cycle-start', 'iteration'].includes(nodeType.type)); } else { // Original filtering for non-loop child nodes - filteredNodes = category.nodes.filter(nodeType => !['start', 'end', 'break', 'cycle-start'].includes(nodeType.type)); + filteredNodes = category.nodes.filter(nodeType => !['start', 'break', 'cycle-start'].includes(nodeType.type)); filteredNodes = category.nodes.filter(nodeType => - nodeType.type !== 'start' && nodeType.type !== 'end' && nodeType.type !== 'cycle-start' && nodeType.type !== 'break' + nodeType.type !== 'start' && nodeType.type !== 'cycle-start' && nodeType.type !== 'break' ); } From 973a0b2d472fa1de1129a3ce4585b200948d40af Mon Sep 17 00:00:00 2001 From: yujiangping Date: Thu, 15 Jan 2026 15:18:25 +0800 Subject: [PATCH 18/47] feat(home): add help center quick operation link - Add helpCenter.svg and helpCenter_active.svg menu icons for help center navigation - Add "Help Center" translation strings to English and Chinese i18n files - Update QuickOperation component to include help center as fourth quick operation - Implement external link handler that opens help documentation based on current language (zh or en) - Change grid layout from 3 columns to 4 columns to accommodate new help center card - Add file header documentation to QuickOperation component - Help center link redirects to https://docs.redbearai.com/s/{lang}-memorybear with language-specific routing --- web/src/assets/images/menu/helpCenter.svg | 14 ++++++++ .../assets/images/menu/helpCenter_active.svg | 14 ++++++++ web/src/i18n/en.ts | 5 ++- web/src/i18n/zh.ts | 3 +- .../views/Home/components/QuickOperation.tsx | 28 ++++++++++++++-- .../[knowledgeBaseId]/Private.tsx | 33 ++++++++++++++++++- 6 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 web/src/assets/images/menu/helpCenter.svg create mode 100644 web/src/assets/images/menu/helpCenter_active.svg diff --git a/web/src/assets/images/menu/helpCenter.svg b/web/src/assets/images/menu/helpCenter.svg new file mode 100644 index 00000000..504e309c --- /dev/null +++ b/web/src/assets/images/menu/helpCenter.svg @@ -0,0 +1,14 @@ + + + 使用帮助备份 + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/menu/helpCenter_active.svg b/web/src/assets/images/menu/helpCenter_active.svg new file mode 100644 index 00000000..2840c421 --- /dev/null +++ b/web/src/assets/images/menu/helpCenter_active.svg @@ -0,0 +1,14 @@ + + + 使用帮助 + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 27986e76..9201372d 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -91,6 +91,7 @@ export const en = { memberManagement: 'Member Management', memorySummary: 'Memory Summary', memoryConversation: 'Memory Validation', + helpCenter: 'Help Center', memorySummaryHandlers: 'Memory Summary Handlers', createMemorySummary: 'Create Memory Summary', memoryManagement: 'Memory Management', @@ -190,7 +191,8 @@ export const en = { memoryConversation: 'Memory Conversation', memoryConversationDesc: 'Memory Conversation', - + helpCenter: 'Help Center', + helpCenterDesc: 'Help Center', memorySummary: 'View Memory Summary', memorySummaryDesc: 'View Memory Summary Report', @@ -616,6 +618,7 @@ export const en = { retrieve:'Retrieve', processing: 'Processing', processingMode: 'Processing Mode', + processMsg: 'Processing Message', dataSize: 'Data Size', createUpdateTime: 'Create/Update Time', operation: 'Operation', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 8fa73fd3..63799463 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -789,7 +789,8 @@ export const zh = { memoryConversation: '记忆对话', memoryConversationDesc: '记忆对话', - + helpCenter: '帮助中心', + helpCenterDesc: '帮助中心', memorySummary: '查看记忆摘要', memorySummaryDesc: '查看记忆摘要报告', diff --git a/web/src/views/Home/components/QuickOperation.tsx b/web/src/views/Home/components/QuickOperation.tsx index 892dd8a0..d894417a 100644 --- a/web/src/views/Home/components/QuickOperation.tsx +++ b/web/src/views/Home/components/QuickOperation.tsx @@ -1,3 +1,11 @@ +/* + * @Description: + * @Version: 0.0.1 + * @Author: yujiangping + * @Date: 2026-01-05 17:22:23 + * @LastEditors: yujiangping + * @LastEditTime: 2026-01-15 14:55:51 + */ import { type FC } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom'; @@ -5,33 +13,49 @@ import Card from './Card'; import applicationIcon from '@/assets/images/menu/application_active.svg'; import knowledgeIcon from '@/assets/images/menu/knowledge_active.svg'; import memoryConversationIcon from '@/assets/images/menu/memoryConversation_active.svg'; +import helpCenterIcon from '@/assets/images/menu/helpCenter_active.svg' import arrowTopRight from '@/assets/images/home/arrow_top_right.svg'; const quickOperations = [ { key: 'createNewApplication', url: '/application' }, { key: 'createNewKnowledge', url: '/knowledge-base' }, { key: 'memoryConversation', url: '/memory-conversation' }, + { key: 'helpCenter', url: '' }, ] const quickOperationIcons: {[key: string]: string | undefined} = { createNewApplication: applicationIcon, createNewKnowledge: knowledgeIcon, memoryConversation: memoryConversationIcon, + helpCenter: helpCenterIcon } const QuickOperation:FC = () => { - const { t } = useTranslation() + const { t, i18n } = useTranslation() const navigate = useNavigate(); const handleJump = (url: string | null) => { if (url) { navigate(url) + }else{ + const currentLang = i18n.language; + const lang = currentLang === 'zh' ? 'zh' : 'en'; + const helpUrl = `https://docs.redbearai.com/s/${lang}-memorybear`; + + // 创建隐藏的 a 标签来避免弹窗拦截 + const link = document.createElement('a'); + link.href = helpUrl; + link.target = '_blank'; + link.rel = 'noopener noreferrer'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); } } return ( -
+
{quickOperations.map(item => (
handleJump(item.url)}>
diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx index 8087e596..382deac0 100644 --- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx +++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx @@ -2,7 +2,7 @@ import { useEffect, useState, useRef, useCallback, type FC } from 'react'; import { useNavigate, useParams, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Switch, Button, Dropdown, Space, Modal, message, Radio } from 'antd'; +import { Switch, Button, Dropdown, Space, Modal, message, Radio, Tooltip } from 'antd'; import type { MenuProps } from 'antd'; import SearchInput from '@/components/SearchInput' import Table, { type TableRef } from '@/components/Table' @@ -564,6 +564,37 @@ const Private: FC = () => { ); } + },{ + title: t('knowledgeBase.processMsg'), + dataIndex: 'progress_msg', + key: 'progress_msg', + width: 320, + render: (value: string) => { + if (!value) return '-'; + + // 解析日志格式,将 \n 转换为换行 + const formattedText = value.replace(/\\n/g, '\n'); + + return ( + {formattedText}} placement="topLeft"> +
+ {formattedText} +
+
+ ); + } }, { title: t('knowledgeBase.processingMode'), From d03a1a9a55ba21d7e6b51161e77921b32137e6e8 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Thu, 15 Jan 2026 15:45:22 +0800 Subject: [PATCH 19/47] fix(web): update app method --- web/src/views/ApplicationConfig/Api.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/views/ApplicationConfig/Api.tsx b/web/src/views/ApplicationConfig/Api.tsx index 02c066e4..ab33ba19 100644 --- a/web/src/views/ApplicationConfig/Api.tsx +++ b/web/src/views/ApplicationConfig/Api.tsx @@ -16,7 +16,7 @@ import { maskApiKeys } from '@/utils/apiKeyReplacer' const Api: FC<{ application: Application | null }> = ({ application }) => { const { t } = useTranslation(); - const activeMethods = ['GET']; + const activeMethods = ['POST']; const { message, modal } = App.useApp() const copyContent = window.location.origin + '/v1/chat' const apiKeyModalRef = useRef(null); From 3edca01dc91a488e1b808318c7aad9a204476528 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Thu, 15 Jan 2026 16:25:40 +0800 Subject: [PATCH 20/47] feat(web): add contact link --- web/src/views/Pricing/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/views/Pricing/index.tsx b/web/src/views/Pricing/index.tsx index 3da6b185..36861348 100644 --- a/web/src/views/Pricing/index.tsx +++ b/web/src/views/Pricing/index.tsx @@ -10,6 +10,7 @@ import commerce from '@/assets/images/order/commerce.png' import checkIcon from '@/assets/images/login/checkBg.png' import alertIcon from '@/assets/images/order/alert.svg'; import { useUser } from '@/store/user' +import { useI18n } from '@/store/locale' interface PriceItem { type: string; @@ -116,6 +117,7 @@ const PricingView: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); const { user } = useUser(); + const { language } = useI18n() const handleChoosePlan = (type: string) => { switch(type) { @@ -127,6 +129,7 @@ const PricingView: React.FC = () => { navigate(user.current_workspace_id ? '/' : '/space'); break case 'commerce': + window.open(`https://docs.redbearai.com/s/${language || 'en'}-memorybear`, '_blank') break } }; From 61f3a1805c5daee0142f23b19453b5b66b078317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=90=E5=8A=9B=E9=BD=90?= <162269739+lanceyq@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:45:20 +0800 Subject: [PATCH 21/47] [fix]Fix the timestamp in milliseconds (#127) --- api/app/services/memory_forget_service.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/app/services/memory_forget_service.py b/api/app/services/memory_forget_service.py index 8979682d..2db4cdc7 100644 --- a/api/app/services/memory_forget_service.py +++ b/api/app/services/memory_forget_service.py @@ -267,14 +267,14 @@ class MemoryForgetService: elif node_type_label == 'memorysummary': node_type_label = 'summary' - # 将 Neo4j DateTime 对象转换为时间戳 + # 将 Neo4j DateTime 对象转换为时间戳(毫秒) last_access_time = result['last_access_time'] last_access_dt = convert_neo4j_datetime_to_python(last_access_time) # 确保 datetime 带有时区信息(假定为 UTC),避免 naive datetime 导致的时区偏差 if last_access_dt: if last_access_dt.tzinfo is None: last_access_dt = last_access_dt.replace(tzinfo=timezone.utc) - last_access_timestamp = int(last_access_dt.timestamp()) + last_access_timestamp = int(last_access_dt.timestamp() * 1000) else: last_access_timestamp = 0 @@ -520,7 +520,7 @@ class MemoryForgetService: 'average_activation_value': result['average_activation'], 'low_activation_nodes': result['low_activation_nodes'] or 0, 'forgetting_threshold': forgetting_threshold, - 'timestamp': int(datetime.now().timestamp()) + 'timestamp': int(datetime.now().timestamp() * 1000) } else: activation_metrics = { @@ -530,7 +530,7 @@ class MemoryForgetService: 'average_activation_value': None, 'low_activation_nodes': 0, 'forgetting_threshold': forgetting_threshold, - 'timestamp': int(datetime.now().timestamp()) + 'timestamp': int(datetime.now().timestamp() * 1000) } # 收集节点类型分布 @@ -620,7 +620,7 @@ class MemoryForgetService: 'merged_count': record.merged_count, 'average_activation': record.average_activation_value, 'total_nodes': record.total_nodes, - 'execution_time': int(record.execution_time.timestamp()) + 'execution_time': int(record.execution_time.timestamp() * 1000) }) api_logger.info(f"成功获取最近 {len(recent_trends)} 个日期的历史趋势数据") @@ -661,7 +661,7 @@ class MemoryForgetService: 'node_distribution': node_distribution, 'recent_trends': recent_trends, 'pending_nodes': pending_nodes, - 'timestamp': int(datetime.now().timestamp()) + 'timestamp': int(datetime.now().timestamp() * 1000) } api_logger.info( From cdfe43ce2ce8bece8739d1683006b98f4995ec27 Mon Sep 17 00:00:00 2001 From: Eternity <61316157+myhMARS@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:45:52 +0800 Subject: [PATCH 22/47] fix(memory): Fix issue where no response is returned when conversation content is empty (#126) --- api/app/services/conversation_service.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api/app/services/conversation_service.py b/api/app/services/conversation_service.py index 3695a222..275d6413 100644 --- a/api/app/services/conversation_service.py +++ b/api/app/services/conversation_service.py @@ -516,8 +516,16 @@ class ConversationService: conversation_messages = self.get_conversation_history( conversation_id=conversation_id, - max_history=30 + max_history=20 ) + if len(conversation_messages) == 0: + return ConversationOut( + theme="", + question=[], + summary="", + takeaways=[], + info_score=0, + ) with open('app/services/prompt/conversation_summary_system.jinja2', 'r', encoding='utf-8') as f: system_prompt = f.read() @@ -536,6 +544,7 @@ class ConversationService: ] logger.info(f"Invoking LLM for conversation_id={conversation_id}") model_resp = await llm.ainvoke(messages) + try: if isinstance(model_resp.content, str): result = json_repair.repair_json(model_resp.content, return_objects=True) From 000fbf6e9844102e4109aea5ac89839bb1885902 Mon Sep 17 00:00:00 2001 From: lixinyue11 <94037597+lixinyue11@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:54:09 +0800 Subject: [PATCH 23/47] Fix/memory bug fix (#128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 图谱数据量限制数量去掉 * 图谱数据量限制数量去掉 * 图谱数据量限制数量去掉 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 读取的接口,去掉全局锁 --- api/app/services/memory_agent_service.py | 402 +++++++++++------------ 1 file changed, 195 insertions(+), 207 deletions(-) diff --git a/api/app/services/memory_agent_service.py b/api/app/services/memory_agent_service.py index e05daf4a..f0756764 100644 --- a/api/app/services/memory_agent_service.py +++ b/api/app/services/memory_agent_service.py @@ -9,7 +9,7 @@ import os import re import time import uuid -from threading import Lock + from typing import Any, AsyncGenerator, Dict, List, Optional import redis @@ -51,9 +51,7 @@ _neo4j_connector = Neo4jConnector() class MemoryAgentService: """Service for memory agent operations""" - def __init__(self): - self.user_locks: Dict[str, Lock] = {} - self.locks_lock = Lock() + def writer_messages_deal(self,messages,start_time,group_id,config_id,message): messages = str(messages).replace("'", '"').replace('\\n', '').replace('\n', '').replace('\\', '') @@ -83,12 +81,7 @@ class MemoryAgentService: raise ValueError(f"写入失败: {messages}") - def get_group_lock(self, group_id: str) -> Lock: - """Get lock for specific group to prevent concurrent processing""" - with self.locks_lock: - if group_id not in self.user_locks: - self.user_locks[group_id] = Lock() - return self.user_locks[group_id] + def extract_tool_call_info(self, event: Dict) -> bool: """Extract tool call information from event""" @@ -417,241 +410,236 @@ class MemoryAgentService: except ImportError: audit_logger = None - # Get group lock to prevent concurrent processing - group_lock = self.get_group_lock(group_id) + try: + config_service = MemoryConfigService(db) + memory_config = config_service.load_memory_config( + config_id=config_id, + service_name="MemoryAgentService" + ) + logger.info(f"Configuration loaded successfully: {memory_config.config_name}") + except ConfigurationError as e: + error_msg = f"Failed to load configuration for config_id: {config_id}: {e}" + logger.error(error_msg) - with group_lock: - # Step 1: Load configuration from database only - try: - config_service = MemoryConfigService(db) - memory_config = config_service.load_memory_config( + # Log failed operation + if audit_logger: + duration = time.time() - start_time + audit_logger.log_operation( + operation="READ", config_id=config_id, - service_name="MemoryAgentService" + group_id=group_id, + success=False, + duration=duration, + error=error_msg ) - logger.info(f"Configuration loaded successfully: {memory_config.config_name}") - except ConfigurationError as e: - error_msg = f"Failed to load configuration for config_id: {config_id}: {e}" - logger.error(error_msg) - # Log failed operation - if audit_logger: - duration = time.time() - start_time - audit_logger.log_operation( - operation="READ", - config_id=config_id, - group_id=group_id, - success=False, - duration=duration, - error=error_msg - ) + raise ValueError(error_msg) - raise ValueError(error_msg) + # Step 2: Prepare history + history.append({"role": "user", "content": message}) + logger.debug(f"Group ID:{group_id}, Message:{message}, History:{history}, Config ID:{config_id}") - # Step 2: Prepare history - history.append({"role": "user", "content": message}) - logger.debug(f"Group ID:{group_id}, Message:{message}, History:{history}, Config ID:{config_id}") + # Step 3: Initialize MCP client and execute read workflow + mcp_config = get_mcp_server_config() + client = MultiServerMCPClient(mcp_config) - # Step 3: Initialize MCP client and execute read workflow - mcp_config = get_mcp_server_config() - client = MultiServerMCPClient(mcp_config) + async with client.session('data_flow') as session: + session_start = time.time() + logger.debug("Connected to MCP Server: data_flow") - async with client.session('data_flow') as session: - session_start = time.time() - logger.debug("Connected to MCP Server: data_flow") - - tools_start = time.time() - tools = await load_mcp_tools(session) - tools_time = time.time() - tools_start - logger.info(f"[PERF] MCP tools loading took: {tools_time:.4f}s") - - outputs = [] - intermediate_outputs = [] - seen_intermediates = set() # Track seen intermediate outputs to avoid duplicates + tools_start = time.time() + tools = await load_mcp_tools(session) + tools_time = time.time() - tools_start + logger.info(f"[PERF] MCP tools loading took: {tools_time:.4f}s") - # Pass memory_config to the graph workflow - graph_start = time.time() - async with make_read_graph(group_id, tools, search_switch, group_id, group_id, memory_config=memory_config, storage_type=storage_type, user_rag_memory_id=user_rag_memory_id) as graph: - graph_init_time = time.time() - graph_start - logger.info(f"[PERF] Graph initialization took: {graph_init_time:.4f}s") - - start = time.time() - config = {"configurable": {"thread_id": group_id}} - workflow_errors = [] # Track errors from workflow - - event_count = 0 - async for event in graph.astream( - {"messages": history, "memory_config": memory_config, "errors": []}, - stream_mode="values", - config=config - ): - event_count += 1 - event_start = time.time() - messages = event.get('messages') - # Capture any errors from the state - if event.get('errors'): - workflow_errors.extend(event.get('errors', [])) + outputs = [] + intermediate_outputs = [] + seen_intermediates = set() # Track seen intermediate outputs to avoid duplicates - for msg in messages: - msg_content = msg.content - msg_role = msg.__class__.__name__.lower().replace("message", "") - outputs.append({ - "role": msg_role, - "content": msg_content - }) + # Pass memory_config to the graph workflow + graph_start = time.time() + async with make_read_graph(group_id, tools, search_switch, group_id, group_id, memory_config=memory_config, storage_type=storage_type, user_rag_memory_id=user_rag_memory_id) as graph: + graph_init_time = time.time() - graph_start + logger.info(f"[PERF] Graph initialization took: {graph_init_time:.4f}s") - # Extract intermediate outputs - if hasattr(msg, 'content'): - try: - # Handle MCP content format: [{'type': 'text', 'text': '...'}] - content_to_parse = msg_content - if isinstance(msg_content, list): - for block in msg_content: - if isinstance(block, dict) and block.get('type') == 'text': - content_to_parse = block.get('text', '') - break - else: - continue # No text block found + start = time.time() + config = {"configurable": {"thread_id": group_id}} + workflow_errors = [] # Track errors from workflow - # Try to parse content as JSON - if isinstance(content_to_parse, str): - try: - parsed = json.loads(content_to_parse) - if isinstance(parsed, dict): - # Check for single intermediate output - if '_intermediate' in parsed: - intermediate_data = parsed['_intermediate'] + event_count = 0 + async for event in graph.astream( + {"messages": history, "memory_config": memory_config, "errors": []}, + stream_mode="values", + config=config + ): + event_count += 1 + event_start = time.time() + messages = event.get('messages') + # Capture any errors from the state + if event.get('errors'): + workflow_errors.extend(event.get('errors', [])) + + for msg in messages: + msg_content = msg.content + msg_role = msg.__class__.__name__.lower().replace("message", "") + outputs.append({ + "role": msg_role, + "content": msg_content + }) + + # Extract intermediate outputs + if hasattr(msg, 'content'): + try: + # Handle MCP content format: [{'type': 'text', 'text': '...'}] + content_to_parse = msg_content + if isinstance(msg_content, list): + for block in msg_content: + if isinstance(block, dict) and block.get('type') == 'text': + content_to_parse = block.get('text', '') + break + else: + continue # No text block found + + # Try to parse content as JSON + if isinstance(content_to_parse, str): + try: + parsed = json.loads(content_to_parse) + if isinstance(parsed, dict): + # Check for single intermediate output + if '_intermediate' in parsed: + intermediate_data = parsed['_intermediate'] + output_key = self._create_intermediate_key(intermediate_data) + + if output_key not in seen_intermediates: + seen_intermediates.add(output_key) + intermediate_outputs.append(self._format_intermediate_output(intermediate_data)) + + # Check for multiple intermediate outputs (from Retrieve) + if '_intermediates' in parsed: + for intermediate_data in parsed['_intermediates']: output_key = self._create_intermediate_key(intermediate_data) if output_key not in seen_intermediates: seen_intermediates.add(output_key) intermediate_outputs.append(self._format_intermediate_output(intermediate_data)) + except (json.JSONDecodeError, ValueError): + pass + except Exception as e: + logger.debug(f"Failed to extract intermediate output: {e}") - # Check for multiple intermediate outputs (from Retrieve) - if '_intermediates' in parsed: - for intermediate_data in parsed['_intermediates']: - output_key = self._create_intermediate_key(intermediate_data) + event_time = time.time() - event_start + logger.info(f"[PERF] Event {event_count} processing took: {event_time:.4f}s") - if output_key not in seen_intermediates: - seen_intermediates.add(output_key) - intermediate_outputs.append(self._format_intermediate_output(intermediate_data)) - except (json.JSONDecodeError, ValueError): - pass - except Exception as e: - logger.debug(f"Failed to extract intermediate output: {e}") - - event_time = time.time() - event_start - logger.info(f"[PERF] Event {event_count} processing took: {event_time:.4f}s") + workflow_duration = time.time() - start + session_duration = time.time() - session_start + logger.info(f"[PERF] Read graph workflow completed in {workflow_duration}s") + logger.info(f"[PERF] Total session duration: {session_duration:.4f}s") + logger.info(f"[PERF] Total events processed: {event_count}") + # Extract final answer + final_answer = "" + for messages in outputs: + if messages['role'] == 'tool': + message = messages['content'] - workflow_duration = time.time() - start - session_duration = time.time() - session_start - logger.info(f"[PERF] Read graph workflow completed in {workflow_duration}s") - logger.info(f"[PERF] Total session duration: {session_duration:.4f}s") - logger.info(f"[PERF] Total events processed: {event_count}") - # Extract final answer - final_answer = "" - for messages in outputs: - if messages['role'] == 'tool': - message = messages['content'] + # Handle MCP content format: [{'type': 'text', 'text': '...'}] + if isinstance(message, list): + # Extract text from MCP content blocks + for block in message: + if isinstance(block, dict) and block.get('type') == 'text': + message = block.get('text', '') + break + else: + continue # No text block found - # Handle MCP content format: [{'type': 'text', 'text': '...'}] - if isinstance(message, list): - # Extract text from MCP content blocks - for block in message: - if isinstance(block, dict) and block.get('type') == 'text': - message = block.get('text', '') - break - else: - continue # No text block found + try: + parsed = json.loads(message) if isinstance(message, str) else message + if isinstance(parsed, dict): + if parsed.get('status') == 'success': + summary_result = parsed.get('summary_result') + if summary_result: + final_answer = summary_result + except (json.JSONDecodeError, ValueError): + pass - try: - parsed = json.loads(message) if isinstance(message, str) else message - if isinstance(parsed, dict): - if parsed.get('status') == 'success': - summary_result = parsed.get('summary_result') - if summary_result: - final_answer = summary_result - except (json.JSONDecodeError, ValueError): - pass + # 记录成功的操作 + total_duration = time.time() - start_time - # 记录成功的操作 - total_duration = time.time() - start_time + # Check for workflow errors + if workflow_errors: + error_details = "; ".join([f"{e['tool']}: {e['error']}" for e in workflow_errors]) + logger.warning(f"Read workflow completed with errors: {error_details}") - # Check for workflow errors - if workflow_errors: - error_details = "; ".join([f"{e['tool']}: {e['error']}" for e in workflow_errors]) - logger.warning(f"Read workflow completed with errors: {error_details}") - - if audit_logger: - audit_logger.log_operation( - operation="READ", - config_id=config_id, - group_id=group_id, - success=False, - duration=total_duration, - error=error_details, - details={ - "search_switch": search_switch, - "history_length": len(history), - "intermediate_outputs_count": len(intermediate_outputs), - "has_answer": bool(final_answer), - "errors": workflow_errors - } - ) - - # Raise error if no answer was produced - if not final_answer: - raise ValueError(f"Read workflow failed: {error_details}") - - if audit_logger and not workflow_errors: + if audit_logger: audit_logger.log_operation( operation="READ", config_id=config_id, group_id=group_id, - success=True, + success=False, duration=total_duration, + error=error_details, details={ "search_switch": search_switch, "history_length": len(history), "intermediate_outputs_count": len(intermediate_outputs), - "has_answer": bool(final_answer) + "has_answer": bool(final_answer), + "errors": workflow_errors } ) - retrieved_content=[] - repo = ShortTermMemoryRepository(db) - if str(search_switch)!="2": - for intermediate in intermediate_outputs: - print(intermediate) - intermediate_type=intermediate['type'] - if intermediate_type=="search_result": - query=intermediate['query'] - raw_results=intermediate['raw_results'] - reranked_results=raw_results.get('reranked_results',[]) - try: - statements=[statement['statement'] for statement in reranked_results.get('statements', [])] - except Exception: - statements=[] - statements=list(set(statements)) - retrieved_content.append({query:statements}) - if retrieved_content==[]: - retrieved_content='' - if '信息不足,无法回答。' != str(final_answer) and str(search_switch).strip() != "2":#and retrieved_content!=[] - # 使用 upsert 方法 - repo.upsert( - end_user_id=end_user_id, # 确保这个变量在作用域内 - messages=ori_message, - aimessages=final_answer, - retrieved_content=retrieved_content, - search_switch=str(search_switch) - ) - print("写入成功") + + # Raise error if no answer was produced + if not final_answer: + raise ValueError(f"Read workflow failed: {error_details}") + + if audit_logger and not workflow_errors: + audit_logger.log_operation( + operation="READ", + config_id=config_id, + group_id=group_id, + success=True, + duration=total_duration, + details={ + "search_switch": search_switch, + "history_length": len(history), + "intermediate_outputs_count": len(intermediate_outputs), + "has_answer": bool(final_answer) + } + ) + retrieved_content=[] + repo = ShortTermMemoryRepository(db) + if str(search_switch)!="2": + for intermediate in intermediate_outputs: + print(intermediate) + intermediate_type=intermediate['type'] + if intermediate_type=="search_result": + query=intermediate['query'] + raw_results=intermediate['raw_results'] + reranked_results=raw_results.get('reranked_results',[]) + try: + statements=[statement['statement'] for statement in reranked_results.get('statements', [])] + except Exception: + statements=[] + statements=list(set(statements)) + retrieved_content.append({query:statements}) + if retrieved_content==[]: + retrieved_content='' + if '信息不足,无法回答。' != str(final_answer) and str(search_switch).strip() != "2":#and retrieved_content!=[] + # 使用 upsert 方法 + repo.upsert( + end_user_id=end_user_id, # 确保这个变量在作用域内 + messages=ori_message, + aimessages=final_answer, + retrieved_content=retrieved_content, + search_switch=str(search_switch) + ) + print("写入成功") - return { - "answer": final_answer, - "intermediate_outputs": intermediate_outputs - } - + return { + "answer": final_answer, + "intermediate_outputs": intermediate_outputs + } + def _create_intermediate_key(self, output: Dict) -> str: """ Create a unique key for an intermediate output to detect duplicates. From 0ed78f7a62d81bd07c2dfdf4a929c7029d725e93 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Thu, 15 Jan 2026 17:13:56 +0800 Subject: [PATCH 24/47] fix(web): update FORGET_MEMORY type --- web/src/i18n/en.ts | 2 +- web/src/i18n/zh.ts | 4 ++-- .../views/UserMemoryDetail/components/NodeStatistics.tsx | 2 +- web/src/views/UserMemoryDetail/pages/index.tsx | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 6923e5fb..8177cd5c 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1221,7 +1221,7 @@ export const en = { IMPLICIT_MEMORY: 'Implicit Memory', EMOTIONAL_MEMORY: 'Emotional Memory', EPISODIC_MEMORY: 'Episodic Memory', - FORGETTING_MANAGEMENT: 'Forgetting Management', + FORGET_MEMORY: 'Forget Memory', endUserProfile: 'Core Profile', editEndUserProfile: 'Edit', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 92804d3c..1a33ee65 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1299,8 +1299,8 @@ export const zh = { IMPLICIT_MEMORY: '隐性记忆', EMOTIONAL_MEMORY: '情绪记忆', EPISODIC_MEMORY: '情景记忆', - FORGETTING_MANAGEMENT: '遗忘', - + FORGET_MEMORY: '遗忘记忆', + endUserProfile: '核心档案', editEndUserProfile: '编辑', other_name: '姓名', diff --git a/web/src/views/UserMemoryDetail/components/NodeStatistics.tsx b/web/src/views/UserMemoryDetail/components/NodeStatistics.tsx index 8cc3fd6c..e84024fa 100644 --- a/web/src/views/UserMemoryDetail/components/NodeStatistics.tsx +++ b/web/src/views/UserMemoryDetail/components/NodeStatistics.tsx @@ -32,7 +32,7 @@ const typeList = [ { key: 'EXPLICIT_MEMORY' } ] }, - { key: 'FORGETTING_MANAGEMENT', bg: 5 }, + { key: 'FORGET_MEMORY', bg: 5 }, ] const NodeStatistics: FC = () => { diff --git a/web/src/views/UserMemoryDetail/pages/index.tsx b/web/src/views/UserMemoryDetail/pages/index.tsx index f5b1a937..f225b1f0 100644 --- a/web/src/views/UserMemoryDetail/pages/index.tsx +++ b/web/src/views/UserMemoryDetail/pages/index.tsx @@ -38,7 +38,7 @@ const Detail: FC = () => { }) } const items = useMemo(() => { - return ['PERCEPTUAL_MEMORY', 'WORKING_MEMORY', 'EMOTIONAL_MEMORY', 'SHORT_TERM_MEMORY', 'IMPLICIT_MEMORY', 'EPISODIC_MEMORY', 'EXPLICIT_MEMORY', 'FORGETTING_MANAGEMENT'] + return ['PERCEPTUAL_MEMORY', 'WORKING_MEMORY', 'EMOTIONAL_MEMORY', 'SHORT_TERM_MEMORY', 'IMPLICIT_MEMORY', 'EPISODIC_MEMORY', 'EXPLICIT_MEMORY', 'FORGET_MEMORY'] .map(key => ({ key, label: t(`userMemory.${key}`) })) }, [t]) const onClick = ({ key }: { key: string }) => { @@ -67,7 +67,7 @@ const Detail: FC = () => {
} - extra={type === 'FORGETTING_MANAGEMENT' && + extra={type === 'FORGET_MEMORY' && - +
+ graphRef.current?.zoom(-0.1)} /> - + - +
@@ -131,9 +131,9 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres {formatDateTime(selectedVersion.published_at, 'YYYY-MM-DD HH:mm:ss')}
} - extra={{selectedVersion.publisher_name}} + extra={{selectedVersion.publisher_name}} > -
+
From c2998154e0440b6d9315c7b7e7207e2550ad12b4 Mon Sep 17 00:00:00 2001 From: lixinyue11 <94037597+lixinyue11@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:10:10 +0800 Subject: [PATCH 35/47] Fix/memory bug fix (#134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 图谱数据量限制数量去掉 * 图谱数据量限制数量去掉 * 图谱数据量限制数量去掉 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 用户详情优化 * 读取的接口,去掉全局锁 * 输出数组 --- api/app/services/memory_entity_relationship_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/app/services/memory_entity_relationship_service.py b/api/app/services/memory_entity_relationship_service.py index ca97fb39..eedb7c29 100644 --- a/api/app/services/memory_entity_relationship_service.py +++ b/api/app/services/memory_entity_relationship_service.py @@ -597,7 +597,7 @@ class MemoryInteraction: group_id = ori_data[0]['group_id'] Space_User = await self.connector.execute_query(Memory_Space_User, group_id=group_id) if not Space_User: - return '不存在用户' + return [] user_id=Space_User[0]['id'] results = await self.connector.execute_query(Memory_Space_Associative, id=self.id,user_id=user_id) From 437dc275863a7e6943ab9bbb62b27a1fb4b056fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BF=8A=E7=94=B7?= Date: Fri, 16 Jan 2026 11:44:19 +0800 Subject: [PATCH 36/47] feat(home page): add the function of switching between Chinese and English in the version introduction --- api/app/controllers/home_page_controller.py | 11 ++- api/app/services/home_page_service.py | 84 +++++++++---------- api/app/version_info.json | 91 ++++++++++++++------- 3 files changed, 110 insertions(+), 76 deletions(-) diff --git a/api/app/controllers/home_page_controller.py b/api/app/controllers/home_page_controller.py index 0b758cd1..de4a78a3 100644 --- a/api/app/controllers/home_page_controller.py +++ b/api/app/controllers/home_page_controller.py @@ -33,5 +33,12 @@ def get_workspace_list( def get_system_version(): """获取系统版本号+说明""" current_version = settings.SYSTEM_VERSION - version_introduction = HomePageService.load_version_introduction(current_version) - return success(data={"version": current_version, "introduction": version_introduction}, msg="系统版本获取成功") \ No newline at end of file + version_info = HomePageService.load_version_introduction(current_version) + return success( + data={ + "version": current_version, + "introduction": version_info.get("introduction"), + "introduction_en": version_info.get("introduction_en") + }, + msg="系统版本获取成功" + ) \ No newline at end of file diff --git a/api/app/services/home_page_service.py b/api/app/services/home_page_service.py index a84a8214..8326ad40 100644 --- a/api/app/services/home_page_service.py +++ b/api/app/services/home_page_service.py @@ -11,6 +11,22 @@ from app.repositories.home_page_repository import HomePageRepository from app.schemas.home_page_schema import HomeStatistics, WorkspaceInfo class HomePageService: + + DEFAULT_RETURN_DATA: Dict[str, Any] = { + "message": "", + "introduction": { + "codeName": "", + "releaseDate": "", + "upgradePosition": "", + "coreUpgrades": [] + }, + "introduction_en": { + "codeName": "", + "releaseDate": "", + "upgradePosition": "", + "coreUpgrades": [] + } + } @staticmethod def get_home_statistics(db: Session, tenant_id: UUID) -> HomeStatistics: @@ -82,60 +98,36 @@ class HomePageService: :param version: 系统版本号(如 "0.2.0") :return: 对应版本的详细介绍 """ - # 1. 定义 JSON 文件路径(使用 Path 处理跨平台路径问题) - json_file_path = Path(__file__).parent.parent / "version_info.json" - # 转换为绝对路径,便于调试 - json_abs_path = json_file_path.resolve() + # 2. 定义 JSON 文件路径(简化路径处理,保留绝对路径调试特性) + json_abs_path = Path(__file__).parent.parent / "version_info.json" + json_abs_path = json_abs_path.resolve() + + # 3. 初始化返回结果(深拷贝默认模板,避免修改原常量) + from copy import deepcopy + result = deepcopy(HomePageService.DEFAULT_RETURN_DATA) try: - # 2. 读取 JSON 文件 + # 4. 简化文件存在性判断(合并逻辑,减少分支) if not json_abs_path.exists(): - return { - "message": f"版本介绍文件不存在:{json_abs_path}", - "codeName": "", - "releaseDate": "", - "upgradePosition": "", - "coreUpgrades": [] - } + result["message"] = f"版本介绍文件不存在:{json_abs_path}" + return result + # 5. 读取并解析 JSON 文件(简化文件操作流程) with open(json_abs_path, "r", encoding="utf-8") as f: changelogs = json.load(f) - # 3. 匹配对应版本的介绍,若版本不存在返回默认提示 - if version not in changelogs: - return { - "message": f"暂未查询到 {version} 版本的详细介绍", - "codeName": "", - "releaseDate": "", - "upgradePosition": "", - "coreUpgrades": [] - } - return changelogs[version] + # 6. 简化版本匹配逻辑,直接返回结果或更新提示信息 + if version in changelogs: + return changelogs[version] + result["message"] = f"暂未查询到 {version} 版本的详细介绍" + return result except FileNotFoundError as e: - # 处理文件不存在异常 - return { - "message": f"系统内部错误:{str(e)}", - "codeName": "", - "releaseDate": "", - "upgradePosition": "", - "coreUpgrades": [] - } + result["message"] = f"系统内部错误:{str(e)}" + return result except json.JSONDecodeError: - # 处理 JSON 格式错误 - return { - "message": "版本介绍文件格式错误,无法解析 JSON", - "codeName": "", - "releaseDate": "", - "upgradePosition": "", - "coreUpgrades": [] - } + result["message"] = "版本介绍文件格式错误,无法解析 JSON" + return result except Exception as e: - # 处理其他未知异常 - return { - "message": f"加载版本介绍失败:{str(e)}", - "codeName": "", - "releaseDate": "", - "upgradePosition": "", - "coreUpgrades": [] - } \ No newline at end of file + result["message"] = f"加载版本介绍失败:{str(e)}" + return result \ No newline at end of file diff --git a/api/app/version_info.json b/api/app/version_info.json index 87e313e4..20896845 100644 --- a/api/app/version_info.json +++ b/api/app/version_info.json @@ -1,33 +1,68 @@ { "v0.2.0": { - "codeName": "启知", - "releaseDate": "2026-1-16", - "upgradePosition": "本次为架构升级,核心目标是把“被动存储”升级为“主动认知”,让系统具备情绪感知、情景理解与类人记忆机制,为后续多智能体协作与专业场景落地奠定底座。", - "coreUpgrades": [ - "记忆详情:拟人记忆——情绪引擎、情景记忆、短期记忆、工作记忆、感知记忆、显性记忆、隐性记忆,并配套类脑遗忘机制,实现从感知→情绪→情景→长期沉淀的完整人类记忆闭环", - "可视化工作流:拖拽式节点编排(LLM、知识库、逻辑、工具),业务落地周期由天缩至小时。", - "多模态知识处理:PDF、PPT、MP3、MP4 一键解析,时间感知检索准确率 94.3%,问答对数据即插即用。", - "Agent集群内置“记忆-知识-工具-审核”四类角色模板,用户一键生成;主控Agent把复杂任务拆为子任务并行分发,再靠情景记忆统一消解冲突、校验一致性,输出完整报告。" - ] + "introduction": { + "codeName": "启知", + "releaseDate": "2026-1-16", + "upgradePosition": "本次为架构升级,核心目标是把\"被动存储\"升级为\"主动认知\",让系统具备情绪感知、情景理解与类人记忆机制,为后续多智能体协作与专业场景落地奠定底座。", + "coreUpgrades": [ + "记忆详情:拟人记忆——情绪引擎、情景记忆、短期记忆、工作记忆、感知记忆、显性记忆、隐性记忆,并配套类脑遗忘机制,实现从感知→情绪→情景→长期沉淀的完整人类记忆闭环", + "可视化工作流:拖拽式节点编排(LLM、知识库、逻辑、工具),业务落地周期由天缩至小时。", + "多模态知识处理:PDF、PPT、MP3、MP4 一键解析,时间感知检索准确率 94.3%,问答对数据即插即用。", + "Agent集群内置\"记忆-知识-工具-审核\"四类角色模板,用户一键生成;主控Agent把复杂任务拆为子任务并行分发,再靠情景记忆统一消解冲突、校验一致性,输出完整报告。" + ] + }, + "introduction_en": { + "codeName": "Qizhi", + "releaseDate": "2026-1-16", + "upgradePosition": "This release marks a foundational upgrade to the system’s cognitive architecture. The core objective is to evolve the platform from passive information storage into active cognitive intelligence—enabling emotional awareness, situational understanding, and human-like memory mechanisms. This upgrade lays the groundwork for future multi-agent collaboration and domain-specific, production-grade AI applications.", + "coreUpgrades": [ + "Human-Like Memory Architecture: A comprehensive, human-inspired memory system is introduced, encompassing emotional processing, situational memory, short-term and working memory, perceptual memory, as well as explicit and implicit memory. Combined with brain-inspired forgetting mechanisms, the system now supports a complete cognitive loop—from perception → emotion → context → long-term consolidation, closely mirroring human memory formation.", + "Visual Workflow Orchestration: A fully visual, drag-and-drop workflow enables modular composition of LLMs, knowledge bases, logic, and tools. This dramatically reduces the time required to move from experimentation to production—from days to hours.", + "Multimodal Knowledge Processing: The system now supports one-click parsing and ingestion of PDF, PPT, MP3, and MP4 content. With time-aware retrieval accuracy reaching 94.3%, structured Q&A data becomes instantly usable for downstream reasoning and generation.", + "Built-in Agent Clusters: Predefined role templates across four categories—Memory, Knowledge, Tools, and Review—can be generated with a single click. A Coordinator Agent decomposes complex tasks into parallel subtasks, while situational memory is used to resolve conflicts, validate consistency, and synthesize outputs into a coherent, end-to-end report." + ] + } }, "v0.1.0": { - "codeName": "初心", - "releaseDate": "2025-12-01", - "upgradePosition": "这是一款专注于管理和利用AI记忆的工具,支持RAG和知识图谱两种主流存储方式,旨在为AI应用提供持久化、结构化的“记忆”能力。", - "coreUpgrades": [ - "记忆空间:用户可以创建独立的空间来隔离不同记忆,并灵活选择存储方式。", - "记忆配置:简化了配置流程,内置自动提取关键信息的“记忆萃取”和管理生命周期的\"遗忘\"引擎。", - "知识检索:提供语义、分词和混合三种检索模式,并支持多种参数微调和结果重排序,以提升召回效果。", - "全局管理:支持统一设置默认检索参数,并可一键应用到所有知识库。", - "测试与调试:内置\"召回测试\"功能,方便用户实时验证检索效果并调整参数,支持通过分享码与他人协作。", - "记忆洞察:可查看详细的对话记录、用户画像和分析报告,帮助理解AI的\"记忆\"内容。", - "集成与管理:提供API Key用于系统集成,并包含基本的用户管理功能。", - "界面与体验:采用现代化的卡片式布局和渐变色设计,注重交互的流畅性和视觉美感。", - "起步与使用:文档中提供了清晰的基础使用流程,引导用户从创建空间、配置记忆到测试检索快速上手。", - "版本说明与限制: 记忆熊 v0.1.0 版本\"初心\"囊括智能记忆管理的核心思路和基础能力,为后续开发奠定了基础。", - "文档资源:用户手册、API文档、FAQ", - "问题反馈:GitHub Issues、邮件支持", - "致谢:感谢所有参与测试和提供反馈的用户!" - ] + "introduction": { + "codeName": "初心", + "releaseDate": "2025-12-01", + "upgradePosition": "这是一款专注于管理和利用AI记忆的工具,支持RAG和知识图谱两种主流存储方式,旨在为AI应用提供持久化、结构化的\"记忆\"能力。", + "coreUpgrades": [ + "记忆空间:用户可以创建独立的空间来隔离不同记忆,并灵活选择存储方式。", + "记忆配置:简化了配置流程,内置自动提取关键信息的\"记忆萃取\"和管理生命周期的\"遗忘\"引擎。", + "知识检索:提供语义、分词和混合三种检索模式,并支持多种参数微调和结果重排序,以提升召回效果。", + "全局管理:支持统一设置默认检索参数,并可一键应用到所有知识库。", + "测试与调试:内置\"召回测试\"功能,方便用户实时验证检索效果并调整参数,支持通过分享码与他人协作。", + "记忆洞察:可查看详细的对话记录、用户画像和分析报告,帮助理解AI的\"记忆\"内容。", + "集成与管理:提供API Key用于系统集成,并包含基本的用户管理功能。", + "界面与体验:采用现代化的卡片式布局和渐变色设计,注重交互的流畅性和视觉美感。", + "起步与使用:文档中提供了清晰的基础使用流程,引导用户从创建空间、配置记忆到测试检索快速上手。", + "版本说明与限制: 记忆熊 v0.1.0 版本\"初心\"囊括智能记忆管理的核心思路和基础能力,为后续开发奠定了基础。", + "文档资源:用户手册、API文档、FAQ", + "问题反馈:GitHub Issues、邮件支持", + "致谢:感谢所有参与测试和提供反馈的用户!" + ] + }, + "introduction_en": { + "codeName": "Original Intent", + "releaseDate": "2025-12-01", + "upgradePosition": "A tool focused on managing and utilizing AI memory, supporting both RAG and knowledge graph storage methods, aiming to provide persistent and structured 'memory' capabilities for AI applications.", + "coreUpgrades": [ + "Memory Space: Users can create independent spaces to isolate different memories and flexibly choose storage methods.", + "Memory Configuration: Simplified configuration process with built-in 'memory extraction' for automatic key information extraction and 'forgetting' engine for lifecycle management.", + "Knowledge Retrieval: Provides semantic, tokenization, and hybrid retrieval modes with various parameter tuning and result reranking to improve recall.", + "Global Management: Supports unified default retrieval parameter settings with one-click application to all knowledge bases.", + "Testing & Debugging: Built-in 'recall testing' for real-time verification of retrieval effects and parameter adjustment, with sharing code support for collaboration.", + "Memory Insights: View detailed conversation records, user profiles, and analysis reports to understand AI 'memory' content.", + "Integration & Management: Provides API Key for system integration with basic user management features.", + "Interface & Experience: Modern card-based layout with gradient design, focusing on interaction fluidity and visual aesthetics.", + "Getting Started: Documentation provides clear basic usage flow, guiding users from creating spaces, configuring memory to testing retrieval.", + "Version Notes: MemoryBear v0.1.0 'Original Intent' encompasses core concepts and basic capabilities of intelligent memory management, laying foundation for future development.", + "Documentation: User Manual, API Documentation, FAQ", + "Feedback: GitHub Issues, Email Support", + "Acknowledgments: Thanks to all users who participated in testing and provided feedback!" + ] + } } -} \ No newline at end of file +} From 339f6280e14a8a3c3c8b4b5120ee807e925dbdde Mon Sep 17 00:00:00 2001 From: zhaoying Date: Fri, 16 Jan 2026 12:11:02 +0800 Subject: [PATCH 37/47] feat(web): en update --- web/src/components/Table/index.tsx | 4 +- web/src/i18n/en.ts | 134 +++++++++--------- web/src/i18n/zh.ts | 2 + web/src/utils/auth.ts | 4 +- web/src/views/ApplicationConfig/Agent.tsx | 2 +- web/src/views/MemoryConversation/index.tsx | 2 +- .../components/CustomToolModal.tsx | 9 +- 7 files changed, 82 insertions(+), 75 deletions(-) diff --git a/web/src/components/Table/index.tsx b/web/src/components/Table/index.tsx index 62c68dc3..08a7e627 100644 --- a/web/src/components/Table/index.tsx +++ b/web/src/components/Table/index.tsx @@ -16,6 +16,7 @@ interface TableComponentProps extends Omit { rowSelection?: TableProps['rowSelection']; initialData?: Record[]; emptySize?: number; + emptyText?: string; isScroll?: boolean; scrollX?: number | string | true; // 支持自定义横向滚动宽度 scrollY?: number | string; // 支持自定义纵向滚动高度 @@ -46,6 +47,7 @@ const TableComponent = forwardRef(({ rowSelection, initialData, emptySize = 160, + emptyText, isScroll = false, scrollX, scrollY, @@ -169,7 +171,7 @@ const TableComponent = forwardRef(({ rowSelection={rowSelection} rowClassName={styles.row} className={styles.table} - locale={{ emptyText: }} + locale={{ emptyText: }} scroll={getScrollConfig()} tableLayout="auto" /> diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 8ec4bdef..0f2543ab 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -7,7 +7,7 @@ export const en = { viewGuide: 'View Guide', watchVideo: 'Watch Video', viewDetails: 'View Details', - changeLog: 'Change Log', + changeLog: 'Changelog', latestUpdate: 'Latest Update', appCount: 'Number of Spaces', userCount: 'Number of Users', @@ -80,7 +80,7 @@ export const en = { userManagement: 'User Management', roleManagement: 'Role Management', systemManage: 'System Manage', - applicationManagement: 'Application Manage', + applicationManagement: 'Application', productManagement: 'Product Management', knowledgeManagement: 'KnowledgeBase ', knowledgeBase: 'Datasets Management', @@ -102,7 +102,7 @@ export const en = { knowledgeCreateDataset: 'Create Dataset', knowledgeDocumentDetails: 'Document Details', userMemoryDetail: 'UserMemory Detail', - apiKeyManagement: 'API KEY Management', + apiKeyManagement: 'API Key Management', toolManagement: 'Tool Management', emotionEngine: 'Emotion Engine', statementDetail: 'Emotion Memory', @@ -113,27 +113,27 @@ export const en = { spaceConfig: 'Space Configuration' }, dashboard: { - total_models: 'Total number of available models', - total_spaces: 'Number of active spaces', - total_users: 'Total number of users', - total_running_apps: 'Number of application runs', - desc_models: 'Contains {{ account }} LLMs and {{ nums }} Embeddings', - desc_spaces: 'more than last week', + total_models: 'Available Models', + total_spaces: 'Active Spaces', + total_users: 'Users', + total_running_apps: 'Application Runs', + desc_models: 'Includes {{ account }} LLM and {{ nums }} embedding', + desc_spaces: 'compared to last week', desc_users: 'New additions this week', desc_running_apps: "Today's success rate", - totalMemoryCapacity: 'Total Memory Capacity', + totalMemoryCapacity: 'Total Stored Memories', userMemory: 'User Memory', - knowledgeBaseCount: 'Knowledge Base Count', - apiCallCount: 'API Call Count', + knowledgeBaseCount: 'Knowledge Bases', + apiCallCount: 'API Calls', comparedToYesterday: 'compared to yesterday', thisWeek: 'this week', thisDay: 'day on day', failureRate: 'Failure Rate', - application: 'Application Count', + application: 'Applications', total_num: 'Total Memory Capacity', lastDays: 'last {{days}} days', - lastHalfYear: 'last half year', + lastHalfYear: 'last 6 months', lastYear: 'last year', enterpriseMemory: 'Enterprise Memory', @@ -183,13 +183,13 @@ export const en = { createNewMemorySummary: 'Create New Memory Entry', createNewApplication: 'Create New Application', - createNewApplicationDesc: 'Create New Space Application', + createNewApplicationDesc: 'Create a new application for this space', createNewKnowledge: 'Create New Knowledge', createNewKnowledgeDesc: 'Create a new memory entry', memoryConversation: 'Memory Conversation', - memoryConversationDesc: 'Memory Conversation', + memoryConversationDesc: 'Create a new memory conversation', memorySummary: 'View Memory Summary', memorySummaryDesc: 'View Memory Summary Report', @@ -198,13 +198,13 @@ export const en = { tagEmpty: 'There are no tag records at the moment~', chunk_count: 'Data Chunk', - chunk_count_desc: 'Current Processing {{count}} Data Chunks', + chunk_count_desc: '{{count}} data chunks currently being processed', statements_count: 'Statements', - statements_count_desc: 'Manage {{count}} knowledge statements', + statements_count_desc: '{{count}} knowledge statements', triplet_count: 'Entity Relation Extraction', - triplet_count_desc: 'Build {{entities_count}} entity nodes and {{relations_count}} relation connections', + triplet_count_desc: '{{entities_count}} entity nodes and {{relations_count}} relationships built', temporal_count: 'Time Extraction', - temporal_count_desc: 'Record {{count}} time series information', + temporal_count_desc: '{{count}} time series records', dialogue: 'Dialogue', chunk: 'Chunk', @@ -943,20 +943,20 @@ export const en = { apiEndpoint: 'API Endpoint', apiKey: 'API-Key', returnToApplicationList: 'Return to application list', - arrangement: 'Arrangement', + arrangement: 'Configuration', api: 'API', release: 'Release', promptConfiguration: 'Prompt Configuration', configurationDesc: 'Define the role, capabilities, and behavioral guidelines of the Agent', aiPrompt: 'AI Prompt', promptPlaceholder: 'You are a professional AI assistant, and your responsibility is to help users solve problems.', - knowledgeBaseAssociation: 'Knowledge base association', - associatedKnowledgeBase: 'Associated Knowledge Base', + knowledgeBaseAssociation: 'Knowledge Base Association', + associatedKnowledgeBase: 'Associated Knowledge Bases', addKnowledgeBase: 'Add Knowledge Base', - knowledgeEmpty: 'There is currently no knowledge base association', + knowledgeEmpty: 'No knowledge base associated.', memoryConfiguration: 'Memory Configuration', - dialogueHistoricalMemory: 'Dialogue Historical Memory', - dialogueHistoricalMemoryDesc: 'After activation, the memory content in the memory management can be selected', + dialogueHistoricalMemory: 'Conversation History Memory', + dialogueHistoricalMemoryDesc: 'Enable this to select memory content from memory management.', toolConfiguration: 'Tool Configuration', webSearch: 'Web Search', webSearchDesc: 'Allow the Agent to access the Internet for real-time search', @@ -971,14 +971,14 @@ export const en = { VariableManagementDesc: 'Configure the available variables for the Agent', addVariables: 'Add Variables', variablesEmpty: 'There are currently no variables available', - debuggingEmpty: 'Currently, there are no debugging models available', - debuggingEmptyDesc: 'Click the "+" button on the page, select and add the model you need', - debuggingAndPreview: 'Debugging and Preview', + debuggingEmpty: 'No models available for debugging.', + debuggingEmptyDesc: 'Click the “+” button to select and add a model.', + debuggingAndPreview: 'Preview and Debugging', addModel: 'Add Model', fieldName: 'Field Name', Optional: 'Optional', - chatEmpty: 'Send message to start testing', - chatPlaceholder: 'Start chatting with the robot…', + chatEmpty: 'Send a message to start testing', + chatPlaceholder: 'Start a conversation...', endpointConfiguration: 'Endpoint Configuration', authenticationMethod: 'Authentication Method', @@ -1005,22 +1005,22 @@ export const en = { whitelistIPDesc: 'supports CIDR', publicAPIDocumentation: 'Public API Documentation', - versionList: 'Version List', - versionListDesc: 'All release records and status', + versionList: 'Versions', + versionListDesc: 'Release history and status', current: 'Current', rolledBack: 'rolled back', history: 'history', VersionInformation: 'Version Information', - publishedOn: 'Published On', - publisher: 'Publisher', - DetailsOfVersion: 'Details of {{version}} version', + publishedOn: 'Published on', + publisher: 'Published by', + DetailsOfVersion: 'Version Details: {{version}}', exportDSLFile: 'Export DSL file', willRollToThisVersion: 'Will roll to this version', share: 'Share', lastUpdateTime: 'Last Update Time', editor: 'Editor', releaseTime: 'Release Time', - changeLog: 'Change Log', + changeLog: 'Changelog', fix: 'Fix', optimization: 'Optimization', new: 'New', @@ -1125,7 +1125,7 @@ export const en = { ReplyException: 'Reply exception', endpointConfigurationSubTitle: 'Configure API access address and supported HTTP methods', - apiKeys: 'API Keys Management', + apiKeys: 'API Key Management', apiKeySubTitle: 'Manage API keys, view usage and traffic statistics for each key', addApiKey: 'Add New API Key', apiKeyName: 'Key Name', @@ -1194,7 +1194,7 @@ export const en = { drag: 'Drag and drop to move nodes', zoom: 'Scroll zoom view', memoryDetailEmpty: 'Please select a memory node', - memoryDetailEmptyDesc: 'Click on the node in the left graph to view the details of entity memory', + memoryDetailEmptyDesc: 'Click a node in the graph to view memory details.', totalNumOfMemories: 'Total Number of Memories', footprintCity: 'Footprint City', @@ -1212,20 +1212,20 @@ export const en = { editConfig: 'Edit Config', chooseModel: 'Choose Model', - nodeStatistics: 'Memory Classification', + nodeStatistics: 'Memory Categories', total: 'Total', PERCEPTUAL_MEMORY: 'Perceptual Memory', WORKING_MEMORY: 'Working Memory', - SHORT_TERM_MEMORY: 'Shot Term Memory', - LONG_TERM_MEMORY: 'Long Term Memory', + SHORT_TERM_MEMORY: 'Short-Term Memory', + LONG_TERM_MEMORY: 'Long-Term Memory', EXPLICIT_MEMORY: 'Explicit Memory', IMPLICIT_MEMORY: 'Implicit Memory', EMOTIONAL_MEMORY: 'Emotional Memory', EPISODIC_MEMORY: 'Episodic Memory', FORGET_MEMORY: 'Forget Memory', - endUserProfile: 'Core Profile', + endUserProfile: 'Profile', editEndUserProfile: 'Edit', other_name: 'Name', position: 'Position', @@ -1235,10 +1235,10 @@ export const en = { hire_date: 'Hire Date', memoryContent: 'Memory Content', created_at: 'Created At', - updated_at: 'Updated At', + updated_at: 'Last Updated', fullScreen: 'Full Screen', - memoryWindow: "{{name}}'s Window of Memory", + memoryWindow: "{{name}}'s Memory Overview", memory_insight: 'Overall Overview', key_findings: 'Key Findings', behavior_pattern: 'Behavior Pattern', @@ -1311,7 +1311,7 @@ export const en = { tTypeStrict: 'Type Matching Threshold', tOverall: 'Comprehensive Matching Threshold', - arrangementLayerModule: 'Arrangement Layer Module', + arrangementLayerModule: 'Configuration Layer Module', queryMode: 'Query Mode', queryModeSubTitle: 'Control whether to activate deeper search functions', deepRetrieval: 'Deep Retrieval', @@ -1448,11 +1448,11 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re deduplication_desc: 'Deduplication and disambiguation completed, {{count}} unique entities in total' }, memoryConversation: { - searchPlaceholder: 'Input user ID...', + searchPlaceholder: 'Enter user ID...', userID: 'User ID', testMemoryConversation: 'Test Memory Conversation', conversationContent: 'Conversation Content', - conversationContentEmpty: 'There is currently no conversation content available', + conversationContentEmpty: 'No conversation content available.', memoryConversationAnalysis: 'Memory Conversation Analysis', memoryFunction: 'Memory Function', onlineSearch: 'Online Search', @@ -1472,8 +1472,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re quickReply: 'Quick Reply', web_search: 'Online search', memory: 'Memory', - memoryConversationAnalysisEmpty: 'There is currently no dialogue analysis content available', - memoryConversationAnalysisEmptySubTitle: 'After entering your user ID, click on "Test Memory" to view the conversation memory', + memoryConversationAnalysisEmpty: 'No conversation analysis available.', + memoryConversationAnalysisEmptySubTitle: 'Conversation analysis will appear here.', }, login: { title: 'Red Bear Memory Science', @@ -1526,7 +1526,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re notFoundDesc: 'Try returning to the previous page', noPermission: 'Oh, this is an exclusive domain for permissions', noPermissionDesc: ' Please contact the administrator to grant permission', - tableEmpty: 'There are currently no data', + tableEmpty: 'No data available.', loadingEmpty: 'The content is loading…', loadingEmptyDesc: 'Your content is on its way by rocket! It will soon land on your screen' }, @@ -1556,12 +1556,12 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re inactive: 'Expired' }, tool: { - mcp: 'MCP Service', + mcp: 'MCP Services', inner: 'Built-in Tools', custom: 'Custom Tools', - mcpSearchPlaceholder: 'Search MCP services...', - innerSearchPlaceholder: 'Search tools...', - customSearchPlaceholder: 'Search custom tools...', + mcpSearchPlaceholder: 'Search MCP Services...', + innerSearchPlaceholder: 'Search Tools...', + customSearchPlaceholder: 'Search Custom Tools...', addService: 'Add MCP Service', editService: 'Edit MCP Service', addServiceSuccess: 'Service added successfully', @@ -1594,7 +1594,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re requestHeader: 'Request Headers', config: 'Configuration', auth_type: 'Authentication Type', - none: 'No Authentication', + none: 'None', api_key: 'API Key', basic_auth: 'Basic Auth', bearer_token: 'Bearer Token', @@ -1722,9 +1722,11 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re created_at: 'Created At', headerName: 'Header Name', null: 'None', - tagDesc: 'Multiple tags separated by commas', + tagDesc: 'Enter tags (comma-separated)', availableTools: 'Available Tools', name: 'Name', + enterNamePlaceholder: 'Please enter a name', + toolEmpty: 'No tools detected.', desc: 'Description', method: 'Method', path: 'Path', @@ -2145,34 +2147,34 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re personal: { type: 'Personal', label: 'Current Package', - typeDesc: 'For individuals', + typeDesc: 'For Individuals', solution: "A person's second brain, capable of storing up to 2000 memories.", targetAudience: 'individual users, students, and first-time users', - priceDesc: '/Forever free', + priceDesc: '/Free forever', supportServices: 'Community Forum + Email Support', }, team: { type: 'Team', label: 'Small Team', - typeDesc: 'Small Team Version', + typeDesc: 'For Small Teams', solution: "Enable every team to build a shared second brain in seconds.", targetAudience: 'Small teams, early-stage startups, and small projects.', - priceDesc: '/Month', + priceDesc: '/month', supportServices: 'Standard customer service support', }, biz: { type: 'Biz+', label: 'Most Popular', - typeDesc: 'Enterprise Growth Edition', + typeDesc: 'Enterprise Growth Plan', solution: "Scale your organization with a powerful, enterprise-ready second-brain system.", targetAudience: 'Growing teams, startups, and SMBs requiring advanced memory capabilities.', - priceDesc: '/Month/workspace', + priceDesc: 'per workspace / month', supportServices: 'Priority customer service support', }, commerce: { type: 'Commerce', label: 'Commercial OEM', - typeDesc: 'Commercial OEM version', + typeDesc: 'Commercial OEM plan', solution: "Seamlessly integrate advanced memory capabilities into your SaaS or enterprise product.", targetAudience: 'Large enterprises, SaaS vendors, and system integrators requiring fully customizable and secure deployment.', priceDesc: 'On-premises deployment', @@ -2191,8 +2193,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re supportServices: 'Support Services:', flexibleDeployment: 'Flexible deployment:', reliableGuarantee: 'Reliable guarantee:', - alertTitle: 'Intellectual Property Authorization Reminder', - alertContent: 'Please note: Using certain AI models (such as GPT-4, Claude, etc.) may involve third-party API call fees, which are not included in the Memory Bear platform subscription fee. You need to pay the relevant fees separately to the model provider. Memory Bear only charges platform management and service fees and does not bear the usage fees of third-party APIs.', + alertTitle: 'Third-Party API Usage Notice', + alertContent: "Please note: Some AI models(such as GPT- 4, Claude, etc.) may incur third- party API usage fees.These fees are not included in your Memory Bear subscription and must be paid directly to the model provider. Memory Bear charges only for platform management and related services and does not cover or assume responsibility for any third- party API usage fees.", currentAccountType: 'Current Account Type', validUntil: 'Valid Until', orderHistory: 'Order History', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 7663f806..b1a245d3 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1820,6 +1820,8 @@ export const zh = { tagDesc: '多个标签用逗号分隔', availableTools: '可用工具', name: '名称', + enterNamePlaceholder: '请输入名称', + toolEmpty: '未检测到工具', desc: '描述', method: '方法', path: '路径', diff --git a/web/src/utils/auth.ts b/web/src/utils/auth.ts index b7c72527..5ec30028 100644 --- a/web/src/utils/auth.ts +++ b/web/src/utils/auth.ts @@ -1,7 +1,7 @@ import { cookieUtils } from './request' export const clearAuthData = () => { console.log("Clearing auth data and redirecting to login"); - sessionStorage.clear(); - localStorage.clear() + localStorage.removeItem('user') + localStorage.removeItem('breadcrumbs') cookieUtils.clear(); } \ No newline at end of file diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx index 8907e0f1..92170d55 100644 --- a/web/src/views/ApplicationConfig/Agent.tsx +++ b/web/src/views/ApplicationConfig/Agent.tsx @@ -445,7 +445,7 @@ const Agent = forwardRef((_props, ref) => {
diff --git a/web/src/views/MemoryConversation/index.tsx b/web/src/views/MemoryConversation/index.tsx index bb8df5e4..424b9878 100644 --- a/web/src/views/MemoryConversation/index.tsx +++ b/web/src/views/MemoryConversation/index.tsx @@ -151,7 +151,7 @@ const MemoryConversation: FC = () => { > + } contentClassName='rb:h-[calc(100vh-362px)]' data={chatData} diff --git a/web/src/views/ToolManagement/components/CustomToolModal.tsx b/web/src/views/ToolManagement/components/CustomToolModal.tsx index bcda8d51..e3203a74 100644 --- a/web/src/views/ToolManagement/components/CustomToolModal.tsx +++ b/web/src/views/ToolManagement/components/CustomToolModal.tsx @@ -1,5 +1,5 @@ import { forwardRef, useImperativeHandle, useState } from 'react'; -import { Form, Input, Select, Row, Col, App, Button } from 'antd'; +import { Form, Input, Select, App } from 'antd'; import { useTranslation } from 'react-i18next'; import type { CustomToolItem, CustomToolModalRef, ToolItem } from '../types' @@ -134,9 +134,9 @@ const CustomToolModal = forwardRef(({ - + {/* 名称和图标 */} {/* @@ -195,6 +195,7 @@ const CustomToolModal = forwardRef(({ ]} initialData={parseSchemaData.operations || []} emptySize={88} + emptyText={t('tool.toolEmpty')} /> @@ -269,7 +270,7 @@ const CustomToolModal = forwardRef(({