fix(web): user memory

This commit is contained in:
zhaoying
2026-01-12 18:44:09 +08:00
parent 18d4a5e865
commit ea944d0ee2
10 changed files with 241 additions and 18 deletions

View File

@@ -2246,6 +2246,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
context_details: 'Preference Details',
supporting_evidence: 'Preference Source',
specific_examples: 'Source',
wordEmpty: 'Click on a node in the left chart to view preference details'
},
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.',

View File

@@ -2345,6 +2345,7 @@ export const zh = {
context_details: '偏好详情',
supporting_evidence: '偏好来源',
specific_examples: '来源',
wordEmpty: '点击左侧图表中的节点查看偏好详情'
},
shortTermDetail: {
title: '短期记忆是AI系统的"工作台",连接即时对话与长期知识库。通过实时捕获、深度检索、智能提取和筛选转化,将临时的非结构化信息转化为有价值的长期知识。',

198
web/src/utils/event.md Normal file

File diff suppressed because one or more lines are too long

View File

@@ -23,10 +23,20 @@ export function parseSSEToJSON(sseString: string) {
currentEvent.event = line.substring(6).trim()
} else if (line.startsWith('data:')) {
const dataStr = line.substring(5).trim()
try {
currentEvent.data = JSON.parse(dataStr.replace(/"/g, '"'))
} catch {
currentEvent.data = dataStr
if (dataStr) {
try {
// 尝试解析为 JSON
currentEvent.data = JSON.parse(dataStr)
} catch {
// JSON 解析失败时,检查是否是被转义的 JSON 字符串
try {
const unescaped = dataStr.replace(/"/g, '"').replace(/&/g, '&')
currentEvent.data = JSON.parse(unescaped)
} catch {
// 如果仍然失败,保存为原始字符串
currentEvent.data = dataStr
}
}
}
}
}

View File

@@ -72,7 +72,7 @@ const ExplicitDetailModal = forwardRef<ExplicitDetailModalRef>((_props, ref) =>
onCancel={handleClose}
>
{loading ? <Skeleton active />
: <Descriptions column={data.memory_type === 'semantic' ? 1 : 2} classNames={{ label: 'rb:w-20' }}>
: <Descriptions column={1} classNames={{ label: 'rb:w-20' }}>
{data.emotion && <Descriptions.Item label={t('explicitDetail.emotion')}>
<div className="rb:flex rb:items-center rb:gap-2">
<div className="rb:w-3 rb:h-3 rb:rounded-full" style={{ backgroundColor: getEmotionColor(data.emotion) }}></div>
@@ -88,7 +88,7 @@ const ExplicitDetailModal = forwardRef<ExplicitDetailModalRef>((_props, ref) =>
{data.created_at && <Descriptions.Item label={t('explicitDetail.created_at')}>
{formatDateTime(data.created_at)}
</Descriptions.Item>}
{data.content && <Descriptions.Item span="filled" label={t('explicitDetail.content')}>
{data.content && <Descriptions.Item label={t('explicitDetail.content')}>
{data.content}
</Descriptions.Item>}
</Descriptions>

View File

@@ -8,6 +8,7 @@ import 'echarts-wordcloud'
import Empty from '@/components/Empty'
import RbCard from '@/components/RbCard/Card'
import { getImplicitPreferences } from '@/api/memory'
import detailEmpty from '@/assets/images/userMemory/detail_empty.png'
interface PreferenceItem {
tag_name: string;
@@ -164,7 +165,12 @@ const Preferences: FC = () => {
bodyClassName='rb:p-3! rb:h-[326px]'
>
{selectedWord === null
? <Empty size={88} className="rb:h-full!" />
? <Empty
url={detailEmpty}
subTitle={t('implicitDetail.wordEmpty')}
className="rb:h-full rb:mx-10 rb:text-center"
size={[197.81, 150]}
/>
: <>
<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>

View File

@@ -39,6 +39,7 @@ const TAG_COLORS: Record<string, "processing" | "success" | "warning" | "error"
learning: "warning",
decision: "warning",
important_event: "error",
default: 'default'
}
const BG_COLORS: Record<string, string> = {
conversation: "rb:bg-[#155EEF]",
@@ -46,10 +47,12 @@ const BG_COLORS: Record<string, string> = {
learning: "rb:bg-[#FF5D34]",
decision: "rb:bg-[#FF5D34]",
important_event: "rb:bg-[#5B6167]",
default: 'rb:bg-[#F0F3F8] rb:text-[#5B6167]!'
}
// Map display types to internal keys
const getTypeKey = (type: string): string => {
if (!type) return 'default'
const typeMap: Record<string, string> = {
'Learning': 'learning',
'Project/Work': 'project_work',
@@ -176,6 +179,7 @@ const EpisodicDetail: FC = () => {
<RbCard
title={<>{t('episodicDetail.curResult')}<span className="rb:text-[#5B6167] rb:font-regular!"> ({data.total || 0}{t('episodicDetail.unix')})</span></>}
headerType="borderless"
bodyClassName="rb:h-[calc(100vh-349px)] rb:overflow-y-auto"
>
{loading
? <Skeleton active />
@@ -192,9 +196,12 @@ const EpisodicDetail: FC = () => {
})}
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={clsx("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 rb:w-[calc(100%-36px)]">
<div className="rb:flex rb:items-center rb:justify-between">
<div className="rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap rb:flex-1">{vo.title}</div>
{vo.type && <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>
@@ -202,13 +209,13 @@ const EpisodicDetail: FC = () => {
</Space>
)
}
</RbCard>
</Col>
<Col span={16}>
<RbCard
title={selected?.title}
headerType="borderless"
bodyClassName="rb:h-[calc(100vh-349px)] rb:overflow-y-auto"
>
{detailLoading
? <Skeleton active />

View File

@@ -1,7 +1,7 @@
import { type FC, useEffect, useState, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom'
import { List, Skeleton, Row, Col } from 'antd'
import { Skeleton, Row, Col } from 'antd'
import RbCard from '@/components/RbCard/Card'
import {
getExplicitMemory,
@@ -65,7 +65,7 @@ const ExplicitDetail: FC = () => {
{loading ?
<Skeleton active />
: data.episodic_memories?.length > 0 ? (
<Row gutter={16}>
<Row gutter={[16, 16]}>
{data.episodic_memories.map(item => (
<Col key={item.id} span={6}>
<RbCard
@@ -85,7 +85,7 @@ const ExplicitDetail: FC = () => {
{loading ?
<Skeleton active />
: data.semantic_memories?.length > 0 ? (
<Row gutter={16}>
<Row gutter={[16, 16]}>
{data.semantic_memories.map(item => (
<Col key={item.id} span={6}>
<RbCard

View File

@@ -113,7 +113,7 @@ const WorkingDetail: FC = () => {
<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)}
onClick={() => setSelected(item)}
>
{item.title}
</div>

View File

@@ -62,10 +62,10 @@ const Detail: FC = () => {
{type === 'FORGETTING_MANAGEMENT' && <ForgetDetail />}
{type === 'IMPLICIT_MEMORY' && <ImplicitDetail />}
{type === 'SHORT_TERM_MEMORY' && <ShortTermDetail />}
{type === 'PERCEPTUAL_MEMORY' && <PerceptualDetail />} {/** TODO */}
{type === 'PERCEPTUAL_MEMORY' && <PerceptualDetail />}
{type === 'EPISODIC_MEMORY' && <EpisodicDetail />}
{type === 'WORKING_MEMORY' && <WorkingDetail />} {/** TODO */}
{type === 'EXPLICIT_MEMORY' && <ExplicitDetail />} {/** TODO */}
{type === 'WORKING_MEMORY' && <WorkingDetail />}
{type === 'EXPLICIT_MEMORY' && <ExplicitDetail />}
</div>
</div>
)