feat(web): forgetting memory
This commit is contained in:
159
web/src/views/UserMemoryDetail/pages/ForgetDetail.tsx
Normal file
159
web/src/views/UserMemoryDetail/pages/ForgetDetail.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { type FC, useEffect, useState, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Row, Col, Progress } from 'antd'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import {
|
||||
getForgetStats,
|
||||
} from '@/api/memory'
|
||||
import type { ForgetData } from '../types'
|
||||
import ActivationMetricsPieCard from '../components/ActivationMetricsPieCard'
|
||||
import RecentTrendsLineCard from '../components/RecentTrendsLineCard'
|
||||
import Table from '@/components/Table'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import StatusTag from '@/components/StatusTag'
|
||||
|
||||
const statusTagColors: Record<string, 'success' | 'purple' | 'default' | 'warning' | 'error' | 'lightBlue'> = {
|
||||
statement: 'success',
|
||||
entity: 'purple',
|
||||
summary: 'default',
|
||||
chunk: 'warning',
|
||||
}
|
||||
|
||||
const ForgetOverview: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { id } = useParams()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [data, setData] = useState<ForgetData>({} as ForgetData)
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return
|
||||
getData()
|
||||
}, [id])
|
||||
|
||||
// 记忆洞察
|
||||
const getData = () => {
|
||||
if (!id) return
|
||||
setLoading(true)
|
||||
getForgetStats(id).then((res) => {
|
||||
const response = res as ForgetData
|
||||
setData(response)
|
||||
setLoading(false)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
const chartData = useMemo(() => {
|
||||
const { activation_metrics } = data
|
||||
if (!activation_metrics) return []
|
||||
|
||||
let health_nodes = (activation_metrics.total_nodes || 0) - (activation_metrics.low_activation_nodes || 0) - (activation_metrics.nodes_without_activation || 0)
|
||||
|
||||
return [
|
||||
{ name: t('forgetDetail.health_nodes'), value: health_nodes },
|
||||
{ name: t('forgetDetail.nodes_without_activation'), value: activation_metrics.nodes_without_activation || 0 },
|
||||
{ name: t('forgetDetail.low_activation_nodes'), value: activation_metrics.low_activation_nodes || 0 },
|
||||
]
|
||||
|
||||
}, [data.activation_metrics, t])
|
||||
|
||||
const seriesList = useMemo(() => {
|
||||
const { recent_trends = [] } = data
|
||||
if (!recent_trends || recent_trends.length === 0) return { chartData: [], seriesList: [] }
|
||||
|
||||
return {
|
||||
chartData: recent_trends,
|
||||
seriesList: ['merged_count', 'average_activation']
|
||||
}
|
||||
}, [data.recent_trends])
|
||||
|
||||
return (
|
||||
<div className="rb:h-full rb:max-w-266 rb:mx-auto">
|
||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:mt-3">{t('forgetDetail.title')}</div>
|
||||
<div className="rb:bg-[rgba(21,94,239,0.12)] rb:px-4 rb:py-2.5 rb:font-medium rb:leading-5 rb:mb-4 rb:mt-6 rb:rounded-md">{t('forgetDetail.overviewTitle')}</div>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<RbCard>
|
||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular rb:mb-2">{t('forgetDetail.totalMemory')}</div>
|
||||
<div className="rb:text-[26px] rb:font-bold rb:leading-8.5">{data?.activation_metrics?.total_nodes ?? 0}</div>
|
||||
<div className="rb:mt-4 rb:grid rb:grid-cols-2 rb:gap-x-2 rb:gap-y-5 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:px-3 rb:py-2">
|
||||
{['statement_count', 'entity_count', 'summary_count', 'chunk_count'].map((key, index) => (
|
||||
<div key={index}>
|
||||
<div className="rb:text-[16px] rb:font-bold rb:leading-5.5">{data?.node_distribution?.[key as keyof typeof data.node_distribution] ?? 0}</div>
|
||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular rb:mt-1">{t(`forgetDetail.${key}`)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RbCard>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<RbCard>
|
||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular rb:mb-2">{t('forgetDetail.MemoryHealth')}</div>
|
||||
<div className="rb:text-[26px] rb:font-bold rb:leading-8.5">{data?.activation_metrics?.average_activation_value ?? 0}</div>
|
||||
<Progress className="rb:mt-px" showInfo={false} percent={data?.activation_metrics?.average_activation_value ?? 0} />
|
||||
<div className="rb:mt-4 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:px-3 rb:py-2">
|
||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular">{t('forgetDetail.healthStatus')}</div>
|
||||
<div className="rb:text-[20px] rb:font-semibold rb:leading-7">{data?.activation_metrics?.average_activation_value > data.activation_metrics?.forgetting_threshold ? t('forgetDetail.healthy') : t('forgetDetail.unhealthy')}</div>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.25 rb:mt-2">
|
||||
{t('forgetDetail.average')}<br />
|
||||
{t('forgetDetail.threshold')}{data.activation_metrics?.forgetting_threshold ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
</RbCard>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<RbCard>
|
||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular rb:mb-2">{t('forgetDetail.riskOfForgetting')}</div>
|
||||
<div className="rb:text-[26px] rb:font-bold rb:leading-8.5">{data.activation_metrics?.low_activation_nodes ?? 0}</div>
|
||||
<div className="rb:mb-31.5 rb:text-[#A8A9AA] rb:text-[12px] rb:leading-4 rb:font-regular rb:mt-1">{t('forgetDetail.low_nodes')}</div>
|
||||
</RbCard>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div className="rb:bg-[rgba(21,94,239,0.12)] rb:px-4 rb:py-2.5 rb:font-medium rb:leading-5 rb:mb-4 rb:mt-6 rb:rounded-md">{t('forgetDetail.memoryHealthVisualization')}</div>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<ActivationMetricsPieCard chartData={chartData} loading={loading} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<RecentTrendsLineCard chartData={seriesList.chartData} seriesList={seriesList.seriesList} loading={loading} />
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="rb:bg-[rgba(21,94,239,0.12)] rb:px-4 rb:py-2.5 rb:font-medium rb:leading-5 rb:mb-4 rb:mt-6 rb:rounded-md">{t('forgetDetail.pending_nodes')}</div>
|
||||
<Table
|
||||
rowKey='node_id'
|
||||
initialData={data.pending_nodes ?? []}
|
||||
columns={[
|
||||
{
|
||||
title: t('forgetDetail.content_summary'),
|
||||
dataIndex: 'content_summary',
|
||||
key: 'content_summary',
|
||||
width: 340,
|
||||
render: (content_summary) => <div className="rb:wrap-break-word rb:line-clamp-2">{content_summary}</div>
|
||||
},
|
||||
{
|
||||
title: t('forgetDetail.node_type'),
|
||||
dataIndex: 'node_type',
|
||||
key: 'node_type',
|
||||
render: (node_type: string) => {
|
||||
return <StatusTag status={statusTagColors[node_type] || 'default'} text={node_type} />}
|
||||
},
|
||||
{
|
||||
title: t('forgetDetail.last_access_time'),
|
||||
dataIndex: 'last_access_time',
|
||||
key: 'last_access_time',
|
||||
render: (last_access_time) => formatDateTime(last_access_time, 'YYYY-MM-DD HH:mm')
|
||||
},
|
||||
{
|
||||
title: t('forgetDetail.activation_value'),
|
||||
dataIndex: 'activation_value',
|
||||
key: 'activation_value',
|
||||
},
|
||||
]}
|
||||
pagination={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ForgetOverview
|
||||
@@ -1,53 +1,26 @@
|
||||
import { type FC, useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { type FC } from 'react'
|
||||
import { Row, Col, Space } from 'antd';
|
||||
|
||||
import WordCloud from '../components/WordCloud'
|
||||
import EmotionTags from '../components/EmotionTags'
|
||||
import Health from '../components/Health'
|
||||
import Suggestions from '../components/Suggestions'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import {
|
||||
getEndUserProfile,
|
||||
} from '@/api/memory'
|
||||
|
||||
|
||||
const StatementDetail: FC = () => {
|
||||
const { id } = useParams()
|
||||
const [name, setName] = useState<string>('')
|
||||
useEffect(() => {
|
||||
if (!id) return
|
||||
getData()
|
||||
}, [id])
|
||||
|
||||
const getData = () => {
|
||||
if (!id) return
|
||||
getEndUserProfile(id).then((res) => {
|
||||
const response = res as { other_name: string; id: string; }
|
||||
setName(response.other_name ?? response.id)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="rb:h-full rb:w-full">
|
||||
<PageHeader
|
||||
name={name}
|
||||
source="statement"
|
||||
/>
|
||||
<div className="rb:h-[calc(100vh-64px)] rb:overflow-y-auto rb:py-3 rb:px-4">
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Space size={16} direction="vertical" className="rb:w-full">
|
||||
<WordCloud />
|
||||
<EmotionTags />
|
||||
<Health />
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Suggestions />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Space size={16} direction="vertical" className="rb:w-full">
|
||||
<WordCloud />
|
||||
<EmotionTags />
|
||||
<Health />
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Suggestions />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
42
web/src/views/UserMemoryDetail/pages/index.tsx
Normal file
42
web/src/views/UserMemoryDetail/pages/index.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { type FC, useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import PageHeader from '../components/PageHeader'
|
||||
import StatementDetail from './StatementDetail'
|
||||
import ForgetDetail from './ForgetDetail'
|
||||
import {
|
||||
getEndUserProfile,
|
||||
} from '@/api/memory'
|
||||
|
||||
const Detail: FC = () => {
|
||||
const { id, type } = useParams()
|
||||
const [name, setName] = useState<string>('')
|
||||
useEffect(() => {
|
||||
if (!id) return
|
||||
getData()
|
||||
}, [id])
|
||||
|
||||
const getData = () => {
|
||||
if (!id) return
|
||||
getEndUserProfile(id).then((res) => {
|
||||
const response = res as { other_name: string; id: string; }
|
||||
setName(response.other_name || response.id)
|
||||
})
|
||||
}
|
||||
|
||||
console.log('Detail', name)
|
||||
return (
|
||||
<div className="rb:h-full rb:w-full">
|
||||
<PageHeader
|
||||
name={name}
|
||||
source="node"
|
||||
/>
|
||||
<div className="rb:h-[calc(100vh-64px)] rb:overflow-y-auto rb:py-3 rb:px-4">
|
||||
{type === 'EMOTIONAL_MEMORY' && <StatementDetail />}
|
||||
{type === 'FORGETTING_MANAGEMENT' && <ForgetDetail />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Detail
|
||||
Reference in New Issue
Block a user