diff --git a/web/src/assets/images/common/arrow_up.svg b/web/src/assets/images/common/arrow_up.svg new file mode 100644 index 00000000..a5105d46 --- /dev/null +++ b/web/src/assets/images/common/arrow_up.svg @@ -0,0 +1,14 @@ + + + 下拉 + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/check_green.svg b/web/src/assets/images/common/check_green.svg new file mode 100644 index 00000000..a16b1ee2 --- /dev/null +++ b/web/src/assets/images/common/check_green.svg @@ -0,0 +1,20 @@ + + + 完成 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/save.svg b/web/src/assets/images/common/save.svg new file mode 100644 index 00000000..5970236d --- /dev/null +++ b/web/src/assets/images/common/save.svg @@ -0,0 +1,19 @@ + + + 保存 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/empty/noData.png b/web/src/assets/images/empty/noData.png new file mode 100644 index 00000000..5258d466 Binary files /dev/null and b/web/src/assets/images/empty/noData.png differ diff --git a/web/src/assets/images/memory/clock_orange.svg b/web/src/assets/images/memory/clock_orange.svg new file mode 100644 index 00000000..5c2b58cf --- /dev/null +++ b/web/src/assets/images/memory/clock_orange.svg @@ -0,0 +1,18 @@ + + + 时间戳 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/memory/debug.svg b/web/src/assets/images/memory/debug.svg new file mode 100644 index 00000000..325a355a --- /dev/null +++ b/web/src/assets/images/memory/debug.svg @@ -0,0 +1,15 @@ + + + 配置管理 + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/userMemory/aboutUs.svg b/web/src/assets/images/userMemory/aboutUs.svg index 1d75eeae..b8fa9e45 100644 --- a/web/src/assets/images/userMemory/aboutUs.svg +++ b/web/src/assets/images/userMemory/aboutUs.svg @@ -1,13 +1,15 @@ - + - - - - - - - + + + + + + + + + diff --git a/web/src/assets/images/userMemory/ai.png b/web/src/assets/images/userMemory/ai.png new file mode 100644 index 00000000..3783a543 Binary files /dev/null and b/web/src/assets/images/userMemory/ai.png differ diff --git a/web/src/assets/images/userMemory/me.svg b/web/src/assets/images/userMemory/me.svg new file mode 100644 index 00000000..b8fa9e45 --- /dev/null +++ b/web/src/assets/images/userMemory/me.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/userMemory/memoryInsight.svg b/web/src/assets/images/userMemory/memoryInsight.svg index bee9cafe..7dfa3dcf 100644 --- a/web/src/assets/images/userMemory/memoryInsight.svg +++ b/web/src/assets/images/userMemory/memoryInsight.svg @@ -1,12 +1,29 @@ - - 热点洞察 - - - - - - + + 编组 26 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/images/userMemory/user.png b/web/src/assets/images/userMemory/user.png new file mode 100644 index 00000000..671ab044 Binary files /dev/null and b/web/src/assets/images/userMemory/user.png differ diff --git a/web/src/components/BtnTabs/index.tsx b/web/src/components/BtnTabs/index.tsx new file mode 100644 index 00000000..772a4c8d --- /dev/null +++ b/web/src/components/BtnTabs/index.tsx @@ -0,0 +1,49 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-03-19 14:05:09 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-19 14:05:09 + */ +import { type FC } from 'react' +import { Flex } from 'antd'; +import clsx from 'clsx' + +/** A single tab item with a display label and unique key */ +interface Tab { + label: string + key: string +} + +/** Props for the BtnTabs component */ +interface BtnTabsProps { + /** List of tab items to render */ + items: Tab[] + /** Key of the currently active tab */ + activeKey: string + /** Callback fired when a tab is clicked */ + onChange: (key: string) => void; + /** Optional extra class name for the container */ + className?: string; +} + +/** Button-style tab switcher — renders tabs as pill-shaped buttons with active highlight */ +const BtnTabs: FC = ({ items, activeKey, onChange, className }) => { + return ( + + {items.map((tab) => ( +
onChange(tab.key)} + className={clsx('rb:px-2 rb:py-1 rb:rounded-[13px] rb:text-[12px] rb:leading-4.5 rb:cursor-pointer', { + 'rb:bg-[#F6F6F6]': activeKey !== tab.key, + 'rb:bg-[#171719] rb:text-white': activeKey === tab.key, + })} + > + {tab.label} +
+ ))} + + ) +} + +export default BtnTabs diff --git a/web/src/components/ModelSelect/index.tsx b/web/src/components/ModelSelect/index.tsx index e5fc280a..a71e9703 100644 --- a/web/src/components/ModelSelect/index.tsx +++ b/web/src/components/ModelSelect/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-03-07 16:49:59 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-07 17:14:57 + * @Last Modified time: 2026-03-18 10:12:23 */ import { useEffect, useState, type FC } from 'react'; import { Select, Flex, Space } from 'antd'; @@ -19,11 +19,13 @@ interface ModelSelectProps extends SelectProps { /** Extra query params passed to getModelList */ params?: Query; placeholder?: string; + fontClassName?: string; } const ModelSelect: FC = ({ params, placeholder, + fontClassName, ...props }) => { const { t } = useTranslation(); @@ -48,7 +50,7 @@ const ModelSelect: FC = ({ return ( {logo && } -
{item.name}
+
{item.name}
); }; diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index b4ce3681..f8f9f59c 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1548,7 +1548,7 @@ export const en = { Meaning: 'Meaning', exampleMemoryExtractionResults: 'Example Memory Extraction Results', - exampleMemoryExtractionResultsSubTitle: '(from a technology conference)', + exampleMemoryExtractionResultsSubTitle: 'from a technology conference', extractTheNumberOfEntities: 'Extract the number of entities', extractTheNumberOfEntitiesDesc: 'Merge after deduplication: {{num}} (exact: {{exact}}, fuzzy: {{fuzzy}}, LLM: {{llm}})', @@ -1670,7 +1670,12 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re disagreementCase: 'Disagreement Case', Pruned: 'Pruned', pruning: 'Pruning', - pruning_desc: 'Text pruning {{count}} fragments' + pruning_desc: 'Text pruning {{count}} fragments', + + processData: 'Process Data', + finalResult: 'Final Result', + chunking: 'Chunking', + dataStatistics: 'Data Statistics', }, memoryConversation: { searchPlaceholder: 'Enter user ID...', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 0eda80e4..ab0896cf 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1546,7 +1546,7 @@ export const zh = { Meaning: '含义', exampleMemoryExtractionResults: '示例记忆提取结果', - exampleMemoryExtractionResultsSubTitle: '(来自技术会议)', + exampleMemoryExtractionResultsSubTitle: '来自技术会议', extractTheNumberOfEntities: '提取实体数量', extractTheNumberOfEntitiesDesc: '去重后合并:{{num}}(精确:{{exact}},模糊:{{fuzzy}},LLM:{{llm}})', @@ -1666,7 +1666,12 @@ export const zh = { disagreementCase: '不一致案例', Pruned: '已剪枝', pruning: '剪枝', - pruning_desc: '文本剪枝{{count}}个片段' + pruning_desc: '文本剪枝{{count}}个片段', + + processData: '处理数据', + finalResult: '最终结果', + chunking: '分块', + dataStatistics: '数据统计', }, memoryConversation: { chatEmpty:'有什么我可以帮您的吗?', diff --git a/web/src/views/ForgettingEngine/components/LineChart.tsx b/web/src/views/ForgettingEngine/components/LineChart.tsx index af36603a..aceb8434 100644 --- a/web/src/views/ForgettingEngine/components/LineChart.tsx +++ b/web/src/views/ForgettingEngine/components/LineChart.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 17:00:20 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-04 10:03:35 + * @Last Modified time: 2026-03-16 15:43:42 */ /** * Line Chart Component @@ -84,7 +84,7 @@ const SeriesConfig = { /** * Chart color palette */ -const Colors = ['#155EEF', '#4DA8FF', '#FFB048'] +const Colors = ['#155EEF', '#4DA8FF', '#369F21'] /** @@ -228,8 +228,8 @@ const LineChart: FC = ({ config }) => { grid: { left: 4, right: '2%', - bottom: 60, - top: 32, + bottom: 48, + top: 8, containLabel: true }, xAxis: { @@ -243,7 +243,7 @@ const LineChart: FC = ({ config }) => { show: true, }, axisTick: { - show: true + show: false }, axisLabel: { color: '#5B6167' @@ -268,7 +268,7 @@ const LineChart: FC = ({ config }) => { ...initialData || [] ] }} - style={{ height: '450px', width: '100%' }} + style={{ height: '400px', width: '100%' }} opts={{ renderer: 'canvas' }} notMerge={true} lazyUpdate={true} diff --git a/web/src/views/ForgettingEngine/index.tsx b/web/src/views/ForgettingEngine/index.tsx index 7a4d94fe..ccfa7769 100644 --- a/web/src/views/ForgettingEngine/index.tsx +++ b/web/src/views/ForgettingEngine/index.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 17:00:12 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 17:00:12 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-16 15:41:54 */ /** * Forgetting Engine Configuration Page @@ -11,16 +11,17 @@ */ import React, { useState, useEffect } from 'react'; -import { Row, Col, Form, Slider, Button, Space, message } from 'antd'; +import { Row, Col, Form, Button, Space, message, Flex, Tooltip } from 'antd'; import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import RbCard from '@/components/RbCard/Card'; -import strategyImpactSimulator from '@/assets/images/memory/strategyImpactSimulator.svg' import LineChart from './components/LineChart' import { getMemoryForgetConfig, updateMemoryForgetConfig } from '@/api/memory' import type { ConfigForm } from './types' import SwitchFormItem from '@/components/FormItem/SwitchFormItem' +import RbSlider from '@/components/RbSlider'; +import DescWrapper from '@/components/FormItem/DescWrapper' /** * Configuration field definitions @@ -154,16 +155,18 @@ const ForgettingEngine: React.FC = () => { } return ( - - - - - {t('forgettingEngine.forgettingEngineConfigParams')} - - } - className='rb:h-full!' + + + + + + } + headerType="borderless" + headerClassName="rb:min-h-[54px]! rb:font-[MiSans-Bold] rb:font-bold" + className="rb:h-[calc(100vh-76px)]!" + bodyClassName="rb:h-[calc(100%-54px)] rb:overflow-y-auto! rb:p-3! rb:pt-0!" >
{ lambda_mem: 0.03, }} > - + {configList.map(config => { if (config.type === 'button') { return ( @@ -182,51 +185,53 @@ const ForgettingEngine: React.FC = () => { title={t(`forgettingEngine.${config.key}`)} name={config.name} desc={config.type && {t(`forgettingEngine.type`)}: {config.type}} - className="rb:mb-2" + className="rb:bg-[#F6F6F6] rb:rounded-xl rb:p-3!" /> ) } return ( -
-
+
+ {t(`forgettingEngine.${config.key}`)} -
- {!config.hiddenDesc &&
- {t(`forgettingEngine.${config.key}Desc`)} -
} + {!config.hiddenDesc && +
+
} + + {t(`forgettingEngine.range`)}: {config.range?.join('-')} | {t(`forgettingEngine.type`)}: {config.type} + } + />} + className="rb:mb-0!" > {config.type === 'decimal' - ? + ? {t('emotionEngine.currentValue')}:} + inputClassName="rb:w-[155px]!" + /> : null } -
- - {config.range && {t(`forgettingEngine.range`)}: {config.range?.join('-')}} - {config.type && {t(`forgettingEngine.type`)}: {config.type}} - - <>{t('forgettingEngine.CurrentValue')}: {values?.[config.name] || 0} -
) })} - - - - - - - - - + - + = ({ @@ -41,27 +41,33 @@ const Card: FC = ({ className, headerClassName, bodyClassName, + extra, }) => { - const { t } = useTranslation() return ( handleExpand(type)} - > - {expanded ? t('common.foldUp') : t('common.expanded')} - handleExpand(type) : undefined} + > + + {title} + {subTitle && +
+
} +
+ {handleExpand &&
-
- )} + })} + >
} +
} + headerType="borderless" className={className} - headerClassName={headerClassName} - bodyClassName={bodyClassName} + headerClassName={`rb:h-[50px]! rb:pb-[12px]! rb:pt-[16px]! rb:leading-[22px]! rb:font-[MiSans-Bold] rb:font-bold rb:text-[16px] ${headerClassName}`} + bodyClassName={`rb:px-3! rb:py-0! ${expanded ? 'rb:pb-3!' : 'rb:pb-0!'} ${bodyClassName}`} + extra={extra} > {(expanded || !(type && handleExpand)) && children}
diff --git a/web/src/views/MemoryExtractionEngine/components/Result.tsx b/web/src/views/MemoryExtractionEngine/components/Result.tsx index 6504f571..3c39bcc1 100644 --- a/web/src/views/MemoryExtractionEngine/components/Result.tsx +++ b/web/src/views/MemoryExtractionEngine/components/Result.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 17:30:11 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-02 11:41:12 + * @Last Modified time: 2026-03-19 14:22:20 */ /** * Result Component @@ -13,13 +13,13 @@ import { type FC, useState } from 'react' import { useParams } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import { Space, Button, Progress, Form, Input } from 'antd' -import { ExclamationCircleFilled, CheckCircleFilled, ClockCircleOutlined, LoadingOutlined } from '@ant-design/icons' +import { Space, Button, Progress, Form, Input, Flex } from 'antd' +import { ExclamationCircleFilled, LoadingOutlined } from '@ant-design/icons' import clsx from 'clsx' +import ResultCard from './ResultCard' import type { AnyObject } from 'antd/es/_util/type'; import Card from './Card' -import RbCard from '@/components/RbCard/Card' import RbAlert from '@/components/RbAlert' import type { TestResult, OntologyCoverage } from '../types' import { pilotRunMemoryExtractionConfig } from '@/api/memory' @@ -27,6 +27,8 @@ import { type SSEMessage } from '@/utils/stream' import Tag, { type TagProps } from '@/components/Tag' import Markdown from '@/components/Markdown' import { groupDataByType } from '../constant' +import Empty from '@/components/Empty' +import NoDataIcon from '@/assets/images/empty/noData.png' /** Result metric mapping */ const resultObj = { @@ -56,7 +58,7 @@ interface ModuleItem { const tagColors: { [key: string]: TagProps['color'] } = { - pending: 'default', + pending: 'warning', processing: 'processing', completed: 'success', failed: 'error' @@ -67,29 +69,55 @@ const initObj = { status: 'pending', result: null } +const initialExpanded = { + text_preprocessing: false, + knowledge_extraction: false, + creating_nodes_edges: false, + deduplication: false, + dataStatistics: false, + entityDeduplicationImpact: false, + disambiguation: false, + coreEntities: false, + triplet_samples: false, + ontologyCoverage: false, +} const Result: FC = ({ loading, handleSave }) => { const { t } = useTranslation(); const { id } = useParams() const [runLoading, setRunLoading] = useState(false) + const [activeTab, setActiveTab] = useState('processData') const [testResult, setTestResult] = useState({} as TestResult) - + const [coreEntitiesTab, setCoreEntitiesTab] = useState(null) const [textPreprocessing, setTextPreprocessing] = useState(initObj as ModuleItem) + const [textPreprocessingTab, setTextPreprocessingTab] = useState('chunking') const [knowledgeExtraction, setKnowledgeExtraction] = useState(initObj as ModuleItem) const [creatingNodesEdges, setCreatingNodesEdges] = useState(initObj as ModuleItem) const [deduplication, setDeduplication] = useState(initObj as ModuleItem) const [ontologyCoverage, setOntologyCoverage] = useState({} as OntologyCoverage) + const [expandedCards, setExpandedCards] = useState>(initialExpanded) + const toggleCard = (key: string) => { + console.log('toggleCard', key) + setExpandedCards(prev => ({ ...prev, [key]: !prev[key] })) + } + console.log('expandedCards', expandedCards) + const [runForm] = Form.useForm() + const customText = Form.useWatch(['custom_text'], runForm) /** Run pilot test */ const handleRun = () => { if(!id) return + setActiveTab('processData') + setCoreEntitiesTab(null) setTextPreprocessing({...initObj} as ModuleItem) + setTextPreprocessingTab('chunking') setKnowledgeExtraction({...initObj} as ModuleItem) setCreatingNodesEdges({...initObj} as ModuleItem) setDeduplication({...initObj} as ModuleItem) setTestResult({} as TestResult) + setExpandedCards(initialExpanded) const handleStreamMessage = (list: SSEMessage[]) => { list.forEach((data: AnyObject) => { @@ -100,6 +128,7 @@ const Result: FC = ({ loading, handleSave }) => { status: 'processing', start_at: data.data.time })) + toggleCard('text_preprocessing') break case 'text_preprocessing_result': // Text preprocessing in progress setTextPreprocessing(prev => ({ @@ -121,6 +150,7 @@ const Result: FC = ({ loading, handleSave }) => { status: 'processing', start_at: data.data.time })) + toggleCard('knowledge_extraction') break case 'knowledge_extraction_result': // Knowledge extraction in progress setKnowledgeExtraction(prev => ({ @@ -142,6 +172,7 @@ const Result: FC = ({ loading, handleSave }) => { status: 'processing', start_at: data.data.time })) + toggleCard('creating_nodes_edges') break case 'creating_nodes_edges_result': // Creating nodes and edges in progress setCreatingNodesEdges(prev => ({ @@ -163,6 +194,7 @@ const Result: FC = ({ loading, handleSave }) => { status: 'processing', start_at: data.data.time })) + toggleCard('deduplication') break case 'dedup_disambiguation_result': // Deduplication and disambiguation in progress setDeduplication(prev => ({ @@ -183,6 +215,15 @@ const Result: FC = ({ loading, handleSave }) => { case 'result': // Result setTestResult(data.data?.extracted_result) setOntologyCoverage(data.data?.ontology_coverage) + setExpandedCards(prev => ({ + ...prev, + dataStatistics: true, + entityDeduplicationImpact: true, + disambiguation: true, + coreEntities: true, + triplet_samples: true, + ontologyCoverage: true, + })) break } }) @@ -203,9 +244,10 @@ const Result: FC = ({ loading, handleSave }) => { /** Format status tag */ const formatTag = (status: string) => { return ( - - {status === 'pending' && } + + {status === 'pending' &&
} {status === 'processing' && } + {status === 'completed' &&
} {t(`memoryExtractionEngine.status.${status}`)}
) @@ -213,294 +255,411 @@ const Result: FC = ({ loading, handleSave }) => { /** Format processing time */ const formatTime = (data: ModuleItem, color?: string) => { if (typeof data.end_at === 'number' && typeof data.start_at === 'number') { - return
{t('memoryExtractionEngine.time')}{data.end_at - data.start_at}ms
+ return
{t('memoryExtractionEngine.time')}{data.end_at - data.start_at}ms
} return null } /** Convert first character to lowercase */ const lowercaseFirst = (str: string) => str.charAt(0).toLowerCase() + str.slice(1) + return ( + + + } > -
+ {/* } className="rb:mb-3!"> + {t('memoryExtractionEngine.warning')} + */} + + +
{t('memoryExtractionEngine.custom_text')}
+
{customText?.length || 0}
+
- +
-
- {runLoading - ? <> - } className="rb:mb-3.5"> + + {runLoading + ? <> + +
{t('memoryExtractionEngine.processing')} - - {/* Overall Progress */} -
-
- {t('memoryExtractionEngine.overallProgress')} - {`${completedNum}/4`} -
- + + {/* Overall Progress */} + + +
+ {t('memoryExtractionEngine.overallProgress')}{`${(completedNum*100/4).toFixed(0)}%`} +
+
- - : !testResult || Object.keys(testResult).length === 0 - ? } className="rb:mb-3.5"> - {t('memoryExtractionEngine.warning')} - - : } className="rb:mb-3.5"> - {t('memoryExtractionEngine.success')} - - } - - {/* Text Preprocessing */} - - {textPreprocessing.data.map((vo, index) => { - if (vo.deleted_messages) { - return
-
{t('memoryExtractionEngine.Pruned')}
- {vo.deleted_messages.map((msg: any, idx: number) => ( -
- -
- ))} -
- } - return ( -
- -
- ) + + + : !testResult || Object.keys(testResult).length === 0 + ? }> + {t('memoryExtractionEngine.warning')} + + :
}> + {t('memoryExtractionEngine.success')} +
+ } + + + {['processData', 'finalResult'].map(tab => ( +
} className="rb:mt-3"> + onClick={() => setActiveTab(tab)} + >{t(`memoryExtractionEngine.${tab}`)}
+ ))} +
+ + {activeTab === 'processData' && + {/* Text Preprocessing */} + toggleCard('text_preprocessing')} + > + {expandedCards['text_preprocessing'] && textPreprocessing.data?.length > 0 && + + {(['chunking', ...(textPreprocessing.data.some(vo => vo.deleted_messages) ? ['pruning'] : [])] as string[]).map(type => ( +
setTextPreprocessingTab(type)} + > + {t(`memoryExtractionEngine.${type}`)} +
+ ))} +
+ } + {expandedCards['text_preprocessing'] && textPreprocessing.result && + +
+
{formatTime(textPreprocessing)}
{t('memoryExtractionEngine.pruning_desc', { count: textPreprocessing.result.pruning.deleted_count || 0 })}, {t('memoryExtractionEngine.text_preprocessing_desc', { count: textPreprocessing.result.total_chunks })}, {t('memoryExtractionEngine.chunkerStrategy')}: {t(`memoryExtractionEngine.${lowercaseFirst(textPreprocessing.result.chunker_strategy)}`)} - - } - - {/* Knowledge Extraction */} - - {knowledgeExtraction.data.map((vo, index) => -
{vo.statement}
- )} - {formatTime(knowledgeExtraction)} - {knowledgeExtraction.result && } className="rb:mt-3"> - {t('memoryExtractionEngine.knowledge_extraction_desc', { - entities: knowledgeExtraction.result.entities_count, - statements: knowledgeExtraction.result.statements_count, - temporal_ranges_count: knowledgeExtraction.result.temporal_ranges_count, - triplets: knowledgeExtraction.result.triplets_count - })} - } -
- {/* Creating Entity Relationships */} - - {creatingNodesEdges.data?.map((vo, index) => ( -
- {vo?.result_type === 'entity_nodes_creation' - ? <>{vo.type_display_name}: {vo.entity_names.join(', ')} - : <>{vo?.relationship_text} - }
- ))} - {formatTime(creatingNodesEdges, '#9C6FFF')} - {creatingNodesEdges.result && } className="rb:mt-3"> - {t('memoryExtractionEngine.creating_nodes_edges_desc', {num: creatingNodesEdges.result.entity_entity_edges_count})} - } -
- {/* Deduplication and Disambiguation */} - - {Object.keys(deduplicationData).length > 0 && Object.keys(deduplicationData).map(key => { - return deduplicationData[key].map((vo, index) => ( -
- {vo.message} -
- )) - })} - {formatTime(deduplication, '#9C6FFF')} - {deduplication.result && } className="rb:mt-3"> - {t('memoryExtractionEngine.deduplication_desc', { count: deduplication.result.summary.total_merges })}
-
} -
- - {testResult && Object.keys(testResult).length > 0 && resultObj && Object.keys(resultObj).length > 0 && - -
- {Object.keys(resultObj).map((key, index) => { - const keys = (resultObj as Record)[key].split('.') - return ( -
-
{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}
-
{t(`memoryExtractionEngine.${key}`)}
-
- {key === 'extractTheNumberOfEntities' && testResult.dedup - ? t(`memoryExtractionEngine.${key}Desc`, { - num: testResult.dedup.total_merged_count, - exact: testResult.dedup.breakdown.exact, - fuzzy: testResult.dedup.breakdown.fuzzy, - llm: testResult.dedup.breakdown.llm, - }) - : key === 'numberOfEntityDisambiguation' && testResult.disambiguation - ? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.disambiguation.effects?.length, block_count: testResult.disambiguation.block_count }) - : key === 'numberOfRelationalTriples' && testResult.triplets - ? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.triplets.count }) - :t(`memoryExtractionEngine.${key}Desc`) - } -
-
- )})} -
-
+ } - - {testResult?.dedup?.impact && testResult.dedup.impact?.length > 0 && - -
{t('memoryExtractionEngine.identifyDuplicates')}
- {testResult.dedup.impact.map((item, index) => ( -
- -{t('memoryExtractionEngine.identifyDuplicatesDesc', { ...item })} + {expandedCards['text_preprocessing'] && textPreprocessing.data.map((vo, index) => { + if (vo.deleted_messages && textPreprocessingTab === 'pruning') { + return
+
{t('memoryExtractionEngine.Pruned')}
+ {vo.deleted_messages.map((msg: any, idx: number) => ( +
+
-{t('memoryExtractionEngine.pruning')}{idx}:
+ +
+ ))} +
+ } + if (textPreprocessingTab === 'chunking') { + return ( +
+
-{t('memoryExtractionEngine.fragment')}{vo.chunk_index}:
+
- ))} + ) + } + return null + })} + + {/* Knowledge Extraction */} + toggleCard('knowledge_extraction')} + > + {knowledgeExtraction.result && + +
+
{formatTime(knowledgeExtraction)}
+ {t('memoryExtractionEngine.knowledge_extraction_desc', { + entities: knowledgeExtraction.result.entities_count, + statements: knowledgeExtraction.result.statements_count, + temporal_ranges_count: knowledgeExtraction.result.temporal_ranges_count, + triplets: knowledgeExtraction.result.triplets_count + })} +
+
+ } + {knowledgeExtraction.data?.length > 0 && +
    + {knowledgeExtraction.data.map((vo, index) => +
  • {vo.statement}
  • + )} +
+ } +
+ {/* Creating Entity Relationships */} + toggleCard('creating_nodes_edges')} + > + {creatingNodesEdges.result && + +
+
{formatTime(creatingNodesEdges)}
+ {t('memoryExtractionEngine.creating_nodes_edges_desc', { num: creatingNodesEdges.result.entity_entity_edges_count })} +
+
+ } + {creatingNodesEdges.data?.length > 0 && +
    + {creatingNodesEdges.data.map((vo, index) => +
  • + {vo?.result_type === 'entity_nodes_creation' + ? <>{vo.type_display_name}: {vo.entity_names.join(', ')} + : <>{vo?.relationship_text} + } +
  • + )} +
+ } +
+ {/* Deduplication and Disambiguation */} + toggleCard('deduplication')} + > + {deduplication.result && + +
+
{formatTime(deduplication)}
+ {t('memoryExtractionEngine.deduplication_desc', { count: deduplication.result.summary.total_merges })} +
+
+ } + {Object.keys(deduplicationData).length > 0 && +
    + {Object.keys(deduplicationData).map(key => { + return deduplicationData[key].map((vo, index) => ( +
  • + {vo.message} +
  • + )) + })} +
+ } +
+ } - } className="rb:mt-3"> + {activeTab === 'finalResult' && + {!testResult || Object.keys(testResult).length === 0 + ? + : null + } + + {testResult && Object.keys(testResult).length > 0 && resultObj && Object.keys(resultObj).length > 0 && + toggleCard('dataStatistics')} + > +
+ {Object.keys(resultObj).map((key, index) => { + const keys = (resultObj as Record)[key].split('.') + return ( +
+
{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}
+
{t(`memoryExtractionEngine.${key}`)}
+
+ {key === 'extractTheNumberOfEntities' && testResult.dedup + ? t(`memoryExtractionEngine.${key}Desc`, { + num: testResult.dedup.total_merged_count, + exact: testResult.dedup.breakdown.exact, + fuzzy: testResult.dedup.breakdown.fuzzy, + llm: testResult.dedup.breakdown.llm, + }) + : key === 'numberOfEntityDisambiguation' && testResult.disambiguation + ? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.disambiguation.effects?.length, block_count: testResult.disambiguation.block_count }) + : key === 'numberOfRelationalTriples' && testResult.triplets + ? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.triplets.count }) + : t(`memoryExtractionEngine.${key}Desc`) + } +
+
+ ) + })} +
+
+ } + + {testResult?.dedup?.impact && testResult.dedup.impact?.length > 0 && + toggleCard('entityDeduplicationImpact')} + > +
+ {t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })} - - } +
{t('memoryExtractionEngine.identifyDuplicates')}:
- {testResult?.disambiguation && testResult.disambiguation?.effects?.length > 0 && - +
    + {testResult.dedup.impact.map((item, index) => ( +
  • + {t('memoryExtractionEngine.identifyDuplicatesDesc', { ...item })} +
  • + ))} +
+
+
+ } + + {testResult?.disambiguation && testResult.disambiguation?.effects?.length > 0 && + toggleCard('disambiguation')} + > +
+ + {t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })} + {testResult.disambiguation.effects.map((item, index) => (
0, + 'rb:mt-5': index > 0, })}> -
{t('memoryExtractionEngine.disagreementCase')} {index +1}:
- -{item.left.name}({item.left.type}) vs {item.right.name}({item.right.type}) → {item.result} +
{t('memoryExtractionEngine.disagreementCase')} {index + 1}:
+ +
    +
  • + {item.left.name}({item.left.type}) vs {item.right.name}({item.right.type}) → {item.result} +
  • +
))} +
+
+ } - } className="rb:mt-3"> - {t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })} - - - } + {testResult?.core_entities && testResult?.core_entities.length > 0 && + toggleCard('coreEntities')} + > + + {testResult.core_entities.map((item, index) => ( +
setCoreEntitiesTab(item.type)} + > + {item.type}({item.count}) +
+ ))} +
+
+ {testResult.core_entities.filter((item, index) => (coreEntitiesTab && item.type === coreEntitiesTab) || (!coreEntitiesTab && index === 0)).map((item, idx) => ( +
+
{item.type}({item.count})
- {testResult?.core_entities && testResult?.core_entities.length > 0 && - -
- {testResult.core_entities.map((item, idx) => ( -
-
{item.type}({item.count})
+
    + {item.entities.map((entity, index) => ( +
  • + {entity} +
  • + ))} +
+
+ ))} +
+ + } -
- {item.entities.map((entity, index) => ( -
- -{entity} -
- ))} -
-
- ))} -
- - } - - {testResult?.triplet_samples && testResult?.triplet_samples.length > 0 && - - - {testResult.triplet_samples.map((item, index) => ( -
- -({item.subject}, {item.predicate}, {item.object}) -
- ))} -
- } className="rb:mt-3"> + {testResult?.triplet_samples && testResult?.triplet_samples.length > 0 && + toggleCard('triplet_samples')} + > +
+ {t('memoryExtractionEngine.extractRelationalTriplesDesc', { count: testResult.triplet_samples.length })} - - } - {ontologyCoverage && Object.keys(ontologyCoverage).length > 0 && - {t('memoryExtractionEngine.ontologyCoverage')}({ontologyCoverage.total_entities})} - headerType="borderL" - headerClassName="rb:before:bg-[#369F21]!" - > -
+
    + {testResult.triplet_samples.map((item, index) => ( +
  • + ({item.subject}, {item.predicate}, {item.object}) +
  • + ))} +
