feat(web): user memory detail
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
"echarts-for-react": "^3.0.2",
|
"echarts-for-react": "^3.0.2",
|
||||||
|
"echarts-wordcloud": "^2.1.0",
|
||||||
"i18next": "^25.6.0",
|
"i18next": "^25.6.0",
|
||||||
"js-yaml": "^4.1.1",
|
"js-yaml": "^4.1.1",
|
||||||
"lexical": "^0.39.0",
|
"lexical": "^0.39.0",
|
||||||
|
|||||||
@@ -134,9 +134,53 @@ 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) => {
|
export const getForgetStats = (group_id: string) => {
|
||||||
return request.get(`/memory/forget/stats`, { group_id })
|
return request.get(`/memory/forget/stats`, { group_id })
|
||||||
}
|
}
|
||||||
|
// 隐性记忆-偏好
|
||||||
|
export const getImplicitPreferences = (end_user_id: string) => {
|
||||||
|
return request.get(`/memory/implicit-memory/preferences/${end_user_id}`)
|
||||||
|
}
|
||||||
|
// 隐性记忆-核心特质
|
||||||
|
export const getImplicitPortrait = (end_user_id: string) => {
|
||||||
|
return request.get(`/memory/implicit-memory/portrait/${end_user_id}`)
|
||||||
|
}
|
||||||
|
// 隐性记忆-兴趣领域分布
|
||||||
|
export const getImplicitInterestAreas = (end_user_id: string) => {
|
||||||
|
return request.get(`/memory/implicit-memory/interest-areas/${end_user_id}`)
|
||||||
|
}
|
||||||
|
// 隐性记忆-用户习惯分析
|
||||||
|
export const getImplicitHabits = (end_user_id: string) => {
|
||||||
|
return request.get(`/memory/implicit-memory/habits/${end_user_id}`)
|
||||||
|
}
|
||||||
|
// 短期记忆
|
||||||
|
export const getShortTerm = (end_user_id: string) => {
|
||||||
|
return request.get(`/memory/short/short_term`, { end_user_id })
|
||||||
|
}
|
||||||
|
// 感知记忆-视觉记忆
|
||||||
|
export const getPerceptualLastVisual = (end_user: string) => {
|
||||||
|
return request.get(`/memory/perceptual/${end_user}/last_visual`)
|
||||||
|
}
|
||||||
|
// 感知记忆-音频记忆
|
||||||
|
export const getPerceptualLastListen = (end_user: string) => {
|
||||||
|
return request.get(`/memory/perceptual/${end_user}/last_listen`)
|
||||||
|
}
|
||||||
|
// 感知记忆-文本记忆
|
||||||
|
export const getPerceptualLastText = (end_user: string) => {
|
||||||
|
return request.get(`/memory/perceptual/${end_user}/last_text`)
|
||||||
|
}
|
||||||
|
// 感知记忆-感知记忆时间线
|
||||||
|
export const getPerceptualTimeline = (end_user: string) => {
|
||||||
|
return request.get(`/memory/perceptual/${end_user}/timeline`)
|
||||||
|
}
|
||||||
|
// 情景记忆-总览
|
||||||
|
export const getEpisodicOverview = (data: { end_user_id: string; time_range: string; episodic_type: string; } ) => {
|
||||||
|
return request.post(`/memory-storage/classifications/episodic-memory`, data)
|
||||||
|
}
|
||||||
|
export const getEpisodicDetail = (data: { end_user_id: string; summary_id: string; } ) => {
|
||||||
|
return request.post(`/memory-storage/classifications/episodic-memory-details`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*************** end 用户记忆 相关接口 ******************************/
|
/*************** end 用户记忆 相关接口 ******************************/
|
||||||
|
|||||||
BIN
web/src/assets/images/userMemory/shortTerm.png
Normal file
BIN
web/src/assets/images/userMemory/shortTerm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
14
web/src/assets/images/userMemory/up_border.svg
Normal file
14
web/src/assets/images/userMemory/up_border.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>下拉备份</title>
|
||||||
|
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="红熊空间-记忆管理-交互" transform="translate(-210, -24)" stroke="#212332">
|
||||||
|
<g id="编组-3" transform="translate(20, 20)">
|
||||||
|
<g id="下拉备份" transform="translate(198, 12) scale(1, -1) translate(-198, -12)translate(190, 4)">
|
||||||
|
<rect id="矩形" opacity="0.196382068" fill-rule="nonzero" x="0.5" y="0.5" width="15" height="15" rx="4"></rect>
|
||||||
|
<polyline id="路径" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" points="11 6.5 8 9.5 5 6.5"></polyline>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 925 B |
19
web/src/assets/images/userMemory/view.svg
Normal file
19
web/src/assets/images/userMemory/view.svg
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>查看</title>
|
||||||
|
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<g id="红熊空间-记忆管理-个人记忆-2" transform="translate(-245, -141)" stroke="#5B6167">
|
||||||
|
<g id="实体详情备份" transform="translate(62, 125)">
|
||||||
|
<g id="查看" transform="translate(183, 16)">
|
||||||
|
<g id="编组-22" transform="translate(2, 2)">
|
||||||
|
<path d="M0,4 L0,1 C0,0.44771525 0.44771525,1.11022302e-16 1,0 L4,0 L4,0" id="路径"></path>
|
||||||
|
<path d="M8,4 L8,1 C8,0.44771525 8.44771525,1.11022302e-16 9,0 L12,0 L12,0" id="路径备份-2" transform="translate(10, 2) scale(-1, 1) translate(-10, -2)"></path>
|
||||||
|
<circle id="椭圆形" cx="8" cy="8" r="3"></circle>
|
||||||
|
<path d="M0,12 L0,9 C0,8.44771525 0.44771525,8 1,8 L4,8 L4,8" id="路径备份" transform="translate(2, 10) scale(1, -1) translate(-2, -10)"></path>
|
||||||
|
<line x1="10.1926589" y1="10.2894584" x2="11.6795155" y2="11.7850158" id="路径-10"></line>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
19
web/src/assets/images/userMemory/view_hover.svg
Normal file
19
web/src/assets/images/userMemory/view_hover.svg
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>查看</title>
|
||||||
|
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<g id="红熊空间-记忆管理-个人记忆-2" transform="translate(-245, -141)" stroke="#5B6167">
|
||||||
|
<g id="实体详情备份" transform="translate(62, 125)">
|
||||||
|
<g id="查看" transform="translate(183, 16)">
|
||||||
|
<g id="编组-22" transform="translate(2, 2)">
|
||||||
|
<path d="M0,4 L0,1 C0,0.44771525 0.44771525,1.11022302e-16 1,0 L4,0 L4,0" id="路径"></path>
|
||||||
|
<path d="M8,4 L8,1 C8,0.44771525 8.44771525,1.11022302e-16 9,0 L12,0 L12,0" id="路径备份-2" transform="translate(10, 2) scale(-1, 1) translate(-10, -2)"></path>
|
||||||
|
<circle id="椭圆形" cx="8" cy="8" r="3"></circle>
|
||||||
|
<path d="M0,12 L0,9 C0,8.44771525 0.44771525,8 1,8 L4,8 L4,8" id="路径备份" transform="translate(2, 10) scale(1, -1) translate(-2, -10)"></path>
|
||||||
|
<line x1="10.1926589" y1="10.2894584" x2="11.6795155" y2="11.7850158" id="路径-10"></line>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -1223,6 +1223,23 @@ export const en = {
|
|||||||
growth_trajectory: 'Growth Trajectory',
|
growth_trajectory: 'Growth Trajectory',
|
||||||
personality: 'Personality Traits',
|
personality: 'Personality Traits',
|
||||||
core_values: 'Core Values',
|
core_values: 'Core Values',
|
||||||
|
|
||||||
|
Statement_emotion_keywords: 'Emotion Keywords',
|
||||||
|
Statement_emotion_type: 'Emotion Type',
|
||||||
|
Statement_emotion_subject: 'Emotion Subject',
|
||||||
|
Statement_importance_score: 'Importance Score',
|
||||||
|
|
||||||
|
ExtractedEntity_description: 'Description',
|
||||||
|
ExtractedEntity_name: 'Content',
|
||||||
|
ExtractedEntity_entity_type: 'Type',
|
||||||
|
ExtractedEntity_created_at: 'Created At',
|
||||||
|
ExtractedEntity_aliases: 'Aliases',
|
||||||
|
ExtractedEntity_connect_strngth: 'Connection Strength',
|
||||||
|
ExtractedEntity_importance_score: 'Importance Score',
|
||||||
|
|
||||||
|
associative_memory: 'Associative Memory',
|
||||||
|
unix: 'items',
|
||||||
|
completeMemory: 'Complete Memory',
|
||||||
},
|
},
|
||||||
space: {
|
space: {
|
||||||
createSpace: 'Create Space',
|
createSpace: 'Create Space',
|
||||||
@@ -2151,15 +2168,15 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
|||||||
create_time: 'Creation Time',
|
create_time: 'Creation Time',
|
||||||
},
|
},
|
||||||
forgetDetail: {
|
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.',
|
title: 'The forgetting management system helps AI intelligently manage memory lifecycle by automatically identifying low-value memories, setting forgetting strategies, and executing regular cleanup to optimize memory storage space and improve retrieval efficiency.',
|
||||||
overviewTitle: 'Core Metrics Overview',
|
overviewTitle: 'Core Metrics Overview',
|
||||||
totalMemory: 'Total Memory',
|
totalMemory: 'Total Memory',
|
||||||
MemoryHealth: 'Memory Health',
|
MemoryHealth: 'Memory Health',
|
||||||
riskOfForgetting: 'Risk of Forgetting',
|
riskOfForgetting: 'Forgetting Risk',
|
||||||
statement_count: 'Statement',
|
statement_count: 'Statements',
|
||||||
entity_count: 'Entity',
|
entity_count: 'Entities',
|
||||||
summary_count: 'Summary',
|
summary_count: 'Summaries',
|
||||||
chunk_count: 'Chunk',
|
chunk_count: 'Chunks',
|
||||||
healthStatus: 'Health Status',
|
healthStatus: 'Health Status',
|
||||||
average: 'Average Activation Value',
|
average: 'Average Activation Value',
|
||||||
threshold: 'Threshold Reference:',
|
threshold: 'Threshold Reference:',
|
||||||
@@ -2174,7 +2191,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
|||||||
low_activation_nodes: 'Forgetting Zone',
|
low_activation_nodes: 'Forgetting Zone',
|
||||||
health_nodes: 'Healthy Zone',
|
health_nodes: 'Healthy Zone',
|
||||||
average_activation: 'Average Activation Value',
|
average_activation: 'Average Activation Value',
|
||||||
merged_count: 'Daily Merged Nodes Count',
|
merged_count: 'Daily Merged Node Count',
|
||||||
|
|
||||||
pending_nodes: 'Risk Node Forgetting Pool',
|
pending_nodes: 'Risk Node Forgetting Pool',
|
||||||
content_summary: 'Content Summary',
|
content_summary: 'Content Summary',
|
||||||
@@ -2182,5 +2199,75 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
|||||||
last_access_time: 'Last Activation Time',
|
last_access_time: 'Last Activation Time',
|
||||||
activation_value: 'Current Activation Value',
|
activation_value: 'Current Activation Value',
|
||||||
},
|
},
|
||||||
|
episodicDetail: {
|
||||||
|
title: 'Record every important scene you have truly experienced',
|
||||||
|
total_all: 'Total Episodic Memories',
|
||||||
|
all: "All",
|
||||||
|
today: 'Today',
|
||||||
|
this_week: 'This Week',
|
||||||
|
this_month: 'This Month',
|
||||||
|
conversation: "Conversation",
|
||||||
|
project_work: "Project/Work",
|
||||||
|
learning: "Learning",
|
||||||
|
decision: "Decision",
|
||||||
|
important_event: "Important Event",
|
||||||
|
titleKeywordPlaceholder: 'Search episode title or content',
|
||||||
|
curResult: 'Current Filter Results',
|
||||||
|
unix: 'items',
|
||||||
|
created: 'Occurrence Time',
|
||||||
|
episodic_type: 'Episode Type',
|
||||||
|
involved_objects: 'Involved Objects',
|
||||||
|
content_records: 'Episode Content Records',
|
||||||
|
emotion: 'Emotion and State Records',
|
||||||
|
},
|
||||||
|
implicitDetail: {
|
||||||
|
title: 'The invisible forces that shaped me',
|
||||||
|
preferences: 'My Subconscious Preferences',
|
||||||
|
preferencesDetail: 'Association Network',
|
||||||
|
portraitTitle: 'My Subconscious Portrait',
|
||||||
|
portraitSubTitle: 'Personalized insights generated by AI based on your preference tags',
|
||||||
|
portrait: 'Core Traits',
|
||||||
|
aesthetic: 'Aesthetic Driven',
|
||||||
|
creativity: 'Creative Thinking',
|
||||||
|
literature: 'Cultural Sensitivity',
|
||||||
|
technology: 'Technology Affinity',
|
||||||
|
interestAreas: 'Interest Area Distribution',
|
||||||
|
art: 'Art & Design',
|
||||||
|
music: 'Music & Culture',
|
||||||
|
tech: 'Technology & Future',
|
||||||
|
lifestyle: 'Lifestyle',
|
||||||
|
habits: 'User Habit Analysis',
|
||||||
|
habitsSubTitle: 'Habit characteristics identified based on your behavior patterns',
|
||||||
|
context_details: 'Preference Details',
|
||||||
|
supporting_evidence: 'Preference Source',
|
||||||
|
specific_examples: 'Source',
|
||||||
|
},
|
||||||
|
shortTermDetail: {
|
||||||
|
title: 'Short-term memory is the "workbench" of the AI system, connecting instant conversations with long-term knowledge bases. Through real-time capture, deep retrieval, intelligent extraction and filtering transformation, temporary unstructured information is converted into valuable long-term knowledge.',
|
||||||
|
retrieval_number: 'Retrieval Count',
|
||||||
|
entity: 'Extracted Entities',
|
||||||
|
long_term_number: 'Long-term Candidates',
|
||||||
|
shortTermTitle: 'Deep Retrieval & Extended Answer Area',
|
||||||
|
shortTermSubTitle: 'Stores deep information retrieval performed to answer questions and the extended answers generated from it, including original questions, retrieved information, and generated answers.',
|
||||||
|
longTermTitle: 'Long-term Memory Candidate Pool',
|
||||||
|
longTermSubTitle: 'Aggregates short-term memory, filters and prepares content for storage in long-term memory. This is the "transfer station" and "filter" from short-term to long-term memory.',
|
||||||
|
answer: 'Answer',
|
||||||
|
query: 'Question',
|
||||||
|
noAnswer: 'No reply yet',
|
||||||
|
},
|
||||||
|
perceptualDetail: {
|
||||||
|
last_visual: 'Visual Perception Stream',
|
||||||
|
last_listen: 'Auditory Perception Stream',
|
||||||
|
last_text: 'Text Perception',
|
||||||
|
summary: 'Summary',
|
||||||
|
keywords: 'Keywords',
|
||||||
|
topic: 'Topic',
|
||||||
|
domain: 'Domain',
|
||||||
|
scene: 'Scene',
|
||||||
|
speaker_count: 'Number of Speakers',
|
||||||
|
section_count: 'Number of Sections',
|
||||||
|
timeLine: 'Perception Timeline',
|
||||||
|
lastInfo: 'Real-time Perception Dashboard',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1304,6 +1304,23 @@ export const zh = {
|
|||||||
growth_trajectory: '成长轨迹',
|
growth_trajectory: '成长轨迹',
|
||||||
personality: '性格特点',
|
personality: '性格特点',
|
||||||
core_values: '核心价值观',
|
core_values: '核心价值观',
|
||||||
|
|
||||||
|
Statement_emotion_keywords: '情感关键词',
|
||||||
|
Statement_emotion_type: '情感类型',
|
||||||
|
Statement_emotion_subject: '情感主体',
|
||||||
|
Statement_importance_score: '重要性评分',
|
||||||
|
|
||||||
|
ExtractedEntity_description: '描述',
|
||||||
|
ExtractedEntity_name: '内容',
|
||||||
|
ExtractedEntity_entity_type: '类型',
|
||||||
|
ExtractedEntity_created_at: '创建时间',
|
||||||
|
ExtractedEntity_aliases: '别名',
|
||||||
|
ExtractedEntity_connect_strngth: '连接强度',
|
||||||
|
ExtractedEntity_importance_score: '重要性评分',
|
||||||
|
|
||||||
|
associative_memory: '关联记忆',
|
||||||
|
unix: '个',
|
||||||
|
completeMemory: '完整记忆',
|
||||||
},
|
},
|
||||||
space: {
|
space: {
|
||||||
createSpace: '创建空间',
|
createSpace: '创建空间',
|
||||||
@@ -2282,5 +2299,75 @@ export const zh = {
|
|||||||
last_access_time: '最后激活时间',
|
last_access_time: '最后激活时间',
|
||||||
activation_value: '当前激活值',
|
activation_value: '当前激活值',
|
||||||
},
|
},
|
||||||
|
episodicDetail: {
|
||||||
|
title: '记录你真实经历过的每一个重要场景',
|
||||||
|
total_all: '情景记忆总数',
|
||||||
|
all: "全部",
|
||||||
|
today: '今天',
|
||||||
|
this_week: '本周',
|
||||||
|
this_month: '本月',
|
||||||
|
conversation: "对话",
|
||||||
|
project_work: "项目/工作",
|
||||||
|
learning: "学习",
|
||||||
|
decision: "决策",
|
||||||
|
important_event: "重要事件",
|
||||||
|
titleKeywordPlaceholder: '搜索情景标题或内容',
|
||||||
|
curResult: '当前筛选结果',
|
||||||
|
unix: '条',
|
||||||
|
created: '发生时间',
|
||||||
|
episodic_type: '情景类型',
|
||||||
|
involved_objects: '涉及对象',
|
||||||
|
content_records: '情景内容记录',
|
||||||
|
emotion: '情绪与状态记录',
|
||||||
|
},
|
||||||
|
implicitDetail: {
|
||||||
|
title: '那些塑造了我的无形力量',
|
||||||
|
preferences: '我的潜意识偏好',
|
||||||
|
preferencesDetail: '的联想网络',
|
||||||
|
portraitTitle: '我的潜意识画像',
|
||||||
|
portraitSubTitle: '基于您的偏好标签,AI为您生成的个性化洞察',
|
||||||
|
portrait: '核心特质',
|
||||||
|
aesthetic: '审美驱动',
|
||||||
|
creativity: '创造性思维',
|
||||||
|
literature: '文化敏感度',
|
||||||
|
technology: '技术亲和力',
|
||||||
|
interestAreas: '兴趣领域分布',
|
||||||
|
art: '艺术与设计',
|
||||||
|
music: '音乐与文化',
|
||||||
|
tech: '科技与未来',
|
||||||
|
lifestyle: '生活方式',
|
||||||
|
habits: '用户习惯分析',
|
||||||
|
habitsSubTitle: '基于您的行为模式识别的习惯特征',
|
||||||
|
context_details: '偏好详情',
|
||||||
|
supporting_evidence: '偏好来源',
|
||||||
|
specific_examples: '来源',
|
||||||
|
},
|
||||||
|
shortTermDetail: {
|
||||||
|
title: '短期记忆是AI系统的"工作台",连接即时对话与长期知识库。通过实时捕获、深度检索、智能提取和筛选转化,将临时的非结构化信息转化为有价值的长期知识。',
|
||||||
|
retrieval_number: '检索次数',
|
||||||
|
entity: '提取实体',
|
||||||
|
long_term_number: '长期候选',
|
||||||
|
shortTermTitle: '深度检索与扩展答案区',
|
||||||
|
shortTermSubTitle: '存放为回答问题而进行的深度信息检索和由此生成的扩展答案,包含原始问题、检索信息和生成答案。',
|
||||||
|
longTermTitle: '长期记忆候选池',
|
||||||
|
longTermSubTitle: '聚合短期记忆,筛选并准备存入长期记忆的内容。这是从短时记忆到长时记忆的"中转站"和"过滤器"。',
|
||||||
|
answer: '回答',
|
||||||
|
query: '问题',
|
||||||
|
noAnswer: '暂无回复',
|
||||||
|
},
|
||||||
|
perceptualDetail: {
|
||||||
|
last_visual: '视觉感知流',
|
||||||
|
last_listen: '听觉感知流',
|
||||||
|
last_text: '文本感知',
|
||||||
|
summary: '摘要',
|
||||||
|
keywords: '关键词',
|
||||||
|
topic: '主题',
|
||||||
|
domain: '领域',
|
||||||
|
scene: '场景',
|
||||||
|
speaker_count: '对话人数',
|
||||||
|
section_count: '段落数',
|
||||||
|
timeLine: '感知时间线',
|
||||||
|
lastInfo: '实时感知仪表盘',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -61,6 +61,7 @@ const componentMap: Record<string, LazyExoticComponent<ComponentType<object>>> =
|
|||||||
StatementDetail: lazy(() => import('@/views/UserMemoryDetail/pages/StatementDetail')),
|
StatementDetail: lazy(() => import('@/views/UserMemoryDetail/pages/StatementDetail')),
|
||||||
ForgetDetail: lazy(() => import('@/views/UserMemoryDetail/pages/ForgetDetail')),
|
ForgetDetail: lazy(() => import('@/views/UserMemoryDetail/pages/ForgetDetail')),
|
||||||
MemoryNodeDetail: lazy(() => import('@/views/UserMemoryDetail/pages/index')),
|
MemoryNodeDetail: lazy(() => import('@/views/UserMemoryDetail/pages/index')),
|
||||||
|
GraphDetail: lazy(() => import('@/views/UserMemoryDetail/pages/GraphDetail')),
|
||||||
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')),
|
||||||
|
|||||||
@@ -44,7 +44,8 @@
|
|||||||
{ "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" }
|
{ "path": "/user-memory/detail/:id/:type", "element": "MemoryNodeDetail" },
|
||||||
|
{ "path": "/graph/:id", "element": "GraphDetail" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -104,15 +104,15 @@ export default function UserMemory() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Row gutter={16} className="rb:mb-[16px]">
|
<Row gutter={16} className="rb:mb-4">
|
||||||
{countList.map(key => (
|
{countList.map(key => (
|
||||||
<Col key={key} span={6}>
|
<Col key={key} span={6}>
|
||||||
<div className="rb:bg-[#FBFDFF] rb:border-[1px] rb:border-[#DFE4ED] rb:rounded-[12px] rb:p-[18px_20px_20px_20px]">
|
<div className="rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-xl rb:p-[18px_20px_20px_20px]">
|
||||||
<div className="rb:text-[28px] rb:font-extrabold rb:leading-[35px] rb:flex rb:items-center rb:justify-between rb:mb-[12px]">
|
<div className="rb:text-[28px] rb:font-extrabold rb:leading-8.75 rb:flex rb:items-center rb:justify-between rb:mb-3">
|
||||||
{countData[key] || 0}{key === 'avgInteractionTime' ? 's' : ''}
|
{countData[key] || 0}{key === 'avgInteractionTime' ? 's' : ''}
|
||||||
<img className="rb:w-[24px] rb:h-[24px]" src={IconList[key]} />
|
<img className="rb:w-6 rb:h-6" src={IconList[key]} />
|
||||||
</div>
|
</div>
|
||||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-[16px]">{t(`userMemory.${key}`)}</div>
|
<div className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`userMemory.${key}`)}</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
@@ -140,22 +140,22 @@ export default function UserMemory() {
|
|||||||
return (
|
return (
|
||||||
<List.Item key={index}>
|
<List.Item key={index}>
|
||||||
<div
|
<div
|
||||||
className="rb:p-[20px] rb:rounded-[12px] rb:border-[1px] rb:border-[#DFE4ED] rb:cursor-pointer"
|
className="rb:p-5 rb:rounded-xl rb:border rb:border-[#DFE4ED] rb:cursor-pointer"
|
||||||
style={{
|
style={{
|
||||||
background: bgList[index % bgList.length],
|
background: bgList[index % bgList.length],
|
||||||
}}
|
}}
|
||||||
onClick={() => handleViewDetail(end_user.id)}
|
onClick={() => handleViewDetail(end_user.id)}
|
||||||
>
|
>
|
||||||
<div className="rb:flex rb:items-center">
|
<div className="rb:flex rb:items-center">
|
||||||
<div className="rb:w-[48px] rb:h-[48px] rb:text-center rb:font-semibold rb:text-[28px] rb:leading-[48px] rb:rounded-[8px] rb:text-[#FBFDFF] rb:bg-[#155EEF]">{name[0]}</div>
|
<div className="rb:w-12 rb:h-12 rb:text-center rb:font-semibold rb:text-[28px] rb:leading-12 rb:rounded-lg rb:text-[#FBFDFF] rb:bg-[#155EEF]">{name[0]}</div>
|
||||||
<div className="rb:max-w-[calc(100%-60px)] rb:text-base rb:font-medium rb:leading-[24px] rb:ml-[12px] rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">
|
<div className="rb:max-w-[calc(100%-60px)] rb:text-base rb:font-medium rb:leading-6 rb:ml-3 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">
|
||||||
{name || '-'}<br/>
|
{name || '-'}<br/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rb:grid rb:grid-cols-1 rb:gap-[12px] rb:mt-[28px] rb:mb-[28px]">
|
<div className="rb:grid rb:grid-cols-1 rb:gap-3 rb:mt-7 rb:mb-7">
|
||||||
<div className="rb:text-center">
|
<div className="rb:text-center">
|
||||||
<div className="rb:text-[24px] rb:leading-[30px] rb:font-extrabold">{memory_num.total || 0}</div>
|
<div className="rb:text-[24px] rb:leading-7.5 rb:font-extrabold">{memory_num.total || 0}</div>
|
||||||
<div className="rb:break-words">{t(`userMemory.knowledgeEntryCount`)}</div>
|
<div className="rb:wrap-break-word">{t(`userMemory.knowledgeEntryCount`)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type FC, useEffect, useRef, useState } from 'react'
|
import { type FC, useRef, useState } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { Row, Col, Space, Button } from 'antd'
|
import { Row, Col, Space, Button } from 'antd'
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|||||||
85
web/src/views/UserMemoryDetail/components/Habits.tsx
Normal file
85
web/src/views/UserMemoryDetail/components/Habits.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { type FC, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { Skeleton, Space, Progress } from 'antd';
|
||||||
|
import RbCard from '@/components/RbCard/Card'
|
||||||
|
import Empty from '@/components/Empty'
|
||||||
|
import {
|
||||||
|
getImplicitHabits,
|
||||||
|
} from '@/api/memory'
|
||||||
|
|
||||||
|
interface HabitsItem {
|
||||||
|
habit_description: string;
|
||||||
|
frequency_pattern: string;
|
||||||
|
time_context: string;
|
||||||
|
confidence_level: string;
|
||||||
|
supporting_summaries: string[];
|
||||||
|
first_observed: string;
|
||||||
|
last_observed: string;
|
||||||
|
is_current: boolean;
|
||||||
|
specific_examples: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Habits: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { id } = useParams()
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [data, setData] = useState<HabitsItem[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id) return
|
||||||
|
getData()
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
// 记忆洞察
|
||||||
|
const getData = () => {
|
||||||
|
if (!id) return
|
||||||
|
setLoading(true)
|
||||||
|
getImplicitHabits(id).then((res) => {
|
||||||
|
const response = res as HabitsItem[]
|
||||||
|
setData(response)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="rb:bg-[rgba(21,94,239,0.12)] rb:px-3 rb:py-2.5 rb:font-medium rb:leading-5 rb:mb-4 rb:mt-6 rb:rounded-md">{t('implicitDetail.habits')}</div>
|
||||||
|
<div className="rb:my-3 rb:text-[#5B6167] rb:leading-5">{t('implicitDetail.habitsSubTitle')}</div>
|
||||||
|
<RbCard>
|
||||||
|
{loading
|
||||||
|
? <Skeleton active />
|
||||||
|
: data.length === 0
|
||||||
|
? <Empty size={88} />
|
||||||
|
: <Space size={12} direction="vertical" className="rb:w-full!">
|
||||||
|
{data.map((vo, voIdx) => (
|
||||||
|
<div key={voIdx} className="rb:leading-5 rb:shadow-[inset_3px_0px_0px_0px_#155EEF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:p-3">
|
||||||
|
<div className="rb:flex rb:items-center rb:justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="rb:mb-1">{vo.habit_description}</div>
|
||||||
|
<div className="rb:mb-1 rb:text-[#5B6167]">{vo.time_context}</div>
|
||||||
|
</div>
|
||||||
|
<div className="rb:text-[24px] rb:font-medium">{vo.confidence_level}%</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{vo.specific_examples.length > 0 && <>
|
||||||
|
<div className="rb:mt-3 rb:mb-2">{t('implicitDetail.specific_examples')}</div>
|
||||||
|
<div className="rb:bg-[#FFFFFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:p-3">
|
||||||
|
{vo.specific_examples.map((item, index) => (
|
||||||
|
<div key={index} className="rb:text-[#5B6167] rb:text-[12px] rb:mt-1">- {item}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
<Progress percent={vo.confidence_level} showInfo={false} className="rb:mt-3" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
</RbCard>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Habits
|
||||||
75
web/src/views/UserMemoryDetail/components/InterestAreas.tsx
Normal file
75
web/src/views/UserMemoryDetail/components/InterestAreas.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { type FC, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { Skeleton, Progress } from 'antd';
|
||||||
|
import RbCard from '@/components/RbCard/Card'
|
||||||
|
import {
|
||||||
|
getImplicitInterestAreas,
|
||||||
|
} from '@/api/memory'
|
||||||
|
|
||||||
|
interface Item {
|
||||||
|
category_name: string;
|
||||||
|
percentage: number;
|
||||||
|
evidence: string[];
|
||||||
|
trending_direction: string | null;
|
||||||
|
}
|
||||||
|
interface InterestAreasItem {
|
||||||
|
user_id: string;
|
||||||
|
analysis_timestamp: number | string;
|
||||||
|
total_summaries_analyzed: number;
|
||||||
|
tech: Item;
|
||||||
|
lifestyle: Item;
|
||||||
|
music: Item;
|
||||||
|
art: Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InterestAreas: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { id } = useParams()
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [data, setData] = useState<InterestAreasItem>({} as InterestAreasItem)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id) return
|
||||||
|
getData()
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
// 记忆洞察
|
||||||
|
const getData = () => {
|
||||||
|
if (!id) return
|
||||||
|
setLoading(true)
|
||||||
|
getImplicitInterestAreas(id).then((res) => {
|
||||||
|
const response = res as InterestAreasItem
|
||||||
|
setData(response)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RbCard
|
||||||
|
title={t('implicitDetail.interestAreas')}
|
||||||
|
headerType="borderless"
|
||||||
|
>
|
||||||
|
{loading
|
||||||
|
? <Skeleton active />
|
||||||
|
: <div>
|
||||||
|
{(['art', 'music', 'tech', 'lifestyle'] as const).map((key) => {
|
||||||
|
return (
|
||||||
|
<div key={key} >
|
||||||
|
<div className="rb:flex rb:justify-between rb:items-center">
|
||||||
|
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular rb:mb-1">{t(`implicitDetail.${key}`)}</div>
|
||||||
|
{data[key]?.percentage ?? 0}%
|
||||||
|
</div>
|
||||||
|
<Progress percent={data[key]?.percentage || 0} showInfo={false} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</RbCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default InterestAreas
|
||||||
120
web/src/views/UserMemoryDetail/components/PerceptualLastInfo.tsx
Normal file
120
web/src/views/UserMemoryDetail/components/PerceptualLastInfo.tsx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { type FC, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { Skeleton, Space, Tooltip, Image } from 'antd';
|
||||||
|
import RbCard from '@/components/RbCard/Card'
|
||||||
|
import {
|
||||||
|
getPerceptualLastVisual,
|
||||||
|
getPerceptualLastListen,
|
||||||
|
getPerceptualLastText,
|
||||||
|
} from '@/api/memory'
|
||||||
|
|
||||||
|
interface PerceptualLastInfoItem {
|
||||||
|
id: string;
|
||||||
|
file_name: string;
|
||||||
|
file_ext: string;
|
||||||
|
file_path: string;
|
||||||
|
storage_type: number;
|
||||||
|
summary: string;
|
||||||
|
keywords: string[];
|
||||||
|
topic: string;
|
||||||
|
domain: string;
|
||||||
|
created_time: number | string;
|
||||||
|
scene: string[]
|
||||||
|
speaker_count: number;
|
||||||
|
section_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const KEYS = {
|
||||||
|
last_visual: ['summary', 'keywords', 'topic', 'domain', 'scene'],
|
||||||
|
last_listen: ['summary', 'keywords', 'topic', 'domain', 'speaker_count'],
|
||||||
|
last_text: ['summary', 'keywords', 'topic', 'domain', 'section_count'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const PerceptualLastInfo: FC<{ type: 'last_visual' | 'last_listen' | 'last_text' }> = ({ type }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { id } = useParams()
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [data, setData] = useState<PerceptualLastInfoItem>({} as PerceptualLastInfoItem)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id) return
|
||||||
|
getData()
|
||||||
|
}, [id, type])
|
||||||
|
const getData = () => {
|
||||||
|
if (!id || !type) return
|
||||||
|
setLoading(true)
|
||||||
|
const request = type === 'last_visual'
|
||||||
|
? getPerceptualLastVisual(id)
|
||||||
|
: type === 'last_listen'
|
||||||
|
? getPerceptualLastListen(id)
|
||||||
|
: getPerceptualLastText(id)
|
||||||
|
request.then((res) => {
|
||||||
|
const response = res as PerceptualLastInfoItem
|
||||||
|
setData(response)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RbCard
|
||||||
|
title={t(`perceptualDetail.${type}`)}
|
||||||
|
headerType="borderless"
|
||||||
|
>
|
||||||
|
{loading
|
||||||
|
? <Skeleton active />
|
||||||
|
: <div>
|
||||||
|
<div className="rb:bg-[#F0F3F8] rb:h-36 rb:rounded-sm rb:flex rb:items-center rb:justify-center rb:overflow-hidden">
|
||||||
|
{data.file_path ? (
|
||||||
|
type === 'last_visual' ? (
|
||||||
|
/\.(mp4|webm|ogg|mov)$/i.test(data.file_name) ? (
|
||||||
|
<video controls className="rb:max-w-full rb:max-h-full">
|
||||||
|
<source src={data.file_path} />
|
||||||
|
</video>
|
||||||
|
) : /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(data.file_name) ? (
|
||||||
|
<Image src={data.file_path} alt={data.file_name} />
|
||||||
|
// <img src={data.file_path} alt={data.file_name} className="rb:max-w-full rb:max-h-full rb:object-contain" />
|
||||||
|
) : (
|
||||||
|
<div className="rb:text-gray-500">{data.file_name}</div>
|
||||||
|
)
|
||||||
|
) : type === 'last_listen' && /\.(mp3|wav|ogg|m4a|aac)$/i.test(data.file_name) ? (
|
||||||
|
<audio controls className="rb:w-full">
|
||||||
|
<source src={data.file_path} />
|
||||||
|
</audio>
|
||||||
|
) : (
|
||||||
|
<div className="rb:text-gray-500">{data.file_name}</div>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className="rb:text-gray-400">No file</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Space size={4} direction="vertical" className="rb:w-full rb:mt-3">
|
||||||
|
{KEYS[type].map(key => {
|
||||||
|
const value = (data as any)[key]
|
||||||
|
return (
|
||||||
|
<div key={key} className="rb:flex rb:justify-between rb:items-center rb:gap-3">
|
||||||
|
<div className="rb:text-[#5B6167]">{t(`perceptualDetail.${key}`)}</div>
|
||||||
|
{key === 'summary' ? (
|
||||||
|
<Tooltip title={value}>
|
||||||
|
<div className="rb:flex-1 rb:text-right rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">
|
||||||
|
{typeof value === 'string' ? value : Array.isArray(value) ? value.join('、') : '-'}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
: <div className="rb:flex-1 rb:text-right">
|
||||||
|
{typeof value === 'string' ? value : Array.isArray(value) ? value.join('、') : '-'}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</RbCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default PerceptualLastInfo
|
||||||
77
web/src/views/UserMemoryDetail/components/Portrait.tsx
Normal file
77
web/src/views/UserMemoryDetail/components/Portrait.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { type FC, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { Skeleton, Progress } from 'antd';
|
||||||
|
import RbCard from '@/components/RbCard/Card'
|
||||||
|
import {
|
||||||
|
getImplicitPortrait,
|
||||||
|
} from '@/api/memory'
|
||||||
|
|
||||||
|
interface Item {
|
||||||
|
dimension_name: string;
|
||||||
|
percentage: number;
|
||||||
|
evidence: string[];
|
||||||
|
reasoning: string;
|
||||||
|
confidence_level: string;
|
||||||
|
}
|
||||||
|
interface PortraitItem {
|
||||||
|
user_id: string;
|
||||||
|
analysis_timestamp: number | string;
|
||||||
|
total_summaries_analyzed: number;
|
||||||
|
historical_trends: null;
|
||||||
|
creativity: Item;
|
||||||
|
aesthetic: Item;
|
||||||
|
technology: Item;
|
||||||
|
literature: Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Portrait: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { id } = useParams()
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [data, setData] = useState<PortraitItem>({} as PortraitItem)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id) return
|
||||||
|
getData()
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
const getData = () => {
|
||||||
|
if (!id) return
|
||||||
|
setLoading(true)
|
||||||
|
getImplicitPortrait(id).then((res) => {
|
||||||
|
const response = res as PortraitItem
|
||||||
|
setData(response)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RbCard
|
||||||
|
title={t('implicitDetail.portrait')}
|
||||||
|
headerType="borderless"
|
||||||
|
>
|
||||||
|
{loading
|
||||||
|
? <Skeleton active />
|
||||||
|
: <div className="rb:mt-1">
|
||||||
|
{(['aesthetic', 'creativity', 'literature', 'technology'] as const).map((key) => {
|
||||||
|
const item = data[key] as Item
|
||||||
|
return (
|
||||||
|
<div key={key}>
|
||||||
|
<div className="rb:flex rb:justify-between rb:items-center">
|
||||||
|
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular rb:mb-1">{t(`implicitDetail.${key}`)}</div>
|
||||||
|
{item?.percentage ?? 0}%
|
||||||
|
</div>
|
||||||
|
<Progress percent={item?.percentage || 0} showInfo={false} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</RbCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Portrait
|
||||||
183
web/src/views/UserMemoryDetail/components/Preferences.tsx
Normal file
183
web/src/views/UserMemoryDetail/components/Preferences.tsx
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
import { type FC, useEffect, useState, useRef, useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { Row, Col, Skeleton } from 'antd'
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
import 'echarts-wordcloud'
|
||||||
|
|
||||||
|
import Empty from '@/components/Empty'
|
||||||
|
import RbCard from '@/components/RbCard/Card'
|
||||||
|
import { getImplicitPreferences } from '@/api/memory'
|
||||||
|
|
||||||
|
interface PreferenceItem {
|
||||||
|
tag_name: string;
|
||||||
|
confidence_score: number;
|
||||||
|
supporting_evidence: string[];
|
||||||
|
context_details: string;
|
||||||
|
created_at: number | string; // TODO
|
||||||
|
updated_at: number | string; // TODO
|
||||||
|
conversation_references: string[];
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_COLORS = ['#FF5D34', '#155EEF', '#9C6FFF', '#369F21', '#4DA8FF', '#FF8C00', '#32CD32', '#FF69B4', '#20B2AA', '#DDA0DD']
|
||||||
|
|
||||||
|
const generateCategoryColors = (categories: string[]) => {
|
||||||
|
const colors: Record<string, string> = {}
|
||||||
|
categories.forEach((category, index) => {
|
||||||
|
colors[category] = DEFAULT_COLORS[index % DEFAULT_COLORS.length]
|
||||||
|
})
|
||||||
|
return colors
|
||||||
|
}
|
||||||
|
|
||||||
|
const Preferences: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { id } = useParams()
|
||||||
|
const chartRef = useRef<HTMLDivElement>(null)
|
||||||
|
const chartInstance = useRef<echarts.ECharts | null>(null)
|
||||||
|
const [selectedWord, setSelectedWord] = useState<number | null>(null)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [data, setData] = useState<PreferenceItem[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id) return
|
||||||
|
getData()
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
const getData = () => {
|
||||||
|
if (!id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setLoading(true)
|
||||||
|
setSelectedWord(null)
|
||||||
|
getImplicitPreferences(id)
|
||||||
|
.then((res) => {
|
||||||
|
setData(res as PreferenceItem[])
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueCategories = [...new Set(data.map(item => item.category).filter(Boolean))]
|
||||||
|
const categoryColors = generateCategoryColors(uniqueCategories)
|
||||||
|
|
||||||
|
const getCategoryColor = (category: string) => {
|
||||||
|
return categoryColors[category] || '#4DA8FF'
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!chartRef.current || !data.length) return
|
||||||
|
|
||||||
|
if (chartInstance.current) {
|
||||||
|
chartInstance.current.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
chartInstance.current = echarts.init(chartRef.current)
|
||||||
|
|
||||||
|
const wordCloudData = data.map((item, index) => ({
|
||||||
|
name: item.tag_name,
|
||||||
|
value: Math.round(item.confidence_score * 100),
|
||||||
|
itemIndex: index,
|
||||||
|
textStyle: {
|
||||||
|
color: getCategoryColor(item.category)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
series: [{
|
||||||
|
type: 'wordCloud',
|
||||||
|
gridSize: 8,
|
||||||
|
sizeRange: [14, 60],
|
||||||
|
rotationRange: [-45, 45],
|
||||||
|
shape: 'pentagon',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
textStyle: {
|
||||||
|
fontFamily: 'sans-serif',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
textStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowColor: '#333'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: wordCloudData
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
chartInstance.current.setOption(option)
|
||||||
|
|
||||||
|
chartInstance.current.on('click', (params) => {
|
||||||
|
const clickedIndex = (params.data as any).itemIndex
|
||||||
|
if (selectedWord !== clickedIndex) {
|
||||||
|
setSelectedWord(clickedIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight selected word without redrawing
|
||||||
|
chartInstance.current?.dispatchAction({
|
||||||
|
type: 'highlight',
|
||||||
|
dataIndex: clickedIndex
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (chartInstance.current) {
|
||||||
|
chartInstance.current.dispose()
|
||||||
|
chartInstance.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
|
||||||
|
console.log(selectedWord, data)
|
||||||
|
|
||||||
|
const detailTitle = useMemo(() => {
|
||||||
|
return selectedWord !== null && data[selectedWord].tag_name ? <>{data[selectedWord].tag_name}{t('implicitDetail.preferencesDetail')}</> : ''
|
||||||
|
}, [selectedWord, data, t])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<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={16}>
|
||||||
|
<RbCard
|
||||||
|
title={t('implicitDetail.preferences')}
|
||||||
|
headerType="borderless"
|
||||||
|
headerClassName="rb:text-[18px]! rb:leading-[24px]"
|
||||||
|
bodyClassName='rb:p-0! rb:pb-3! rb:relative rb:h-[350px]'
|
||||||
|
>
|
||||||
|
{loading
|
||||||
|
? <Skeleton active className="rb:px-4" />
|
||||||
|
: data && data.length > 0
|
||||||
|
? <div ref={chartRef} className="rb:mt-6 rb:px-6" style={{ height: '350px' }} />
|
||||||
|
: <Empty size={88} className="rb:h-full" />
|
||||||
|
}
|
||||||
|
</RbCard>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<RbCard
|
||||||
|
title={detailTitle}
|
||||||
|
headerType="borderless"
|
||||||
|
height="100%"
|
||||||
|
bodyClassName='rb:p-3! rb:h-[326px]'
|
||||||
|
>
|
||||||
|
{selectedWord === null
|
||||||
|
? <Empty size={88} className="rb:h-full!" />
|
||||||
|
: <>
|
||||||
|
<div className="rb:leading-5 rb:mb-1 rb:font-medium">{t('implicitDetail.context_details')}</div>
|
||||||
|
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular">{data[selectedWord].context_details}</div>
|
||||||
|
|
||||||
|
<div className="rb:leading-5 rb:mt-3 rb:font-medium">{t('implicitDetail.supporting_evidence')}</div>
|
||||||
|
{data[selectedWord].supporting_evidence.map((vo, index) => <div key={index} className="rb:text-[#5B6167] rb:leading-5 rb:font-regular">-{vo}</div>)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</RbCard>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Preferences
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
import React, { type FC, useEffect, useState, useRef, useCallback } from 'react'
|
import React, { type FC, useEffect, useState, useRef, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { Col, Row } from 'antd'
|
import { Col, Row, Space, Button } from 'antd'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import ReactEcharts from 'echarts-for-react'
|
import ReactEcharts from 'echarts-for-react'
|
||||||
import detailEmpty from '@/assets/images/userMemory/detail_empty.png'
|
import detailEmpty from '@/assets/images/userMemory/detail_empty.png'
|
||||||
import type { Node, Edge, GraphData } from '../types'
|
import type { Node, Edge, GraphData, StatementNodeProperties, ExtractedEntityNodeProperties } from '../types'
|
||||||
import {
|
import {
|
||||||
getMemorySearchEdges,
|
getMemorySearchEdges,
|
||||||
} from '@/api/memory'
|
} from '@/api/memory'
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
|
import Tag from '@/components/Tag'
|
||||||
|
|
||||||
const colors = ['#155EEF', '#369F21', '#4DA8FF', '#FF5D34', '#9C6FFF', '#FF8A4C', '#8BAEF7', '#FFB048']
|
const colors = ['#155EEF', '#369F21', '#4DA8FF', '#FF5D34', '#9C6FFF', '#FF8A4C', '#8BAEF7', '#FFB048']
|
||||||
const RelationshipNetwork:FC = () => {
|
const RelationshipNetwork:FC = () => {
|
||||||
@@ -136,6 +137,11 @@ const RelationshipNetwork:FC = () => {
|
|||||||
|
|
||||||
console.log('selectedNode', selectedNode)
|
console.log('selectedNode', selectedNode)
|
||||||
|
|
||||||
|
const handleViewAll = () => {
|
||||||
|
if (!selectedNode) return
|
||||||
|
window.open(`/#/graph/${selectedNode.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
{/* 关系网络 */}
|
{/* 关系网络 */}
|
||||||
@@ -240,8 +246,14 @@ const RelationshipNetwork:FC = () => {
|
|||||||
title={t('userMemory.memoryDetails')}
|
title={t('userMemory.memoryDetails')}
|
||||||
headerType="borderless"
|
headerType="borderless"
|
||||||
bodyClassName='rb:p-0!'
|
bodyClassName='rb:p-0!'
|
||||||
|
extra={selectedNode && <Button type="text" onClick={handleViewAll}>
|
||||||
|
<div
|
||||||
|
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/userMemory/view.svg')] rb:hover:bg-[url('@/assets/images/userMemory/view_hover.svg')]"
|
||||||
|
></div>
|
||||||
|
{t('userMemory.completeMemory')}
|
||||||
|
</Button>}
|
||||||
>
|
>
|
||||||
<div className="rb:h-133.5">
|
<div className="rb:h-133.5 rb:overflow-y-auto">
|
||||||
{!selectedNode
|
{!selectedNode
|
||||||
? <Empty
|
? <Empty
|
||||||
url={detailEmpty}
|
url={detailEmpty}
|
||||||
@@ -267,9 +279,52 @@ const RelationshipNetwork:FC = () => {
|
|||||||
</>
|
</>
|
||||||
<div className="rb:font-medium rb:mb-2 rb:mt-4">
|
<div className="rb:font-medium rb:mb-2 rb:mt-4">
|
||||||
<div className="rb:font-medium rb:leading-5">{t('userMemory.created_at')}</div>
|
<div className="rb:font-medium rb:leading-5">{t('userMemory.created_at')}</div>
|
||||||
<div className="rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-1 rb:pb-4">
|
<div className="rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-1 rb:pb-4 rb:border-b rb:border-[#DFE4ED]">
|
||||||
{dayjs(selectedNode?.properties.created_at).format('YYYY-MM-DD HH:mm:ss')}
|
{dayjs(selectedNode?.properties.created_at).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{selectedNode?.properties.associative_memory > 0 && <div className="rb:mt-4">
|
||||||
|
<div className="rb:font-medium rb:leading-5">{t('userMemory.associative_memory')}</div>
|
||||||
|
<div className="rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-1 rb:pb-4 rb:border-b rb:border-[#DFE4ED]">
|
||||||
|
<span className="rb:text-[#155EEF] rb:font-medium">{selectedNode?.properties.associative_memory}</span> {t('userMemory.unix')}{t('userMemory.associative_memory')}
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
{selectedNode.label === 'Statement' && <>
|
||||||
|
{(['emotion_keywords', 'emotion_type', 'emotion_subject', 'importance_score'] as const).map(key => {
|
||||||
|
const statementProps = selectedNode.properties as StatementNodeProperties;
|
||||||
|
if ((key === 'emotion_keywords' && statementProps[key]?.length > 0) || statementProps[key]) {
|
||||||
|
return (
|
||||||
|
<div className="rb:mt-4" key={key}>
|
||||||
|
{t(`userMemory.Statement_${key}`)}
|
||||||
|
<div className="rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-1 rb:pb-4 rb:border-b rb:border-[#DFE4ED]">
|
||||||
|
{key === 'emotion_keywords'
|
||||||
|
? <Space>{statementProps.emotion_keywords.map((vo, index) => <Tag key={index}>{vo}</Tag>)}</Space>
|
||||||
|
: statementProps[key]
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
</>}
|
||||||
|
{selectedNode.label === 'ExtractedEntity' && <>
|
||||||
|
{(['name', 'entity_type', 'aliases', 'connect_strngth', 'importance_score'] as const).map(key => {
|
||||||
|
const entityProps = selectedNode.properties as ExtractedEntityNodeProperties;
|
||||||
|
if (entityProps[key]) {
|
||||||
|
return (
|
||||||
|
<div className="rb:mt-4" key={key}>
|
||||||
|
{t(`userMemory.ExtractedEntity_${key}`)}
|
||||||
|
<div className="rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-1 rb:pb-4 rb:border-b rb:border-[#DFE4ED]">
|
||||||
|
{entityProps[key]}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
</>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
82
web/src/views/UserMemoryDetail/components/Timeline.tsx
Normal file
82
web/src/views/UserMemoryDetail/components/Timeline.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { type FC, useEffect, useState } from 'react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { Skeleton, Progress, Space, Tooltip, Divider } from 'antd';
|
||||||
|
import RbCard from '@/components/RbCard/Card'
|
||||||
|
import {
|
||||||
|
getPerceptualTimeline
|
||||||
|
} from '@/api/memory'
|
||||||
|
import { formatDateTime } from '@/utils/format';
|
||||||
|
import Empty from '@/components/Empty'
|
||||||
|
|
||||||
|
interface TimelineItem {
|
||||||
|
id: string;
|
||||||
|
perceptual_type: number;
|
||||||
|
file_path: string;
|
||||||
|
file_name: string;
|
||||||
|
summary: string;
|
||||||
|
storage_type: number;
|
||||||
|
created_time: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const KEYS = {
|
||||||
|
last_visual: ['summary', 'keywords', 'topic', 'domain', 'scene'],
|
||||||
|
last_listen: ['summary', 'keywords', 'topic', 'domain', 'speaker_count'],
|
||||||
|
last_text: ['summary', 'keywords', 'topic', 'domain', 'section_count'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const perceptual_type: Record<number, string> = {
|
||||||
|
1: 'last_visual',
|
||||||
|
2: 'last_listen',
|
||||||
|
3: 'last_text',
|
||||||
|
}
|
||||||
|
|
||||||
|
const Timeline: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { id } = useParams()
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [data, setData] = useState<TimelineItem[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id) return
|
||||||
|
getData()
|
||||||
|
}, [id])
|
||||||
|
const getData = () => {
|
||||||
|
if (!id) return
|
||||||
|
setLoading(true)
|
||||||
|
getPerceptualTimeline(id).then((res) => {
|
||||||
|
const response = res as { memories: TimelineItem[] }
|
||||||
|
setData(response.memories || [])
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RbCard>
|
||||||
|
{loading
|
||||||
|
? <Skeleton active />
|
||||||
|
: data.length === 0
|
||||||
|
? <Empty />
|
||||||
|
: <Space size={8} direction="vertical" className="rb:w-full">
|
||||||
|
{data.map((vo, index) => (
|
||||||
|
<div key={vo.id} className="rb:flex rb:gap-6 rb:min-h-16">
|
||||||
|
<div className="rb:text-[#155EEF] rb:leading-5 rb:font-medium rb:flex rb:flex-col rb:gap-2 rb:items-center">
|
||||||
|
{formatDateTime(vo.created_time)}
|
||||||
|
{index !== data.length - 1 && <Divider type="vertical" className="rb:flex-1 rb:w-px rb:border-[#155EEF]!" />}
|
||||||
|
</div>
|
||||||
|
<div className="rb:flex rb:justify-between rb:flex-1 rb:mb-4">
|
||||||
|
<div className="rb:w-150 rb:leading-5">{vo.summary}</div>
|
||||||
|
<div className="rb:text-[#5B6167] rb:font-medium">{t(`perceptualDetail.${perceptual_type[vo.perceptual_type]}`)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
</RbCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Timeline
|
||||||
@@ -114,7 +114,7 @@ const WordCloud: FC = () => {
|
|||||||
<div key={item.emotion_type}>
|
<div key={item.emotion_type}>
|
||||||
<div className="rb:flex rb:items-center rb:justify-between rb:font-medium">
|
<div className="rb:flex rb:items-center rb:justify-between rb:font-medium">
|
||||||
{t(`statementDetail.${item.emotion_type}`)}
|
{t(`statementDetail.${item.emotion_type}`)}
|
||||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular">{item.count}{t('statementDetail.pieces')}</div>
|
<div className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular">{item.count} {t('statementDetail.pieces')}</div>
|
||||||
</div>
|
</div>
|
||||||
<Progress size="small" percent={item.percentage} />
|
<Progress size="small" percent={item.percentage} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
250
web/src/views/UserMemoryDetail/pages/EpisodicDetail.tsx
Normal file
250
web/src/views/UserMemoryDetail/pages/EpisodicDetail.tsx
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
import { type FC, useEffect, useState } from 'react'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { Row, Col, Select, Form, Space, Skeleton, Input } from 'antd'
|
||||||
|
import RbCard from '@/components/RbCard/Card'
|
||||||
|
import {
|
||||||
|
getEpisodicOverview,
|
||||||
|
getEpisodicDetail,
|
||||||
|
} from '@/api/memory'
|
||||||
|
import { formatDateTime } from '@/utils/format'
|
||||||
|
import Tag from '@/components/Tag'
|
||||||
|
import RbAlert from '@/components/RbAlert'
|
||||||
|
import Empty from '@/components/Empty'
|
||||||
|
|
||||||
|
interface EpisodicMemory {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
interface EpisodicOverviewData {
|
||||||
|
total: number;
|
||||||
|
total_all: number;
|
||||||
|
episodic_memories: EpisodicMemory[]
|
||||||
|
}
|
||||||
|
interface EpisodicMemoryDetail {
|
||||||
|
id: string;
|
||||||
|
created_at: number;
|
||||||
|
involved_objects: string[];
|
||||||
|
episodic_type: string;
|
||||||
|
content_records: string[];
|
||||||
|
emotion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAG_COLORS: Record<string, "processing" | "success" | "warning" | "error" | "default"> = {
|
||||||
|
conversation: "processing",
|
||||||
|
project_work: "success",
|
||||||
|
learning: "warning",
|
||||||
|
decision: "warning",
|
||||||
|
important_event: "error",
|
||||||
|
}
|
||||||
|
const BG_COLORS: Record<string, string> = {
|
||||||
|
conversation: "rb:bg-[#155EEF]",
|
||||||
|
project_work: "rb:bg-[#369F21]",
|
||||||
|
learning: "rb:bg-[#FF5D34]",
|
||||||
|
decision: "rb:bg-[#FF5D34]",
|
||||||
|
important_event: "rb:bg-[#5B6167]",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map display types to internal keys
|
||||||
|
const getTypeKey = (type: string): string => {
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
'Learning': 'learning',
|
||||||
|
'Project/Work': 'project_work',
|
||||||
|
'Conversation': 'conversation',
|
||||||
|
'Decision': 'decision',
|
||||||
|
'Important Event': 'important_event',
|
||||||
|
}
|
||||||
|
return typeMap[type] || type.toLowerCase().replace(/[^a-z0-9]/g, '_')
|
||||||
|
}
|
||||||
|
const EpisodicDetail: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { id } = useParams()
|
||||||
|
const [form] = Form.useForm()
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [data, setData] = useState<EpisodicOverviewData>({} as EpisodicOverviewData)
|
||||||
|
const values = Form.useWatch([], form)
|
||||||
|
const [detailLoading, setDetailLoading] = useState<boolean>(false)
|
||||||
|
const [detail, setDetail] = useState<EpisodicMemoryDetail | null>(null)
|
||||||
|
const [selected, setSelected] = useState<EpisodicMemory | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id) return
|
||||||
|
// getData()
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
// 记忆洞察
|
||||||
|
const getData = () => {
|
||||||
|
if (!id) return
|
||||||
|
setLoading(true)
|
||||||
|
setSelected(null)
|
||||||
|
setDetail(null)
|
||||||
|
getEpisodicOverview({
|
||||||
|
end_user_id: id,
|
||||||
|
...values
|
||||||
|
}).then((res) => {
|
||||||
|
const response = res as EpisodicOverviewData
|
||||||
|
setData(response)
|
||||||
|
if (response.episodic_memories.length > 0) {
|
||||||
|
setSelected(response.episodic_memories[0])
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getData()
|
||||||
|
}, [values])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDetail()
|
||||||
|
}, [selected])
|
||||||
|
|
||||||
|
const getDetail = () => {
|
||||||
|
if (!selected || !selected.id) return
|
||||||
|
|
||||||
|
setDetailLoading(true)
|
||||||
|
getEpisodicDetail({
|
||||||
|
end_user_id: id as string,
|
||||||
|
summary_id: selected.id
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
setDetail(res as EpisodicMemoryDetail)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setDetailLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rb:h-full rb:max-w-266 rb:mx-auto">
|
||||||
|
<div className="rb:flex rb:justify-between rb:items-center rb:text-[#FFFFFF] rb:leading-5 rb:h-30 rb:p-5 rb:bg-[url('@/assets/images/userMemory/shortTerm.png')] rb:bg-cover rb:mb-6">
|
||||||
|
<div className="rb:max-w-135">{t('episodicDetail.title')}</div>
|
||||||
|
|
||||||
|
<div className="rb:grid rb:grid-cols-1 rb:gap-4">
|
||||||
|
<div className="rb:bg-[rgba(255,255,255,0.2)] rb:rounded-lg rb:p-3.5 rb:text-[12px] rb:text-center">
|
||||||
|
<div className="rb:text-[24px] rb:leading-8 rb:mb-1">{data.total_all ?? 0}</div>
|
||||||
|
{t(`episodicDetail.total_all`)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form form={form} initialValues={{ time_range: 'all', episodic_type: 'all' }}>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item name="time_range">
|
||||||
|
<Select
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
options={[
|
||||||
|
{ value: 'all', label: t('episodicDetail.all') },
|
||||||
|
{ value: 'today', label: t('episodicDetail.today') },
|
||||||
|
{ value: 'this_week', label: t('episodicDetail.this_week') },
|
||||||
|
{ value: 'this_month', label: t('episodicDetail.this_month') },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item name="episodic_type">
|
||||||
|
<Select
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
options={[
|
||||||
|
{ value: 'all', label: t('episodicDetail.all') },
|
||||||
|
{ value: 'conversation', label: t('episodicDetail.conversation') },
|
||||||
|
{ value: 'project_work', label: t('episodicDetail.project_work') },
|
||||||
|
{ value: 'learning', label: t('episodicDetail.learning') },
|
||||||
|
{ value: 'decision', label: t('episodicDetail.decision') },
|
||||||
|
{ value: 'important_event', label: t('episodicDetail.important_event') },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item name="title_keyword">
|
||||||
|
<Input placeholder={t('episodicDetail.titleKeywordPlaceholder')} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={8}>
|
||||||
|
<RbCard
|
||||||
|
title={<>{t('episodicDetail.curResult')}<span className="rb:text-[#5B6167] rb:font-regular!"> ({data.total || 0}{t('episodicDetail.unix')})</span></>}
|
||||||
|
headerType="borderless"
|
||||||
|
>
|
||||||
|
{loading
|
||||||
|
? <Skeleton active />
|
||||||
|
: !data.episodic_memories || data.episodic_memories.length === 0
|
||||||
|
? <Empty />
|
||||||
|
: (
|
||||||
|
<Space size={8} direction="vertical" className="rb:w-full">
|
||||||
|
{data.episodic_memories.map((vo, index) => (
|
||||||
|
<div
|
||||||
|
key={vo.id}
|
||||||
|
className={clsx("rb:cursor-pointer rb:flex rb:items-center rb:bg-[#FFFFFF] rb:border rb:rounded-lg rb:px-3 rb:py-2 rb:leading-5", {
|
||||||
|
'rb:border-[#DFE4ED] rb:shadow-[0px_2px_4px_0px_rgba(33,35,50,0.16)]': selected?.id !== vo.id,
|
||||||
|
'rb:border-[#155EEF]': selected?.id === vo.id,
|
||||||
|
})}
|
||||||
|
onClick={() => setSelected(vo)}
|
||||||
|
>
|
||||||
|
<div className={clsx("rb:bg-[#369F21] rb:rounded-lg rb:text-[#FFFFFF] rb:size-6 rb:text-[12px] rb:leading-6 rb:text-center rb:mr-3", BG_COLORS[getTypeKey(vo.type)])}>{index + 1}</div>
|
||||||
|
<div className="rb:flex-1">
|
||||||
|
<div className="rb:flex rb:items-center rb:justify-between">{vo.title} <Tag color={TAG_COLORS[getTypeKey(vo.type)]}>{t(`episodicDetail.${getTypeKey(vo.type)}`)}</Tag></div>
|
||||||
|
<div className="rb:text-[#5B6167] rb:text-[12px]">{formatDateTime(vo.created_at)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
</RbCard>
|
||||||
|
</Col>
|
||||||
|
<Col span={16}>
|
||||||
|
<RbCard
|
||||||
|
title={selected?.title}
|
||||||
|
headerType="borderless"
|
||||||
|
>
|
||||||
|
{detailLoading
|
||||||
|
? <Skeleton active />
|
||||||
|
: !selected || !detail
|
||||||
|
? <Empty className="rb:mt-14" />
|
||||||
|
: (
|
||||||
|
<Space size={12} direction="vertical" className="rb:w-full">
|
||||||
|
<div className="rb:bg-[#FFFFFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:px-3 rb:py-2 rb:leading-5">
|
||||||
|
<Row gutter={[12, 16]}>
|
||||||
|
<Col span={12}>
|
||||||
|
<div className="rb:text-[#5B6167]">{t('episodicDetail.created')}<br />{formatDateTime(detail.created_at)}</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<div className="rb:text-[#5B6167]">{t('episodicDetail.episodic_type')}<br />{detail.episodic_type}</div>
|
||||||
|
</Col>
|
||||||
|
{detail.involved_objects.length > 0 && <Col span={24}>
|
||||||
|
<div className="rb:font-medium rb:leading-5 rb:mb-1">{t('episodicDetail.involved_objects')}</div>
|
||||||
|
<Space size={8}>{detail.involved_objects.map((vo, index) => <Tag key={index}>{vo}</Tag>)}</Space>
|
||||||
|
</Col>}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
<div className="rb:bg-[#FFFFFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:px-3 rb:py-2 rb:leading-5">
|
||||||
|
<div className="rb:font-medium rb:leading-5 rb:mb-1">{t('episodicDetail.content_records')}</div>
|
||||||
|
{detail.content_records.map((vo, index) => <div key={index} className="rb:text-[#5B6167] rb:leading-5">- {vo}</div>)}
|
||||||
|
</div>
|
||||||
|
<RbAlert>
|
||||||
|
{t('episodicDetail.emotion')}: {t(`statementDetail.${detail.emotion}`)}
|
||||||
|
</RbAlert>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</RbCard>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default EpisodicDetail
|
||||||
@@ -20,7 +20,7 @@ const statusTagColors: Record<string, 'success' | 'purple' | 'default' | 'warnin
|
|||||||
chunk: 'warning',
|
chunk: 'warning',
|
||||||
}
|
}
|
||||||
|
|
||||||
const ForgetOverview: FC = () => {
|
const ForgetDetail: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
@@ -156,4 +156,4 @@ const ForgetOverview: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default ForgetOverview
|
export default ForgetDetail
|
||||||
14
web/src/views/UserMemoryDetail/pages/GraphDetail.tsx
Normal file
14
web/src/views/UserMemoryDetail/pages/GraphDetail.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { type FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Row, Col } from 'antd'
|
||||||
|
|
||||||
|
const GraphDetail: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rb:h-full rb:max-w-266 rb:mx-auto">
|
||||||
|
GraphDetail
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default GraphDetail
|
||||||
34
web/src/views/UserMemoryDetail/pages/ImplicitDetail.tsx
Normal file
34
web/src/views/UserMemoryDetail/pages/ImplicitDetail.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { type FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Row, Col } from 'antd'
|
||||||
|
|
||||||
|
import Preferences from '../components/Preferences'
|
||||||
|
import Portrait from '../components/Portrait'
|
||||||
|
import InterestAreas from '../components/InterestAreas'
|
||||||
|
import Habits from '../components/Habits'
|
||||||
|
|
||||||
|
const ImplicitDetail: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
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('implicitDetail.title')}</div>
|
||||||
|
|
||||||
|
<Preferences />
|
||||||
|
|
||||||
|
<div className="rb:bg-[rgba(21,94,239,0.12)] rb:px-3 rb:py-2.5 rb:font-medium rb:leading-5 rb:mb-4 rb:mt-6 rb:rounded-md">{t('implicitDetail.portraitTitle')}</div>
|
||||||
|
<div className="rb:my-3 rb:text-[#5B6167] rb:leading-5">{t('implicitDetail.portraitSubTitle')}</div>
|
||||||
|
<Row gutter={[16, 16]} className="rb:mt-4">
|
||||||
|
<Col span={12}>
|
||||||
|
<Portrait />
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<InterestAreas />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Habits />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ImplicitDetail
|
||||||
32
web/src/views/UserMemoryDetail/pages/PerceptualDetail.tsx
Normal file
32
web/src/views/UserMemoryDetail/pages/PerceptualDetail.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { type FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Row, Col } from 'antd'
|
||||||
|
|
||||||
|
import PerceptualLastInfo from '../components/PerceptualLastInfo'
|
||||||
|
import Timeline from '../components/Timeline'
|
||||||
|
|
||||||
|
const PerceptualDetail: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rb:h-full rb:max-w-266 rb:mx-auto">
|
||||||
|
<div className="rb:bg-[rgba(21,94,239,0.12)] rb:px-3 rb:py-2.5 rb:font-medium rb:leading-5 rb:mt-6 rb:rounded-md rb:mb-4">{t('perceptualDetail.lastInfo')}</div>
|
||||||
|
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={8}>
|
||||||
|
<PerceptualLastInfo type="last_visual" />
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<PerceptualLastInfo type="last_listen" />
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<PerceptualLastInfo type="last_text" />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<div className="rb:bg-[rgba(21,94,239,0.12)] rb:px-3 rb:py-2.5 rb:font-medium rb:leading-5 rb:mt-6 rb:rounded-md rb:mb-4">{t('perceptualDetail.timeLine')}</div>
|
||||||
|
<Timeline />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default PerceptualDetail
|
||||||
114
web/src/views/UserMemoryDetail/pages/ShortTermDetail.tsx
Normal file
114
web/src/views/UserMemoryDetail/pages/ShortTermDetail.tsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { type FC, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { Space, Skeleton } from 'antd'
|
||||||
|
import {
|
||||||
|
getShortTerm,
|
||||||
|
} from '@/api/memory'
|
||||||
|
import Empty from '@/components/Empty'
|
||||||
|
|
||||||
|
interface ShortTermItem {
|
||||||
|
retrieval: Array<{ query: string; retrieval: string[]; }>;
|
||||||
|
message: string;
|
||||||
|
answer: string;
|
||||||
|
}
|
||||||
|
interface LongTermItem {
|
||||||
|
query: string;
|
||||||
|
retrieval: string;
|
||||||
|
}
|
||||||
|
interface ShortData {
|
||||||
|
short_term: ShortTermItem[];
|
||||||
|
long_term: LongTermItem[];
|
||||||
|
entity: number;
|
||||||
|
retrieval_number: number;
|
||||||
|
long_term_number: number;
|
||||||
|
}
|
||||||
|
const ShortTermDetail: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { id } = useParams()
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [data, setData] = useState<ShortData>({} as ShortData)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id) return
|
||||||
|
getData()
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
const getData = () => {
|
||||||
|
if (!id) return
|
||||||
|
setLoading(true)
|
||||||
|
getShortTerm(id).then((res) => {
|
||||||
|
const response = res as ShortData
|
||||||
|
setData(response)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rb:h-full rb:max-w-266 rb:mx-auto">
|
||||||
|
<div className="rb:flex rb:justify-between rb:items-center rb:text-[#FFFFFF] rb:leading-5 rb:h-30 rb:p-5 rb:bg-[url('@/assets/images/userMemory/shortTerm.png')] rb:bg-cover">
|
||||||
|
<div className="rb:max-w-135">{t('shortTermDetail.title')}</div>
|
||||||
|
|
||||||
|
<div className="rb:grid rb:grid-cols-3 rb:gap-4">
|
||||||
|
{(['retrieval_number', 'entity', 'long_term_number'] as const).map(key => (
|
||||||
|
<div key={key} className="rb:bg-[rgba(255,255,255,0.2)] rb:rounded-lg rb:p-3.5 rb:text-[12px] rb:text-center">
|
||||||
|
<div className="rb:text-[24px] rb:leading-8 rb:mb-1">{(data as any)[key] ?? 0}</div>
|
||||||
|
{t(`shortTermDetail.${key}`)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="rb:bg-[rgba(21,94,239,0.12)] rb:px-3 rb:py-2.5 rb:font-medium rb:leading-5 rb:mb-4 rb:mt-6 rb:rounded-md">{t('shortTermDetail.shortTermTitle')}</div>
|
||||||
|
<div className="rb:my-3 rb:text-[#5B6167] rb:leading-5">{t('shortTermDetail.shortTermSubTitle')}</div>
|
||||||
|
<Space size={16} direction="vertical" className="rb:w-full">
|
||||||
|
{loading
|
||||||
|
? <Skeleton active />
|
||||||
|
: !data.short_term || data.short_term.length === 0
|
||||||
|
? <Empty />
|
||||||
|
:data.short_term?.map((vo, voIdx) => (
|
||||||
|
<div key={voIdx} className="rb:leading-5 rb:shadow-[inset_3px_0px_0px_0px_#155EEF] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:px-6 rb:py-3">
|
||||||
|
<div className="rb:font-medium rb:text-[16px] rb:leading-5.5 rb:mb-3">{vo.message}</div>
|
||||||
|
<Space size={16} direction="vertical" className="rb:w-full">
|
||||||
|
{vo.retrieval.map((item, index) => (
|
||||||
|
<div key={index} className="rb:bg-[#FFFFFF] rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-3 rb:py-2.5 rb:leading-5">
|
||||||
|
<div className="rb:font-medium rb:mb-3">{t('shortTermDetail.query')}: {item.query}</div>
|
||||||
|
<div className="rb:font-medium rb:leading-5 rb:mb-1">{t('shortTermDetail.answer')}:</div>
|
||||||
|
{item.retrieval.length > 0 ? item.retrieval.map((retrieval, retrievalIdx) => (
|
||||||
|
<div key={retrievalIdx} className="rb:text-[#5B6167] rb:text-[12px]">- {retrieval}</div>
|
||||||
|
)) : <div className="rb:text-[#5B6167] rb:text-[12px]">{t('shortTermDetail.noAnswer')}</div>}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div>
|
||||||
|
<div className="rb:font-medium rb:leading-5 rb:mb-1">{t('shortTermDetail.answer')}</div>
|
||||||
|
<div className="rb:bg-[#FFFFFF] rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-3 rb:py-2.5 rb:leading-5">{vo.answer}</div>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<div className="rb:bg-[rgba(21,94,239,0.12)] rb:px-3 rb:py-2.5 rb:font-medium rb:leading-5 rb:mb-4 rb:mt-6 rb:rounded-md">{t('shortTermDetail.longTermTitle')}</div>
|
||||||
|
<div className="rb:my-3 rb:text-[#5B6167] rb:leading-5">{t('shortTermDetail.shortTermSubTitle')}</div>
|
||||||
|
<Space size={16} direction="vertical" className="rb:w-full">
|
||||||
|
{loading
|
||||||
|
? <Skeleton active />
|
||||||
|
: !data.long_term || data.long_term.length === 0
|
||||||
|
? <Empty />
|
||||||
|
: data.long_term?.map((vo, voIdx) => (
|
||||||
|
<div key={voIdx} className="rb:leading-5 rb:shadow-[inset_3px_0px_0px_0px_#155EEF] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:px-6 rb:py-3">
|
||||||
|
<div className="rb:mb-1 rb:font-medium rb:leading-5.5">{vo.query}</div>
|
||||||
|
<div className="rb:mt-1 rb:leading-5 rb:text-[#5B6167] rb:text-[12px]">{vo.retrieval}</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ShortTermDetail
|
||||||
@@ -1,15 +1,23 @@
|
|||||||
import { type FC, useEffect, useState } from 'react'
|
import { type FC, useEffect, useState, useMemo } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Dropdown } from 'antd'
|
||||||
|
|
||||||
import PageHeader from '../components/PageHeader'
|
import PageHeader from '../components/PageHeader'
|
||||||
import StatementDetail from './StatementDetail'
|
import StatementDetail from './StatementDetail'
|
||||||
import ForgetDetail from './ForgetDetail'
|
import ForgetDetail from './ForgetDetail'
|
||||||
|
import ImplicitDetail from './ImplicitDetail'
|
||||||
|
import ShortTermDetail from './ShortTermDetail'
|
||||||
|
import PerceptualDetail from './PerceptualDetail'
|
||||||
|
import EpisodicDetail from './EpisodicDetail'
|
||||||
import {
|
import {
|
||||||
getEndUserProfile,
|
getEndUserProfile,
|
||||||
} from '@/api/memory'
|
} from '@/api/memory'
|
||||||
|
|
||||||
const Detail: FC = () => {
|
const Detail: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { id, type } = useParams()
|
const { id, type } = useParams()
|
||||||
|
const navigate = useNavigate()
|
||||||
const [name, setName] = useState<string>('')
|
const [name, setName] = useState<string>('')
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
@@ -23,17 +31,37 @@ const Detail: FC = () => {
|
|||||||
setName(response.other_name || response.id)
|
setName(response.other_name || response.id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const items = useMemo(() => {
|
||||||
|
return ['PERCEPTUAL_MEMORY', 'WORKING_MEMORY', 'EMOTIONAL_MEMORY', 'SHORT_TERM_MEMORY', 'IMPLICIT_MEMORY', 'EPISODIC_MEMORY', 'EXPLICIT_MEMORY', 'FORGETTING_MANAGEMENT']
|
||||||
|
.map(key => ({ key, label: t(`userMemory.${key}`) }))
|
||||||
|
}, [t])
|
||||||
|
const onClick = ({ key }: { key: string }) => {
|
||||||
|
navigate(`/user-memory/detail/${id}/${key}`, { replace: true })
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Detail', name)
|
|
||||||
return (
|
return (
|
||||||
<div className="rb:h-full rb:w-full">
|
<div className="rb:h-full rb:w-full">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
name={name}
|
name={name}
|
||||||
source="node"
|
source="node"
|
||||||
|
operation={
|
||||||
|
<Dropdown menu={{ items, onClick, selectedKeys: type ? [type] : [] }}>
|
||||||
|
<div className="rb:cursor-pointer rb:group rb:flex rb:items-center rb:gap-1">
|
||||||
|
- {type ? t(`userMemory.${type}`) : ''}
|
||||||
|
<div
|
||||||
|
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/userMemory/up_border.svg')] rb:transform-[rotate(180deg)] rb:group-hover:transform-[rotate(0deg)]"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div className="rb:h-[calc(100vh-64px)] rb:overflow-y-auto rb:py-3 rb:px-4">
|
<div className="rb:h-[calc(100vh-64px)] rb:overflow-y-auto rb:py-3 rb:px-4">
|
||||||
{type === 'EMOTIONAL_MEMORY' && <StatementDetail />}
|
{type === 'EMOTIONAL_MEMORY' && <StatementDetail />}
|
||||||
{type === 'FORGETTING_MANAGEMENT' && <ForgetDetail />}
|
{type === 'FORGETTING_MANAGEMENT' && <ForgetDetail />}
|
||||||
|
{type === 'IMPLICIT_MEMORY' && <ImplicitDetail />}
|
||||||
|
{type === 'SHORT_TERM_MEMORY' && <ShortTermDetail />}
|
||||||
|
{type === 'PERCEPTUAL_MEMORY' && <PerceptualDetail />} {/** TODO */}
|
||||||
|
{type === 'EPISODIC_MEMORY' && <EpisodicDetail />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export interface Data {
|
|||||||
export interface BaseProperties {
|
export interface BaseProperties {
|
||||||
content: string;
|
content: string;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
associative_memory: number;
|
||||||
}
|
}
|
||||||
export interface StatementNodeProperties {
|
export interface StatementNodeProperties {
|
||||||
temporal_info: string;
|
temporal_info: string;
|
||||||
@@ -51,12 +52,21 @@ export interface StatementNodeProperties {
|
|||||||
statement: string;
|
statement: string;
|
||||||
valid_at: string;
|
valid_at: string;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
emotion_keywords: string[];
|
||||||
|
emotion_type: string;
|
||||||
|
emotion_subject: string;
|
||||||
|
importance_score: number;
|
||||||
|
associative_memory: number;
|
||||||
}
|
}
|
||||||
export interface ExtractedEntityNodeProperties {
|
export interface ExtractedEntityNodeProperties {
|
||||||
description: string;
|
description: string;
|
||||||
name: string;
|
name: string;
|
||||||
entity_type: string;
|
entity_type: string;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
aliases: string;
|
||||||
|
connect_strngth: string;
|
||||||
|
importance_score: number;
|
||||||
|
associative_memory: number;
|
||||||
}
|
}
|
||||||
export interface MemorySummaryNode {
|
export interface MemorySummaryNode {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -72,7 +82,7 @@ export interface MemorySummaryNode {
|
|||||||
created_at: number;
|
created_at: number;
|
||||||
}
|
}
|
||||||
caption: string;
|
caption: string;
|
||||||
|
associative_memory: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Node {
|
export interface Node {
|
||||||
|
|||||||
Reference in New Issue
Block a user