Merge pull request #72 from SuanmoSuanyangTechnology/feature/workflow_zy
Feature/workflow zy
This commit is contained in:
@@ -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 用户记忆 相关接口 ******************************/
|
||||
|
||||
BIN
web/src/assets/images/empty/pageLoading.png
Normal file
BIN
web/src/assets/images/empty/pageLoading.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 208 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 117 KiB |
16
web/src/components/Empty/PageLoading.tsx
Normal file
16
web/src/components/Empty/PageLoading.tsx
Normal file
@@ -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 (
|
||||
<Empty
|
||||
url={LoadingIcon}
|
||||
title={t('empty.loadingEmpty')}
|
||||
subTitle={t('empty.loadingEmptyDesc')}
|
||||
size={size}
|
||||
className="rb:h-full"
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default PageLoading;
|
||||
@@ -1196,10 +1196,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',
|
||||
@@ -1250,6 +1246,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',
|
||||
@@ -1993,6 +1993,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',
|
||||
@@ -2287,6 +2288,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'
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1275,11 +1275,6 @@ export const zh = {
|
||||
nodeStatistics: '记忆分类',
|
||||
total: '总计',
|
||||
|
||||
Chunk: '长期记忆',
|
||||
MemorySummary: '情景记忆',
|
||||
Statement: '情绪记忆',
|
||||
ExtractedEntity: '短期记忆',
|
||||
|
||||
PERCEPTUAL_MEMORY: '感知记忆',
|
||||
WORKING_MEMORY: '工作记忆',
|
||||
SHORT_TERM_MEMORY: '短期记忆',
|
||||
@@ -1331,6 +1326,10 @@ export const zh = {
|
||||
timelineMemories: '共同记忆时间线',
|
||||
emotionLine: '情绪随时间变化',
|
||||
interaction: '互动频率 & 关系阶段',
|
||||
timelines_memory: '全部',
|
||||
MemorySummary: '长期沉淀',
|
||||
Statement: '情绪记忆',
|
||||
ExtractedEntity: '情景记忆',
|
||||
},
|
||||
space: {
|
||||
createSpace: '创建空间',
|
||||
@@ -2093,6 +2092,7 @@ export const zh = {
|
||||
},
|
||||
statementDetail: {
|
||||
wordCloud: '情感分布分析',
|
||||
totalCount: '样本数',
|
||||
pieces: '条',
|
||||
emotionTags: '高频情绪关键词',
|
||||
joy: '喜悦',
|
||||
@@ -2387,6 +2387,14 @@ export const zh = {
|
||||
emotion: '情绪',
|
||||
core_definition: '核心定义',
|
||||
detailed_notes: '详细笔记',
|
||||
},
|
||||
workingDetail: {
|
||||
conversationStream: '实时对话流',
|
||||
refresh: '刷新',
|
||||
successfulTitle: '成功经验',
|
||||
question: '踩过的坑',
|
||||
summary: '核心洞察',
|
||||
none: '无'
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -48,7 +48,8 @@ const AboutMe = forwardRef<AboutMeRef>((_props, ref) => {
|
||||
|
||||
return (
|
||||
<RbCard
|
||||
title={t('userMemory.aboutMe')}
|
||||
title={t('userMemory.aboutMe')}
|
||||
headerClassName="rb:min-h-[46px]!"
|
||||
>
|
||||
{loading
|
||||
? <Skeleton className="rb:mt-4" />
|
||||
|
||||
@@ -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<EmotionLineProps> = ({ 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',
|
||||
|
||||
@@ -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<HTMLDivElement>(null)
|
||||
const chartInstance = useRef<echarts.ECharts | null>(null)
|
||||
const [data, setData] = useState<TagList | null>(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<string, string> = {
|
||||
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 = () => {
|
||||
<RbCard
|
||||
title={t('statementDetail.emotionTags')}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:text-[18px]! rb:leading-[24px]"
|
||||
bodyClassName='rb:p-0! rb:pb-3! rb:relative'
|
||||
headerClassName="rb:leading-[24px] rb:bg-[#F6F8FC]! rb:min-h-[46px]! rb:border-b! rb:border-b-[#DFE4ED]!"
|
||||
bodyClassName="rb:p-0!"
|
||||
>
|
||||
{data?.keywords && data?.keywords.length > 0
|
||||
? <>
|
||||
<div className="rb:flex rb:flex-wrap rb:items-center rb:gap-6 rb:text-sm rb:mt-3 rb:p-3 rb:bg-[#F0F3F8]">
|
||||
? <div>
|
||||
<div ref={chartRef} className="rb:mt-6 rb:px-6" style={{ height: '320px', width: '100%' }} />
|
||||
<div className="rb:flex rb:flex-wrap rb:items-center rb:justify-center rb:gap-10 rb:text-sm rb:mt-3 rb:p-3 rb:bg-[#F0F3F8] rb:rounded-[0_0_8px_8px]">
|
||||
{Object.entries(emotionStats).map(([type, count]) => {
|
||||
console.log(type)
|
||||
return (
|
||||
<div key={type} className="rb:flex rb:items-center rb:gap-2">
|
||||
<div className="rb:w-3 rb:h-3 rb:rounded-full" style={{ backgroundColor: getEmotionColor(type) }}></div>
|
||||
<span className="rb:text-gray-600">{t(`statementDetail.${type || 'neutral'}`)} ({count}个)</span>
|
||||
<span className="rb:leading-5">{t(`statementDetail.${type || 'neutral'}`)} ({count}个)</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="rb:mt-6 rb:flex rb:items-center rb:flex-wrap rb:gap-3 rb:mb-3 rb:px-6">
|
||||
{data.keywords.slice(0, visibleCount).map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rb:flex rb:items-center rb:justify-center rb:animate-fadeIn rb:px-4 rb:py-2 rb:rounded-full rb:text-white rb:font-medium"
|
||||
style={{
|
||||
backgroundColor: getEmotionColor(item.emotion_type),
|
||||
fontSize: `${12 + item.avg_intensity * 8}px`,
|
||||
animationDelay: `${index * 200}ms`,
|
||||
height: `${20 + item.avg_intensity * 20}px`,
|
||||
transition: 'all 0.3s ease-in-out'
|
||||
}}
|
||||
>
|
||||
{item.keyword}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
: <Empty size={88} />
|
||||
</div>
|
||||
: <Empty size={88} className="rb:h-full" />
|
||||
}
|
||||
</RbCard>
|
||||
)
|
||||
|
||||
@@ -68,6 +68,7 @@ const EndUserProfile = forwardRef<EndUserProfileRef, EndUserProfileProps>(({ onD
|
||||
onClick={handleEdit}
|
||||
></div>
|
||||
}
|
||||
headerClassName="rb:min-h-[46px]!"
|
||||
>
|
||||
{loading
|
||||
? <Skeleton />
|
||||
|
||||
@@ -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<GraphDetailRef>((_props, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const { id } = useParams()
|
||||
const [open, setOpen] = useState(false);
|
||||
const [vo, setVo] = useState<Node | null>(null)
|
||||
const [emotionData, setEmotionData] = useState<Emotion[]>([
|
||||
{
|
||||
"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<Interaction[]>([
|
||||
{
|
||||
"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<Emotion[]>([])
|
||||
const [interactionData, setInteractionData] = useState<Interaction[]>([])
|
||||
const [activeTab, setActiveTab] = useState('timelines_memory')
|
||||
const [timelineLoading, setTimelineLoading] = useState(false)
|
||||
const [timelineMemories, setTimelineMemories] = useState<Timeline>({ 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 (
|
||||
<RbDrawer
|
||||
title={vo?.name}
|
||||
@@ -157,17 +91,49 @@ const GraphDetail = forwardRef<GraphDetailRef>((_props, ref) => {
|
||||
onClose={handleCancel}
|
||||
width={1000}
|
||||
>
|
||||
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3">{t('useMemory.relationshipEvolution')}</div>
|
||||
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3">{t('userMemory.relationshipEvolution')}</div>
|
||||
<RbCard>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<EmotionLine chartData={emotionData} />
|
||||
<EmotionLine chartData={emotionData} loading={loading} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div>{t('userMemory.interaction')}</div>
|
||||
<InteractionBar chartData={interactionData} loading={loading} />
|
||||
</Col>
|
||||
</Row>
|
||||
</RbCard>
|
||||
|
||||
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3 rb:mt-6">{t('userMemory.timelineMemories')}</div>
|
||||
<RbCard>
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
items={['timelines_memory', 'ExtractedEntity', 'Statement', 'MemorySummary'].map(key => ({
|
||||
label: t(`userMemory.${key}`),
|
||||
key
|
||||
}))}
|
||||
onChange={(key: string) => setActiveTab(key)}
|
||||
/>
|
||||
{timelineLoading
|
||||
? <Skeleton active />
|
||||
: !activeContent || activeContent.length === 0
|
||||
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
|
||||
: <Space size={16} direction="vertical" className="rb:w-full">
|
||||
{activeContent.map((vo, index) => (
|
||||
<RbCard
|
||||
key={index}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#155EEF]!"
|
||||
title={vo.text}
|
||||
>
|
||||
<div className="rb:text-[#A8A9AA] rb:text-[12px] rb:leading-4">{formatDateTime(vo.created_at)}</div>
|
||||
<Tag className="rb:mt-2">{vo.type}</Tag>
|
||||
</RbCard>
|
||||
))}
|
||||
</Space>
|
||||
}
|
||||
|
||||
|
||||
</RbCard>
|
||||
</RbDrawer>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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 = () => {
|
||||
<RbCard
|
||||
title={t('statementDetail.health')}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:text-[18px]! rb:leading-[24px]"
|
||||
headerClassName="rb:leading-[24px] rb:bg-[#F6F8FC]! rb:min-h-[46px]! rb:border-b! rb:border-b-[#DFE4ED]!"
|
||||
bodyClassName="rb:px-[28px]! rb:py-[16px]!"
|
||||
>
|
||||
{health?.health_score && health?.health_score > 0
|
||||
? <>
|
||||
<div className="rb:flex rb:justify-center rb:items-center">
|
||||
<Progress
|
||||
size={250}
|
||||
type="circle"
|
||||
strokeColor={{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068',
|
||||
}}
|
||||
percent={health.health_score}
|
||||
format={(percent) => `${percent}(${health.level})`}
|
||||
/>
|
||||
</div>
|
||||
<Row gutter={59}>
|
||||
<Col span={12}>
|
||||
<div className="rb:flex rb:justify-center rb:items-center">
|
||||
<ReactEcharts
|
||||
option={{
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['65%', '80%'],
|
||||
center: ['50%', '50%'],
|
||||
startAngle: 90,
|
||||
data: [
|
||||
{
|
||||
value: health.health_score,
|
||||
name: health.level,
|
||||
itemStyle: {
|
||||
color: '#155EEF',
|
||||
borderRadius: [10, 10, 10, 10]
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 100 - health.health_score,
|
||||
name: '',
|
||||
itemStyle: {
|
||||
color: '#DFE4ED',
|
||||
borderRadius: [10, 10, 10, 10]
|
||||
}
|
||||
}
|
||||
],
|
||||
label: {
|
||||
show: true,
|
||||
position: 'center',
|
||||
formatter: '{score|' + health.health_score + '}\n{level|' + health.level + '}',
|
||||
rich: {
|
||||
score: {
|
||||
fontSize: 36,
|
||||
fontWeight: 'bold',
|
||||
color: '#212332',
|
||||
lineHeight: 36
|
||||
},
|
||||
level: {
|
||||
fontSize: 14,
|
||||
color: '#5B6167',
|
||||
lineHeight: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
labelLine: { show: false },
|
||||
emphasis: { disabled: true },
|
||||
itemStyle: {
|
||||
borderRadius: 10
|
||||
}
|
||||
}]
|
||||
}}
|
||||
style={{ height: '200px', width: '200px' }}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
{health.dimensions && <div className="rb:space-y-7">
|
||||
<div>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:text-[#5B6167]">
|
||||
{t('statementDetail.positivity_rate')}
|
||||
<div className="rb:text-[12px] rb:text-[#155EEF] rb:font-medium">{health.dimensions.positivity_rate.score}%</div>
|
||||
</div>
|
||||
<Progress strokeColor="#155EEF" percent={health.dimensions.positivity_rate.score} showInfo={false} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:text-[#5B6167]">
|
||||
{t('statementDetail.stability')}
|
||||
<div className="rb:text-[12px] rb:text-[#155EEF] rb:font-medium">{health.dimensions.stability.score}%</div>
|
||||
</div>
|
||||
<Progress strokeColor="#155EEF" percent={health.dimensions.stability.score} showInfo={false} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:text-[#5B6167]">
|
||||
{t('statementDetail.resilience')}
|
||||
<div className="rb:text-[12px] rb:text-[#155EEF] rb:font-medium">{health.dimensions.resilience.score}%</div>
|
||||
</div>
|
||||
<Progress strokeColor="#155EEF" percent={health.dimensions.resilience.score} showInfo={false} />
|
||||
</div>
|
||||
</div>}
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{health.dimensions && <>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:mt-6">
|
||||
<div className="rb:w-40 rb:mr-3">{t('statementDetail.positivity_rate')}</div>
|
||||
<Progress className="rb:w-[calc(100%-180px)]" percent={health.dimensions.positivity_rate.score} />
|
||||
</div>
|
||||
<div className="rb:flex rb:items-center rb:gap-3 rb:mt-3">
|
||||
<div className="rb:w-40 rb:mr-3">{t('statementDetail.stability')}</div>
|
||||
<Progress className="rb:w-[calc(100%-180px)]" percent={health.dimensions.stability.score} />
|
||||
</div>
|
||||
<div className="rb:flex rb:items-center rb:gap-3 rb:mt-3">
|
||||
<div className="rb:w-40 rb:mr-3">{t('statementDetail.resilience')}</div>
|
||||
<Progress className="rb:w-[calc(100%-180px)]" percent={health.dimensions.resilience.score} />
|
||||
</div>
|
||||
</>}
|
||||
</>
|
||||
: <Empty size={88} className="rb:h-full" />
|
||||
}
|
||||
|
||||
119
web/src/views/UserMemoryDetail/components/InteractionBar.tsx
Normal file
119
web/src/views/UserMemoryDetail/components/InteractionBar.tsx
Normal file
@@ -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<InteractionBarProps> = ({ chartData, loading }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const series = [{
|
||||
name: 'Interaction Count',
|
||||
type: 'bar',
|
||||
data: chartData.map(item => item.count)
|
||||
}]
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>{t('userMemory.interaction')}</div>
|
||||
{loading
|
||||
? <Loading size={249} />
|
||||
: !chartData || chartData.length === 0
|
||||
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
|
||||
: <ReactEcharts
|
||||
option={{
|
||||
color: Colors,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
extraCssText: 'box-shadow: 0px 2px 6px 0px rgba(33,35,50,0.16); border-radius: 8px;',
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
crossStyle: {
|
||||
color: '#5F6266',
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#5F6266',
|
||||
}
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
top: 16,
|
||||
left: 30,
|
||||
right: 36,
|
||||
bottom: 48,
|
||||
// containLabel: false
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: chartData.map(item => 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
|
||||
@@ -63,6 +63,7 @@ const InterestDistribution: FC = () => {
|
||||
return (
|
||||
<RbCard
|
||||
title={t('userMemory.interestDistribution')}
|
||||
headerClassName="rb:min-h-[46px]!"
|
||||
>
|
||||
{loading
|
||||
? <Loading size={249} />
|
||||
|
||||
@@ -50,6 +50,7 @@ const MemoryInsight = forwardRef<MemoryInsightRef>((_props, ref) => {
|
||||
<RbCard
|
||||
title={t('userMemory.memoryInsight')}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:min-h-[46px]!"
|
||||
>
|
||||
{loading
|
||||
? <Skeleton />
|
||||
|
||||
@@ -98,6 +98,7 @@ const NodeStatistics: FC = () => {
|
||||
<RbCard
|
||||
title={<>{t('userMemory.nodeStatistics')} <span className="rb:text-[#5B6167] rb:font-normal!">({t('userMemory.total')}: {total})</span></>}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:min-h-[46px]!"
|
||||
>
|
||||
{loading
|
||||
? <Skeleton active />
|
||||
|
||||
@@ -14,8 +14,8 @@ interface PreferenceItem {
|
||||
confidence_score: number;
|
||||
supporting_evidence: string[];
|
||||
context_details: string;
|
||||
created_at: number | string; // TODO
|
||||
updated_at: number | string; // TODO
|
||||
created_at: number | string;
|
||||
updated_at: number | string;
|
||||
conversation_references: string[];
|
||||
category: string;
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ const RelationshipNetwork:FC = () => {
|
||||
<RbCard
|
||||
title={t('userMemory.relationshipNetwork')}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:min-h-[46px]!"
|
||||
// extra={
|
||||
// <div
|
||||
// onClick={handleFullScreen}
|
||||
@@ -247,6 +248,7 @@ const RelationshipNetwork:FC = () => {
|
||||
<RbCard
|
||||
title={t('userMemory.memoryDetails')}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:min-h-[46px]!"
|
||||
bodyClassName='rb:p-0!'
|
||||
extra={selectedNode && <Button type="text" onClick={handleViewAll}>
|
||||
<div
|
||||
@@ -261,7 +263,7 @@ const RelationshipNetwork:FC = () => {
|
||||
url={detailEmpty}
|
||||
subTitle={t('userMemory.memoryDetailEmptyDesc')}
|
||||
className="rb:h-full rb:mx-10 rb:text-center"
|
||||
size={90}
|
||||
size={[197.81, 150]}
|
||||
/>
|
||||
: <>
|
||||
<div className="rb:bg-[#F6F8FC] rb:border-t rb:border-b rb:border-[#DFE4ED] rb:font-medium rb:py-2 rb:px-4 rb:h-10">{selectedNode.name}</div>
|
||||
|
||||
@@ -41,18 +41,24 @@ const Suggestions: FC = () => {
|
||||
<RbCard
|
||||
title={t('statementDetail.suggestions')}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:text-[18px]! rb:leading-[24px]"
|
||||
headerClassName="rb:leading-[24px] rb:bg-[#F6F8FC]! rb:min-h-[46px]! rb:border-b! rb:border-b-[#DFE4ED]!"
|
||||
bodyClassName="rb:px-[16px]! rb:pt-[20px]! rb:pb-[24px]!"
|
||||
>
|
||||
{suggestions?.suggestions && suggestions?.suggestions.length > 0
|
||||
? <>
|
||||
<RbAlert className="rb:mb-3">{suggestions.health_summary}</RbAlert>
|
||||
{suggestions.suggestions.map((item, index) => (
|
||||
<div key={index} className="rb:mb-3">
|
||||
<div className="rb:font-medium">{index + 1}. {item.title}</div>
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-1 rb:mb-2">{item.content}</div>
|
||||
{item.actionable_steps.map((vo, idx) => <div key={idx} className="rb:ml-6 rb:text-[12px] rb:text-[#5B6167] rb:mt-1">- {vo}</div>)}
|
||||
</div>
|
||||
))}
|
||||
<div className="rb:space-y-8">
|
||||
{suggestions.suggestions.map((item, index) => (
|
||||
<div key={index}>
|
||||
<div className="rb:font-medium">{index + 1}. {item.title}</div>
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-2 rb:mb-2 rb:leading-5">{item.content}</div>
|
||||
|
||||
<ul className="rb:list-disc rb:ml-4 rb:text-[12px] rb:text-[#5B6167] rb:leading-5">
|
||||
{item.actionable_steps.map((vo, idx) => <li key={idx}>{vo}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
: <Empty size={88} className="rb:h-full" />
|
||||
}
|
||||
|
||||
@@ -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 = () => {
|
||||
<RbCard
|
||||
title={t('statementDetail.wordCloud')}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:text-[18px]! rb:leading-[24px]"
|
||||
height="100%"
|
||||
headerClassName="rb:leading-[24px] rb:bg-[#F6F8FC]! rb:min-h-[46px]! rb:border-b! rb:border-b-[#DFE4ED]!"
|
||||
bodyClassName="rb:px-[28px]! rb:py-[16px]!"
|
||||
>
|
||||
{wordCloud?.total_count && wordCloud?.total_count > 0
|
||||
? <div className="rb:flex rb:h-100">
|
||||
<ReactEcharts ref={chartRef} option={radarOption} style={{ width: '50%', height: '100%' }} />
|
||||
<div className="rb:w-[50%] rb:pl-4 rb:flex rb:flex-col rb:justify-center">
|
||||
<div className="rb:text-[18px] rb:font-medium rb:mb-4">样本数:{wordCloud.total_count}</div>
|
||||
<div className="rb:space-y-3">
|
||||
? <Row gutter={50}>
|
||||
<Col span={12}>
|
||||
<ReactEcharts ref={chartRef} option={radarOption} style={{ width: '100%', height: 'calc(100% - 100px)' }} />
|
||||
<div className="rb:mb-4 rb:text-center rb:bg-[#F5F7FC] rb:rounded-lg rb:p-2.5 rb:mt-4">
|
||||
<span className="rb:text-[#155EEF] rb:text-[28px] rb:font-bold rb:leading-8">{wordCloud.total_count}</span><br />
|
||||
<span className="rb:text-[#5B6167] rb:leading-5">{t('statementDetail.totalCount')}</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="rb:space-y-5">
|
||||
{wordCloud.tags.map(item => (
|
||||
<div key={item.emotion_type}>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:font-medium">
|
||||
{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:flex rb:items-center rb:justify-between">
|
||||
<div>
|
||||
<span className="rb:font-medium">{t(`statementDetail.${item.emotion_type}`)}</span>
|
||||
<span className="rb:font-regular rb:text-[#5B6167]"> ( {item.count} {t('statementDetail.pieces')} )</span>
|
||||
</div>
|
||||
<div className="rb:text-[12px] rb:text-[#155EEF] rb:font-medium">{item.percentage.toFixed(1)}%</div>
|
||||
</div>
|
||||
<Progress size="small" percent={item.percentage} />
|
||||
<Progress strokeColor="#155EEF" percent={item.percentage} showInfo={false} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
: <Empty size={88} />
|
||||
}
|
||||
</RbCard>
|
||||
|
||||
209
web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx
Normal file
209
web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx
Normal file
@@ -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<boolean>(false)
|
||||
const [data, setData] = useState<Conversation[]>([])
|
||||
const [messagesLoading, setMessagesLoading] = useState<boolean>(false)
|
||||
const [messages, setMessages] = useState<ChatItem[]>([])
|
||||
const [detailLoading, setDetailLoading] = useState<boolean>(false)
|
||||
const [detail, setDetail] = useState<Detail | null>(null)
|
||||
const [selected, setSelected] = useState<Conversation | null>(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 (
|
||||
<div className="rb:h-[calc(100vh-64px)]! rb:w-full rb:-mx-4 rb:-my-3">
|
||||
{loading
|
||||
? <PageLoading />
|
||||
: data.length === 0
|
||||
? <Empty />
|
||||
:(
|
||||
<Row gutter={16} className="rb:h-full">
|
||||
<Col span={5}>
|
||||
<div className="rb:h-full! rb:border-r rb:border-[#EAECEE] rb:py-3 rb:px-4">
|
||||
{data.map(item => (
|
||||
<div key={item.id} className="rb:mb-3">
|
||||
<div className={clsx("rb:p-[8px_13px] rb:rounded-lg rb:leading-5 rb:cursor-pointer rb:hover:bg-[#F0F3F8]", {
|
||||
'rb:bg-[#FFFFFF] rb:shadow-[0px_2px_4px_0px_rgba(0,0,0,0.15)] rb:font-medium rb:hover:bg-[#FFFFFF]!': item.id === selected?.id,
|
||||
})}
|
||||
onClick={() => getDetail(item.id)}
|
||||
>
|
||||
{item.title}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Col>
|
||||
{selected && <>
|
||||
<Col span={19}>
|
||||
<div className="rb:text-[18px] rb:font-medium rb:leading-6 rb:mt-4">{selected.title}</div>
|
||||
<div className="rb:mt-1 rb:text-[#5B6167] rb:leading-5">{timeRange}</div>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={16}>
|
||||
<RbCard
|
||||
title={t('workingDetail.conversationStream')}
|
||||
extra={<Button className="rb:h-6!" onClick={() => getDetail(selected.id)}>{t('workingDetail.refresh')}</Button>}
|
||||
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
|
||||
? <Skeleton active />
|
||||
: messages.length === 0
|
||||
? <Empty />
|
||||
: (
|
||||
<ChatContent
|
||||
classNames="rb:h-[calc(100vh-244px)]"
|
||||
data={messages}
|
||||
streamLoading={false}
|
||||
labelFormat={(item) => formatDateTime(item.created_at)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</RbCard>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<RbCard className="rb:mt-4!" bodyClassName="rb:h-[calc(100vh-166px)] rb:overflow-y-auto">
|
||||
{detailLoading
|
||||
? <Skeleton active />
|
||||
: detail
|
||||
? <>
|
||||
<>
|
||||
<div className="rb:text-[#369F21] rb:font-medium rb:text-[18px] rb:leading-4 rb:mb-3">{t('workingDetail.successfulTitle')}</div>
|
||||
|
||||
{detail.takeaways.length > 0
|
||||
? (
|
||||
<ul className="rb:text-[#5B6167] rb:leading-5.5 rb:list-disc rb:ml-4">
|
||||
{detail.takeaways.map(vo => <li>{vo}</li>)}
|
||||
</ul>
|
||||
)
|
||||
: <Empty size={88} />
|
||||
}
|
||||
</>
|
||||
|
||||
<>
|
||||
<Divider />
|
||||
<div className="rb:text-[#FF5D34] rb:font-medium rb:text-[18px] rb:leading-4 rb:mb-3">{t('workingDetail.question')}</div>
|
||||
|
||||
{detail.question.length > 0
|
||||
? (
|
||||
<ul className="rb:text-[#5B6167] rb:leading-5.5 rb:list-disc rb:ml-4">
|
||||
{detail.question.map(vo => <li>{vo}</li>)}
|
||||
</ul>
|
||||
)
|
||||
: <Empty size={88} />
|
||||
}
|
||||
</>
|
||||
|
||||
<>
|
||||
<Divider />
|
||||
<div className="rb:text-[#369F21] rb:font-medium rb:text-[18px] rb:leading-4 rb:mb-3">{t('workingDetail.summary')}</div>
|
||||
{detail.summary
|
||||
? <RbAlert className="rb:text-[#212332]! rb:text-[14px]! rb:leading-5.5! rb:p-3!">{detail.summary}</RbAlert>
|
||||
: <Empty size={88} />
|
||||
}
|
||||
</>
|
||||
</>
|
||||
: <Empty />
|
||||
}
|
||||
</RbCard>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</>}
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default WorkingDetail
|
||||
@@ -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' && <ShortTermDetail />}
|
||||
{type === 'PERCEPTUAL_MEMORY' && <PerceptualDetail />} {/** TODO */}
|
||||
{type === 'EPISODIC_MEMORY' && <EpisodicDetail />}
|
||||
{/* {type === 'WORKING_MEMORY' && <WorkingDetail />} */} {/** TODO */}
|
||||
{type === 'WORKING_MEMORY' && <WorkingDetail />} {/** TODO */}
|
||||
{type === 'EXPLICIT_MEMORY' && <ExplicitDetail />} {/** TODO */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user