feat(web): memory manage & memory detail ui upgrade
This commit is contained in:
@@ -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<LineCardProps> = ({ config }) => {
|
||||
grid: {
|
||||
left: 4,
|
||||
right: '2%',
|
||||
bottom: 60,
|
||||
top: 32,
|
||||
bottom: 48,
|
||||
top: 8,
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
@@ -243,7 +243,7 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
|
||||
show: true,
|
||||
},
|
||||
axisTick: {
|
||||
show: true
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#5B6167'
|
||||
@@ -268,7 +268,7 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
|
||||
...initialData || []
|
||||
]
|
||||
}}
|
||||
style={{ height: '450px', width: '100%' }}
|
||||
style={{ height: '400px', width: '100%' }}
|
||||
opts={{ renderer: 'canvas' }}
|
||||
notMerge={true}
|
||||
lazyUpdate={true}
|
||||
|
||||
@@ -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 (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={9}>
|
||||
<RbCard
|
||||
title={
|
||||
<div className="rb:flex rb:items-center">
|
||||
<img src={strategyImpactSimulator} className="rb:w-5 rb:h-5 rb:mr-2" />
|
||||
{t('forgettingEngine.forgettingEngineConfigParams')}
|
||||
</div>
|
||||
}
|
||||
className='rb:h-full!'
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<RbCard
|
||||
title={t('forgettingEngine.forgettingEngineConfigParams')}
|
||||
extra={<Space>
|
||||
<Button block onClick={handleReset}>{t('common.reset')}</Button>
|
||||
<Button type="primary" loading={loading} block onClick={handleSave}>{t('common.save')}</Button>
|
||||
</Space>}
|
||||
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!"
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
@@ -174,7 +177,7 @@ const ForgettingEngine: React.FC = () => {
|
||||
lambda_mem: 0.03,
|
||||
}}
|
||||
>
|
||||
<Space size={24} direction="vertical" style={{ width: '100%' }}>
|
||||
<Flex vertical gap={12}>
|
||||
{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 && <span>{t(`forgettingEngine.type`)}: {config.type}</span>}
|
||||
className="rb:mb-2"
|
||||
className="rb:bg-[#F6F6F6] rb:rounded-xl rb:p-3!"
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div key={config.key}>
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mb-2">
|
||||
<div key={config.key} className="rb:bg-[#F6F6F6] rb:rounded-xl rb:p-3">
|
||||
<Flex align="center" gap={4} className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mb-2">
|
||||
{t(`forgettingEngine.${config.key}`)}
|
||||
</div>
|
||||
{!config.hiddenDesc && <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 ">
|
||||
{t(`forgettingEngine.${config.key}Desc`)}
|
||||
</div>}
|
||||
{!config.hiddenDesc && <Tooltip title={t(`forgettingEngine.${config.key}Desc`)}>
|
||||
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
|
||||
</Tooltip>}
|
||||
</Flex>
|
||||
|
||||
<Form.Item
|
||||
name={config.name}
|
||||
extra={<DescWrapper
|
||||
desc={<>
|
||||
<span className="rb:text-[12px]">{t(`forgettingEngine.range`)}: {config.range?.join('-')}</span> | <span>{t(`forgettingEngine.type`)}: {config.type}</span>
|
||||
</>}
|
||||
/>}
|
||||
className="rb:mb-0!"
|
||||
>
|
||||
{config.type === 'decimal'
|
||||
? <Slider tooltip={{ open: false }} max={config.range?.[1] || 1} min={config.range?.[0] || 0} step={config.step ?? 0.01} style={{ margin: '0' }} />
|
||||
? <RbSlider
|
||||
max={config.range?.[1] || 1}
|
||||
min={config.range?.[0] || 0}
|
||||
step={config.step ?? 0.01}
|
||||
isInput={true}
|
||||
prefix={<span className="rb:text-[#5B6167]">{t('emotionEngine.currentValue')}:</span>}
|
||||
inputClassName="rb:w-[155px]!"
|
||||
/>
|
||||
: null
|
||||
}
|
||||
</Form.Item>
|
||||
<div className="rb:flex rb:text-[12px] rb:items-center rb:justify-between rb:text-[#5B6167] rb:leading-5 rb:-mt-6.5">
|
||||
<Space size={4}>
|
||||
{config.range && <span>{t(`forgettingEngine.range`)}: {config.range?.join('-')}</span>}
|
||||
{config.type && <span>{t(`forgettingEngine.type`)}: {config.type}</span>}
|
||||
</Space>
|
||||
<>{t('forgettingEngine.CurrentValue')}: {values?.[config.name] || 0}</>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Button block onClick={handleReset}>{t('common.reset')}</Button>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Button type="primary" loading={loading} block onClick={handleSave}>{t('common.save')}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Space>
|
||||
</Flex>
|
||||
</Form>
|
||||
</RbCard>
|
||||
</Col>
|
||||
<Col span={15}>
|
||||
<Col span={12}>
|
||||
<RbCard
|
||||
title={t('forgettingEngine.forgettingCurve')}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:min-h-[54px]! rb:font-[MiSans-Bold] rb:font-bold"
|
||||
bodyClassName="rb:p-3! rb:pt-0!"
|
||||
>
|
||||
<LineChart
|
||||
config={values}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 17:30:51
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 17:30:51
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-19 14:06:38
|
||||
*/
|
||||
/**
|
||||
* Card Component
|
||||
@@ -10,11 +10,10 @@
|
||||
*/
|
||||
|
||||
import { type FC, type ReactNode } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import clsx from 'clsx';
|
||||
import { Flex, Space, Tooltip } from 'antd';
|
||||
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import down from '@/assets/images/userMemory/down.svg'
|
||||
|
||||
/**
|
||||
* Component props
|
||||
@@ -29,6 +28,7 @@ interface CardProps {
|
||||
className?: string;
|
||||
headerClassName?: string;
|
||||
bodyClassName?: string;
|
||||
extra?: ReactNode;
|
||||
}
|
||||
|
||||
const Card: FC<CardProps> = ({
|
||||
@@ -41,27 +41,33 @@ const Card: FC<CardProps> = ({
|
||||
className,
|
||||
headerClassName,
|
||||
bodyClassName,
|
||||
extra,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<RbCard
|
||||
title={title}
|
||||
subTitle={subTitle}
|
||||
headerType="borderless"
|
||||
extra={type && handleExpand && (
|
||||
<div
|
||||
className="rb:flex rb:items-center rb:text-[14px] rb:text-[#5B6167] rb:cursor-pointer rb:font-regular rb:leading-5"
|
||||
onClick={() => handleExpand(type)}
|
||||
>
|
||||
{expanded ? t('common.foldUp') : t('common.expanded')}
|
||||
<img src={down} className={clsx("rb:w-4 rb:h-4 rb:ml-1", {
|
||||
title={() => <Flex
|
||||
align="center"
|
||||
justify="space-between"
|
||||
className="rb:font-[MiSans-Bold] rb:font-bold rb:cursor-pointer"
|
||||
onClick={type && handleExpand ? () => handleExpand(type) : undefined}
|
||||
>
|
||||
<Space size={4}>
|
||||
{title}
|
||||
{subTitle && <Tooltip title={subTitle}>
|
||||
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
|
||||
</Tooltip>}
|
||||
</Space>
|
||||
{handleExpand && <div
|
||||
className={clsx("rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/arrow_up.svg')]", {
|
||||
'rb:rotate-180': !expanded,
|
||||
})} />
|
||||
</div>
|
||||
)}
|
||||
})}
|
||||
></div>}
|
||||
</Flex>}
|
||||
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}
|
||||
</RbCard>
|
||||
|
||||
@@ -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<ResultProps> = ({ loading, handleSave }) => {
|
||||
const { t } = useTranslation();
|
||||
const { id } = useParams()
|
||||
const [runLoading, setRunLoading] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState('processData')
|
||||
const [testResult, setTestResult] = useState<TestResult>({} as TestResult)
|
||||
|
||||
const [coreEntitiesTab, setCoreEntitiesTab] = useState<string | null>(null)
|
||||
const [textPreprocessing, setTextPreprocessing] = useState<ModuleItem>(initObj as ModuleItem)
|
||||
const [textPreprocessingTab, setTextPreprocessingTab] = useState('chunking')
|
||||
const [knowledgeExtraction, setKnowledgeExtraction] = useState<ModuleItem>(initObj as ModuleItem)
|
||||
const [creatingNodesEdges, setCreatingNodesEdges] = useState<ModuleItem>(initObj as ModuleItem)
|
||||
const [deduplication, setDeduplication] = useState<ModuleItem>(initObj as ModuleItem)
|
||||
const [ontologyCoverage, setOntologyCoverage] = useState<OntologyCoverage>({} as OntologyCoverage)
|
||||
|
||||
const [expandedCards, setExpandedCards] = useState<Record<string, boolean>>(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<ResultProps> = ({ 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<ResultProps> = ({ 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<ResultProps> = ({ 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<ResultProps> = ({ 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<ResultProps> = ({ 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<ResultProps> = ({ loading, handleSave }) => {
|
||||
/** Format status tag */
|
||||
const formatTag = (status: string) => {
|
||||
return (
|
||||
<Tag color={tagColors[status]}>
|
||||
{status === 'pending' && <ClockCircleOutlined className="rb:mr-1" />}
|
||||
<Tag color={tagColors[status]} className="rb:flex! rb:items-center rb:gap-1 rb:bg-white! rb:border-white!">
|
||||
{status === 'pending' && <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/memory/clock_orange.svg')]"></div>}
|
||||
{status === 'processing' && <LoadingOutlined spin className="rb:mr-1" />}
|
||||
{status === 'completed' && <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/check_green.svg')]"></div>}
|
||||
{t(`memoryExtractionEngine.status.${status}`)}
|
||||
</Tag>
|
||||
)
|
||||
@@ -213,294 +255,411 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
||||
/** Format processing time */
|
||||
const formatTime = (data: ModuleItem, color?: string) => {
|
||||
if (typeof data.end_at === 'number' && typeof data.start_at === 'number') {
|
||||
return <div className={`rb:mt-3 rb:text-[${color ?? '#155EEF'}]`}>{t('memoryExtractionEngine.time')}{data.end_at - data.start_at}ms</div>
|
||||
return <div className={`rb:text-[${color ?? '#155EEF'}] rb:mb-0.5`}>{t('memoryExtractionEngine.time')}{data.end_at - data.start_at}ms</div>
|
||||
}
|
||||
return null
|
||||
}
|
||||
/** Convert first character to lowercase */
|
||||
const lowercaseFirst = (str: string) => str.charAt(0).toLowerCase() + str.slice(1)
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('memoryExtractionEngine.exampleMemoryExtractionResults')}
|
||||
subTitle={t('memoryExtractionEngine.exampleMemoryExtractionResultsSubTitle')}
|
||||
className="rb:min-h-[calc(100vh-330px)]!"
|
||||
headerClassName="rb:pb-0! rb:pt-4!"
|
||||
bodyClassName="rb:min-h-[calc(100vh-388px)] rb:p-[16px_20px]!"
|
||||
bodyClassName="rb:h-[calc(100vh-163px)]! rb:overflow-y-auto rb:p-[16px_20px]!"
|
||||
extra={<Space size={8}>
|
||||
<Button
|
||||
icon={<div className="rb:size-3.5 rb:bg-cover rb:bg-[url('@/assets/images/common/save.svg')]"></div>}
|
||||
loading={loading}
|
||||
onClick={handleSave}
|
||||
>{t('common.save')}</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<div className="rb:size-3.5 rb:bg-cover rb:bg-[url('@/assets/images/memory/debug.svg')]"></div>}
|
||||
loading={runLoading}
|
||||
onClick={handleRun}
|
||||
>{t('memoryExtractionEngine.debug')}</Button>
|
||||
</Space>}
|
||||
>
|
||||
<Form form={runForm} layout="vertical">
|
||||
{/* <RbAlert color="orange" icon={<ExclamationCircleFilled />} className="rb:mb-3!">
|
||||
{t('memoryExtractionEngine.warning')}
|
||||
</RbAlert> */}
|
||||
<Form form={runForm} layout="vertical" className="rb:bg-[#F6F6F6]! rb:rounded-xl rb:py-2! rb:mb-4!">
|
||||
<Flex align="center" justify="space-between" className="rb:px-3! rb:mb-2!">
|
||||
<div className="rb:text-[#212332] rb:font-medium rb:leading-5">{t('memoryExtractionEngine.custom_text')}</div>
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4.5">{customText?.length || 0}</div>
|
||||
</Flex>
|
||||
<Form.Item
|
||||
name="custom_text"
|
||||
label={t('memoryExtractionEngine.custom_text')}
|
||||
noStyle
|
||||
>
|
||||
<Input.TextArea placeholder={t('common.pleaseEnter')} />
|
||||
<Input.TextArea placeholder={t('common.pleaseEnter')} variant="borderless" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<div className="rb:min-h-[calc(100vh-480px)] rb:overflow-y-auto">
|
||||
{runLoading
|
||||
? <>
|
||||
<RbAlert color="blue" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
|
||||
|
||||
{runLoading
|
||||
? <>
|
||||
<RbAlert color="blue">
|
||||
<div className="rb:w-full">
|
||||
{t('memoryExtractionEngine.processing')}
|
||||
</RbAlert>
|
||||
{/* Overall Progress */}
|
||||
<div className="rb:mb-2">
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:text-[12px] rb:leading-4 rb:font-regular">
|
||||
{t('memoryExtractionEngine.overallProgress')}
|
||||
<span className="rb:text-[#155eef]">{`${completedNum}/4`}</span>
|
||||
</div>
|
||||
<Progress percent={completedNum * 100/4} showInfo={false} />
|
||||
|
||||
{/* Overall Progress */}
|
||||
<Flex gap={13} align="center">
|
||||
<Progress percent={completedNum * 100 / 4} showInfo={false} className="rb:flex-1!" />
|
||||
<div className="rb:text-[12px] rb:leading-4 rb:font-regular">
|
||||
{t('memoryExtractionEngine.overallProgress')}{`${(completedNum*100/4).toFixed(0)}%`}
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
</>
|
||||
: !testResult || Object.keys(testResult).length === 0
|
||||
? <RbAlert color="orange" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
|
||||
{t('memoryExtractionEngine.warning')}
|
||||
</RbAlert>
|
||||
: <RbAlert color="green" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
|
||||
{t('memoryExtractionEngine.success')}
|
||||
</RbAlert>
|
||||
}
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
{/* Text Preprocessing */}
|
||||
<RbCard
|
||||
title={t(`memoryExtractionEngine.text_preprocessing`)}
|
||||
extra={formatTag(textPreprocessing.status)}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#155EEF]!"
|
||||
>
|
||||
{textPreprocessing.data.map((vo, index) => {
|
||||
if (vo.deleted_messages) {
|
||||
return <div key={index} className="rb:mb-3 rb:pb-1 rb:border-b rb:border-b-[#EBEBEB]">
|
||||
<div className="rb:font-medium rb:text-[12px] rb:mb-2">{t('memoryExtractionEngine.Pruned')}</div>
|
||||
{vo.deleted_messages.map((msg: any, idx: number) => (
|
||||
<div key={idx} className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
|
||||
<Markdown content={'-' + t('memoryExtractionEngine.pruning') + (idx + 1) + ': ' + msg.content} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
return (
|
||||
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
|
||||
<Markdown content={'-' + t('memoryExtractionEngine.fragment') + vo.chunk_index + ': ' + (vo.content.startsWith('\n') ? vo.content : '\n' + vo.content)} />
|
||||
</div>
|
||||
)
|
||||
</RbAlert>
|
||||
</>
|
||||
: !testResult || Object.keys(testResult).length === 0
|
||||
? <RbAlert color="orange" icon={<ExclamationCircleFilled />}>
|
||||
{t('memoryExtractionEngine.warning')}
|
||||
</RbAlert>
|
||||
: <RbAlert color="green" icon={<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/check_green.svg')]"></div>}>
|
||||
{t('memoryExtractionEngine.success')}
|
||||
</RbAlert>
|
||||
}
|
||||
|
||||
<Space size={24} className="rb:mt-4! rb:mb-3!">
|
||||
{['processData', 'finalResult'].map(tab => (
|
||||
<div
|
||||
className={clsx('rb:font-[MiSans-Bold] rb:font-bold rb:leading-5 rb:cursor-pointer', {
|
||||
'rb:text-[#212332]': activeTab === tab,
|
||||
'rb:text-[#A8A9AA]': activeTab !== tab,
|
||||
})}
|
||||
{formatTime(textPreprocessing)}
|
||||
{textPreprocessing.result &&
|
||||
<RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
|
||||
onClick={() => setActiveTab(tab)}
|
||||
>{t(`memoryExtractionEngine.${tab}`)}</div>
|
||||
))}
|
||||
</Space>
|
||||
|
||||
{activeTab === 'processData' && <Flex vertical gap={12} className="rb:pb-3!">
|
||||
{/* Text Preprocessing */}
|
||||
<ResultCard
|
||||
title={t(`memoryExtractionEngine.text_preprocessing`)}
|
||||
extra={formatTag(textPreprocessing.status)}
|
||||
expanded={expandedCards['text_preprocessing']}
|
||||
handleExpand={() => toggleCard('text_preprocessing')}
|
||||
>
|
||||
{expandedCards['text_preprocessing'] && textPreprocessing.data?.length > 0 &&
|
||||
<Space size={10} className="rb:px-1! rb:mb-3!">
|
||||
{(['chunking', ...(textPreprocessing.data.some(vo => vo.deleted_messages) ? ['pruning'] : [])] as string[]).map(type => (
|
||||
<div
|
||||
key={type}
|
||||
className={clsx("rb:rounded-[13px] rb:py-0.5 rb:px-3 rb:leading-5 rb:cursor-pointer", {
|
||||
'rb:bg-white': textPreprocessingTab !== type,
|
||||
'rb:bg-[#171719] rb:text-white': textPreprocessingTab === type
|
||||
})}
|
||||
onClick={() => setTextPreprocessingTab(type)}
|
||||
>
|
||||
{t(`memoryExtractionEngine.${type}`)}
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
}
|
||||
{expandedCards['text_preprocessing'] && textPreprocessing.result &&
|
||||
<RbAlert color="blue" className="rb:mb-2!">
|
||||
<div>
|
||||
<div>{formatTime(textPreprocessing)}</div>
|
||||
{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)}`)}
|
||||
</RbAlert>
|
||||
}
|
||||
</RbCard>
|
||||
{/* Knowledge Extraction */}
|
||||
<RbCard
|
||||
title={t(`memoryExtractionEngine.knowledge_extraction`)}
|
||||
extra={formatTag(knowledgeExtraction.status)}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#155EEF]!"
|
||||
>
|
||||
{knowledgeExtraction.data.map((vo, index) =>
|
||||
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">{vo.statement}</div>
|
||||
)}
|
||||
{formatTime(knowledgeExtraction)}
|
||||
{knowledgeExtraction.result && <RbAlert color="blue" icon={<CheckCircleFilled />} 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
|
||||
})}
|
||||
</RbAlert>}
|
||||
</RbCard>
|
||||
{/* Creating Entity Relationships */}
|
||||
<RbCard
|
||||
title={t(`memoryExtractionEngine.creating_nodes_edges`)}
|
||||
extra={formatTag(creatingNodesEdges.status)}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#9C6FFF]!"
|
||||
>
|
||||
{creatingNodesEdges.data?.map((vo, index) => (
|
||||
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
|
||||
{vo?.result_type === 'entity_nodes_creation'
|
||||
? <>{vo.type_display_name}: {vo.entity_names.join(', ')}</>
|
||||
: <>{vo?.relationship_text}</>
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
{formatTime(creatingNodesEdges, '#9C6FFF')}
|
||||
{creatingNodesEdges.result && <RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
|
||||
{t('memoryExtractionEngine.creating_nodes_edges_desc', {num: creatingNodesEdges.result.entity_entity_edges_count})}
|
||||
</RbAlert>}
|
||||
</RbCard>
|
||||
{/* Deduplication and Disambiguation */}
|
||||
<RbCard
|
||||
title={t(`memoryExtractionEngine.deduplication`)}
|
||||
extra={formatTag(deduplication.status)}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#9C6FFF]!"
|
||||
>
|
||||
{Object.keys(deduplicationData).length > 0 && Object.keys(deduplicationData).map(key => {
|
||||
return deduplicationData[key].map((vo, index) => (
|
||||
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
|
||||
{vo.message}
|
||||
</div>
|
||||
))
|
||||
})}
|
||||
{formatTime(deduplication, '#9C6FFF')}
|
||||
{deduplication.result && <RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
|
||||
{t('memoryExtractionEngine.deduplication_desc', { count: deduplication.result.summary.total_merges })}<br />
|
||||
</RbAlert>}
|
||||
</RbCard>
|
||||
|
||||
{testResult && Object.keys(testResult).length > 0 && resultObj && Object.keys(resultObj).length > 0 &&
|
||||
<RbCard>
|
||||
<div className="rb:grid rb:grid-cols-2 rb:gap-[40px_57px]">
|
||||
{Object.keys(resultObj).map((key, index) => {
|
||||
const keys = (resultObj as Record<string, string>)[key].split('.')
|
||||
return (
|
||||
<div key={index}>
|
||||
<div className="rb:text-[24px] rb:leading-7.5 rb:font-extrabold">{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}</div>
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">{t(`memoryExtractionEngine.${key}`)}</div>
|
||||
<div className="rb:mt-1 rb:text-[12px] rb:text-[#369F21] rb:leading-3.5 rb:font-regular">
|
||||
{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`)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)})}
|
||||
</div>
|
||||
</RbCard>
|
||||
</RbAlert>
|
||||
}
|
||||
|
||||
{testResult?.dedup?.impact && testResult.dedup.impact?.length > 0 &&
|
||||
<RbCard
|
||||
title={t('memoryExtractionEngine.entityDeduplicationImpact')}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#155EEF]!"
|
||||
>
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:font-medium rb:leading-4">{t('memoryExtractionEngine.identifyDuplicates')}</div>
|
||||
{testResult.dedup.impact.map((item, index) => (
|
||||
<div key={index} className="rb:pl-2 rb:mt-2 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">
|
||||
-{t('memoryExtractionEngine.identifyDuplicatesDesc', { ...item })}
|
||||
{expandedCards['text_preprocessing'] && textPreprocessing.data.map((vo, index) => {
|
||||
if (vo.deleted_messages && textPreprocessingTab === 'pruning') {
|
||||
return <div key={index} className="rb:mb-3 rb:pb-1 rb:border-b rb:border-b-[#EBEBEB]">
|
||||
<div className="rb:font-medium rb:text-[12px] rb:mb-2">{t('memoryExtractionEngine.Pruned')}</div>
|
||||
{vo.deleted_messages.map((msg: any, idx: number) => (
|
||||
<div key={idx} className="rb:leading-5">
|
||||
<div className="rb:font-medium">-{t('memoryExtractionEngine.pruning')}{idx}:</div>
|
||||
<Markdown content={msg.content} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
if (textPreprocessingTab === 'chunking') {
|
||||
return (
|
||||
<div key={index} className="rb:leading-5">
|
||||
<div className="rb:font-medium">-{t('memoryExtractionEngine.fragment')}{vo.chunk_index}:</div>
|
||||
<Markdown content={vo.content.startsWith('\n') ? vo.content : '\n' + vo.content} className="rb:text-[#212332]" />
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
</ResultCard>
|
||||
{/* Knowledge Extraction */}
|
||||
<ResultCard
|
||||
title={t(`memoryExtractionEngine.knowledge_extraction`)}
|
||||
extra={formatTag(knowledgeExtraction.status)}
|
||||
expanded={expandedCards['knowledge_extraction']}
|
||||
handleExpand={() => toggleCard('knowledge_extraction')}
|
||||
>
|
||||
{knowledgeExtraction.result &&
|
||||
<RbAlert color="blue" className="rb:mb-2!">
|
||||
<div>
|
||||
<div>{formatTime(knowledgeExtraction)}</div>
|
||||
{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
|
||||
})}
|
||||
</div>
|
||||
</RbAlert>
|
||||
}
|
||||
{knowledgeExtraction.data?.length > 0 &&
|
||||
<ul className="rb:list-disc rb:ml-4 rb:mb-3">
|
||||
{knowledgeExtraction.data.map((vo, index) =>
|
||||
<li key={index} className="rb:leading-6">{vo.statement}</li>
|
||||
)}
|
||||
</ul>
|
||||
}
|
||||
</ResultCard>
|
||||
{/* Creating Entity Relationships */}
|
||||
<ResultCard
|
||||
title={t(`memoryExtractionEngine.creating_nodes_edges`)}
|
||||
extra={formatTag(creatingNodesEdges.status)}
|
||||
expanded={expandedCards['creating_nodes_edges']}
|
||||
handleExpand={() => toggleCard('creating_nodes_edges')}
|
||||
>
|
||||
{creatingNodesEdges.result &&
|
||||
<RbAlert color="blue" className="rb:mb-2!">
|
||||
<div>
|
||||
<div>{formatTime(creatingNodesEdges)}</div>
|
||||
{t('memoryExtractionEngine.creating_nodes_edges_desc', { num: creatingNodesEdges.result.entity_entity_edges_count })}
|
||||
</div>
|
||||
</RbAlert>
|
||||
}
|
||||
{creatingNodesEdges.data?.length > 0 &&
|
||||
<ul className="rb:list-disc rb:ml-4 rb:mb-3">
|
||||
{creatingNodesEdges.data.map((vo, index) =>
|
||||
<li key={index} className="rb:leading-6">
|
||||
{vo?.result_type === 'entity_nodes_creation'
|
||||
? <>{vo.type_display_name}: {vo.entity_names.join(', ')}</>
|
||||
: <>{vo?.relationship_text}</>
|
||||
}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
}
|
||||
</ResultCard>
|
||||
{/* Deduplication and Disambiguation */}
|
||||
<ResultCard
|
||||
title={t(`memoryExtractionEngine.deduplication`)}
|
||||
extra={formatTag(deduplication.status)}
|
||||
expanded={expandedCards['deduplication']}
|
||||
handleExpand={() => toggleCard('deduplication')}
|
||||
>
|
||||
{deduplication.result &&
|
||||
<RbAlert color="blue" className="rb:mb-2!">
|
||||
<div>
|
||||
<div>{formatTime(deduplication)}</div>
|
||||
{t('memoryExtractionEngine.deduplication_desc', { count: deduplication.result.summary.total_merges })}
|
||||
</div>
|
||||
</RbAlert>
|
||||
}
|
||||
{Object.keys(deduplicationData).length > 0 &&
|
||||
<ul className="rb:list-disc rb:ml-4 rb:mb-3">
|
||||
{Object.keys(deduplicationData).map(key => {
|
||||
return deduplicationData[key].map((vo, index) => (
|
||||
<li key={index} className="rb:leading-6">
|
||||
{vo.message}
|
||||
</li>
|
||||
))
|
||||
})}
|
||||
</ul>
|
||||
}
|
||||
</ResultCard>
|
||||
</Flex>}
|
||||
|
||||
<RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
|
||||
{activeTab === 'finalResult' && <Flex vertical gap={12} className="rb:pb-3!">
|
||||
{!testResult || Object.keys(testResult).length === 0
|
||||
? <Empty url={NoDataIcon} />
|
||||
: null
|
||||
}
|
||||
|
||||
{testResult && Object.keys(testResult).length > 0 && resultObj && Object.keys(resultObj).length > 0 &&
|
||||
<ResultCard
|
||||
title={t(`memoryExtractionEngine.dataStatistics`)}
|
||||
expanded={expandedCards['dataStatistics']}
|
||||
handleExpand={() => toggleCard('dataStatistics')}
|
||||
>
|
||||
<div className="rb:grid rb:grid-cols-2 rb:gap-2.5 rb:mb-3">
|
||||
{Object.keys(resultObj).map((key, index) => {
|
||||
const keys = (resultObj as Record<string, string>)[key].split('.')
|
||||
return (
|
||||
<div key={index} className="rb:bg-white rb:rounded-lg rb:py-2 rb:px-3">
|
||||
<div className="rb:text-[24px] rb:leading-8 rb:font-bold rb:font-[MiSans-Bold] rb:mb-1">{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}</div>
|
||||
<div className="rb:text-[12px] rb:leading-4 rb:mb-0.5">{t(`memoryExtractionEngine.${key}`)}</div>
|
||||
<div className="rb:text-[12px] rb:text-[#369F21] rb:leading-4">
|
||||
{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`)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</ResultCard>
|
||||
}
|
||||
|
||||
{testResult?.dedup?.impact && testResult.dedup.impact?.length > 0 &&
|
||||
<ResultCard
|
||||
title={t('memoryExtractionEngine.entityDeduplicationImpact')}
|
||||
expanded={expandedCards['entityDeduplicationImpact']}
|
||||
handleExpand={() => toggleCard('entityDeduplicationImpact')}
|
||||
>
|
||||
<div className="rb:bg-white rb:rounded-xl rb:p-3 rb:mb-3">
|
||||
<RbAlert color="blue" className="rb:mb-2!">
|
||||
{t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })}
|
||||
</RbAlert>
|
||||
</RbCard>
|
||||
}
|
||||
<div className="rb:font-medium rb:leading-5 rb:mb-2">{t('memoryExtractionEngine.identifyDuplicates')}:</div>
|
||||
|
||||
{testResult?.disambiguation && testResult.disambiguation?.effects?.length > 0 &&
|
||||
<RbCard
|
||||
title={t('memoryExtractionEngine.theEffectOfEntityDisambiguationLLMDriven')}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#155EEF]!"
|
||||
>
|
||||
<ul className="rb:list-disc rb:ml-4">
|
||||
{testResult.dedup.impact.map((item, index) => (
|
||||
<li key={index} className="rb:leading-6">
|
||||
{t('memoryExtractionEngine.identifyDuplicatesDesc', { ...item })}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</ResultCard>
|
||||
}
|
||||
|
||||
{testResult?.disambiguation && testResult.disambiguation?.effects?.length > 0 &&
|
||||
<ResultCard
|
||||
title={t('memoryExtractionEngine.theEffectOfEntityDisambiguationLLMDriven')}
|
||||
expanded={expandedCards['disambiguation']}
|
||||
handleExpand={() => toggleCard('disambiguation')}
|
||||
>
|
||||
<div className="rb:bg-white rb:rounded-xl rb:p-3 rb:mb-3">
|
||||
<RbAlert color="blue" className="rb:mb-2!">
|
||||
{t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })}
|
||||
</RbAlert>
|
||||
{testResult.disambiguation.effects.map((item, index) => (
|
||||
<div key={index} className={clsx("rb:text-[12px] rb:text-[#5B6167] rb:leading-4", {
|
||||
'rb:mt-4': index > 0,
|
||||
'rb:mt-5': index > 0,
|
||||
})}>
|
||||
<div className="rb:font-medium rb:mb-2">{t('memoryExtractionEngine.disagreementCase')} {index +1}:</div>
|
||||
-{item.left.name}({item.left.type}) vs {item.right.name}({item.right.type}) → <span className="rb:text-[#369F21]">{item.result}</span>
|
||||
<div className="rb:font-medium rb:leading-5 rb:mb-1">{t('memoryExtractionEngine.disagreementCase')} {index + 1}:</div>
|
||||
|
||||
<ul className="rb:list-disc rb:ml-4">
|
||||
<li key={index} className="rb:leading-6">
|
||||
{item.left.name}({item.left.type}) vs {item.right.name}({item.right.type}) → {item.result}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ResultCard>
|
||||
}
|
||||
|
||||
<RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
|
||||
{t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })}
|
||||
</RbAlert>
|
||||
</RbCard>
|
||||
}
|
||||
{testResult?.core_entities && testResult?.core_entities.length > 0 &&
|
||||
<ResultCard
|
||||
title={t('memoryExtractionEngine.coreEntitiesAfterDedup')}
|
||||
expanded={expandedCards['coreEntities']}
|
||||
handleExpand={() => toggleCard('coreEntities')}
|
||||
>
|
||||
<Flex gap={10} wrap className="rb:px-1! rb:mb-3! rb:gap-y-2!">
|
||||
{testResult.core_entities.map((item, index) => (
|
||||
<div
|
||||
key={item.type}
|
||||
className={clsx("rb:rounded-[13px] rb:py-0.5 rb:px-3 rb:leading-5 rb:cursor-pointer", {
|
||||
'rb:bg-white': !((coreEntitiesTab && item.type === coreEntitiesTab) || (!coreEntitiesTab && index === 0)),
|
||||
'rb:bg-[#171719] rb:text-white': (coreEntitiesTab && item.type === coreEntitiesTab) || (!coreEntitiesTab && index === 0)
|
||||
})}
|
||||
onClick={() => setCoreEntitiesTab(item.type)}
|
||||
>
|
||||
{item.type}({item.count})
|
||||
</div>
|
||||
))}
|
||||
</Flex>
|
||||
<div className="rb:bg-white rb:rounded-lg rb:py-2.5 rb:px-3 rb:mb-3">
|
||||
{testResult.core_entities.filter((item, index) => (coreEntitiesTab && item.type === coreEntitiesTab) || (!coreEntitiesTab && index === 0)).map((item, idx) => (
|
||||
<div key={idx} className="rb:leading-5">
|
||||
<div className="rb:text-[#155EEF] rb:font-medium rb:mb-2">{item.type}({item.count})</div>
|
||||
|
||||
{testResult?.core_entities && testResult?.core_entities.length > 0 &&
|
||||
<RbCard
|
||||
title={t('memoryExtractionEngine.coreEntitiesAfterDedup')}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#369F21]!"
|
||||
>
|
||||
<div className="rb:grid rb:grid-cols-2 rb:gap-6">
|
||||
{testResult.core_entities.map((item, idx) => (
|
||||
<div key={idx} className="rb:text-[12px]">
|
||||
<div className="rb:text-[#369F21] rb:font-medium">{item.type}({item.count})</div>
|
||||
<ul className="rb:list-disc rb:ml-4">
|
||||
{item.entities.map((entity, index) => (
|
||||
<li key={index} className="rb:leading-6">
|
||||
{entity}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ResultCard>
|
||||
}
|
||||
|
||||
<div>
|
||||
{item.entities.map((entity, index) => (
|
||||
<div key={index} className="rb:text-[#5B6167] rb:font-regular rb:leading-4">
|
||||
-{entity}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RbCard>
|
||||
}
|
||||
|
||||
{testResult?.triplet_samples && testResult?.triplet_samples.length > 0 &&
|
||||
<RbCard
|
||||
title={t('memoryExtractionEngine.extractRelationalTriples')}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#9C6FFF]!"
|
||||
>
|
||||
<Space size={8} direction="vertical" className="rb:w-full">
|
||||
{testResult.triplet_samples.map((item, index) => (
|
||||
<div key={index} className="rb:text-[12px]">
|
||||
-({item.subject}, <span className="rb:text-[#9C6FFF] rb:font-medium">{item.predicate}</span>, {item.object})
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
<RbAlert color="purple" icon={<CheckCircleFilled />} className="rb:mt-3">
|
||||
{testResult?.triplet_samples && testResult?.triplet_samples.length > 0 &&
|
||||
<ResultCard
|
||||
title={t('memoryExtractionEngine.extractRelationalTriples')}
|
||||
expanded={expandedCards['triplet_samples']}
|
||||
handleExpand={() => toggleCard('triplet_samples')}
|
||||
>
|
||||
<div className="rb:bg-white rb:rounded-xl rb:p-3 rb:mb-3">
|
||||
<RbAlert color="blue"className="rb:mb-2!">
|
||||
{t('memoryExtractionEngine.extractRelationalTriplesDesc', { count: testResult.triplet_samples.length })}
|
||||
</RbAlert>
|
||||
</RbCard>
|
||||
}
|
||||
{ontologyCoverage && Object.keys(ontologyCoverage).length > 0 &&
|
||||
<RbCard
|
||||
title={<>{t('memoryExtractionEngine.ontologyCoverage')}({ontologyCoverage.total_entities})</>}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#369F21]!"
|
||||
>
|
||||
<div className="rb:grid rb:grid-cols-2 rb:gap-3">
|
||||
<ul className="rb:list-disc rb:ml-4">
|
||||
{testResult.triplet_samples.map((item, index) => (
|
||||
<li key={index} className="rb:leading-6">
|
||||
({item.subject}, <span className="rb:text-[#155EEF] rb:font-medium">{item.predicate}</span>, {item.object})
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</ResultCard>
|
||||
}
|
||||
{ontologyCoverage && Object.keys(ontologyCoverage).length > 0 &&
|
||||
<ResultCard
|
||||
title={<>{t('memoryExtractionEngine.ontologyCoverage')}({ontologyCoverage.total_entities})</>}
|
||||
expanded={expandedCards['ontologyCoverage']}
|
||||
handleExpand={() => toggleCard('ontologyCoverage')}
|
||||
>
|
||||
<div className="rb:bg-white rb:rounded-xl rb:p-3 rb:mb-3 rb:leading-5">
|
||||
<div className="rb:grid rb:grid-cols-1 rb:gap-3">
|
||||
{(['scene_type_distribution', 'general_type_distribution', 'unmatched'] as const).map((key, idx) => {
|
||||
if (!ontologyCoverage[key]) return null
|
||||
return (
|
||||
<div key={idx} className="rb:text-[12px]">
|
||||
<div className="rb:text-[#369F21] rb:font-medium">{t(`memoryExtractionEngine.${key}`)}({ontologyCoverage[key].type_count})</div>
|
||||
<div>{t('memoryExtractionEngine.entity_total', { num: ontologyCoverage[key].entity_total })}</div>
|
||||
<div>
|
||||
<div key={idx}>
|
||||
<div className="rb:text-[#155EEF] rb:font-medium rb:mb-1">{t(`memoryExtractionEngine.${key}`)}({ontologyCoverage[key].type_count})</div>
|
||||
<div className="rb:text-[#212332] rb:mb-1">{t('memoryExtractionEngine.entity_total', { num: ontologyCoverage[key].entity_total })}</div>
|
||||
|
||||
<ul className="rb:list-disc rb:ml-4">
|
||||
{ontologyCoverage[key].types.map((type, index) => {
|
||||
if (!type.type || type.type === '') return null
|
||||
return (
|
||||
<div key={index} className="rb:text-[#5B6167] rb:font-regular rb:leading-4">
|
||||
-{type.type}({type.count})
|
||||
</div>
|
||||
<li key={index} className="rb:leading-6 rb:text-[#5B6167]">
|
||||
{type.type}({type.count})
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</RbCard>
|
||||
}
|
||||
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div className="rb:grid rb:grid-cols-2 rb:gap-4 rb:mt-5">
|
||||
<Button block loading={loading} onClick={handleSave}>{t('common.save')}</Button>
|
||||
<Button block type="primary" loading={runLoading} onClick={handleRun}>{t('memoryExtractionEngine.debug')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ResultCard>
|
||||
}
|
||||
</Flex>}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<ResultCardProps> = ({
|
||||
title,
|
||||
subTitle,
|
||||
children,
|
||||
expanded,
|
||||
handleExpand,
|
||||
extra,
|
||||
className,
|
||||
headerClassName,
|
||||
bodyClassName,
|
||||
}) => {
|
||||
return (
|
||||
<RbCard
|
||||
title={() => <Flex
|
||||
align="center"
|
||||
justify="space-between"
|
||||
className="rb:font-[MiSans-Bold] rb:font-bold rb:cursor-pointer"
|
||||
onClick={handleExpand}
|
||||
>
|
||||
<Space size={4}>
|
||||
{title}
|
||||
{subTitle && <Tooltip title={subTitle}>
|
||||
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
|
||||
</Tooltip>}
|
||||
</Space>
|
||||
<Space size={4}>
|
||||
{extra}
|
||||
{handleExpand && <div
|
||||
className={clsx("rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/arrow_up.svg')] rb:transition-transform", {
|
||||
'rb:rotate-180': !expanded,
|
||||
'rb:rotate-0': expanded,
|
||||
})}
|
||||
></div>}
|
||||
</Space>
|
||||
</Flex>}
|
||||
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}
|
||||
</RbCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default ResultCard
|
||||
@@ -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 (
|
||||
<div className={className}>
|
||||
@@ -44,7 +45,6 @@ const ConfigDesc: FC<{ config: Variable, className?: string; onlyMeaning?: boole
|
||||
{config.control && <span className="rb:font-regular">{t('memoryExtractionEngine.control')}: {t(`memoryExtractionEngine.${config.control}`)}</span>}
|
||||
{config.type && <span className="rb:font-regular">{t('memoryExtractionEngine.type')}: {config.type}</span>}
|
||||
</Space>}
|
||||
{config.meaning && <div className={clsx("rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 ")}>{t('memoryExtractionEngine.Meaning')}: {t(`memoryExtractionEngine.${config.meaning}`)}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -122,163 +122,139 @@ const MemoryExtractionEngine: FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="rb:text-[24px] rb:font-semibold rb:leading-8 rb:mb-2">{t('memoryExtractionEngine.title')}</div>
|
||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:mb-6">{t('memoryExtractionEngine.subTitle')}</div>
|
||||
<Flex align="center" gap={4} className="rb:font-[MiSans-Bold] rb:text-[16px] rb:font-bold rb:leading-5.5 rb:mb-4!">
|
||||
{t('memoryExtractionEngine.title')}
|
||||
<Tooltip title={t('memoryExtractionEngine.subTitle')}>
|
||||
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<Form form={modelForm}>
|
||||
<Form.Item
|
||||
label={t('memoryExtractionEngine.model')}
|
||||
name="llm_id"
|
||||
>
|
||||
<CustomSelect
|
||||
url={getModelListUrl}
|
||||
params={{ type: 'llm,chat', pagesize: 100, is_active: true }}
|
||||
valueKey="id"
|
||||
labelKey="name"
|
||||
hasAll={false}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
<Card
|
||||
type="example"
|
||||
title={t('memoryExtractionEngine.example')}
|
||||
expanded={expandedKeys.includes('example')}
|
||||
handleExpand={handleExpand}
|
||||
>
|
||||
{expandedKeys.includes('example') &&
|
||||
<div className="rb:text-[14px] rb:text-[#5B6167] rb:font-regular rb:leading-5">
|
||||
<Markdown content={t('memoryExtractionEngine.exampleText')} />
|
||||
</div>
|
||||
}
|
||||
</Card>
|
||||
<Row gutter={[16, 16]} className="rb:mt-4">
|
||||
<Col span={14}>
|
||||
<Form
|
||||
form={form}
|
||||
>
|
||||
<Space direction="vertical" size={16} style={{ width: '100%' }}>
|
||||
{configList.map((item, index) => (
|
||||
<Card
|
||||
type={item.type}
|
||||
title={t(`memoryExtractionEngine.${item.type}`)}
|
||||
key={index}
|
||||
expanded={expandedKeys.includes(item.type)}
|
||||
handleExpand={handleExpand}
|
||||
>
|
||||
<Space size={20} direction="vertical" style={{width: '100%'}}>
|
||||
{item.data.map(vo => (
|
||||
<div
|
||||
key={vo.title}
|
||||
className={clsx(
|
||||
`rb:p-[16px_24px] rb:rounded-lg`,
|
||||
'rb:border rb:border-[#DFE4ED]',
|
||||
{
|
||||
'rb:shadow-[inset_4px_0px_0px_0px_#155EEF]': index % 2 === 0,
|
||||
'rb:shadow-[inset_4px_0px_0px_0px_#369F21]': index % 2 !== 0,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5">{t(`memoryExtractionEngine.${vo.title}`)}</div>
|
||||
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`memoryExtractionEngine.${vo.title}SubTitle`)}</div>
|
||||
<Flex vertical gap={12} className="rb:h-[calc(100vh-114px)]! rb:overflow-y-auto">
|
||||
<Form form={modelForm}>
|
||||
<Form.Item
|
||||
name="llm_id"
|
||||
noStyle
|
||||
>
|
||||
<ModelSelect
|
||||
params={{ type: 'llm,chat' }}
|
||||
className="rb:w-full! rb:h-10! rb:bg-white rb:rounded-xl"
|
||||
variant="borderless"
|
||||
placeholder={t('memoryExtractionEngine.model')}
|
||||
allowClear={false}
|
||||
fontClassName="rb:font-medium!"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
{vo.list.map(config => (
|
||||
<div key={config.label}>
|
||||
{config.control === 'button' &&
|
||||
<SwitchFormItem
|
||||
title={<>-{t(`memoryExtractionEngine.${config.label}`)}</>}
|
||||
name={config.variableName}
|
||||
desc={<ConfigDesc config={config} className="rb:ml-2" />}
|
||||
className="rb:mt-6"
|
||||
/>
|
||||
}
|
||||
{config.control === 'select' &&
|
||||
<>
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
|
||||
-{t(`memoryExtractionEngine.${config.label}`)}
|
||||
</div>
|
||||
<div className="rb:pl-2">
|
||||
<div className="rb:bg-white rb:rounded-xl rb:py-2.5 rb:px-4">
|
||||
<Flex
|
||||
align="center"
|
||||
justify="space-between"
|
||||
className="rb:font-[MiSans-Bold] rb:font-bold rb:cursor-pointer"
|
||||
onClick={() => handleExpand('example')}
|
||||
>
|
||||
{t('memoryExtractionEngine.example')}
|
||||
<div className={clsx("rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/arrow_up.svg')]", {
|
||||
'rb:rotate-180': !expandedKeys.includes('example'),
|
||||
'rb:rotate-0': expandedKeys.includes('example'),
|
||||
})}></div>
|
||||
</Flex>
|
||||
|
||||
{expandedKeys.includes('example') &&
|
||||
<div className="rb:text-[14px] rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-2.5 rb:mb-1.5">
|
||||
<Markdown content={t('memoryExtractionEngine.exampleText')} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<Form form={form}>
|
||||
<Flex vertical gap={16}>
|
||||
{configList.map((item, index) => (
|
||||
<Card
|
||||
type={item.type}
|
||||
title={t(`memoryExtractionEngine.${item.type}`)}
|
||||
key={index}
|
||||
expanded={expandedKeys.includes(item.type)}
|
||||
handleExpand={handleExpand}
|
||||
>
|
||||
<Flex gap={16} vertical>
|
||||
{item.data.map(vo => (
|
||||
<Flex
|
||||
key={vo.title}
|
||||
vertical
|
||||
gap={10}
|
||||
className="rb:bg-[#F6F6F6] rb:rounded-xl rb:p-3! rb:pt-2.5!"
|
||||
>
|
||||
<Space size={4} className="rb:text-[#212332] rb:font-medium rb:leading-5">
|
||||
{t(`memoryExtractionEngine.${vo.title}`)}
|
||||
<Tooltip title={t(`memoryExtractionEngine.${vo.title}SubTitle`)}>
|
||||
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
||||
{vo.list.map(config => (
|
||||
<div key={config.label} className="rb:bg-white rb:rounded-xl rb:p-3 rb:pr-2.5">
|
||||
{config.control === 'button'
|
||||
? <SwitchFormItem
|
||||
title={t(`memoryExtractionEngine.${config.label}`)}
|
||||
name={config.variableName}
|
||||
desc={<DescWrapper desc={<Desc config={config} />} />}
|
||||
className="rb:mt-6"
|
||||
/>
|
||||
: <>
|
||||
{config.meaning
|
||||
? <Space size={4} className="rb:text-[#212332] rb:font-medium rb:leading-5">
|
||||
{t(`memoryExtractionEngine.${config.label}`)}
|
||||
<Tooltip title={<>{t('memoryExtractionEngine.Meaning')}: {t(`memoryExtractionEngine.${config.meaning}`)}</>}>
|
||||
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
: <div className="rb:text-[#212332] rb:font-medium rb:leading-5">
|
||||
{t(`memoryExtractionEngine.${config.label}`)}
|
||||
</div>
|
||||
}
|
||||
{config.control !== 'text' && <DescWrapper desc={<Desc config={config} />} />}
|
||||
<Form.Item
|
||||
name={config.variableName}
|
||||
className="rb:mb-0! rb:mt-2!"
|
||||
>
|
||||
<Select
|
||||
disabled={config.variableName === 'iteration_period' && iterationPeriodDisabled}
|
||||
options={config.options ? config.options.map(item => ({ ...item, label: t(`memoryExtractionEngine.${item.label}`) })) : []}
|
||||
/>
|
||||
{config.control === 'select'
|
||||
? <Select
|
||||
disabled={config.variableName === 'iteration_period' && iterationPeriodDisabled}
|
||||
options={config.options ? config.options.map(item => ({ ...item, label: t(`memoryExtractionEngine.${item.label}`) })) : []}
|
||||
/>
|
||||
: config.control === 'slider'
|
||||
? <RbSlider
|
||||
min={config.min || 0}
|
||||
max={config.max || 1}
|
||||
step={config.step || 0.01}
|
||||
isInput={true}
|
||||
prefix={<span className="rb:text-[#5B6167]">{t('emotionEngine.currentValue')}:</span>}
|
||||
inputClassName="rb:w-[155px]!"
|
||||
/>
|
||||
: config.control === 'inputNumber'
|
||||
? <InputNumber min={config.min || 0} style={{ width: '100%' }} placeholder={t('common.pleaseEnter')} />
|
||||
: config.control === 'text'
|
||||
? <Input placeholder={t('common.pleaseEnter')} disabled />
|
||||
: null
|
||||
}
|
||||
</Form.Item>
|
||||
<ConfigDesc config={config} className="rb:-mt-4!" />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{config.control === 'slider' &&
|
||||
<>
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
|
||||
-{t(`memoryExtractionEngine.${config.label}`)}
|
||||
</div>
|
||||
<div className="rb:pl-2">
|
||||
<ConfigDesc config={config} className="rb:mb-2.5" />
|
||||
<Form.Item
|
||||
name={config.variableName}
|
||||
>
|
||||
<Slider
|
||||
style={{ margin: '0' }}
|
||||
min={config.min || 0}
|
||||
max={config.max || 1}
|
||||
step={config.step || 0.01}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:text-[#5B6167] rb:leading-5 rb:-mt-6.5">
|
||||
{config.min || 0}
|
||||
<span>{t('memoryExtractionEngine.CurrentValue')}: {values?.[config.variableName as keyof ConfigForm]}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{config.control === 'inputNumber' &&
|
||||
<>
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
|
||||
-{t(`memoryExtractionEngine.${config.label}`)}
|
||||
</div>
|
||||
<div className="rb:pl-2">
|
||||
<Form.Item
|
||||
name={config.variableName}
|
||||
>
|
||||
<InputNumber min={config.min || 0} style={{ width: '100%' }} placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
<ConfigDesc config={config} className="rb:-mt-4!" />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{config.control === 'text' &&
|
||||
<>
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
|
||||
-{t(`memoryExtractionEngine.${config.label}`)}
|
||||
</div>
|
||||
<div className="rb:pl-2">
|
||||
<Form.Item
|
||||
name={config.variableName}
|
||||
>
|
||||
<Input placeholder={t('common.pleaseEnter')} disabled />
|
||||
</Form.Item>
|
||||
<ConfigDesc config={config} onlyMeaning={true} className="rb:-mt-4!" />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
</Form>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</Card>
|
||||
))}
|
||||
</Flex>
|
||||
</Form>
|
||||
</Flex>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<Col span={12}>
|
||||
<Result
|
||||
loading={loading}
|
||||
handleSave={handleSave}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 17:46:47
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 17:46:47
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-16 15:54:45
|
||||
*/
|
||||
/**
|
||||
* Self Reflection Engine Configuration Page
|
||||
@@ -11,12 +11,11 @@
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Row, Col, Form, App, Button, Space, Select } from 'antd';
|
||||
import { Row, Col, Form, App, Button, Space, Select, Flex } 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 { getMemoryReflectionConfig, updateMemoryReflectionConfig, pilotRunMemoryReflectionConfig } from '@/api/memory'
|
||||
import type { ConfigForm, Result, ReflexionData, MemoryVerify, QualityAssessment } from './types'
|
||||
import CustomSelect from '@/components/CustomSelect';
|
||||
@@ -24,6 +23,8 @@ import { getModelListUrl } from '@/api/models'
|
||||
import Tag from '@/components/Tag'
|
||||
import { useI18n } from '@/store/locale';
|
||||
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
|
||||
import LabelWrapper from '@/components/FormItem/LabelWrapper'
|
||||
import DescWrapper from '@/components/FormItem/DescWrapper'
|
||||
|
||||
/** Configuration list */
|
||||
const configList = [
|
||||
@@ -172,13 +173,16 @@ const SelfReflectionEngine: React.FC = () => {
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<RbCard
|
||||
title={
|
||||
<div className="rb:flex rb:items-center">
|
||||
<img src={strategyImpactSimulator} className="rb:w-5 rb:h-5 rb:mr-2" />
|
||||
{t('reflectionEngine.reflectionEngineConfig')}
|
||||
</div>
|
||||
}
|
||||
<RbCard
|
||||
title={t('reflectionEngine.reflectionEngineConfig')}
|
||||
extra={<Space>
|
||||
<Button block onClick={handleReset}>{t('common.reset')}</Button>
|
||||
<Button type="primary" loading={loading} block onClick={handleSave}>{t('common.save')}</Button>
|
||||
</Space>}
|
||||
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!"
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
@@ -189,74 +193,68 @@ const SelfReflectionEngine: React.FC = () => {
|
||||
lambda_mem: 0.03,
|
||||
}}
|
||||
>
|
||||
{configList.map(config => {
|
||||
if (config.type === 'customSelect') {
|
||||
return (
|
||||
<div key={config.key}>
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mb-2">
|
||||
{t(`reflectionEngine.${config.key}`)}
|
||||
<Flex vertical gap={24}>
|
||||
{configList.map(config => {
|
||||
if (config.type === 'customSelect') {
|
||||
return (
|
||||
<div key={config.key}>
|
||||
<LabelWrapper title={t(`reflectionEngine.${config.key}`)} className="rb:mb-3">
|
||||
<DescWrapper desc={t(`reflectionEngine.${config.key}_desc`)} className="rb:mt-1" />
|
||||
</LabelWrapper>
|
||||
<Form.Item
|
||||
name={config.key}
|
||||
className="rb:mb-0!"
|
||||
>
|
||||
<CustomSelect
|
||||
url={config.url as string}
|
||||
params={config.params}
|
||||
valueKey='id'
|
||||
labelKey='name'
|
||||
hasAll={false}
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
name={config.key}
|
||||
extra={t(`reflectionEngine.${config.key}_desc`)}
|
||||
>
|
||||
<CustomSelect
|
||||
url={config.url as string}
|
||||
params={config.params}
|
||||
valueKey='id'
|
||||
labelKey='name'
|
||||
hasAll={false}
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (config.type === 'select') {
|
||||
return (
|
||||
<div key={config.key}>
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mb-2">
|
||||
{t(`reflectionEngine.${config.key}`)}
|
||||
)
|
||||
}
|
||||
if (config.type === 'select') {
|
||||
return (
|
||||
<div key={config.key}>
|
||||
<LabelWrapper title={t(`reflectionEngine.${config.key}`)} className="rb:mb-3">
|
||||
<DescWrapper desc={t(`reflectionEngine.${config.key}_desc`)} className="rb:mt-1" />
|
||||
</LabelWrapper>
|
||||
<Form.Item
|
||||
name={config.key}
|
||||
className="rb:mb-0!"
|
||||
>
|
||||
<Select
|
||||
options={config.options?.map(vo => ({
|
||||
...vo,
|
||||
label: t(`reflectionEngine.${vo.label}`),
|
||||
}))}
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
name={config.key}
|
||||
extra={t(`reflectionEngine.${config.key}_desc`)}
|
||||
>
|
||||
<Select
|
||||
options={config.options?.map(vo => ({
|
||||
...vo,
|
||||
label: t(`reflectionEngine.${vo.label}`),
|
||||
}))}
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SwitchFormItem
|
||||
title={t(`reflectionEngine.${config.key}`)}
|
||||
name={config.key}
|
||||
desc={<>
|
||||
{(config as any).hasSubTitle && <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_subTitle`)}</div>}
|
||||
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_desc`)}</div>
|
||||
</>}
|
||||
className="rb:mb-6"
|
||||
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<Row gutter={16} className="rb:mt-3">
|
||||
<Col span={12}>
|
||||
<Button block onClick={handleReset}>{t('common.reset')}</Button>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Button type="primary" loading={loading} block onClick={handleSave}>{t('common.save')}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
return (
|
||||
<SwitchFormItem
|
||||
title={t(`reflectionEngine.${config.key}`)}
|
||||
name={config.key}
|
||||
desc={<>
|
||||
{(config as any).hasSubTitle && <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_subTitle`)}</div>}
|
||||
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_desc`)}</div>
|
||||
</>}
|
||||
className="rb:mb-6"
|
||||
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Flex>
|
||||
</Form>
|
||||
</RbCard>
|
||||
</Col>
|
||||
|
||||
@@ -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={<Flex gap={4}>
|
||||
<div className="rb:size-6 rb:text-center rb:font-semibold rb:leading-6 rb:rounded-md rb:text-white rb:bg-[#155EEF]">{name[0]}</div>
|
||||
|
||||
<Tooltip title={name || '-'}><div className={`rb:w-full rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap`}>{name || '-'}</div></Tooltip>
|
||||
<Tooltip title={name || '-'}><div className={`rb:flex-1 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap`}>{name || '-'}</div></Tooltip>
|
||||
</Flex>}
|
||||
headerType="border"
|
||||
headerClassName="rb:h-[48px]! rb:mx-4!"
|
||||
|
||||
@@ -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<TitleProps> = ({ type, title, icon, t, expanded, onClick }) => (
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:py-4.25 rb:border-b rb:border-[#DFE4ED] rb:text-[16px] rb:font-semibold rb:leading-5.5">
|
||||
<span className="rb:flex rb:items-center">
|
||||
<img src={icon} className="rb:w-5 rb:h-5 rb:mr-2" />
|
||||
{title}
|
||||
</span>
|
||||
|
||||
<span className="rb:flex rb:items-center rb:cursor-pointer rb:text-[#5B6167] rb:text-[14px] rb:font-regular rb:leading-5" onClick={() => onClick(type)}>
|
||||
{t(`userMemory.${expanded ? 'foldUp' : 'expanded'}`)}
|
||||
<img src={down} className={clsx("rb:w-4 rb:h-4 rb:ml-1", {
|
||||
'rb:rotate-180': !expanded,
|
||||
})} />
|
||||
</span>
|
||||
</div>
|
||||
const Title: FC<TitleProps> = ({ title, icon }) => (
|
||||
<Flex align="center" gap={4} className="rb:font-medium rb:leading-5 rb:mb-2.25!">
|
||||
<img src={icon} className="rb:size-4.5 rb:ml-0.5" />
|
||||
{title}
|
||||
</Flex>
|
||||
)
|
||||
|
||||
const Rag: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { id } = useParams()
|
||||
const [data, setData] = useState<Data | null>(null)
|
||||
const [expanded, setExpanded] = useState<string[]>(['aboutUs', 'memoryInsight',])
|
||||
const [summary, setSummary] = useState<string | null>('')
|
||||
const [loading, setLoading] = useState<Record<string, boolean>>({
|
||||
detail: true,
|
||||
summary: true,
|
||||
insight: true,
|
||||
})
|
||||
const [memory, setMemory] = useState<number | null>(null)
|
||||
const [insight, setInsight] = useState<string | null>('')
|
||||
const [tags, setTags] = useState<{ tag: string; frequency: number }[]>([])
|
||||
const [personas, setPersonas] = useState<string[]>([])
|
||||
|
||||
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 (
|
||||
<Row gutter={[16, 16]} className="rb:pb-6">
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={8}>
|
||||
<RbCard>
|
||||
<div className="rb:flex rb:items-center">
|
||||
<div className="rb:flex-[0_0_auto] rb:w-20 rb:h-20 rb:text-center rb:font-semibold rb:text-[28px] rb:leading-20 rb:rounded-lg rb:text-[#FBFDFF] rb:bg-[#155EEF]">{name?.[0]}</div>
|
||||
<div className="rb:text-[24px] rb:font-semibold rb:leading-8 rb:ml-4">
|
||||
{name}<br/>
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 rb:mt-2">{personas?.join(' | ')}</div>
|
||||
<RbCard
|
||||
bodyClassName="rb:p-3! rb:pt-4! rb:h-[calc(100vh-76px)]"
|
||||
>
|
||||
<Flex align="center" gap={12} className="rb:mb-6!">
|
||||
<div className="rb:size-12 rb:text-center rb:font-semibold rb:text-[28px] rb:leading-12 rb:rounded-xl rb:text-white rb:bg-[#155EEF]">{name?.[0]}</div>
|
||||
<div className="rb:text-[16px] rb:font-semibold rb:leading-6 rb:line-clamp-2 rb:flex-1">
|
||||
{name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rb:flex rb:gap-2 rb:mb-2 rb:flex-wrap rb:mt-6.25">
|
||||
{tags?.map((tag, tagIndex) => (
|
||||
<span key={tag.tag} className="rb:rounded-[11px] rb:p-[0_8px] rb:leading-5.5 rb:border"
|
||||
style={{
|
||||
backgroundColor: `rgba(${tagColors[tagIndex % tagColors.length]}, 0.08)`,
|
||||
borderColor: `rgba(${tagColors[tagIndex % tagColors.length]}, 0.3)`,
|
||||
color: `rgba(${tagColors[tagIndex % tagColors.length]}, 1)`,
|
||||
}}
|
||||
>
|
||||
{tag.tag}({tag.frequency})
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Total Memory */}
|
||||
<div className="rb:font-regular rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:mb-6.25">
|
||||
{t('userMemory.totalNumOfMemories')}
|
||||
<div className="rb:font-extrabold rb:text-[24px] rb:text-[#212332] rb:leading-7.5 rb:mt-2">{memory || 0}</div>
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
{/* About Me */}
|
||||
<>
|
||||
<Title
|
||||
type="aboutUs"
|
||||
title={t('userMemory.aboutMe')}
|
||||
icon={aboutUs}
|
||||
t={t}
|
||||
expanded={expanded.includes('aboutUs')}
|
||||
onClick={handleTitleClick}
|
||||
/>
|
||||
{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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user