feat(web): forgetting memory
This commit is contained in:
@@ -134,6 +134,10 @@ export const getEmotionSuggestions = (group_id: string) => {
|
|||||||
export const analyticsRefresh = (end_user_id: string) => {
|
export const analyticsRefresh = (end_user_id: string) => {
|
||||||
return request.post('/memory-storage/analytics/generate_cache', { end_user_id })
|
return request.post('/memory-storage/analytics/generate_cache', { end_user_id })
|
||||||
}
|
}
|
||||||
|
export const getForgetStats = (group_id: string) => {
|
||||||
|
return request.get(`/memory/forget/stats`, { group_id })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*************** end 用户记忆 相关接口 ******************************/
|
/*************** end 用户记忆 相关接口 ******************************/
|
||||||
|
|
||||||
|
|||||||
14
web/src/assets/images/userMemory/arrow_right_hover.svg
Normal file
14
web/src/assets/images/userMemory/arrow_right_hover.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>编组 5</title>
|
||||||
|
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="红熊空间-记忆库-用户记忆-详情" transform="translate(-1212, -359)" stroke="#155EEF">
|
||||||
|
<g id="记忆洞察" transform="translate(1044, 79)">
|
||||||
|
<g id="编组-5" transform="translate(168, 280)">
|
||||||
|
<polyline id="路径" points="12.5 7.5 15 10 12.5 12.5"></polyline>
|
||||||
|
<line x1="15" y1="10" x2="2.5" y2="10" id="路径-2"></line>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 775 B |
@@ -3,23 +3,27 @@ import { Tag } from 'antd';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
interface StatusTagProps {
|
interface StatusTagProps {
|
||||||
status: 'success' | 'error' | 'warning',
|
status: 'success' | 'error' | 'warning' | 'default' | 'lightBlue' | 'purple',
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
const Colors = {
|
const Colors = {
|
||||||
success: 'rb:bg-[#369F21]',
|
success: 'rb:bg-[#369F21]',
|
||||||
error: 'rb:bg-[#FF5D34]',
|
error: 'rb:bg-[#FF5D34]',
|
||||||
warning: 'rb:bg-[#FF8A4C]',
|
warning: 'rb:bg-[#FF8A4C]',
|
||||||
|
default: 'rb:bg-[#155EEF]',
|
||||||
|
lightBlue: 'rb:bg-[#4DA8FF]',
|
||||||
|
purple: 'rb:bg-[#9C6FFF]'
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusTag: FC<StatusTagProps> = ({
|
const StatusTag: FC<StatusTagProps> = ({
|
||||||
status,
|
status,
|
||||||
text
|
text
|
||||||
}) => {
|
}) => {
|
||||||
|
console.log('status', status)
|
||||||
return (
|
return (
|
||||||
<Tag style={{ backgroundColor: '#fff', borderColor: '#DFE4ED' }}>
|
<Tag style={{ backgroundColor: '#fff', borderColor: '#DFE4ED' }}>
|
||||||
<span className='rb:flex rb:items-center rb:text-[#5B6167] rb:pl-[1px] rb:pr-[1px]'>
|
<span className='rb:flex rb:items-center rb:text-[#5B6167] rb:pl-px rb:pr-px'>
|
||||||
<span className={clsx(' rb:w-[5px] rb:h-[5px] rb:rounded-full rb:mr-[4px]', Colors[status])}></span>
|
<span className={clsx(' rb:w-1.25 rb:h-1.25 rb:rounded-full rb:mr-1', Colors[status])}></span>
|
||||||
{ text }
|
{ text }
|
||||||
</span>
|
</span>
|
||||||
</Tag>
|
</Tag>
|
||||||
|
|||||||
@@ -2150,5 +2150,37 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
|||||||
orderPayInfo: 'Payment Information',
|
orderPayInfo: 'Payment Information',
|
||||||
create_time: 'Creation Time',
|
create_time: 'Creation Time',
|
||||||
},
|
},
|
||||||
|
forgetDetail: {
|
||||||
|
title: 'The forgetting management system helps AI intelligently manage memory lifecycle by automatically identifying low-value memories, setting forgetting strategies, and performing regular cleanup to optimize memory storage space and improve retrieval efficiency.',
|
||||||
|
overviewTitle: 'Core Metrics Overview',
|
||||||
|
totalMemory: 'Total Memory',
|
||||||
|
MemoryHealth: 'Memory Health',
|
||||||
|
riskOfForgetting: 'Risk of Forgetting',
|
||||||
|
statement_count: 'Statement',
|
||||||
|
entity_count: 'Entity',
|
||||||
|
summary_count: 'Summary',
|
||||||
|
chunk_count: 'Chunk',
|
||||||
|
healthStatus: 'Health Status',
|
||||||
|
average: 'Average Activation Value',
|
||||||
|
threshold: 'Threshold Reference:',
|
||||||
|
unhealthy: 'Unhealthy',
|
||||||
|
healthy: 'Healthy',
|
||||||
|
low_nodes: 'Low Activation Nodes',
|
||||||
|
memoryHealthVisualization: 'Memory Health Visualization',
|
||||||
|
activationValueDistribution: 'Activation Value Distribution',
|
||||||
|
forgettingTrend: 'Forgetting Trend (Last 7 Days)',
|
||||||
|
|
||||||
|
nodes_without_activation: 'Observation Zone',
|
||||||
|
low_activation_nodes: 'Forgetting Zone',
|
||||||
|
health_nodes: 'Healthy Zone',
|
||||||
|
average_activation: 'Average Activation Value',
|
||||||
|
merged_count: 'Daily Merged Nodes Count',
|
||||||
|
|
||||||
|
pending_nodes: 'Risk Node Forgetting Pool',
|
||||||
|
content_summary: 'Content Summary',
|
||||||
|
node_type: 'Node Type',
|
||||||
|
last_access_time: 'Last Activation Time',
|
||||||
|
activation_value: 'Current Activation Value',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2256,6 +2256,31 @@ export const zh = {
|
|||||||
totalMemory: '记忆总量',
|
totalMemory: '记忆总量',
|
||||||
MemoryHealth: '记忆健康度',
|
MemoryHealth: '记忆健康度',
|
||||||
riskOfForgetting: '遗忘风险',
|
riskOfForgetting: '遗忘风险',
|
||||||
}
|
statement_count: '陈述',
|
||||||
|
entity_count: '实体',
|
||||||
|
summary_count: '摘要',
|
||||||
|
chunk_count: '片段',
|
||||||
|
healthStatus: '健康状态',
|
||||||
|
average: '平均激活值',
|
||||||
|
threshold: '阈值参考:',
|
||||||
|
unhealthy: '不健康',
|
||||||
|
healthy: '健康',
|
||||||
|
low_nodes: '低激活节点',
|
||||||
|
memoryHealthVisualization: '记忆健康可视化',
|
||||||
|
activationValueDistribution: '激活值分布',
|
||||||
|
forgettingTrend: '遗忘趋势(近7天)',
|
||||||
|
|
||||||
|
nodes_without_activation: '观察区',
|
||||||
|
low_activation_nodes: '遗忘区',
|
||||||
|
health_nodes: '健康区',
|
||||||
|
average_activation: '平均激活值',
|
||||||
|
merged_count: '每日融合节点数',
|
||||||
|
|
||||||
|
pending_nodes: '风险节点遗忘池',
|
||||||
|
content_summary: '内容摘要',
|
||||||
|
node_type: '节点类型',
|
||||||
|
last_access_time: '最后激活时间',
|
||||||
|
activation_value: '当前激活值',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,8 @@ const componentMap: Record<string, LazyExoticComponent<ComponentType<object>>> =
|
|||||||
ApiKeyManagement: lazy(() => import('@/views/ApiKeyManagement')),
|
ApiKeyManagement: lazy(() => import('@/views/ApiKeyManagement')),
|
||||||
EmotionEngine: lazy(() => import('@/views/EmotionEngine')),
|
EmotionEngine: lazy(() => import('@/views/EmotionEngine')),
|
||||||
StatementDetail: lazy(() => import('@/views/UserMemoryDetail/pages/StatementDetail')),
|
StatementDetail: lazy(() => import('@/views/UserMemoryDetail/pages/StatementDetail')),
|
||||||
|
ForgetDetail: lazy(() => import('@/views/UserMemoryDetail/pages/ForgetDetail')),
|
||||||
|
MemoryNodeDetail: lazy(() => import('@/views/UserMemoryDetail/pages/index')),
|
||||||
SelfReflectionEngine: lazy(() => import('@/views/SelfReflectionEngine')),
|
SelfReflectionEngine: lazy(() => import('@/views/SelfReflectionEngine')),
|
||||||
OrderPayment: lazy(() => import('@/views/OrderPayment')),
|
OrderPayment: lazy(() => import('@/views/OrderPayment')),
|
||||||
OrderHistory: lazy(() => import('@/views/OrderHistory')),
|
OrderHistory: lazy(() => import('@/views/OrderHistory')),
|
||||||
|
|||||||
@@ -43,7 +43,8 @@
|
|||||||
{ "path": "/application/config/:id", "element": "ApplicationConfig" },
|
{ "path": "/application/config/:id", "element": "ApplicationConfig" },
|
||||||
{ "path": "/conversation/:token", "element": "Conversation" },
|
{ "path": "/conversation/:token", "element": "Conversation" },
|
||||||
{ "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" },
|
{ "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" },
|
||||||
{ "path": "/statement/:id", "element": "StatementDetail" }
|
{ "path": "/statement/:id", "element": "StatementDetail" },
|
||||||
|
{ "path": "/user-memory/detail/:id/:type", "element": "MemoryNodeDetail" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
|||||||
</div>
|
</div>
|
||||||
<Form form={form} layout="vertical">
|
<Form form={form} layout="vertical">
|
||||||
<Space size={20} direction="vertical" style={{width: '100%'}}>
|
<Space size={20} direction="vertical" style={{width: '100%'}}>
|
||||||
<Card title={t('application.handoffs')}>
|
<Card title={t('application.collaboration')}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="orchestration_mode"
|
name="orchestration_mode"
|
||||||
noStyle
|
noStyle
|
||||||
|
|||||||
@@ -88,11 +88,11 @@ export interface MultiAgentConfig {
|
|||||||
app_id: string;
|
app_id: string;
|
||||||
default_model_config_id?: string;
|
default_model_config_id?: string;
|
||||||
model_parameters: ModelConfig;
|
model_parameters: ModelConfig;
|
||||||
orchestration_mode: 'conditional' | 'sequential' | 'parallel';
|
|
||||||
sub_agents?: SubAgentItem[];
|
sub_agents?: SubAgentItem[];
|
||||||
routing_rules: null;
|
routing_rules: null;
|
||||||
|
orchestration_mode: 'supervisor' | 'collaboration';
|
||||||
execution_config: {
|
execution_config: {
|
||||||
routing_mode: 'master' | 'handoffs'
|
sub_agent_execution_mode: 'sequential' | 'parallel';
|
||||||
};
|
};
|
||||||
aggregation_strategy: 'merge' | 'vote' | 'priority'
|
aggregation_strategy: 'merge' | 'vote' | 'priority'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const Neo4j: FC = () => {
|
|||||||
const aboutMeRef = useRef<AboutMeRef>(null)
|
const aboutMeRef = useRef<AboutMeRef>(null)
|
||||||
|
|
||||||
const handleNameUpdate = (data: { other_name?: string; id: string }) => {
|
const handleNameUpdate = (data: { other_name?: string; id: string }) => {
|
||||||
setName(data.other_name ?? data.id)
|
setName(data.other_name && data.other_name !== '' ? data.other_name : data.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { type FC, useRef, useEffect } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import ReactEcharts from 'echarts-for-react';
|
||||||
|
import Loading from '@/components/Empty/Loading'
|
||||||
|
import Empty from '@/components/Empty'
|
||||||
|
import RbCard from '@/components/RbCard/Card'
|
||||||
|
|
||||||
|
interface ActivationMetricsPieCardProps {
|
||||||
|
chartData: Array<Record<string, string | number>>;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
const Colors = ['#155EEF', '#FFB048', '#FF5D34']
|
||||||
|
|
||||||
|
const ActivationMetricsPieCard: FC<ActivationMetricsPieCardProps> = ({ chartData, loading }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const chartRef = useRef<ReactEcharts>(null);
|
||||||
|
const resizeScheduledRef = useRef(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartRef.current && !resizeScheduledRef.current) {
|
||||||
|
resizeScheduledRef.current = true
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
chartRef.current?.getEchartsInstance().resize();
|
||||||
|
resizeScheduledRef.current = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(handleResize)
|
||||||
|
const chartElement = chartRef.current?.getEchartsInstance().getDom().parentElement
|
||||||
|
if (chartElement) {
|
||||||
|
resizeObserver.observe(chartElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
}
|
||||||
|
}, [chartData])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RbCard
|
||||||
|
title={t('forgetDetail.activationValueDistribution')}
|
||||||
|
headerType="borderless"
|
||||||
|
>
|
||||||
|
{loading
|
||||||
|
? <Loading size={249} />
|
||||||
|
: !chartData || chartData.length === 0
|
||||||
|
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
|
||||||
|
: <ReactEcharts
|
||||||
|
option={{
|
||||||
|
color: Colors,
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
textStyle: {
|
||||||
|
color: '#5B6167',
|
||||||
|
fontSize: 12,
|
||||||
|
width: 27,
|
||||||
|
height: 16,
|
||||||
|
},
|
||||||
|
formatter: '{d}%',
|
||||||
|
padding: [8, 5],
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderColor: '#DFE4ED',
|
||||||
|
extraCssText: 'width: 36px; height: 36px; box-shadow: 0px 2px 4px 0px rgba(33,35,50,0.12);border-radius: 36px;'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
bottom: 14 ,
|
||||||
|
padding: 0,
|
||||||
|
itemGap: 24,
|
||||||
|
itemWidth: 40,
|
||||||
|
itemHeight: 12,
|
||||||
|
borderRadius: 2,
|
||||||
|
orient: 'horizontal',
|
||||||
|
textStyle: {
|
||||||
|
color: '#5B6167',
|
||||||
|
fontFamily: 'PingFangSC, PingFang SC',
|
||||||
|
lineHeight: 16,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Access From',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['50%', '90%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
percentPrecision: 0,
|
||||||
|
padAngle: 4,
|
||||||
|
width: 200,
|
||||||
|
height: 200,
|
||||||
|
left: 143,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 0
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
position: 'center'
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#212332',
|
||||||
|
formatter: '{d}%\n{b}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
data: chartData
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
style={{ height: '265px', width: '100%', minWidth: '400px' }}
|
||||||
|
notMerge={true}
|
||||||
|
lazyUpdate={true}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</RbCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActivationMetricsPieCard
|
||||||
@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { useParams, useNavigate } from 'react-router-dom'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import { Skeleton } from 'antd';
|
import { Skeleton } from 'antd';
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import Empty from '@/components/Empty';
|
|
||||||
import {
|
import {
|
||||||
getNodeStatistics,
|
getNodeStatistics,
|
||||||
} from '@/api/memory'
|
} from '@/api/memory'
|
||||||
@@ -15,11 +14,25 @@ const BG_LIST = [
|
|||||||
'rb:bg-[linear-gradient(316deg,rgba(21,94,239,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
'rb:bg-[linear-gradient(316deg,rgba(21,94,239,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
||||||
'rb:bg-[linear-gradient(316deg,rgba(54,159,33,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
'rb:bg-[linear-gradient(316deg,rgba(54,159,33,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
||||||
'rb:bg-[linear-gradient(314deg,rgba(156,111,255,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
'rb:bg-[linear-gradient(314deg,rgba(156,111,255,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
||||||
'rb:bg-[linear-gradient(314deg,rgba(255,93,52,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
'rb:bg-[linear-gradient(332deg,rgba(255,93,52,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
||||||
'rb:bg-[linear-gradient(180deg,rgba(156,111,255,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
'rb:bg-[linear-gradient(313deg,rgba(156,111,255,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
||||||
'rb:bg-[linear-gradient(180deg,rgba(21,94,239,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
'rb:bg-[linear-gradient(332deg,rgba(54,159,33,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
||||||
'rb:bg-[linear-gradient(180deg,rgba(54,159,33,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
]
|
||||||
'rb:bg-[]',
|
const typeList = [
|
||||||
|
{ key: 'PERCEPTUAL_MEMORY', bg: 0 },
|
||||||
|
{ key: 'WORKING_MEMORY', bg: 1 },
|
||||||
|
{ key: 'EMOTIONAL_MEMORY', bg: 2 },
|
||||||
|
{ key: 'SHORT_TERM_MEMORY', bg: 3 },
|
||||||
|
{
|
||||||
|
key: 'LONG_TERM_MEMORY',
|
||||||
|
bg: 4,
|
||||||
|
children: [
|
||||||
|
{ key: 'IMPLICIT_MEMORY' },
|
||||||
|
{ key: 'EPISODIC_MEMORY' },
|
||||||
|
{ key: 'EXPLICIT_MEMORY' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ key: 'FORGETTING_MANAGEMENT', bg: 5 },
|
||||||
]
|
]
|
||||||
|
|
||||||
const NodeStatistics: FC = () => {
|
const NodeStatistics: FC = () => {
|
||||||
@@ -52,43 +65,59 @@ const NodeStatistics: FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const handleViewDetail = (type: string) => {
|
const handleViewDetail = (type: string) => {
|
||||||
switch (type) {
|
navigate(`/user-memory/detail/${id}/${type}`)
|
||||||
case 'EMOTIONAL_MEMORY':
|
|
||||||
navigate(`/statement/${id}`)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const renderCard = (key: string, bgIndex: number | null, isChild: boolean = false) => {
|
||||||
|
const item = data.find((item) => item.type === key)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"rb:flex rb:flex-col rb:justify-between rb:group rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:pt-3 rb:px-4 rb:pb-5 rb:cursor-pointer",
|
||||||
|
{
|
||||||
|
'rb:h-45': !isChild,
|
||||||
|
'rb:h-31': isChild
|
||||||
|
},
|
||||||
|
typeof bgIndex === 'number' ? BG_LIST[bgIndex] : 'rb:bg-[#FBFDFF]'
|
||||||
|
)}
|
||||||
|
onClick={() => handleViewDetail(key)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className={clsx("rb:text-[#5B6167] rb:leading-5 rb:font-regular", {
|
||||||
|
'rb:mb-2': !isChild,
|
||||||
|
'rb:mb-1': isChild
|
||||||
|
})}>
|
||||||
|
{t(`userMemory.${key}`)}
|
||||||
|
</div>
|
||||||
|
<div className="rb:w-3 rb:h-3 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/userMemory/arrow_right.svg')] rb:group-hover:bg-[url('@/assets/images/userMemory/arrow_right_hover.svg')]"></div>
|
||||||
|
</div>
|
||||||
|
<div className="rb:text-[28px] rb:leading-8.75 rb:font-extrabold">{item?.count ?? 0}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RbCard
|
<RbCard
|
||||||
title={<>{t('userMemory.nodeStatistics')} <span className="rb:text-[#5B6167] rb:font-normal!">({t('userMemory.total')}: {total})</span></>}
|
title={<>{t('userMemory.nodeStatistics')} <span className="rb:text-[#5B6167] rb:font-normal!">({t('userMemory.total')}: {total})</span></>}
|
||||||
headerType="borderless"
|
headerType="borderless"
|
||||||
>
|
>
|
||||||
{loading
|
{loading
|
||||||
? <Skeleton />
|
? <Skeleton active />
|
||||||
: data && data.length > 0
|
: <div className="rb:w-full rb:grid rb:grid-cols-8 rb:gap-3">
|
||||||
? <div className={`rb:w-full rb:grid rb:grid-cols-8 rb:gap-3`}>
|
{typeList.map((vo) => {
|
||||||
{data.map((vo, index) => (
|
if (!vo.children) {
|
||||||
<div
|
return <div key={vo.key}>{renderCard(vo.key, vo.bg)}</div>
|
||||||
key={vo.type}
|
}
|
||||||
className={clsx("rb:flex rb:flex-col rb:justify-between rb:group rb:border rb:border-[#DFE4ED] rb:h-45 rb:rounded-lg rb:pt-3 rb:px-4 rb:pb-5", {
|
return (
|
||||||
'rb:cursor-pointer': vo.type === 'EMOTIONAL_MEMORY'
|
<div key={vo.key} className={clsx("rb:col-span-3 rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:p-3", BG_LIST[vo.bg])}>
|
||||||
}, BG_LIST[index])}
|
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular rb:mb-3">{t(`userMemory.${vo.key}`)}</div>
|
||||||
onClick={() => handleViewDetail(vo.type)}
|
<div className="rb:grid rb:grid-cols-3 rb:gap-3">
|
||||||
>
|
{vo.children.map((child) => <div key={child.key}>{renderCard(child.key, null, true)}</div>)}
|
||||||
<div>
|
|
||||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular">
|
|
||||||
{t(`userMemory.${vo.type}`)}
|
|
||||||
</div>
|
|
||||||
{vo.type === 'EMOTIONAL_MEMORY' && <div
|
|
||||||
className="rb:w-3 rb:h-3 rb:-ml-0.75 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/home/arrow_top_right.svg')] rb:group-hover:bg-[url('@/assets/images/home/arrow_top_right_hover.svg')]"
|
|
||||||
></div>}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="rb:text-[28px] rb:leading-8.75 rb:font-extrabold">{vo.count ?? 0}</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
: <Empty size={80} />
|
}
|
||||||
}
|
|
||||||
</RbCard>
|
</RbCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const { Header } = Layout;
|
|||||||
interface ConfigHeaderProps {
|
interface ConfigHeaderProps {
|
||||||
name?: string;
|
name?: string;
|
||||||
operation?: ReactNode;
|
operation?: ReactNode;
|
||||||
source?: 'detail' | 'statement'
|
source?: 'detail' | 'node'
|
||||||
}
|
}
|
||||||
const PageHeader: FC<ConfigHeaderProps> = ({
|
const PageHeader: FC<ConfigHeaderProps> = ({
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -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<Record<string, string | number>>;
|
||||||
|
seriesList: string[];
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Colors = ['#155EEF', '#FF5D34']
|
||||||
|
|
||||||
|
const RecentTrendsLineCard: FC<RecentTrendsLineCardProps> = ({ chartData, seriesList, loading }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const chartRef = useRef<ReactEcharts>(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 (
|
||||||
|
<RbCard
|
||||||
|
title={t('forgetDetail.forgettingTrend')}
|
||||||
|
headerType="borderless"
|
||||||
|
>
|
||||||
|
{loading
|
||||||
|
? <Loading size={249} />
|
||||||
|
: !chartData || chartData.length === 0
|
||||||
|
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
|
||||||
|
: <ReactEcharts
|
||||||
|
ref={chartRef}
|
||||||
|
option={{
|
||||||
|
color: Colors,
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
extraCssText: 'box-shadow: 0px 2px 6px 0px rgba(33,35,50,0.16); border-radius: 8px;',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'line',
|
||||||
|
crossStyle: {
|
||||||
|
color: '#5F6266',
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
color: '#5F6266',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: function(params: any) {
|
||||||
|
let result = `${params[0].axisValue}<br/>`
|
||||||
|
params.forEach((param: any) => {
|
||||||
|
result += `${param.marker}${param.seriesName}: ${param.value}<br/>`
|
||||||
|
})
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</RbCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecentTrendsLineCard
|
||||||
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 { type FC } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
import { Row, Col, Space } from 'antd';
|
import { Row, Col, Space } from 'antd';
|
||||||
|
|
||||||
import WordCloud from '../components/WordCloud'
|
import WordCloud from '../components/WordCloud'
|
||||||
import EmotionTags from '../components/EmotionTags'
|
import EmotionTags from '../components/EmotionTags'
|
||||||
import Health from '../components/Health'
|
import Health from '../components/Health'
|
||||||
import Suggestions from '../components/Suggestions'
|
import Suggestions from '../components/Suggestions'
|
||||||
import PageHeader from '../components/PageHeader'
|
|
||||||
import {
|
|
||||||
getEndUserProfile,
|
|
||||||
} from '@/api/memory'
|
|
||||||
|
|
||||||
|
|
||||||
const StatementDetail: FC = () => {
|
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 (
|
return (
|
||||||
<div className="rb:h-full rb:w-full">
|
<Row gutter={[16, 16]}>
|
||||||
<PageHeader
|
<Col span={12}>
|
||||||
name={name}
|
<Space size={16} direction="vertical" className="rb:w-full">
|
||||||
source="statement"
|
<WordCloud />
|
||||||
/>
|
<EmotionTags />
|
||||||
<div className="rb:h-[calc(100vh-64px)] rb:overflow-y-auto rb:py-3 rb:px-4">
|
<Health />
|
||||||
<Row gutter={[16, 16]}>
|
</Space>
|
||||||
<Col span={12}>
|
</Col>
|
||||||
<Space size={16} direction="vertical" className="rb:w-full">
|
<Col span={12}>
|
||||||
<WordCloud />
|
<Suggestions />
|
||||||
<EmotionTags />
|
</Col>
|
||||||
<Health />
|
</Row>
|
||||||
</Space>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Suggestions />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
@@ -140,4 +140,38 @@ export interface AboutMeRef {
|
|||||||
}
|
}
|
||||||
export interface EndUserProfileRef {
|
export interface EndUserProfileRef {
|
||||||
data: EndUser | null
|
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;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user