+
+ + } + {ontologyCoverage && Object.keys(ontologyCoverage).length > 0 && + {t('memoryExtractionEngine.ontologyCoverage')}({ontologyCoverage.total_entities})} + expanded={expandedCards['ontologyCoverage']} + handleExpand={() => toggleCard('ontologyCoverage')} + > +
+
{(['scene_type_distribution', 'general_type_distribution', 'unmatched'] as const).map((key, idx) => { if (!ontologyCoverage[key]) return null return ( -
-
{t(`memoryExtractionEngine.${key}`)}({ontologyCoverage[key].type_count})
-
{t('memoryExtractionEngine.entity_total', { num: ontologyCoverage[key].entity_total })}
-
+
+
{t(`memoryExtractionEngine.${key}`)}({ontologyCoverage[key].type_count})
+
{t('memoryExtractionEngine.entity_total', { num: ontologyCoverage[key].entity_total })}
+ +
    {ontologyCoverage[key].types.map((type, index) => { if (!type.type || type.type === '') return null return ( -
    - -{type.type}({type.count}) -
    +
  • + {type.type}({type.count}) +
  • ) })} -
+
) })}
- - } - - -
- -
- - -
+
+
+ } + } ) } diff --git a/web/src/views/MemoryExtractionEngine/components/ResultCard.tsx b/web/src/views/MemoryExtractionEngine/components/ResultCard.tsx new file mode 100644 index 00000000..17e065a2 --- /dev/null +++ b/web/src/views/MemoryExtractionEngine/components/ResultCard.tsx @@ -0,0 +1,78 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-02-03 17:30:51 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-19 14:23:58 + */ +/** + * ResultCard Component + * Collapsible card wrapper for configuration sections + */ + +import { type FC, type ReactNode } from 'react' +import clsx from 'clsx'; +import { Flex, Space, Tooltip } from 'antd'; + +import RbCard from '@/components/RbCard/Card' + +/** + * Component props + */ +interface ResultCardProps { + title: string | ReactNode; + subTitle?: string | ReactNode; + children: ReactNode; + expanded?: boolean; + handleExpand?: () => void; + className?: string; + headerClassName?: string; + bodyClassName?: string; + extra?: ReactNode; +} + +const ResultCard: FC = ({ + title, + subTitle, + children, + expanded, + handleExpand, + extra, + className, + headerClassName, + bodyClassName, +}) => { + return ( + + + {title} + {subTitle && +
+
} +
+ + {extra} + {handleExpand &&
} +
+
} + headerType="borderless" + headerClassName={headerClassName ?? "rb:min-h-[40px]! rb:text-[#212332]! rb:text-[14px]!"} + bodyClassName={bodyClassName ?? "rb:py-0! rb:px-3!"} + className={className ?? "rb:bg-[#F6F6F6]!"} + > + {(expanded && handleExpand) && children} +
+ ) +} + +export default ResultCard \ No newline at end of file diff --git a/web/src/views/MemoryExtractionEngine/index.tsx b/web/src/views/MemoryExtractionEngine/index.tsx index e5c8f477..e5e80577 100644 --- a/web/src/views/MemoryExtractionEngine/index.tsx +++ b/web/src/views/MemoryExtractionEngine/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 17:30:02 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-06 13:50:05 + * @Last Modified time: 2026-03-18 17:55:32 */ /** * Memory Extraction Engine Configuration Page @@ -13,18 +13,19 @@ import { type FC, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom' -import { Row, Col, Space, Select, InputNumber, Slider, App, Form, Input } from 'antd' +import { Row, Col, Space, Select, InputNumber, App, Form, Input, Flex, Tooltip } from 'antd' import clsx from 'clsx' import Card from './components/Card' import type { ConfigForm, Variable } from './types' import { getMemoryExtractionConfig, updateMemoryExtractionConfig } from '@/api/memory' import Markdown from '@/components/Markdown' -import { getModelListUrl } from '@/api/models'; import { configList } from './constant' import Result from './components/Result' import SwitchFormItem from '@/components/FormItem/SwitchFormItem' -import CustomSelect from '@/components/CustomSelect' +import ModelSelect from '@/components/ModelSelect' +import RbSlider from '@/components/RbSlider'; +import DescWrapper from '@/components/FormItem/DescWrapper' /** Available configuration section keys */ const keys = [ @@ -35,7 +36,7 @@ const keys = [ /** * Configuration description component */ -const ConfigDesc: FC<{ config: Variable, className?: string; onlyMeaning?: boolean; }> = ({ config, className, onlyMeaning = false}) => { +const Desc: FC<{ config: Variable, className?: string; onlyMeaning?: boolean; }> = ({ config, className, onlyMeaning = false}) => { const { t } = useTranslation(); return (
@@ -44,7 +45,6 @@ const ConfigDesc: FC<{ config: Variable, className?: string; onlyMeaning?: boole {config.control && {t('memoryExtractionEngine.control')}: {t(`memoryExtractionEngine.${config.control}`)}} {config.type && {t('memoryExtractionEngine.type')}: {config.type}} } - {config.meaning &&
{t('memoryExtractionEngine.Meaning')}: {t(`memoryExtractionEngine.${config.meaning}`)}
}
) } @@ -122,163 +122,139 @@ const MemoryExtractionEngine: FC = () => { return ( <> -
{t('memoryExtractionEngine.title')}
-
{t('memoryExtractionEngine.subTitle')}
+ + {t('memoryExtractionEngine.title')} + +
+
+
- + -
- - - -
- -
- - {expandedKeys.includes('example') && -
- -
- } -
- - -
- - {configList.map((item, index) => ( - - - {item.data.map(vo => ( -
-
{t(`memoryExtractionEngine.${vo.title}`)}
-
{t(`memoryExtractionEngine.${vo.title}SubTitle`)}
+ + + + + + - {vo.list.map(config => ( -
- {config.control === 'button' && - -{t(`memoryExtractionEngine.${config.label}`)}} - name={config.variableName} - desc={} - className="rb:mt-6" - /> - } - {config.control === 'select' && - <> -
- -{t(`memoryExtractionEngine.${config.label}`)} -
-
+
+ handleExpand('example')} + > + {t('memoryExtractionEngine.example')} +
+
+ + {expandedKeys.includes('example') && +
+ +
+ } +
+ +
+ + {configList.map((item, index) => ( + + + {item.data.map(vo => ( + + + {t(`memoryExtractionEngine.${vo.title}`)} + +
+
+
+ + {vo.list.map(config => ( +
+ {config.control === 'button' + ? } />} + className="rb:mt-6" + /> + : <> + {config.meaning + ? + {t(`memoryExtractionEngine.${config.label}`)} + {t('memoryExtractionEngine.Meaning')}: {t(`memoryExtractionEngine.${config.meaning}`)}}> +
+
+
+ :
+ {t(`memoryExtractionEngine.${config.label}`)} +
+ } + {config.control !== 'text' && } />} - ({ ...item, label: t(`memoryExtractionEngine.${item.label}`) })) : []} + /> + : config.control === 'slider' + ? {t('emotionEngine.currentValue')}:} + inputClassName="rb:w-[155px]!" + /> + : config.control === 'inputNumber' + ? + : config.control === 'text' + ? + : null + } - -
- - } - {config.control === 'slider' && - <> -
- -{t(`memoryExtractionEngine.${config.label}`)} -
-
- - - - -
- {config.min || 0} - {t('memoryExtractionEngine.CurrentValue')}: {values?.[config.variableName as keyof ConfigForm]} -
-
- - } - {config.control === 'inputNumber' && - <> -
- -{t(`memoryExtractionEngine.${config.label}`)} -
-
- - - - -
- - } - {config.control === 'text' && - <> -
- -{t(`memoryExtractionEngine.${config.label}`)} -
-
- - - - -
- - } -
- ))} -
- ))} - - - ))} - - + + } +
+ ))} + + ))} + +
+ ))} + + + - + { return ( - - - {t('reflectionEngine.reflectionEngineConfig')} -
- } + + + + } + headerType="borderless" + headerClassName="rb:min-h-[54px]! rb:font-[MiSans-Bold] rb:font-bold" + className="rb:h-[calc(100vh-76px)]!" + bodyClassName="rb:h-[calc(100%-54px)] rb:overflow-y-auto! rb:p-4! rb:pt-0!" >
{ lambda_mem: 0.03, }} > - {configList.map(config => { - if (config.type === 'customSelect') { - return ( -
-
- {t(`reflectionEngine.${config.key}`)} + + {configList.map(config => { + if (config.type === 'customSelect') { + return ( +
+ + + + + +
- - - -
- ) - } - if (config.type === 'select') { - return ( -
-
- {t(`reflectionEngine.${config.key}`)} + ) + } + if (config.type === 'select') { + return ( +
+ + + + + ({ - ...vo, - label: t(`reflectionEngine.${vo.label}`), - }))} - placeholder={t('common.pleaseSelect')} - disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'} - /> - -
- ) - } + ) + } - return ( - - {(config as any).hasSubTitle &&
{t(`reflectionEngine.${config.key}_subTitle`)}
} -
{t(`reflectionEngine.${config.key}_desc`)}
- } - className="rb:mb-6" - disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'} - /> - ) - })} - - - - - - - - + return ( + + {(config as any).hasSubTitle &&
{t(`reflectionEngine.${config.key}_subTitle`)}
} +
{t(`reflectionEngine.${config.key}_desc`)}
+ } + className="rb:mb-6" + disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'} + /> + ) + })} + diff --git a/web/src/views/UserMemory/index.tsx b/web/src/views/UserMemory/index.tsx index c8a6b753..765cca05 100644 --- a/web/src/views/UserMemory/index.tsx +++ b/web/src/views/UserMemory/index.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 17:53:44 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-16 15:01:27 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-16 15:23:18 */ /** * User Memory Page @@ -104,7 +104,7 @@ export default function UserMemory() { title={
{name[0]}
-
{name || '-'}
+
{name || '-'}
} headerType="border" headerClassName="rb:h-[48px]! rb:mx-4!" diff --git a/web/src/views/UserMemoryDetail/Rag.tsx b/web/src/views/UserMemoryDetail/Rag.tsx index 2d3443ce..9d714070 100644 --- a/web/src/views/UserMemoryDetail/Rag.tsx +++ b/web/src/views/UserMemoryDetail/Rag.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 17:57:11 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 17:57:11 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-19 11:38:17 */ /** * RAG User Memory Detail View @@ -12,83 +12,55 @@ import { type FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import clsx from 'clsx' -import { Row, Col, Skeleton } from 'antd' +import { Row, Col, Skeleton, Flex } from 'antd' import { useParams } from 'react-router-dom' import aboutUs from '@/assets/images/userMemory/aboutUs.svg' -import down from '@/assets/images/userMemory/down.svg' -import interestDistribution from '@/assets/images/userMemory/interestDistribution.svg' +import memoryInsight from '@/assets/images/userMemory/memoryInsight.svg' import RbCard from '@/components/RbCard/Card' import type { Data } from './types' import { getChunkSummaryTag, getUserProfile, - getTotalRagMemoryCountByUser, getChunkInsight, } from '@/api/memory' import Empty from '@/components/Empty' import ConversationMemory from './components/ConversationMemory' -/** Tag color palette */ -const tagColors = ['21, 94, 239', '156, 111, 255', '255, 93, 52', '54, 159, 33'] - /** * Title component props */ interface TitleProps { - type: string; title: string icon: string - t: (key: string) => string; - expanded: boolean; - onClick: (type: string) => void; } /** Collapsible section title */ -const Title: FC = ({ type, title, icon, t, expanded, onClick }) => ( -
- - - {title} - - - onClick(type)}> - {t(`userMemory.${expanded ? 'foldUp' : 'expanded'}`)} - - -
+const Title: FC = ({ title, icon }) => ( + + + {title} + ) const Rag: FC = () => { const { t } = useTranslation() const { id } = useParams() const [data, setData] = useState(null) - const [expanded, setExpanded] = useState(['aboutUs', 'memoryInsight',]) const [summary, setSummary] = useState('') const [loading, setLoading] = useState>({ detail: true, summary: true, insight: true, }) - const [memory, setMemory] = useState(null) const [insight, setInsight] = useState('') - const [tags, setTags] = useState<{ tag: string; frequency: number }[]>([]) - const [personas, setPersonas] = useState([]) useEffect(() => { if (!id) return - getMemory() getSummary() getDetail() getInsightReport() }, [id]) - /** Toggle section expansion */ - const handleTitleClick = (key: string) => { - setExpanded(expanded.includes(key) ? expanded.filter((item) => item !== key) : [...expanded, key]) - } /** Fetch user memory detail */ const getDetail = () => { if (!id) return @@ -100,13 +72,6 @@ const Rag: FC = () => { setLoading(prev => ({ ...prev, detail: false })) }) } - /** Fetch memory count */ - const getMemory = () => { - if (!id) return - getTotalRagMemoryCountByUser(id).then((res) => { - setMemory(res as number || 0) - }) - } /** Fetch user summary */ const getSummary = () => { if (!id) return @@ -114,8 +79,6 @@ const Rag: FC = () => { getChunkSummaryTag(id).then((res) => { const response = res as { summary?: string; tags?: { tag: string; frequency: number }[]; personas?: string[] } setSummary(response.summary || null) - setTags(response.tags || []) - setPersonas(response.personas || []) }) .finally(() => { setLoading(prev => ({ ...prev, summary: false })) @@ -134,82 +97,51 @@ const Rag: FC = () => { } const name = loading.detail ? '' : data?.name && data?.name !== '' ? data.name : id return ( - + - -
-
{name?.[0]}
-
- {name}
-
{personas?.join(' | ')}
+ + +
{name?.[0]}
+
+ {name}
-
- -
- {tags?.map((tag, tagIndex) => ( - - {tag.tag}({tag.frequency}) - - ))} -
- - {/* Total Memory */} -
- {t('userMemory.totalNumOfMemories')} -
{memory || 0}
-
+ {/* About Me */} <> - {expanded.includes('aboutUs') && ( - <> - {loading.summary - ? <Skeleton className="rb:mt-4" /> - : summary - ? <div className="rb:font-regular rb:leading-5.5 rb:pt-4"> - {summary || '-'} - </div> - : <Empty size={88} className="rb:mt-12 rb:mb-20.25" /> - } - </> - )} + <div className="rb:bg-[#F6F6F6] rb:rounded-lg rb:py-2.5 rb:px-3 rb:mb-4"> + {loading.summary + ? <Skeleton /> + : summary + ? <div className="rb:leading-5 rb:text-[#5B6167]"> + {summary || '-'} + </div> + : <Empty size={88} /> + } + </div> </> {/* Memory Insights */} <> <Title - type="memoryInsight" title={t('userMemory.memoryInsight')} - icon={interestDistribution} - t={t} - expanded={expanded.includes('memoryInsight')} - onClick={handleTitleClick} + icon={memoryInsight} /> - {expanded.includes('memoryInsight') && ( - <> - {loading.insight - ? <Skeleton className="rb:mt-4" /> - : insight - ? <div className="rb:font-regular rb:leading-5.5 rb:pt-4"> - {insight || '-'} - </div> - : <Empty size={88} className="rb:mt-12 rb:mb-20.25" /> - } - </> - )} + <div className="rb:bg-[#F6F6F6] rb:rounded-lg rb:py-2.5 rb:px-3"> + {loading.insight + ? <Skeleton /> + : insight + ? <div className="rb:leading-5 rb:text-[#5B6167]"> + {insight || '-'} + </div> + : <Empty size={88} /> + } + </div> </> </RbCard> </Col> diff --git a/web/src/views/UserMemoryDetail/components/EmotionLine.tsx b/web/src/views/UserMemoryDetail/components/EmotionLine.tsx index b5e76097..43f65758 100644 --- a/web/src/views/UserMemoryDetail/components/EmotionLine.tsx +++ b/web/src/views/UserMemoryDetail/components/EmotionLine.tsx @@ -1,12 +1,13 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 18:33:44 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 18:33:44 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-19 11:55:42 */ import { type FC, useRef } from 'react' import { useTranslation } from 'react-i18next' import ReactEcharts from 'echarts-for-react'; +import { Flex } from 'antd'; import Empty from '@/components/Empty' import Loading from '@/components/Empty/Loading' @@ -63,12 +64,12 @@ const EmotionLine: FC<EmotionLineProps> = ({ chartData, loading }) => { } return ( - <> - <div>{t('userMemory.emotionLine')}</div> + <Flex vertical gap={16} className="rb-border rb:rounded-xl rb:p-4! rb:h-78"> + <div className="rb:text-[#212332] rb:font-medium rb:leading-5">{t('userMemory.emotionLine')}</div> {loading ? <Loading size={249} /> : !chartData || chartData.length === 0 - ? <Empty size={120} className="rb:mt-12 rb:mb-20.25" /> + ? <Empty size={120} className="rb:flex-1" /> : <ReactEcharts ref={chartRef} option={{ @@ -175,12 +176,12 @@ const EmotionLine: FC<EmotionLineProps> = ({ chartData, loading }) => { }, series: getSeries() }} - style={{ height: '265px', width: '100%', minWidth: '100%' }} + style={{ height: '242px', width: '100%', minWidth: '100%' }} notMerge={true} lazyUpdate={true} /> } - </> + </Flex> ) } diff --git a/web/src/views/UserMemoryDetail/components/Habits.tsx b/web/src/views/UserMemoryDetail/components/Habits.tsx index 9dfad7c7..f5ccab03 100644 --- a/web/src/views/UserMemoryDetail/components/Habits.tsx +++ b/web/src/views/UserMemoryDetail/components/Habits.tsx @@ -77,7 +77,7 @@ const Habits = forwardRef<{ handleRefresh: () => void; }>((_props, ref) => { title={() => (<Space size={4}> {t('implicitDetail.habits')} <Tooltip title={t('implicitDetail.habitsSubTitle')}> - <div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/userMemory/question.svg')]"></div> + <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/userMemory/question.svg')]"></div> </Tooltip> </Space>)} headerType="borderless" diff --git a/web/src/views/UserMemoryDetail/components/InteractionBar.tsx b/web/src/views/UserMemoryDetail/components/InteractionBar.tsx index b874caea..314fb024 100644 --- a/web/src/views/UserMemoryDetail/components/InteractionBar.tsx +++ b/web/src/views/UserMemoryDetail/components/InteractionBar.tsx @@ -1,12 +1,13 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 18:32:57 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 18:32:57 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-19 11:56:49 */ import { type FC, useMemo } from 'react' import { useTranslation } from 'react-i18next' import ReactEcharts from 'echarts-for-react' +import { Flex } from 'antd' import Empty from '@/components/Empty' import Loading from '@/components/Empty/Loading' @@ -41,12 +42,12 @@ const InteractionBar: FC<InteractionBarProps> = ({ chartData, loading }) => { }, [chartData, t]) return ( - <> - <div>{t('userMemory.interaction')}</div> + <Flex vertical gap={16} className="rb-border rb:rounded-xl rb:p-4! rb:h-78"> + <div className="rb:text-[#212332] rb:font-medium rb:leading-5">{t('userMemory.emotionLine')}</div> {loading ? <Loading size={249} /> : !chartData || chartData.length === 0 - ? <Empty size={120} className="rb:mt-12 rb:mb-20.25" /> + ? <Empty size={120} className="rb:flex-1" /> : <ReactEcharts option={{ color: Colors, @@ -128,10 +129,10 @@ const InteractionBar: FC<InteractionBarProps> = ({ chartData, loading }) => { }, series }} - style={{ height: '265px', width: '100%' }} + style={{ height: '242px', width: '100%', minWidth: '100%' }} /> } - </> + </Flex> ) } diff --git a/web/src/views/UserMemoryDetail/pages/GraphDetail.tsx b/web/src/views/UserMemoryDetail/pages/GraphDetail.tsx index 47efce76..f8282687 100644 --- a/web/src/views/UserMemoryDetail/pages/GraphDetail.tsx +++ b/web/src/views/UserMemoryDetail/pages/GraphDetail.tsx @@ -1,7 +1,7 @@ import { useState, forwardRef, useImperativeHandle, useMemo, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { useSearchParams } from 'react-router-dom' -import { Row, Col, Tabs, Space, Skeleton } from 'antd' +import { useSearchParams, useNavigate } from 'react-router-dom' +import { Row, Col, Flex, Space, Skeleton, Button } from 'antd' import { getRelationshipEvolution, getTimelineMemories } from '@/api/memory' import type { Node, GraphDetailRef } from '../types' @@ -11,7 +11,8 @@ import { formatDateTime } from '@/utils/format' import Tag from '@/components/Tag' import InteractionBar from '../components/InteractionBar' import Empty from '@/components/Empty' -import PageHeader from '../components/PageHeader' +import PageHeader from '@/components/Layout/PageHeader' +import BtnTabs from '@/components/BtnTabs' export interface Emotion { emotion_intensity: number; @@ -36,6 +37,7 @@ interface Timeline { const GraphDetail = forwardRef<GraphDetailRef>((_props, ref) => { const { t } = useTranslation() + const navigate = useNavigate() const [searchParams] = useSearchParams() const [vo, setVo] = useState<Node | null>(null) const [loading, setLoading] = useState(false) @@ -97,54 +99,75 @@ const GraphDetail = forwardRef<GraphDetailRef>((_props, ref) => { return ( <> <PageHeader - name={vo?.name} - source="node" + title={vo?.name} + extra={ + <Space size={12}> + <Button + className="rb:px-2! rb:gap-0.5!" + icon={<div className="rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/common/return.svg')]"></div>} + onClick={() => navigate(-1)} + > + {t('common.return')} + </Button> + </Space> + } /> - <div className="rb:h-full rb:max-w-266 rb:mx-auto"> - <div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3">{t('userMemory.relationshipEvolution')}</div> - <RbCard> - <Row gutter={16}> - <Col span={12}> + <Row gutter={12} className="rb:p-3! rb:pr-0! rb:h-[calc(100vh-64px)] rb:w-full! rb:flex-nowrap! rb:overflow-hidden!"> + <Col flex="480px"> + <RbCard + title={t('userMemory.relationshipEvolution')} + headerType="borderless" + headerClassName="rb:min-h-[56px]! rb:font-[MiSans-Bold] rb:font-bold" + bodyClassName="rb:p-3! rb:pt-0! rb:h-[calc(100%-56px)] rb:overflow-y-auto!" + className="rb:h-[calc(100vh-88px)]!" + > + <Flex vertical gap={16}> <EmotionLine chartData={emotionData} loading={loading} /> - </Col> - <Col span={12}> <InteractionBar chartData={interactionData} loading={loading} /> - </Col> - </Row> - </RbCard> - - <div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3 rb:mt-6">{t('userMemory.timelineMemories')}</div> - <RbCard> - <Tabs - activeKey={activeTab} - items={['timelines_memory', 'Statement', 'MemorySummary'].map(key => ({ - label: t(`userMemory.${key}`), - key - }))} - onChange={(key: string) => setActiveTab(key)} - /> - {timelineLoading - ? <Skeleton active /> - : !activeContent || activeContent.length === 0 - ? <Empty size={120} className="rb:mt-12 rb:mb-20.25" /> - : <Space size={16} direction="vertical" className="rb:w-full"> - {activeContent.map((vo, index) => ( - <RbCard - key={index} - headerType="borderL" - headerClassName="rb:before:bg-[#155EEF]!" - title={vo.text} - > - <div className="rb:text-[#A8A9AA] rb:text-[12px] rb:leading-4">{formatDateTime(vo.created_at)}</div> - <Tag className="rb:mt-2">{vo.type}</Tag> - </RbCard> - ))} - </Space> - } - - - </RbCard> - </div> + </Flex> + </RbCard> + </Col> + <Col className="rb:w-[calc(100%-480px)]!"> + <RbCard + title={t('userMemory.timelineMemories')} + headerType="borderless" + headerClassName="rb:min-h-[53px]! rb:font-[MiSans-Bold] rb:font-bold" + bodyClassName="rb:p-3! rb:pt-0!" + className="rb:w-full!" + > + <BtnTabs + className="rb:mb-4!" + activeKey={activeTab} + items={['timelines_memory', 'Statement', 'MemorySummary'].map(key => ({ + label: t(`userMemory.${key}`), + key + }))} + onChange={(key: string) => setActiveTab(key)} + /> + <div className="rb:h-[calc(100vh-193px)] rb:overflow-y-auto"> + {timelineLoading + ? <Skeleton active /> + : !activeContent || activeContent.length === 0 + ? <Empty size={120} className="rb:mt-12 rb:mb-20.25" /> + : <Flex gap={12} vertical> + {activeContent.map((vo, index) => ( + <div + key={index} + className="rb-border rb:rounded-xl rb:p-3" + > + <Flex align="center" justify="space-between"> + <div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5">{formatDateTime(vo.created_at)}</div> + <Tag>{vo.type}</Tag> + </Flex> + <div className="rb:mt-3 rb:leading-5 rb:break-all">{vo.text}</div> + </div> + ))} + </Flex> + } + </div> + </RbCard> + </Col> + </Row> </> ) }) diff --git a/web/src/views/UserMemoryDetail/pages/ShortTermDetail.tsx b/web/src/views/UserMemoryDetail/pages/ShortTermDetail.tsx index 3fb222d0..8b38aa54 100644 --- a/web/src/views/UserMemoryDetail/pages/ShortTermDetail.tsx +++ b/web/src/views/UserMemoryDetail/pages/ShortTermDetail.tsx @@ -110,7 +110,7 @@ const ShortTermDetail: FC = () => { title={() => (<Space size={4}> {t('shortTermDetail.shortTermTitle')} <Tooltip title={t('shortTermDetail.shortTermSubTitle')}> - <div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/userMemory/question.svg')]"></div> + <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/userMemory/question.svg')]"></div> </Tooltip> </Space>)} headerType="borderless" @@ -194,7 +194,7 @@ const ShortTermDetail: FC = () => { title={() => (<Space size={4}> {t('shortTermDetail.longTermTitle')} <Tooltip title={t('shortTermDetail.longTermTitleSubTitle')}> - <div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/userMemory/question.svg')]"></div> + <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/userMemory/question.svg')]"></div> </Tooltip> </Space>)} headerType="borderless" diff --git a/web/src/views/Workflow/components/Nodes/AddNode.tsx b/web/src/views/Workflow/components/Nodes/AddNode.tsx index 4f443f07..9b9d2236 100644 --- a/web/src/views/Workflow/components/Nodes/AddNode.tsx +++ b/web/src/views/Workflow/components/Nodes/AddNode.tsx @@ -178,7 +178,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { 'rb:border-[#d1d5db] rb:bg-[#FCFCFD] rb:text-[#374151]': !data.isSelected })} > - <div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/workflow/node_plus.png')]"></div> + <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/workflow/node_plus.png')]"></div> {data.label} </Flex> </Popover> diff --git a/web/src/views/Workflow/components/Nodes/NodeTools.tsx b/web/src/views/Workflow/components/Nodes/NodeTools.tsx index 46aa82e2..4948f35d 100644 --- a/web/src/views/Workflow/components/Nodes/NodeTools.tsx +++ b/web/src/views/Workflow/components/Nodes/NodeTools.tsx @@ -27,7 +27,7 @@ const NodeTools: FC<{ node: Node }> = ({ <Dropdown menu={{ items: [ - { key: 'delete', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/common/delete_dark.svg')]"></div>, label: <Flex>{t('common.delete')}</Flex>}, + { key: 'delete', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/delete_dark.svg')]"></div>, label: <Flex>{t('common.delete')}</Flex>}, // { key: 'copy', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/copy_dark.svg')]"></div>, label: t('common.copy') } ], onClick: handleClick diff --git a/web/src/views/Workflow/components/Properties/CaseList/index.tsx b/web/src/views/Workflow/components/Properties/CaseList/index.tsx index a72e91bf..0fec5c64 100644 --- a/web/src/views/Workflow/components/Properties/CaseList/index.tsx +++ b/web/src/views/Workflow/components/Properties/CaseList/index.tsx @@ -248,7 +248,7 @@ const CaseList: FC<CaseListProps> = ({ <Form.Item name={[caseField.name, 'logical_operator']} noStyle > <Space size={2} className="rb:cursor-pointer rb:text-[#155EEF] rb:leading-4.5 rb:font-medium rb-border rb:py-px! rb:px-1! rb:rounded-sm" onClick={() => handleChangeLogicalOperator(caseIndex)}> {logicalOperator} - <div className="rb:size-3 rb:bg-cover rb:bg-[url('src/assets/images/workflow/refresh_active.svg')]"></div> + <div className="rb:size-3 rb:bg-cover rb:bg-[url('@/assets/images/workflow/refresh_active.svg')]"></div> </Space> </Form.Item> </div> diff --git a/web/src/views/Workflow/components/Properties/ConditionList/index.tsx b/web/src/views/Workflow/components/Properties/ConditionList/index.tsx index 15daf5e0..9028ed9f 100644 --- a/web/src/views/Workflow/components/Properties/ConditionList/index.tsx +++ b/web/src/views/Workflow/components/Properties/ConditionList/index.tsx @@ -114,7 +114,7 @@ const ConditionList: FC<CaseListProps> = ({ <Form.Item name={[parentName, 'logical_operator']} noStyle > <Space size={2} className="rb:cursor-pointer rb:text-[#155EEF] rb:leading-4.5 rb:font-medium rb-border rb:py-px! rb:px-1! rb:rounded-sm" onClick={handleChangeLogicalOperator}> {logicalOperator} - <div className="rb:size-3 rb:bg-cover rb:bg-[url('src/assets/images/workflow/refresh_active.svg')]"></div> + <div className="rb:size-3 rb:bg-cover rb:bg-[url('@/assets/images/workflow/refresh_active.svg')]"></div> </Space> </Form.Item> </div> diff --git a/web/src/views/Workflow/components/Properties/index.tsx b/web/src/views/Workflow/components/Properties/index.tsx index 283640c7..b74c14e3 100644 --- a/web/src/views/Workflow/components/Properties/index.tsx +++ b/web/src/views/Workflow/components/Properties/index.tsx @@ -434,7 +434,7 @@ const Properties: FC<PropertiesProps> = ({ <Dropdown menu={{ items: [ - { key: 'delete', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/common/delete_dark.svg')]"></div>, label: <Flex>{t('common.delete')}</Flex> }, + { key: 'delete', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/delete_dark.svg')]"></div>, label: <Flex>{t('common.delete')}</Flex> }, // { key: 'copy', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/copy_dark.svg')]"></div>, label: t('common.copy') } ], onClick: handleClick @@ -834,7 +834,7 @@ const Properties: FC<PropertiesProps> = ({ <Flex align="center" className="rb:font-medium rb:cursor-pointer" onClick={handleToggle}> {t('workflow.config.output')} <div - className={clsx("rb:size-3 rb:bg-cover rb:bg-[url('src/assets/images/common/caret_right_outlined.svg')]", { + className={clsx("rb:size-3 rb:bg-cover rb:bg-[url('@/assets/images/common/caret_right_outlined.svg')]", { 'rb:rotate-90': !outputCollapsed })} ></div>