-
- {t(`userMemory.${vo.type}`)}
-
- {vo.type === 'EMOTIONAL_MEMORY' &&
}
+ ?
+ :
+ {typeList.map((vo) => {
+ if (!vo.children) {
+ return
{renderCard(vo.key, vo.bg)}
+ }
+ return (
+
+
{t(`userMemory.${vo.key}`)}
+
+ {vo.children.map((child) =>
{renderCard(child.key, null, true)}
)}
-
{vo.count ?? 0}
- ))}
+ )
+ })}
- :
- }
+ }
)
}
diff --git a/web/src/views/UserMemoryDetail/components/PageHeader.tsx b/web/src/views/UserMemoryDetail/components/PageHeader.tsx
index dda60e7e..56da70e0 100644
--- a/web/src/views/UserMemoryDetail/components/PageHeader.tsx
+++ b/web/src/views/UserMemoryDetail/components/PageHeader.tsx
@@ -9,7 +9,7 @@ const { Header } = Layout;
interface ConfigHeaderProps {
name?: string;
operation?: ReactNode;
- source?: 'detail' | 'statement'
+ source?: 'detail' | 'node'
}
const PageHeader: FC
= ({
name,
diff --git a/web/src/views/UserMemoryDetail/components/RecentTrendsLineCard.tsx b/web/src/views/UserMemoryDetail/components/RecentTrendsLineCard.tsx
new file mode 100644
index 00000000..1d6bd213
--- /dev/null
+++ b/web/src/views/UserMemoryDetail/components/RecentTrendsLineCard.tsx
@@ -0,0 +1,191 @@
+import { type FC, useRef } from 'react'
+import { useTranslation } from 'react-i18next'
+import ReactEcharts from 'echarts-for-react';
+import * as echarts from 'echarts';
+import Empty from '@/components/Empty'
+import Loading from '@/components/Empty/Loading'
+import RbCard from '@/components/RbCard/Card'
+
+interface RecentTrendsLineCardProps {
+ chartData: Array>;
+ seriesList: string[];
+ loading?: boolean;
+}
+
+const Colors = ['#155EEF', '#FF5D34']
+
+const RecentTrendsLineCard: FC = ({ chartData, seriesList, loading }) => {
+ const { t } = useTranslation()
+ const chartRef = useRef(null);
+
+ const getSeries = () => {
+ return seriesList.map((key, index) => ({
+ name: key === 'merged_count' ? t('forgetDetail.merged_count') : t('forgetDetail.average_activation'),
+ type: 'line',
+ yAxisIndex: key === 'merged_count' ? 0 : 1,
+ smooth: true,
+ lineStyle: {
+ width: 3,
+ color: Colors[index]
+ },
+ itemStyle: {
+ color: Colors[index]
+ },
+ areaStyle: {
+ color: Colors[index],
+ opacity: 0.08
+ },
+ data: chartData.map(item => item[key])
+ }))
+ }
+
+ return (
+
+ {loading
+ ?
+ : !chartData || chartData.length === 0
+ ?
+ : `
+ params.forEach((param: any) => {
+ result += `${param.marker}${param.seriesName}: ${param.value}
`
+ })
+ return result
+ }
+ },
+ legend: {
+ bottom: 2,
+ padding: 0,
+ itemGap: 24,
+ itemWidth: 40,
+ itemHeight: 12,
+ borderRadius: 2,
+ orient: 'horizontal',
+ textStyle: {
+ color: '#5B6167',
+ fontFamily: 'PingFangSC, PingFang SC',
+ lineHeight: 16,
+ }
+ },
+ grid: {
+ top: 16,
+ left: 30,
+ right: 36,
+ bottom: 48,
+ // containLabel: false
+ },
+ xAxis: {
+ type: 'category',
+ data: chartData.map(item => item.date),
+ boundaryGap: false,
+ axisLabel: {
+ color: '#A8A9AA',
+ fontFamily: 'PingFangSC, PingFang SC'
+ },
+ axisLine: {
+ show: true,
+ lineStyle: {
+ color: '#EBEBEB'
+ }
+ },
+ splitLine: {
+ show: true,
+ lineStyle: {
+ color: '#EBEBEB',
+ type: 'solid'
+ }
+ },
+ axisTick: {
+ show: true,
+ lineStyle: {
+ color: '#EBEBEB',
+ type: 'solid'
+ }
+ }
+ },
+ yAxis: [
+ {
+ type: 'value',
+ position: 'left',
+ axisLabel: {
+ color: Colors[0],
+ fontFamily: 'PingFangSC, PingFang SC'
+ },
+ axisLine: {
+ lineStyle: {
+ color: Colors[0]
+ }
+ },
+ splitLine: {
+ show: true,
+ lineStyle: {
+ color: '#EBEBEB',
+ type: 'solid'
+ }
+ },
+ axisTick: {
+ show: true,
+ lineStyle: {
+ color: '#EBEBEB',
+ type: 'solid'
+ }
+ },
+ },
+ {
+ type: 'value',
+ position: 'right',
+ axisLabel: {
+ color: Colors[1],
+ fontFamily: 'PingFangSC, PingFang SC',
+ formatter: '{value}'
+ },
+ axisLine: {
+ lineStyle: {
+ color: Colors[1]
+ }
+ },
+ splitLine: {
+ show: false,
+ },
+ axisTick: {
+ show: true,
+ lineStyle: {
+ color: '#EBEBEB',
+ type: 'solid'
+ }
+ },
+ max: 1,
+ min: 0
+ }
+ ],
+ series: getSeries()
+ }}
+ style={{ height: '265px', width: '100%', minWidth: '100%' }}
+ notMerge={true}
+ lazyUpdate={true}
+ />
+ }
+
+ )
+}
+
+export default RecentTrendsLineCard
diff --git a/web/src/views/UserMemoryDetail/pages/ForgetDetail.tsx b/web/src/views/UserMemoryDetail/pages/ForgetDetail.tsx
new file mode 100644
index 00000000..6a59d41a
--- /dev/null
+++ b/web/src/views/UserMemoryDetail/pages/ForgetDetail.tsx
@@ -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 = {
+ statement: 'success',
+ entity: 'purple',
+ summary: 'default',
+ chunk: 'warning',
+}
+
+const ForgetOverview: FC = () => {
+ const { t } = useTranslation()
+ const { id } = useParams()
+ const [loading, setLoading] = useState(false)
+ const [data, setData] = useState({} 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 (
+
+
{t('forgetDetail.title')}
+
{t('forgetDetail.overviewTitle')}
+
+
+
+ {t('forgetDetail.totalMemory')}
+ {data?.activation_metrics?.total_nodes ?? 0}
+
+ {['statement_count', 'entity_count', 'summary_count', 'chunk_count'].map((key, index) => (
+
+
{data?.node_distribution?.[key as keyof typeof data.node_distribution] ?? 0}
+
{t(`forgetDetail.${key}`)}
+
+ ))}
+
+
+
+
+
+ {t('forgetDetail.MemoryHealth')}
+ {data?.activation_metrics?.average_activation_value ?? 0}
+
+
+
{t('forgetDetail.healthStatus')}
+
{data?.activation_metrics?.average_activation_value > data.activation_metrics?.forgetting_threshold ? t('forgetDetail.healthy') : t('forgetDetail.unhealthy')}
+
+ {t('forgetDetail.average')}
+ {t('forgetDetail.threshold')}{data.activation_metrics?.forgetting_threshold ?? 0}
+
+
+
+
+
+
+ {t('forgetDetail.riskOfForgetting')}
+ {data.activation_metrics?.low_activation_nodes ?? 0}
+ {t('forgetDetail.low_nodes')}
+
+
+
+
+
{t('forgetDetail.memoryHealthVisualization')}
+
+
+
+
+
+
+
+
+
{t('forgetDetail.pending_nodes')}
+
{content_summary}
+ },
+ {
+ title: t('forgetDetail.node_type'),
+ dataIndex: 'node_type',
+ key: 'node_type',
+ render: (node_type: string) => {
+ return }
+ },
+ {
+ 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}
+ />
+
+ )
+}
+export default ForgetOverview
\ No newline at end of file
diff --git a/web/src/views/UserMemoryDetail/pages/StatementDetail.tsx b/web/src/views/UserMemoryDetail/pages/StatementDetail.tsx
index 744c244d..e6ddfd20 100644
--- a/web/src/views/UserMemoryDetail/pages/StatementDetail.tsx
+++ b/web/src/views/UserMemoryDetail/pages/StatementDetail.tsx
@@ -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('')
- 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 (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/web/src/views/UserMemoryDetail/pages/index.tsx b/web/src/views/UserMemoryDetail/pages/index.tsx
new file mode 100644
index 00000000..6b78a210
--- /dev/null
+++ b/web/src/views/UserMemoryDetail/pages/index.tsx
@@ -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('')
+ 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 (
+
+
+
+ {type === 'EMOTIONAL_MEMORY' && }
+ {type === 'FORGETTING_MANAGEMENT' && }
+
+
+ )
+}
+
+export default Detail
\ No newline at end of file
diff --git a/web/src/views/UserMemoryDetail/types.ts b/web/src/views/UserMemoryDetail/types.ts
index 8fd050a9..77dd653e 100644
--- a/web/src/views/UserMemoryDetail/types.ts
+++ b/web/src/views/UserMemoryDetail/types.ts
@@ -140,4 +140,38 @@ export interface AboutMeRef {
}
export interface EndUserProfileRef {
data: EndUser | null
+}
+
+
+export interface ForgetData {
+ activation_metrics: {
+ total_nodes: number;
+ nodes_with_activation: number;
+ nodes_without_activation: number;
+ average_activation_value: number;
+ low_activation_nodes: number;
+ timestamp: number;
+ forgetting_threshold: number;
+ },
+ node_distribution: {
+ statement_count: number;
+ entity_count: number;
+ summary_count: number;
+ chunk_count: number;
+ },
+ recent_trends: {
+ date: string;
+ merged_count: number;
+ average_activation: number;
+ total_nodes: number;
+ execution_time: number;
+ }[],
+ pending_nodes: {
+ node_id: string;
+ node_type: string;
+ content_summary: string;
+ activation_value: number;
+ last_access_time: number;
+ }[],
+ timestamp: number;
}
\ No newline at end of file