feat(web): user memory feature optimize

This commit is contained in:
zhaoying
2026-01-22 12:26:37 +08:00
parent 783593a79d
commit da75abb223
9 changed files with 75 additions and 34 deletions

View File

@@ -2064,6 +2064,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
stability: 'Stability', stability: 'Stability',
resilience: 'Resilience', resilience: 'Resilience',
suggestions: 'Personalized Suggestions', suggestions: 'Personalized Suggestions',
suggestionLoading: 'Your personalized suggestions are being generated',
}, },
reflectionEngine: { reflectionEngine: {
reflectionEngineConfig: 'Reflection Engine Configuration', reflectionEngineConfig: 'Reflection Engine Configuration',

View File

@@ -2158,6 +2158,7 @@ export const zh = {
stability: '稳定性', stability: '稳定性',
resilience: '恢复力', resilience: '恢复力',
suggestions: '个性化建议', suggestions: '个性化建议',
suggestionLoading: '您的个性化建议正在生成中',
}, },
reflectionEngine: { reflectionEngine: {
reflectionEngineConfig: '反思引擎配置', reflectionEngineConfig: '反思引擎配置',

View File

@@ -35,13 +35,13 @@ const PageHeader: FC<ConfigHeaderProps> = ({
{operation} {operation}
</div> </div>
<Space size={12}> <div className="rb:flex rb:items-center rb:gap-3">
<Button type="primary" ghost className="rb:group rb:h-6! rb:px-2!" onClick={goBack}> <Button type="primary" ghost className="rb:h-6! rb:px-2! rb:leading-5.5!" onClick={goBack}>
<img src={logoutIcon} className="rb:w-4 rb:h-4" /> <img src={logoutIcon} className="rb:w-4 rb:h-4" />
{t('common.return')} {t('common.return')}
</Button> </Button>
{extra} {extra}
</Space> </div>
</Header> </Header>
); );
}; };

View File

@@ -266,7 +266,7 @@ const RelationshipNetwork:FC = () => {
size={[197.81, 150]} 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> {selectedNode.name && <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>}
<div className="rb:p-4"> <div className="rb:p-4">
<> <>
<div className="rb:font-medium rb:leading-5">{t('userMemory.memoryContent')}</div> <div className="rb:font-medium rb:leading-5">{t('userMemory.memoryContent')}</div>
@@ -297,7 +297,8 @@ const RelationshipNetwork:FC = () => {
{selectedNode.label === 'Statement' && <> {selectedNode.label === 'Statement' && <>
{(['emotion_keywords', 'emotion_type', 'emotion_subject', 'importance_score'] as const).map(key => { {(['emotion_keywords', 'emotion_type', 'emotion_subject', 'importance_score'] as const).map(key => {
const statementProps = selectedNode.properties as StatementNodeProperties; const statementProps = selectedNode.properties as StatementNodeProperties;
if ((key === 'emotion_keywords' && statementProps[key]?.length > 0) || statementProps[key]) { if ((key === 'emotion_keywords' && statementProps[key]?.length > 0) || typeof statementProps[key] === 'string') {
console.log('statementProps[key]', statementProps[key])
return ( return (
<div className="rb:mt-4" key={key}> <div className="rb:mt-4" key={key}>
{t(`userMemory.Statement_${key}`)} {t(`userMemory.Statement_${key}`)}
@@ -321,7 +322,10 @@ const RelationshipNetwork:FC = () => {
<div className="rb:mt-4" key={key}> <div className="rb:mt-4" key={key}>
{t(`userMemory.ExtractedEntity_${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]"> <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]} {Array.isArray(entityProps[key]) && entityProps[key].length > 0
? entityProps[key].map((vo, index) => <div key={index}>- {vo}</div>)
: entityProps[key]
}
</div> </div>
</div> </div>
) )

View File

@@ -21,6 +21,7 @@ interface Suggestions {
const Suggestions = forwardRef<{ handleRefresh: () => void; }>((_props, ref) => { const Suggestions = forwardRef<{ handleRefresh: () => void; }>((_props, ref) => {
const { t } = useTranslation() const { t } = useTranslation()
const { id } = useParams() const { id } = useParams()
const [loading, setLoading] = useState(false)
const [suggestions, setSuggestions] = useState<Suggestions | null>(null) const [suggestions, setSuggestions] = useState<Suggestions | null>(null)
useEffect(() => { useEffect(() => {
@@ -31,10 +32,14 @@ const Suggestions = forwardRef<{ handleRefresh: () => void; }>((_props, ref) =>
if (!id) { if (!id) {
return return
} }
setLoading(true)
getEmotionSuggestions(id) getEmotionSuggestions(id)
.then((res) => { .then((res) => {
setSuggestions(res as Suggestions) setSuggestions(res as Suggestions)
}) })
.finally(() => {
setLoading(false)
})
} }
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
@@ -63,7 +68,7 @@ const Suggestions = forwardRef<{ handleRefresh: () => void; }>((_props, ref) =>
))} ))}
</div> </div>
</> </>
: <Empty size={88} className="rb:h-full" /> : <Empty size={88} subTitle={t(loading ? 'statementDetail.suggestionLoading' : 'empty.tableEmpty')} className="rb:h-full" />
} }
</RbCard> </RbCard>
) )

View File

@@ -20,14 +20,22 @@ const ImplicitDetail = forwardRef<{ handleRefresh: () => void; }>((_props, ref)
const habitsRef = useRef<{ handleRefresh: () => void; }>(null) const habitsRef = useRef<{ handleRefresh: () => void; }>(null)
const handleRefresh = () => { const handleRefresh = () => {
if (!id) return if (!id) {
generateProfile(id) return Promise.resolve()
.then(() => { }
preferencesRef.current?.handleRefresh() return new Promise((resolve, reject) => {
portraitRef.current?.handleRefresh() generateProfile(id)
interestAreasRef.current?.handleRefresh() .then(() => {
habitsRef.current?.handleRefresh() preferencesRef.current?.handleRefresh()
}) portraitRef.current?.handleRefresh()
interestAreasRef.current?.handleRefresh()
habitsRef.current?.handleRefresh()
resolve(true)
})
.catch((error) => {
reject(error)
})
})
} }
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleRefresh handleRefresh

View File

@@ -13,11 +13,20 @@ const StatementDetail = forwardRef((_props, ref) => {
const { id } = useParams() const { id } = useParams()
const suggestionsRef = useRef<{ handleRefresh: () => void; }>(null) const suggestionsRef = useRef<{ handleRefresh: () => void; }>(null)
const handleRefresh = () => { const handleRefresh = () => {
if (!id) return if (!id) {
generateSuggestions(id) return Promise.resolve()
.then(() => { }
suggestionsRef.current?.handleRefresh()
}) return new Promise((resolve, reject) => {
generateSuggestions(id)
.then(() => {
suggestionsRef.current?.handleRefresh()
resolve(true)
})
.catch((error) => {
reject(error)
})
})
} }
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleRefresh handleRefresh

View File

@@ -2,7 +2,7 @@ import { type FC, useEffect, useState, useMemo } from 'react'
import clsx from 'clsx' import clsx from 'clsx'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { Row, Col, Select, Form, Space, Skeleton, Input, Button, Divider } from 'antd' import { Row, Col, Skeleton, Button, Divider, Tooltip } from 'antd'
import RbCard from '@/components/RbCard/Card' import RbCard from '@/components/RbCard/Card'
import { import {
getConversations, getConversations,
@@ -10,7 +10,6 @@ import {
getConversationDetail, getConversationDetail,
} from '@/api/memory' } from '@/api/memory'
import { formatDateTime } from '@/utils/format' import { formatDateTime } from '@/utils/format'
import Tag from '@/components/Tag'
import RbAlert from '@/components/RbAlert' import RbAlert from '@/components/RbAlert'
import Empty from '@/components/Empty' import Empty from '@/components/Empty'
import ChatContent from '@/components/Chat/ChatContent' import ChatContent from '@/components/Chat/ChatContent'
@@ -33,7 +32,6 @@ interface Detail {
const WorkingDetail: FC = () => { const WorkingDetail: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { id } = useParams() const { id } = useParams()
const [form] = Form.useForm()
const [loading, setLoading] = useState<boolean>(false) const [loading, setLoading] = useState<boolean>(false)
const [data, setData] = useState<Conversation[]>([]) const [data, setData] = useState<Conversation[]>([])
const [messagesLoading, setMessagesLoading] = useState<boolean>(false) const [messagesLoading, setMessagesLoading] = useState<boolean>(false)
@@ -110,13 +108,15 @@ const WorkingDetail: FC = () => {
<div className="rb:h-full! rb:border-r rb:border-[#EAECEE] rb:py-3 rb:px-4"> <div className="rb:h-full! rb:border-r rb:border-[#EAECEE] rb:py-3 rb:px-4">
{data.map(item => ( {data.map(item => (
<div key={item.id} className="rb:mb-3"> <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]", { <Tooltip title={item.title}>
'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, <div className={clsx("rb:p-[8px_13px] rb:rounded-lg rb:leading-5 rb:cursor-pointer rb:hover:bg-[#F0F3F8] rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap", {
})} '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={() => setSelected(item)} })}
> onClick={() => setSelected(item)}
{item.title} >
</div> {item.title}
</div>
</Tooltip>
</div> </div>
))} ))}
</div> </div>

View File

@@ -2,6 +2,7 @@ import { type FC, useEffect, useState, useMemo, useRef } from 'react'
import { useParams, useNavigate } from 'react-router-dom' import { useParams, useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Dropdown, Button } from 'antd' import { Dropdown, Button } from 'antd'
import { LoadingOutlined } from '@ant-design/icons';
import PageHeader from '../components/PageHeader' import PageHeader from '../components/PageHeader'
import StatementDetail from './StatementDetail' import StatementDetail from './StatementDetail'
@@ -46,18 +47,30 @@ const Detail: FC = () => {
const onClick = ({ key }: { key: string }) => { const onClick = ({ key }: { key: string }) => {
navigate(`/user-memory/detail/${id}/${key}`, { replace: true }) navigate(`/user-memory/detail/${id}/${key}`, { replace: true })
} }
const [loading, setLoading] = useState(false)
const handleRefresh = () => { const handleRefresh = () => {
setLoading(true)
let response: any = null
switch(type) { switch(type) {
case 'FORGET_MEMORY': case 'FORGET_MEMORY':
forgetDetailRef.current?.handleRefresh() forgetDetailRef.current?.handleRefresh()
break; break;
case 'EMOTIONAL_MEMORY': case 'EMOTIONAL_MEMORY':
statementDetailRef.current?.handleRefresh() response = statementDetailRef.current?.handleRefresh()
break break
case 'IMPLICIT_MEMORY': case 'IMPLICIT_MEMORY':
implicitDetailRef.current?.handleRefresh() response = implicitDetailRef.current?.handleRefresh()
break break
} }
if (response instanceof Promise) {
response.finally(() => {
setLoading(false)
})
} else {
setLoading(false)
}
} }
if (type === 'GRAPH') { if (type === 'GRAPH') {
@@ -80,8 +93,8 @@ const Detail: FC = () => {
</Dropdown> </Dropdown>
} }
extra={['FORGET_MEMORY', 'EMOTIONAL_MEMORY', 'IMPLICIT_MEMORY'].includes(type as string) && extra={['FORGET_MEMORY', 'EMOTIONAL_MEMORY', 'IMPLICIT_MEMORY'].includes(type as string) &&
<Button type="primary" ghost className="rb:group rb:h-6! rb:px-2!" onClick={handleRefresh}> <Button type="primary" ghost size="small" className="rb:h-6! rb:px-2! rb:leading-5.5!" loading={loading} onClick={handleRefresh}>
<img src={refreshIcon} className="rb:w-4 rb:h-4" /> {!loading && <img src={refreshIcon} className="rb:w-4 rb:h-4" /> }
{t('common.refresh')} {t('common.refresh')}
</Button>} </Button>}
/> />