diff --git a/web/src/api/memory.ts b/web/src/api/memory.ts index 42f691a4..47177136 100644 --- a/web/src/api/memory.ts +++ b/web/src/api/memory.ts @@ -195,6 +195,15 @@ export const getExplicitMemory = (end_user_id: string) => { export const getExplicitMemoryDetails = (data: { end_user_id: string, memory_id: string; }) => { return request.post(`/memory-storage/classifications/explicit-memory-details`, data) } +export const getConversations = (end_user: string) => { + return request.get(`/memory/work/${end_user}/conversations`) +} +export const getConversationMessages = (end_user: string, conversation_id: string) => { + return request.get(`/memory/work/${end_user}/messages`, { conversation_id }) +} +export const getConversationDetail = (end_user: string, conversation_id: string) => { + return request.get(`/memory/work/${end_user}/detail`, { conversation_id }) +} /*************** end 用户记忆 相关接口 ******************************/ diff --git a/web/src/assets/images/empty/pageLoading.png b/web/src/assets/images/empty/pageLoading.png new file mode 100644 index 00000000..36e0e32b Binary files /dev/null and b/web/src/assets/images/empty/pageLoading.png differ diff --git a/web/src/components/Empty/PageLoading.tsx b/web/src/components/Empty/PageLoading.tsx new file mode 100644 index 00000000..df5041d9 --- /dev/null +++ b/web/src/components/Empty/PageLoading.tsx @@ -0,0 +1,16 @@ +import { useTranslation } from 'react-i18next' +import LoadingIcon from '@/assets/images/empty/pageLoading.png' +import Empty from './index' +const PageLoading = ({ size = [240, 210] }: { size?: number | number[] }) => { + const { t } = useTranslation() + return ( + + ) +} +export default PageLoading; \ No newline at end of file diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 16d07add..6b104bac 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1190,10 +1190,6 @@ export const en = { nodeStatistics: 'Memory Classification', total: 'Total', - Chunk: 'Long-term Memory', - MemorySummary: 'Episodic Memory', - Statement: 'Emotional Memory', - ExtractedEntity: 'Short-term Memory', PERCEPTUAL_MEMORY: 'Perceptual Memory', WORKING_MEMORY: 'Working Memory', @@ -1244,6 +1240,10 @@ export const en = { timelineMemories: 'Shared Memory Timeline', emotionLine: 'Emotion Changes Over Time', interaction: 'Interaction Frequency & Relationship Stages', + timelines_memory: 'All', + MemorySummary: 'Long-term Accumulation', + Statement: 'Emotional Memory', + ExtractedEntity: 'Episodic Memory', }, space: { createSpace: 'Create Space', @@ -1987,6 +1987,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re }, statementDetail: { wordCloud: 'Emotion Distribution Analysis', + totalCount: 'Sample Count', pieces: 'items', emotionTags: 'High-Frequency Emotion Keywords', joy: 'Joy', @@ -2281,6 +2282,14 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re emotion: 'Emotion', core_definition: 'Core Definition', detailed_notes: 'Detailed Notes', + }, + workingDetail: { + conversationStream: 'Real-time Conversation Stream', + refresh: 'Refresh', + successfulTitle: 'Successful Experience', + question: 'Lessons Learned', + summary: 'Core Insights', + none: 'None' } }, }; diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 74c02720..77fade44 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1269,11 +1269,6 @@ export const zh = { nodeStatistics: '记忆分类', total: '总计', - Chunk: '长期记忆', - MemorySummary: '情景记忆', - Statement: '情绪记忆', - ExtractedEntity: '短期记忆', - PERCEPTUAL_MEMORY: '感知记忆', WORKING_MEMORY: '工作记忆', SHORT_TERM_MEMORY: '短期记忆', @@ -1325,6 +1320,10 @@ export const zh = { timelineMemories: '共同记忆时间线', emotionLine: '情绪随时间变化', interaction: '互动频率 & 关系阶段', + timelines_memory: '全部', + MemorySummary: '长期沉淀', + Statement: '情绪记忆', + ExtractedEntity: '情景记忆', }, space: { createSpace: '创建空间', @@ -2087,6 +2086,7 @@ export const zh = { }, statementDetail: { wordCloud: '情感分布分析', + totalCount: '样本数', pieces: '条', emotionTags: '高频情绪关键词', joy: '喜悦', @@ -2381,6 +2381,14 @@ export const zh = { emotion: '情绪', core_definition: '核心定义', detailed_notes: '详细笔记', + }, + workingDetail: { + conversationStream: '实时对话流', + refresh: '刷新', + successfulTitle: '成功经验', + question: '踩过的坑', + summary: '核心洞察', + none: '无' } }, } \ No newline at end of file diff --git a/web/src/views/UserMemoryDetail/components/EmotionLine.tsx b/web/src/views/UserMemoryDetail/components/EmotionLine.tsx index eb53439f..3652e7c5 100644 --- a/web/src/views/UserMemoryDetail/components/EmotionLine.tsx +++ b/web/src/views/UserMemoryDetail/components/EmotionLine.tsx @@ -1,7 +1,6 @@ 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 type { Emotion } from './GraphDetail' @@ -100,10 +99,7 @@ const EmotionLine: FC = ({ chartData, loading }) => { }, xAxis: { type: 'category', - data: [...new Set(chartData.map(item => item.created_at))].sort().map(time => { - const date = new Date(time) - return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}` - }), + data: [...new Set(chartData.map(item => item.created_at))].sort(), boundaryGap: false, axisLabel: { color: '#A8A9AA', diff --git a/web/src/views/UserMemoryDetail/components/EmotionTags.tsx b/web/src/views/UserMemoryDetail/components/EmotionTags.tsx index 76ffa4ac..5fc8f382 100644 --- a/web/src/views/UserMemoryDetail/components/EmotionTags.tsx +++ b/web/src/views/UserMemoryDetail/components/EmotionTags.tsx @@ -1,6 +1,8 @@ -import { type FC, useEffect, useState } from 'react' +import { type FC, useEffect, useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom' +import * as echarts from 'echarts' +import 'echarts-wordcloud' import Empty from '@/components/Empty' import RbCard from '@/components/RbCard/Card' @@ -13,40 +15,23 @@ interface TagList { const EmotionTags: FC = () => { const { t } = useTranslation() const { id } = useParams() + const chartRef = useRef(null) + const chartInstance = useRef(null) const [data, setData] = useState(null) useEffect(() => { + if (!id) return getEmotionTagData() }, [id]) const getEmotionTagData = () => { - if (!id) { - return - } + if (!id) return getWordCloud(id) .then((res) => { setData(res as TagList) }) } - const [visibleCount, setVisibleCount] = useState(0) - - useEffect(() => { - if (!data || data?.keywords.length === 0) return - - const timer = setInterval(() => { - setVisibleCount(prev => { - if (prev >= data?.keywords.length) { - clearInterval(timer) - return prev - } - return prev + 1 - }) - }, 200) - - return () => clearInterval(timer) - }, [data?.keywords.length]) - const getEmotionColor = (emotionType: string) => { const colors: Record = { joy: '#52c41a', @@ -59,6 +44,56 @@ const EmotionTags: FC = () => { return colors[emotionType] || '#8c8c8c' } + useEffect(() => { + if (!chartRef.current || !data?.keywords.length) return + + if (chartInstance.current) { + chartInstance.current.dispose() + } + + chartInstance.current = echarts.init(chartRef.current) + + const wordCloudData = data.keywords.map((item) => ({ + name: item.keyword, + value: item.frequency, + textStyle: { + color: getEmotionColor(item.emotion_type) + } + })) + + 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) + + return () => { + if (chartInstance.current) { + chartInstance.current.dispose() + chartInstance.current = null + } + } + }, [data]) + const emotionStats = data?.keywords.reduce((acc, item) => { acc[item.emotion_type] = (acc[item.emotion_type] || 0) + item.frequency return acc @@ -68,41 +103,25 @@ const EmotionTags: FC = () => { {data?.keywords && data?.keywords.length > 0 - ? <> -
+ ?
+
+
{Object.entries(emotionStats).map(([type, count]) => { console.log(type) return (
- {t(`statementDetail.${type || 'neutral'}`)} ({count}个) + {t(`statementDetail.${type || 'neutral'}`)} ({count}个)
) })}
-
- {data.keywords.slice(0, visibleCount).map((item, index) => ( -
- {item.keyword} -
- ))} -
- - : +
+ : } ) diff --git a/web/src/views/UserMemoryDetail/components/GraphDetail.tsx b/web/src/views/UserMemoryDetail/components/GraphDetail.tsx index 3b654a12..aed795f5 100644 --- a/web/src/views/UserMemoryDetail/components/GraphDetail.tsx +++ b/web/src/views/UserMemoryDetail/components/GraphDetail.tsx @@ -1,13 +1,16 @@ -import { type FC, useState, forwardRef, useImperativeHandle } from 'react' +import { useState, forwardRef, useImperativeHandle, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { useParams } from 'react-router-dom' -import { Row, Col, Tabs } from 'antd' +import { Row, Col, Tabs, Space, Skeleton } from 'antd' import { getRelationshipEvolution, getTimelineMemories } from '@/api/memory' import type { Node, GraphDetailRef } from '../types' import RbDrawer from '@/components/RbDrawer' import RbCard from '@/components/RbCard/Card' import EmotionLine from './EmotionLine' +import { formatDateTime } from '@/utils/format' +import Tag from '@/components/Tag' +import InteractionBar from './InteractionBar' +import Empty from '@/components/Empty' export interface Emotion { emotion_intensity: number; @@ -15,141 +18,72 @@ export interface Emotion { created_at: string | number; } export interface Interaction { - name: string; - importance_score: number; - interaction_count: number; + created_at: string | number; + count: number; +} +interface TimelineMemory { + text: string; + type: string; + created_at: number | string; +} +interface Timeline { + MemorySummary: TimelineMemory[]; + Statement: TimelineMemory[]; + ExtractedEntity: TimelineMemory[]; + timelines_memory: TimelineMemory[]; } const GraphDetail = forwardRef((_props, ref) => { const { t } = useTranslation() - const { id } = useParams() const [open, setOpen] = useState(false); const [vo, setVo] = useState(null) - const [emotionData, setEmotionData] = useState([ - { - "emotion_intensity": 0.1, - "emotion_type": "neutral", - "created_at": "2026-01-07 19:14:34" - }, - { - "emotion_intensity": 0.2, - "emotion_type": "neutral", - "created_at": "2026-02-08 19:14:34" - }, - { - "emotion_intensity": 0.1, - "emotion_type": "neutral", - "created_at": "2026-03-09 19:14:34" - }, - { - "emotion_intensity": 0.1, - "emotion_type": "neutral", - "created_at": "2026-04-10 19:14:34" - }, - { - "emotion_intensity": 0.1, - "emotion_type": "sadness", - "created_at": "2026-01-07 19:14:34" - }, - { - "emotion_intensity": 0.2, - "emotion_type": "sadness", - "created_at": "2026-02-08 19:14:34" - }, - { - "emotion_intensity": 0.1, - "emotion_type": "sadness", - "created_at": "2026-03-09 19:14:34" - }, - { - "emotion_intensity": 0.1, - "emotion_type": "sadness", - "created_at": "2026-04-10 19:14:34" - }, - ]) - const [interactionData, setInteractionData] = useState([ - { - "name": "小蓝", - "importance_score": 0.5, - "interaction_count": 1 - } - ]) - const [timelineMemories, setTimelineMemories] = useState({ - "code": 0, - "msg": "共同记忆时间线", - "data": { - "success": true, - "data": { - "MemorySummary": [ - "小蓝今天原计划与小明野餐、与小绿看电影,但最终选择与姐姐小红一起看戏。", - "用户小明喜欢喝咖啡,每天都要喝拿铁。" - ], - "Statement": [ - "小蓝对是否去野餐或看电影感到犹豫。", - "小蓝和她姐姐小红出去看戏。", - "小明喜欢喝咖啡。", - "小明每天都要喝拿铁。", - "小明今天约小蓝出去野餐。" - ], - "ExtractedEntity": [ - "小明", - "咖啡", - "拿铁", - "小蓝", - "野餐" - ], - "timelines_memory": [ - "小蓝今天原计划与小明野餐、与小绿看电影,但最终选择与姐姐小红一起看戏。", - "用户小明喜欢喝咖啡,每天都要喝拿铁。", - "小蓝对是否去野餐或看电影感到犹豫。", - "小蓝和她姐姐小红出去看戏。", - "小明喜欢喝咖啡。", - "小明每天都要喝拿铁。", - "小明今天约小蓝出去野餐。", - "小明", - "咖啡", - "拿铁", - "小蓝", - "野餐" - ] - } - }, - "error": "", - "time": 1767852781464 - }) + const [loading, setLoading] = useState(false) + const [emotionData, setEmotionData] = useState([]) + const [interactionData, setInteractionData] = useState([]) + const [activeTab, setActiveTab] = useState('timelines_memory') + const [timelineLoading, setTimelineLoading] = useState(false) + const [timelineMemories, setTimelineMemories] = useState({ timelines_memory: [], MemorySummary: [], Statement: [], ExtractedEntity: []}) const handleCancel = () => { setVo(null) setOpen(false) } const handleOpen = (vo: Node) => { + setActiveTab('timelines_memory') setOpen(true) setVo(vo) + getRelationshipEvolutionData(vo) getTimelineMemoriesData(vo) } const getRelationshipEvolutionData = (vo: Node) => { - if (!id || !vo.label) return - - getRelationshipEvolution({ id: id as string, label: vo.label }) + if (!vo.id || !vo.label) return + setLoading(true) + getRelationshipEvolution({ id: vo.id as string, label: vo.label }) .then(res => { - const { emotion, interaction } = res as { emotion: { data: Emotion[]}; interaction: {data: Interaction[]} } || {} - setEmotionData(emotion?.data) - setInteractionData(interaction?.data) + const { emotion, interaction } = res as { emotion: Emotion[]; interaction: Interaction[] } || {} + setEmotionData(emotion) + setInteractionData(interaction) }) + .finally(() => setLoading(false)) } const getTimelineMemoriesData = (vo: Node) => { - if (!id || !vo.label) return - - getTimelineMemories({ id: id as string, label: vo.label }) + if (!vo.id || !vo.label) return + setTimelineLoading(true) + getTimelineMemories({ id: vo.id as string, label: vo.label }) .then(res => { - + setTimelineMemories(res as Timeline) }) + .finally(() => setTimelineLoading(false)) } useImperativeHandle(ref, () => ({ handleOpen, })); + const activeContent = useMemo(() => { + return timelineMemories[activeTab as keyof Timeline] || [] + }, [activeTab, timelineMemories]) + return ( ((_props, ref) => { onClose={handleCancel} width={1000} > -
{t('useMemory.relationshipEvolution')}
+
{t('userMemory.relationshipEvolution')}
- + -
{t('userMemory.interaction')}
+
+ +
{t('userMemory.timelineMemories')}
+ + ({ + label: t(`userMemory.${key}`), + key + }))} + onChange={(key: string) => setActiveTab(key)} + /> + {timelineLoading + ? + : !activeContent || activeContent.length === 0 + ? + : + {activeContent.map((vo, index) => ( + +
{formatDateTime(vo.created_at)}
+ {vo.type} +
+ ))} +
+ } + + +
) }) diff --git a/web/src/views/UserMemoryDetail/components/Health.tsx b/web/src/views/UserMemoryDetail/components/Health.tsx index 98ba5d5c..a7bbb738 100644 --- a/web/src/views/UserMemoryDetail/components/Health.tsx +++ b/web/src/views/UserMemoryDetail/components/Health.tsx @@ -1,7 +1,8 @@ import { type FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom' -import { Progress } from 'antd' +import { Row, Col, Progress } from 'antd' +import ReactEcharts from 'echarts-for-react' import Empty from '@/components/Empty' import RbCard from '@/components/RbCard/Card' @@ -58,37 +59,95 @@ const Health: FC = () => { {health?.health_score && health?.health_score > 0 ? <> -
- `${percent}(${health.level})`} - /> -
+ + +
+ +
+ + + {health.dimensions &&
+
+
+ {t('statementDetail.positivity_rate')} +
{health.dimensions.positivity_rate.score}%
+
+ +
+
+
+ {t('statementDetail.stability')} +
{health.dimensions.stability.score}%
+
+ +
+
+
+ {t('statementDetail.resilience')} +
{health.dimensions.resilience.score}%
+
+ +
+
} + +
- {health.dimensions && <> -
-
{t('statementDetail.positivity_rate')}
- -
-
-
{t('statementDetail.stability')}
- -
-
-
{t('statementDetail.resilience')}
- -
- } : } diff --git a/web/src/views/UserMemoryDetail/components/InteractionBar.tsx b/web/src/views/UserMemoryDetail/components/InteractionBar.tsx new file mode 100644 index 00000000..51224676 --- /dev/null +++ b/web/src/views/UserMemoryDetail/components/InteractionBar.tsx @@ -0,0 +1,119 @@ +import { type FC } from 'react' +import { useTranslation } from 'react-i18next' +import ReactEcharts from 'echarts-for-react' +import Empty from '@/components/Empty' +import Loading from '@/components/Empty/Loading' +import type { Interaction } from './GraphDetail' + +interface InteractionBarProps { + chartData: Interaction[]; + loading?: boolean; +} + +const Colors = ['#155EEF', '#369F21', '#FF5D34'] +const InteractionBar: FC = ({ chartData, loading }) => { + const { t } = useTranslation() + + const series = [{ + name: 'Interaction Count', + type: 'bar', + data: chartData.map(item => item.count) + }] + + return ( + <> +
{t('userMemory.interaction')}
+ {loading + ? + : !chartData || chartData.length === 0 + ? + : item.created_at), + 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', + 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' + } + }, + max: 1, + min: 0 + }, + series + }} + style={{ height: '265px', width: '100%' }} + /> + } + + ) +} + +export default InteractionBar diff --git a/web/src/views/UserMemoryDetail/components/Suggestions.tsx b/web/src/views/UserMemoryDetail/components/Suggestions.tsx index 0346f9dc..35fde91f 100644 --- a/web/src/views/UserMemoryDetail/components/Suggestions.tsx +++ b/web/src/views/UserMemoryDetail/components/Suggestions.tsx @@ -41,18 +41,24 @@ const Suggestions: FC = () => { {suggestions?.suggestions && suggestions?.suggestions.length > 0 ? <> {suggestions.health_summary} - {suggestions.suggestions.map((item, index) => ( -
-
{index + 1}. {item.title}
-
{item.content}
- {item.actionable_steps.map((vo, idx) =>
- {vo}
)} -
- ))} +
+ {suggestions.suggestions.map((item, index) => ( +
+
{index + 1}. {item.title}
+
{item.content}
+ +
    + {item.actionable_steps.map((vo, idx) =>
  • {vo}
  • )} +
+
+ ))} +
: } diff --git a/web/src/views/UserMemoryDetail/components/WordCloud.tsx b/web/src/views/UserMemoryDetail/components/WordCloud.tsx index b249a03a..2f60cf0f 100644 --- a/web/src/views/UserMemoryDetail/components/WordCloud.tsx +++ b/web/src/views/UserMemoryDetail/components/WordCloud.tsx @@ -2,7 +2,7 @@ import { type FC, useEffect, useState, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom' import ReactEcharts from 'echarts-for-react' -import { Progress } from 'antd' +import { Progress, Row, Col } from 'antd' import Empty from '@/components/Empty' import RbCard from '@/components/RbCard/Card' @@ -101,27 +101,35 @@ const WordCloud: FC = () => { {wordCloud?.total_count && wordCloud?.total_count > 0 - ?
- -
-
样本数:{wordCloud.total_count}
-
+ ? + + +
+ {wordCloud.total_count}
+ {t('statementDetail.totalCount')} +
+ + +
{wordCloud.tags.map(item => (
-
- {t(`statementDetail.${item.emotion_type}`)} -
{item.count} {t('statementDetail.pieces')}
+
+
+ {t(`statementDetail.${item.emotion_type}`)} + ( {item.count} {t('statementDetail.pieces')} ) +
+
{item.percentage.toFixed(1)}%
- +
))}
-
-
+ + : } diff --git a/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx b/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx new file mode 100644 index 00000000..3093d1ae --- /dev/null +++ b/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx @@ -0,0 +1,209 @@ +import { type FC, useEffect, useState, useMemo } 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, Button, Divider } from 'antd' +import RbCard from '@/components/RbCard/Card' +import { + getConversations, + getConversationMessages, + getConversationDetail, +} from '@/api/memory' +import { formatDateTime } from '@/utils/format' +import Tag from '@/components/Tag' +import RbAlert from '@/components/RbAlert' +import Empty from '@/components/Empty' +import ChatContent from '@/components/Chat/ChatContent' +import type { ChatItem } from '@/components/Chat/types' +import PageLoading from '@/components/Empty/PageLoading' + +interface Conversation { + title: string; + id: string; +} +interface Detail { + theme: string; + theme_intro: string; + summary: string; + question: string[]; + takeaways: string[]; + info_score: number; +} + +const WorkingDetail: FC = () => { + const { t } = useTranslation() + const { id } = useParams() + const [form] = Form.useForm() + const [loading, setLoading] = useState(false) + const [data, setData] = useState([]) + const [messagesLoading, setMessagesLoading] = useState(false) + const [messages, setMessages] = useState([]) + const [detailLoading, setDetailLoading] = useState(false) + const [detail, setDetail] = useState(null) + const [selected, setSelected] = useState(null) + + useEffect(() => { + if (!id) return + getData() + }, [id]) + + const getData = () => { + if (!id) return + setLoading(true) + setSelected(null) + setDetail(null) + setData([]) + getConversations(id).then((res) => { + const response = res as Conversation[] + setData(response) + setSelected(response[0] || null) + }) + .finally(() => { + setLoading(false) + }) + } + + useEffect(() => { + if (!id || !selected || !selected.id) return + getDetail(selected.id) + }, [id, selected]) + + const getDetail = (conversationId: string) => { + if (!id || !conversationId) return + + setDetail(null) + setMessages([]) + setDetailLoading(true) + setMessagesLoading(true) + getConversationMessages(id, conversationId) + .then(res => { + setMessages(res as ChatItem[]) + }) + .finally(() => { + setMessagesLoading(false) + }) + getConversationDetail(id, conversationId) + .then(res => { + setDetail(res as Detail) + }) + .finally(() => { + setDetailLoading(false) + }) + } + const timeRange = useMemo(() => { + const times = messages.filter(m => m.created_at).map(m => Number(m.created_at)) + if (times.length === 0) return '' + const minTime = Math.min(...times) + const maxTime = Math.max(...times) + return `${formatDateTime(minTime, 'YYYY.MM')} - ${formatDateTime(maxTime, 'YYYY.MM')}` + }, [messages]) + + return ( +
+ {loading + ? + : data.length === 0 + ? + :( + + +
+ {data.map(item => ( +
+
getDetail(item.id)} + > + {item.title} +
+
+ ))} +
+ + {selected && <> + +
{selected.title}
+
{timeRange}
+ + + + getDetail(selected.id)}>{t('workingDetail.refresh')}} + className="rb:mt-4!" + headerClassName='rb:bg-[#F6F8FC]! rb:border-b! rb:border-b-[#DFE4ED]! rb:min-h-11!' + headerType="borderless" + bodyClassName="rb:h-[calc(100vh-210px)]" + > + {messagesLoading + ? + : messages.length === 0 + ? + : ( + formatDateTime(item.created_at)} + /> + ) + } + + + + + {detailLoading + ? + : detail + ? <> + <> +
{t('workingDetail.successfulTitle')}
+ + {detail.takeaways.length > 0 + ? ( +
    + {detail.takeaways.map(vo =>
  • {vo}
  • )} +
+ ) + : + } + + + <> + +
{t('workingDetail.question')}
+ + {detail.question.length > 0 + ? ( +
    + {detail.question.map(vo =>
  • {vo}
  • )} +
+ ) + : + } + + + <> + +
{t('workingDetail.summary')}
+ {detail.summary + ? {detail.summary} + : + } + + + : + } +
+ +
+ + } +
+ ) + } +
+ ) +} +export default WorkingDetail \ No newline at end of file diff --git a/web/src/views/UserMemoryDetail/pages/index.tsx b/web/src/views/UserMemoryDetail/pages/index.tsx index 93468a94..e734fd44 100644 --- a/web/src/views/UserMemoryDetail/pages/index.tsx +++ b/web/src/views/UserMemoryDetail/pages/index.tsx @@ -11,6 +11,7 @@ import ShortTermDetail from './ShortTermDetail' import PerceptualDetail from './PerceptualDetail' import EpisodicDetail from './EpisodicDetail' import ExplicitDetail from './ExplicitDetail' +import WorkingDetail from './WorkingDetail' import { getEndUserProfile, } from '@/api/memory' @@ -63,7 +64,7 @@ const Detail: FC = () => { {type === 'SHORT_TERM_MEMORY' && } {type === 'PERCEPTUAL_MEMORY' && } {/** TODO */} {type === 'EPISODIC_MEMORY' && } - {/* {type === 'WORKING_MEMORY' && } */} {/** TODO */} + {type === 'WORKING_MEMORY' && } {/** TODO */} {type === 'EXPLICIT_MEMORY' && } {/** TODO */}