diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index bc757797..1df2eb6d 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -2064,6 +2064,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re stability: 'Stability', resilience: 'Resilience', suggestions: 'Personalized Suggestions', + suggestionLoading: 'Your personalized suggestions are being generated', }, reflectionEngine: { reflectionEngineConfig: 'Reflection Engine Configuration', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 2e88ad4a..44f17f49 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -2158,6 +2158,7 @@ export const zh = { stability: '稳定性', resilience: '恢复力', suggestions: '个性化建议', + suggestionLoading: '您的个性化建议正在生成中', }, reflectionEngine: { reflectionEngineConfig: '反思引擎配置', diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts index 447ff88c..e49fcb3c 100644 --- a/web/src/utils/request.ts +++ b/web/src/utils/request.ts @@ -52,6 +52,8 @@ service.interceptors.request.use( config.headers.Authorization = `Bearer ${token}`; } } + const language = localStorage.getItem('language') + config.headers['X-Language-Type'] = language || 'en'; config.headers.Cookie = undefined return config; }, @@ -146,7 +148,7 @@ service.interceptors.response.use( break; default: if (!msg && Array.isArray(error.response?.data?.detail)) { - msg = error.response?.data?.detail?.map(item => item.msg).join(';') + msg = error.response?.data?.detail?.map((item: { msg: string }) => item.msg).join(';') } else { msg = msg || i18n.t('common.unknownError'); } diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx index 28c96ec0..77e90440 100644 --- a/web/src/views/ApplicationConfig/Agent.tsx +++ b/web/src/views/ApplicationConfig/Agent.tsx @@ -128,7 +128,11 @@ const Agent = forwardRef((_props, ref) => { let allTools = Array.isArray(response.tools) ? response.tools : [] form.setFieldsValue({ ...response, - tools: allTools + tools: allTools, + memory: { + ...response.memory, + memory_content: response.memory?.memory_content ? Number(response.memory?.memory_content) : undefined + } }) setData({ ...response, diff --git a/web/src/views/UserMemoryDetail/components/MemoryInsight.tsx b/web/src/views/UserMemoryDetail/components/MemoryInsight.tsx index 7dd31fc4..5146a5ac 100644 --- a/web/src/views/UserMemoryDetail/components/MemoryInsight.tsx +++ b/web/src/views/UserMemoryDetail/components/MemoryInsight.tsx @@ -57,7 +57,8 @@ const MemoryInsight = forwardRef((_props, ref) => { : Object.keys(data).length > 0 ? {['memory_insight', 'key_findings', 'behavior_pattern', 'growth_trajectory'].map(key => { - if (data[key as keyof Data]) { + const value = data[key as keyof Data]; + if (Array.isArray(value) && value.length > 0 || (!Array.isArray(value) && value)) { return (
= ({ {operation}
- - {extra} - +
); }; diff --git a/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx b/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx index 7a927479..55e94270 100644 --- a/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx +++ b/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx @@ -266,7 +266,7 @@ const RelationshipNetwork:FC = () => { size={[197.81, 150]} /> : <> -
{selectedNode.name}
+ {selectedNode.name &&
{selectedNode.name}
}
<>
{t('userMemory.memoryContent')}
@@ -297,7 +297,8 @@ const RelationshipNetwork:FC = () => { {selectedNode.label === 'Statement' && <> {(['emotion_keywords', 'emotion_type', 'emotion_subject', 'importance_score'] as const).map(key => { const statementProps = selectedNode.properties as StatementNodeProperties; - if ((key === 'emotion_keywords' && statementProps[key]?.length > 0) || statementProps[key]) { + if ((key === 'emotion_keywords' && statementProps[key]?.length > 0) || typeof statementProps[key] === 'string') { + console.log('statementProps[key]', statementProps[key]) return (
{t(`userMemory.Statement_${key}`)} @@ -321,7 +322,10 @@ const RelationshipNetwork:FC = () => {
{t(`userMemory.ExtractedEntity_${key}`)}
- {entityProps[key]} + {Array.isArray(entityProps[key]) && entityProps[key].length > 0 + ? entityProps[key].map((vo, index) =>
- {vo}
) + : entityProps[key] + }
) diff --git a/web/src/views/UserMemoryDetail/components/Suggestions.tsx b/web/src/views/UserMemoryDetail/components/Suggestions.tsx index c2c8ca8b..2687e457 100644 --- a/web/src/views/UserMemoryDetail/components/Suggestions.tsx +++ b/web/src/views/UserMemoryDetail/components/Suggestions.tsx @@ -21,6 +21,7 @@ interface Suggestions { const Suggestions = forwardRef<{ handleRefresh: () => void; }>((_props, ref) => { const { t } = useTranslation() const { id } = useParams() + const [loading, setLoading] = useState(false) const [suggestions, setSuggestions] = useState(null) useEffect(() => { @@ -31,10 +32,14 @@ const Suggestions = forwardRef<{ handleRefresh: () => void; }>((_props, ref) => if (!id) { return } + setLoading(true) getEmotionSuggestions(id) .then((res) => { setSuggestions(res as Suggestions) }) + .finally(() => { + setLoading(false) + }) } useImperativeHandle(ref, () => ({ @@ -63,7 +68,7 @@ const Suggestions = forwardRef<{ handleRefresh: () => void; }>((_props, ref) => ))}
- : + : } ) diff --git a/web/src/views/UserMemoryDetail/pages/ImplicitDetail.tsx b/web/src/views/UserMemoryDetail/pages/ImplicitDetail.tsx index d79407da..dfe5c1ee 100644 --- a/web/src/views/UserMemoryDetail/pages/ImplicitDetail.tsx +++ b/web/src/views/UserMemoryDetail/pages/ImplicitDetail.tsx @@ -20,14 +20,22 @@ const ImplicitDetail = forwardRef<{ handleRefresh: () => void; }>((_props, ref) const habitsRef = useRef<{ handleRefresh: () => void; }>(null) const handleRefresh = () => { - if (!id) return - generateProfile(id) - .then(() => { - preferencesRef.current?.handleRefresh() - portraitRef.current?.handleRefresh() - interestAreasRef.current?.handleRefresh() - habitsRef.current?.handleRefresh() - }) + if (!id) { + return Promise.resolve() + } + return new Promise((resolve, reject) => { + generateProfile(id) + .then(() => { + preferencesRef.current?.handleRefresh() + portraitRef.current?.handleRefresh() + interestAreasRef.current?.handleRefresh() + habitsRef.current?.handleRefresh() + resolve(true) + }) + .catch((error) => { + reject(error) + }) + }) } useImperativeHandle(ref, () => ({ handleRefresh diff --git a/web/src/views/UserMemoryDetail/pages/StatementDetail.tsx b/web/src/views/UserMemoryDetail/pages/StatementDetail.tsx index 6515263e..72d35c60 100644 --- a/web/src/views/UserMemoryDetail/pages/StatementDetail.tsx +++ b/web/src/views/UserMemoryDetail/pages/StatementDetail.tsx @@ -13,11 +13,20 @@ const StatementDetail = forwardRef((_props, ref) => { const { id } = useParams() const suggestionsRef = useRef<{ handleRefresh: () => void; }>(null) const handleRefresh = () => { - if (!id) return - generateSuggestions(id) - .then(() => { - suggestionsRef.current?.handleRefresh() - }) + if (!id) { + return Promise.resolve() + } + + return new Promise((resolve, reject) => { + generateSuggestions(id) + .then(() => { + suggestionsRef.current?.handleRefresh() + resolve(true) + }) + .catch((error) => { + reject(error) + }) + }) } useImperativeHandle(ref, () => ({ handleRefresh diff --git a/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx b/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx index 7ba7c414..843a3b23 100644 --- a/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx +++ b/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx @@ -2,7 +2,7 @@ import { type FC, useEffect, useState, useMemo } from 'react' import clsx from 'clsx' import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom' -import { Row, Col, Select, Form, Space, Skeleton, Input, Button, Divider } from 'antd' +import { Row, Col, Skeleton, Button, Divider, Tooltip } from 'antd' import RbCard from '@/components/RbCard/Card' import { getConversations, @@ -10,7 +10,6 @@ import { getConversationDetail, } from '@/api/memory' import { formatDateTime } from '@/utils/format' -import Tag from '@/components/Tag' import RbAlert from '@/components/RbAlert' import Empty from '@/components/Empty' import ChatContent from '@/components/Chat/ChatContent' @@ -33,7 +32,6 @@ interface Detail { const WorkingDetail: FC = () => { const { t } = useTranslation() const { id } = useParams() - const [form] = Form.useForm() const [loading, setLoading] = useState(false) const [data, setData] = useState([]) const [messagesLoading, setMessagesLoading] = useState(false) @@ -110,13 +108,15 @@ const WorkingDetail: FC = () => {
{data.map(item => (
-
setSelected(item)} - > - {item.title} -
+ +
setSelected(item)} + > + {item.title} +
+
))}
diff --git a/web/src/views/UserMemoryDetail/pages/index.tsx b/web/src/views/UserMemoryDetail/pages/index.tsx index 16004edc..c5dea163 100644 --- a/web/src/views/UserMemoryDetail/pages/index.tsx +++ b/web/src/views/UserMemoryDetail/pages/index.tsx @@ -2,6 +2,7 @@ import { type FC, useEffect, useState, useMemo, useRef } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { Dropdown, Button } from 'antd' +import { LoadingOutlined } from '@ant-design/icons'; import PageHeader from '../components/PageHeader' import StatementDetail from './StatementDetail' @@ -46,18 +47,30 @@ const Detail: FC = () => { const onClick = ({ key }: { key: string }) => { navigate(`/user-memory/detail/${id}/${key}`, { replace: true }) } + + const [loading, setLoading] = useState(false) const handleRefresh = () => { + setLoading(true) + let response: any = null switch(type) { case 'FORGET_MEMORY': forgetDetailRef.current?.handleRefresh() break; case 'EMOTIONAL_MEMORY': - statementDetailRef.current?.handleRefresh() + response = statementDetailRef.current?.handleRefresh() break case 'IMPLICIT_MEMORY': - implicitDetailRef.current?.handleRefresh() + response = implicitDetailRef.current?.handleRefresh() break } + + if (response instanceof Promise) { + response.finally(() => { + setLoading(false) + }) + } else { + setLoading(false) + } } if (type === 'GRAPH') { @@ -80,8 +93,8 @@ const Detail: FC = () => { } extra={['FORGET_MEMORY', 'EMOTIONAL_MEMORY', 'IMPLICIT_MEMORY'].includes(type as string) && - } /> diff --git a/web/src/views/Workflow/components/Properties/CaseList/index.tsx b/web/src/views/Workflow/components/Properties/CaseList/index.tsx index d9521059..f76bd7db 100644 --- a/web/src/views/Workflow/components/Properties/CaseList/index.tsx +++ b/web/src/views/Workflow/components/Properties/CaseList/index.tsx @@ -6,7 +6,7 @@ import { Form, Button, Select, Space, Divider, InputNumber, Radio, type SelectPr import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' import VariableSelect from '../VariableSelect' import Editor from '../../Editor' -import { edgeAttrs } from '../../../constant' +import { edgeAttrs, portArgs } from '../../../constant' interface CaseListProps { value?: Array<{ logical_operator: 'and' | 'or'; expressions: { left: string; operator: string; right: string; input_type?: string; }[] }>; @@ -92,6 +92,7 @@ const CaseList: FC = ({ selectedNode.addPort({ id: 'CASE1', group: 'right', + args: portArgs, attrs: { text: { text: 'IF', fontSize: 12, fill: '#5B6167' }} }); @@ -100,6 +101,7 @@ const CaseList: FC = ({ selectedNode.addPort({ id: `CASE${i + 1}`, group: 'right', + args: portArgs, attrs: { text: { text: 'ELIF', fontSize: 12, fill: '#5B6167' }} }); } @@ -108,6 +110,7 @@ const CaseList: FC = ({ selectedNode.addPort({ id: `CASE${caseCount + 1}`, group: 'right', + args: portArgs, attrs: { text: { text: 'ELSE', fontSize: 12, fill: '#5B6167' }} }); diff --git a/web/src/views/Workflow/components/Properties/CategoryList/index.tsx b/web/src/views/Workflow/components/Properties/CategoryList/index.tsx index aabc3ad3..22163905 100644 --- a/web/src/views/Workflow/components/Properties/CategoryList/index.tsx +++ b/web/src/views/Workflow/components/Properties/CategoryList/index.tsx @@ -5,7 +5,7 @@ import { Graph, Node } from '@antv/x6'; import Editor from '../../Editor'; import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' -import { edgeAttrs } from '../../../constant' +import { edgeAttrs, portArgs } from '../../../constant' interface CategoryListProps { parentName: string; @@ -55,7 +55,7 @@ const CategoryList: FC = ({ parentName, selectedNode, graphRe selectedNode.addPort({ id: `CASE${i + 1}`, group: 'right', - args: i === 0 ? { dy: 24 } : undefined, + args: portArgs, attrs: { text: { text: `分类${i + 1}`, fontSize: 12, fill: '#5B6167' } } }); } diff --git a/web/src/views/Workflow/components/Properties/hooks/useVariableList.ts b/web/src/views/Workflow/components/Properties/hooks/useVariableList.ts index ab37fec9..16c32b7c 100644 --- a/web/src/views/Workflow/components/Properties/hooks/useVariableList.ts +++ b/web/src/views/Workflow/components/Properties/hooks/useVariableList.ts @@ -190,6 +190,12 @@ export const useVariableList = ( if (iv?.dataType.startsWith('array[')) itemType = iv.dataType.replace(/^array\[(.+)\]$/, '$1'); addVariable(list, keys, `${pid}_item`, 'item', itemType, `${pid}.item`, pd); addVariable(list, keys, `${pid}_index`, 'index', 'number', `${pid}.index`, pd); + } else if (pd.type === 'iteration' && !pd.config.input.defaultValue) { + let itemType = 'object'; + const iv = list.find(v => `{{${v.value}}}` === pd.config.input.defaultValue); + if (iv?.dataType.startsWith('array[')) itemType = iv.dataType.replace(/^array\[(.+)\]$/, '$1'); + addVariable(list, keys, `${pid}_item`, 'item', 'string', `${pid}.item`, pd); + addVariable(list, keys, `${pid}_index`, 'index', 'number', `${pid}.index`, pd); } } diff --git a/web/src/views/Workflow/components/Properties/index.tsx b/web/src/views/Workflow/components/Properties/index.tsx index d55e1d9e..26ad0470 100644 --- a/web/src/views/Workflow/components/Properties/index.tsx +++ b/web/src/views/Workflow/components/Properties/index.tsx @@ -291,7 +291,69 @@ const Properties: FC = ({ return filteredList; } if (nodeType === 'iteration' && key === 'output') { - return variableList.filter(variable => variable.value.includes('sys.')); + let filteredList = variableList.filter(variable => variable.value.includes('sys.')); + // Add child node output variables for loop nodes + if (selectedNode) { + const graph = graphRef.current; + if (graph) { + const nodes = graph.getNodes(); + const childNodes = nodes.filter(node => { + const nodeData = node.getData(); + return nodeData?.cycle === selectedNode.id; + }); + + // Add output variables from child nodes + childNodes.forEach(childNode => { + const childData = childNode.getData(); + const childNodeId = childData.id; + + // Add child node output variables based on their type + switch (childData.type) { + case 'llm': + case 'jinja-render': + case 'tool': + const outputKey = `${childNodeId}_output`; + const existingOutput = filteredList.find(v => v.key === outputKey); + if (!existingOutput) { + filteredList.push({ + key: outputKey, + label: 'output', + type: 'variable', + dataType: 'string', + value: `${childNodeId}.output`, + nodeData: childData, + }); + } + break; + case 'http-request': + const bodyKey = `${childNodeId}_body`; + const statusKey = `${childNodeId}_status_code`; + if (!filteredList.find(v => v.key === bodyKey)) { + filteredList.push({ + key: bodyKey, + label: 'body', + type: 'variable', + dataType: 'string', + value: `${childNodeId}.body`, + nodeData: childData, + }); + } + if (!filteredList.find(v => v.key === statusKey)) { + filteredList.push({ + key: statusKey, + label: 'status_code', + type: 'variable', + dataType: 'number', + value: `${childNodeId}.status_code`, + nodeData: childData, + }); + } + break; + } + }); + } + } + return filteredList; } if (nodeType === 'iteration') { return variableList.filter(variable => variable.dataType.includes('array')); @@ -411,7 +473,7 @@ const Properties: FC = ({ /> : selectedNode?.data?.type === 'tool' ? - : selectedNode?.data.type === 'jinja-render' + : selectedNode?.data?.type === 'jinja-render' ? = { iteration: { width: 240, @@ -591,8 +592,8 @@ export const graphNodeLibrary: Record = { groups: defaultPortGroups, items: [ { group: 'left' }, - { group: 'right', id: 'CASE1', args: { dy: 24 }, attrs: { text: { text: 'IF', fontSize: 12, color: '#5B6167' }} }, - { group: 'right', id: 'CASE2', attrs: { text: { text: 'ELSE', fontSize: 12, color: '#5B6167' }} } + { group: 'right', id: 'CASE1', args: portArgs, attrs: { text: { text: 'IF', fontSize: 12, color: '#5B6167' }} }, + { group: 'right', id: 'CASE2', args: portArgs, attrs: { text: { text: 'ELSE', fontSize: 12, color: '#5B6167' }} } ], }, }, @@ -604,8 +605,8 @@ export const graphNodeLibrary: Record = { groups: defaultPortGroups, items: [ { group: 'left' }, - { group: 'right', id: 'CASE1', args: { dy: 24 }, attrs: { text: { text: '分类1', fontSize: 12, color: '#5B6167' } } }, - { group: 'right', id: 'CASE2', attrs: { text: { text: '分类2', fontSize: 12, color: '#5B6167' } } } + { group: 'right', id: 'CASE1', args: portArgs, attrs: { text: { text: '分类1', fontSize: 12, color: '#5B6167' } } }, + { group: 'right', id: 'CASE2', args: portArgs, attrs: { text: { text: '分类2', fontSize: 12, color: '#5B6167' } } } ], }, }, diff --git a/web/src/views/Workflow/hooks/useWorkflowGraph.ts b/web/src/views/Workflow/hooks/useWorkflowGraph.ts index f8a5a6bc..0cc69fea 100644 --- a/web/src/views/Workflow/hooks/useWorkflowGraph.ts +++ b/web/src/views/Workflow/hooks/useWorkflowGraph.ts @@ -5,7 +5,7 @@ import { App } from 'antd' import { Graph, Node, MiniMap, Snapline, Clipboard, Keyboard, type Edge } from '@antv/x6'; import { register } from '@antv/x6-react-shape'; -import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color } from '../constant'; +import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portArgs } from '../constant'; import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types'; import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application' import type { PortMetadata } from '@antv/x6/lib/model/port'; @@ -132,7 +132,7 @@ export const useWorkflowGraph = ({ const portItems: PortMetadata[] = [ { group: 'left' }, - { group: 'right', id: 'CASE1', args: { dy: 24 }, attrs: { text: { text: 'IF', fontSize: 12, fill: '#5B6167' }} } + { group: 'right', id: 'CASE1', args: portArgs, attrs: { text: { text: 'IF', fontSize: 12, fill: '#5B6167' }} } ]; // 添加 ELIF 端口 @@ -140,6 +140,7 @@ export const useWorkflowGraph = ({ portItems.push({ group: 'right', id: `CASE${i + 1}`, + args: portArgs, attrs: { text: { text: 'ELIF', fontSize: 12, fill: '#5B6167' }} }); } @@ -148,6 +149,7 @@ export const useWorkflowGraph = ({ portItems.push({ group: 'right', id: `CASE${caseCount + 1}`, + args: portArgs, attrs: { text: { text: 'ELSE', fontSize: 12, fill: '#5B6167' }} }); @@ -173,12 +175,12 @@ export const useWorkflowGraph = ({ ]; // 添加分类端口 - config.categories.forEach((category: any, index: number) => { + config.categories.forEach((_category: any, index: number) => { portItems.push({ group: 'right', id: `CASE${index + 1}`, - args: index === 0 ? { dy: 24 } : undefined, - attrs: { text: { text: category.class_name || `分类${index + 1}`, fontSize: 12, fill: '#5B6167' }} + args: portArgs, + attrs: { text: { text: `分类${index + 1}`, fontSize: 12, fill: '#5B6167' }} }); });