feat(web): user memory & detail ui upgrade
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 17:53:44
|
* @Date: 2026-02-03 17:53:44
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-03 17:54:33
|
* @Last Modified time: 2026-02-10 17:52:35
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* User Memory Page
|
* User Memory Page
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
import { useEffect, useState, useMemo } from 'react';
|
import { useEffect, useState, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Row, Col, List, Skeleton } from 'antd';
|
import { Row, Col, Skeleton, Form, Flex } from 'antd';
|
||||||
|
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import type { Data } from './types'
|
import type { Data } from './types'
|
||||||
@@ -27,7 +27,9 @@ export default function UserMemory() {
|
|||||||
const { storageType } = useUser()
|
const { storageType } = useUser()
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [data, setData] = useState<Data[]>([]);
|
const [data, setData] = useState<Data[]>([]);
|
||||||
const [search, setSearch] = useState<string | undefined>(undefined);
|
|
||||||
|
const [form] = Form.useForm()
|
||||||
|
const search = Form.useWatch(['search'], form)
|
||||||
|
|
||||||
/** Fetch user memory list */
|
/** Fetch user memory list */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -76,26 +78,27 @@ export default function UserMemory() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Row gutter={16} className="rb:mb-4">
|
<Form form={form}>
|
||||||
<Col span={8}>
|
<Row gutter={16} className="rb:mb-4">
|
||||||
<SearchInput
|
<Col span={8}>
|
||||||
placeholder={t('userMemory.searchPlaceholder')}
|
<Form.Item name="search" noStyle>
|
||||||
onSearch={(value) => setSearch(value)}
|
<SearchInput
|
||||||
style={{ width: '100%' }}
|
placeholder={t('userMemory.searchPlaceholder')}
|
||||||
/>
|
className="rb:w-full!"
|
||||||
</Col>
|
/>
|
||||||
</Row>
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
{loading ?
|
{loading ?
|
||||||
<Skeleton active />
|
<Skeleton active />
|
||||||
: filterData.length > 0 ? (
|
: filterData.length > 0 ? (
|
||||||
<List
|
<Row gutter={[16, 16]}>
|
||||||
grid={{ gutter: 16, column: 3 }}
|
{filterData.map((item, index) => {
|
||||||
dataSource={filterData}
|
|
||||||
renderItem={(item, index) => {
|
|
||||||
const { end_user, memory_num, memory_config } = item as Data;
|
const { end_user, memory_num, memory_config } = item as Data;
|
||||||
const name = end_user?.other_name && end_user?.other_name !== '' ? end_user?.other_name : end_user?.id
|
const name = end_user?.other_name && end_user?.other_name !== '' ? end_user?.other_name : end_user?.id
|
||||||
return (
|
return (
|
||||||
<List.Item key={index}>
|
<Col key={index} span={8}>
|
||||||
<RbCard
|
<RbCard
|
||||||
avatar={<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] rb:mr-2">{name[0]}</div>}
|
avatar={<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] rb:mr-2">{name[0]}</div>}
|
||||||
title={name || '-'}
|
title={name || '-'}
|
||||||
@@ -105,29 +108,29 @@ export default function UserMemory() {
|
|||||||
className="rb:cursor-pointer"
|
className="rb:cursor-pointer"
|
||||||
onClick={() => handleViewDetail(end_user.id)}
|
onClick={() => handleViewDetail(end_user.id)}
|
||||||
>
|
>
|
||||||
<div className="rb:flex rb:justify-between rb:items-center">
|
<Flex align="center" justify="space-between">
|
||||||
<div>{t('userMemory.capacity')}</div>
|
<div>{t('userMemory.capacity')}</div>
|
||||||
<div>{memory_num?.total || 0} {t('userMemory.memoryNum')}</div>
|
<div>{memory_num?.total || 0} {t('userMemory.memoryNum')}</div>
|
||||||
</div>
|
</Flex>
|
||||||
<div className="rb:flex rb:justify-between rb:items-center rb:mt-2.5">
|
<Flex align="center" justify="space-between" className="rb:mt-2.5!">
|
||||||
<div>{t('userMemory.type')}</div>
|
<div>{t('userMemory.type')}</div>
|
||||||
<div>{t(`userMemory.${item.type || 'person'}`)}</div>
|
<div>{t(`userMemory.${item.type || 'person'}`)}</div>
|
||||||
</div>
|
</Flex>
|
||||||
|
|
||||||
<div className="rb:relative rb:z-2 rb:mt-3 rb:bg-[#F6F8FC] rb:rounded-lg rb:border rb:border-[#DFE4ED] rb:py-2 rb:px-3" onClick={handleViewMemoryConfig}>
|
<div className="rb:relative rb:z-2 rb:mt-3 rb:bg-[#F6F8FC] rb:rounded-lg rb-border rb:py-2 rb:px-3" onClick={handleViewMemoryConfig}>
|
||||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:flex rb:justify-between rb:items-center">
|
<Flex align="center" justify="space-between" className="rb:text-[#5B6167] rb:leading-5">
|
||||||
{t('userMemory.memory_config_name')}
|
{t('userMemory.memory_config_name')}
|
||||||
<div
|
<div
|
||||||
className="rb:w-7 rb:h-7 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/userMemory/arrow_right.svg')]"
|
className="rb:w-7 rb:h-7 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/userMemory/arrow_right.svg')]"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</Flex>
|
||||||
<div className="rb:font-medium rb:leading-5 rb:mt-1">{memory_config?.memory_config_name || '-'}</div>
|
<div className="rb:font-medium rb:leading-5 rb:mt-1">{memory_config?.memory_config_name || '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
</RbCard>
|
</RbCard>
|
||||||
</List.Item>
|
</Col>
|
||||||
)
|
)
|
||||||
}}
|
})}
|
||||||
/>
|
</Row>
|
||||||
) : <Empty />
|
) : <Empty />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 17:57:26
|
* @Date: 2026-02-03 17:57:26
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-03 17:57:26
|
* @Last Modified time: 2026-02-09 14:28:34
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Neo4j User Memory Detail View
|
* Neo4j User Memory Detail View
|
||||||
@@ -10,12 +10,12 @@
|
|||||||
* Shows profile, interests, node statistics, relationships, and insights
|
* Shows profile, interests, node statistics, relationships, and insights
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type FC, useRef, useState } from 'react'
|
import { type FC, useRef, useState, type MouseEvent } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import clsx from 'clsx'
|
||||||
import { Row, Col, Space, Button } from 'antd'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
|
import { Flex, Popover } from 'antd'
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import PageHeader from './components/PageHeader'
|
|
||||||
import EndUserProfile from './components/EndUserProfile'
|
import EndUserProfile from './components/EndUserProfile'
|
||||||
import AboutMe from './components/AboutMe'
|
import AboutMe from './components/AboutMe'
|
||||||
import InterestDistribution from './components/InterestDistribution'
|
import InterestDistribution from './components/InterestDistribution'
|
||||||
@@ -28,21 +28,29 @@ import {
|
|||||||
} from '@/api/memory'
|
} from '@/api/memory'
|
||||||
|
|
||||||
const Neo4j: FC = () => {
|
const Neo4j: FC = () => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [name, setName] = useState('')
|
const [name, setName] = useState('')
|
||||||
const ref = useRef<EndUserProfileRef>(null)
|
const ref = useRef<EndUserProfileRef>(null)
|
||||||
const memoryInsightRef = useRef<MemoryInsightRef>(null)
|
const memoryInsightRef = useRef<MemoryInsightRef>(null)
|
||||||
const aboutMeRef = useRef<AboutMeRef>(null)
|
const aboutMeRef = useRef<AboutMeRef>(null)
|
||||||
|
const [selectedKey, setSelectedKey] = useState<string | null>(null)
|
||||||
|
|
||||||
/** Update displayed name */
|
/** Update displayed name */
|
||||||
const handleNameUpdate = (data: { other_name?: string; id: string }) => {
|
const handleNameUpdate = (data: { other_name?: string; id: string }) => {
|
||||||
setName(data.other_name && data.other_name !== '' ? data.other_name : data.id)
|
setName(data.other_name && data.other_name !== '' ? data.other_name : data.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Navigate back */
|
||||||
|
const goBack = () => {
|
||||||
|
navigate('/user-memory', { replace: true })
|
||||||
|
}
|
||||||
|
|
||||||
/** Refresh analytics data */
|
/** Refresh analytics data */
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
|
if (loading) return;
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
analyticsRefresh(id as string)
|
analyticsRefresh(id as string)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
@@ -59,43 +67,104 @@ const Neo4j: FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onOpenChange = (e: MouseEvent, type: string) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setSelectedKey(type)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rb:h-full rb:w-full">
|
<div className="rb:h-screen rb:w-screen rb:p-3 rb:relative" onClick={() => setSelectedKey(null)}>
|
||||||
<PageHeader
|
<Flex className="rb:h-[calc(100vh-24px)]" gap={12}>
|
||||||
name={name}
|
<Flex gap={15} vertical justify="space-between" align="center" className="rb:h-full! rb:px-4! rb:pt-6! rb:pb-5! rb:bg-white rb:w-20 rb:rounded-xl">
|
||||||
operation={(
|
<Flex gap={15} vertical>
|
||||||
<Button
|
<Popover
|
||||||
loading={loading}
|
content={t('userMemory.memoryWindow', { name: name })}
|
||||||
className="rb:group rb:h-7! rb:bg-transparent! rb:border-[#5B6167] rb:text-[#5B6167] rb:ml-3"
|
placement="right"
|
||||||
onClick={handleRefresh}
|
arrow={false}
|
||||||
>
|
trigger="hover"
|
||||||
{!loading && <div
|
>
|
||||||
className="rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/refresh.svg')] rb:group-hover:bg-[url('@/assets/images/refresh_hover.svg')]"
|
<div className="rb:mb-4.25! rb:size-12 rb:rounded-xl rb:bg-cover rb:bg-[url('@/assets/images/userMemory/logo.png')]"></div>
|
||||||
></div>}
|
</Popover>
|
||||||
{t('common.refresh')}
|
|
||||||
</Button>
|
<Flex
|
||||||
)}
|
align="center"
|
||||||
/>
|
justify="center"
|
||||||
<div className="rb:h-[calc(100vh-64px)] rb:overflow-y-auto rb:py-3 rb:px-4">
|
className={clsx("rb:cursor-pointer rb:size-12 rb:rounded-xl rb:group", {
|
||||||
<Row gutter={16}>
|
'rb:bg-[#155EEF]': selectedKey === 'userProfile',
|
||||||
<Col span={8}>
|
'rb:hover:bg-[#F0F3F8]': selectedKey !== 'userProfile',
|
||||||
<Space size={16} direction="vertical" className="rb:w-full">
|
})}
|
||||||
<EndUserProfile ref={ref} onDataLoaded={handleNameUpdate} />
|
onClick={(e) => onOpenChange(e, 'userProfile')}
|
||||||
<AboutMe ref={aboutMeRef} />
|
>
|
||||||
<InterestDistribution />
|
<div className={clsx("rb:size-6 rb:bg-cover", {
|
||||||
</Space>
|
"rb:bg-[url('@/assets/images/userMemory/userProfile.svg')]": selectedKey !== 'userProfile',
|
||||||
</Col>
|
"rb:bg-[url('@/assets/images/userMemory/userProfile_active.svg')]": selectedKey === 'userProfile'
|
||||||
<Col span={16}>
|
})}></div>
|
||||||
<Space size={16} direction="vertical" className="rb:w-full">
|
</Flex>
|
||||||
<NodeStatistics />
|
|
||||||
<RelationshipNetwork />
|
<Flex
|
||||||
<MemoryInsight ref={memoryInsightRef} />
|
align="center"
|
||||||
</Space>
|
justify="center"
|
||||||
</Col>
|
className={clsx("rb:cursor-pointer rb:size-12 rb:rounded-xl rb:group", {
|
||||||
</Row>
|
'rb:bg-[#155EEF]': selectedKey === 'aboutMe',
|
||||||
</div>
|
'rb:hover:bg-[#F0F3F8]': selectedKey !== 'aboutMe',
|
||||||
|
})}
|
||||||
|
onClick={(e) => onOpenChange(e, 'aboutMe')}
|
||||||
|
>
|
||||||
|
<div className={clsx("rb:size-6 rb:bg-cover", {
|
||||||
|
"rb:bg-[url('@/assets/images/userMemory/aboutMe.svg')]": selectedKey !== 'aboutMe',
|
||||||
|
"rb:bg-[url('@/assets/images/userMemory/aboutMe_active.svg')]": selectedKey === 'aboutMe'
|
||||||
|
})}></div>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
align="center"
|
||||||
|
justify="center"
|
||||||
|
className={clsx("rb:cursor-pointer rb:size-12 rb:rounded-xl rb:group", {
|
||||||
|
'rb:bg-[#155EEF]': selectedKey === 'interestDistribution',
|
||||||
|
'rb:hover:bg-[#F0F3F8]': selectedKey !== 'interestDistribution',
|
||||||
|
})}
|
||||||
|
onClick={(e) => onOpenChange(e, 'interestDistribution')}
|
||||||
|
>
|
||||||
|
<div className={clsx("rb:size-6 rb:bg-cover", {
|
||||||
|
"rb:bg-[url('@/assets/images/userMemory/interestDistribution.svg')]": selectedKey !== 'interestDistribution',
|
||||||
|
"rb:bg-[url('@/assets/images/userMemory/interestDistribution_active.svg')]": selectedKey === 'interestDistribution'
|
||||||
|
})}></div>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
align="center"
|
||||||
|
justify="center"
|
||||||
|
className={clsx("rb:cursor-pointer rb:size-12 rb:rounded-xl rb:group", {
|
||||||
|
'rb:bg-[#155EEF]': selectedKey === 'memoryInsight',
|
||||||
|
'rb:hover:bg-[#F0F3F8]': selectedKey !== 'memoryInsight',
|
||||||
|
})}
|
||||||
|
onClick={(e) => onOpenChange(e, 'memoryInsight')}
|
||||||
|
>
|
||||||
|
<div className={clsx("rb:size-6 rb:bg-cover", {
|
||||||
|
"rb:bg-[url('@/assets/images/userMemory/memoryInsight.svg')]": selectedKey !== 'memoryInsight',
|
||||||
|
"rb:bg-[url('@/assets/images/userMemory/memoryInsight_active.svg')]": selectedKey === 'memoryInsight'
|
||||||
|
})}></div>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex vertical gap={24}>
|
||||||
|
<div className={clsx("rb:cursor-pointer rb:size-6 rb:bg-cover rb:bg-[url('@/assets/images/userMemory/refresh.svg')]", {
|
||||||
|
"rb:animate-spin": loading
|
||||||
|
})} onClick={handleRefresh}></div>
|
||||||
|
<div className="rb:cursor-pointer rb:size-6 rb:bg-cover rb:bg-[url('@/assets/images/userMemory/logout.svg')]" onClick={goBack}></div>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex vertical className="rb:flex-1">
|
||||||
|
<NodeStatistics />
|
||||||
|
<RelationshipNetwork />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<EndUserProfile ref={ref} onDataLoaded={handleNameUpdate} className={selectedKey === 'userProfile' ? 'rb:block!' : 'rb:hidden!'} />
|
||||||
|
<AboutMe ref={aboutMeRef} className={selectedKey === 'aboutMe' ? 'rb:block!' : 'rb:hidden!'} />
|
||||||
|
<InterestDistribution className={selectedKey === 'interestDistribution' ? 'rb:block!' : 'rb:hidden!'} />
|
||||||
|
<MemoryInsight ref={memoryInsightRef} className={selectedKey === 'memoryInsight' ? 'rb:block!' : 'rb:hidden!'} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 18:34:23
|
* @Date: 2026-02-03 18:34:23
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-03 18:34:23
|
* @Last Modified time: 2026-02-11 15:03:05
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* About Me Component
|
* About Me Component
|
||||||
@@ -12,7 +12,8 @@
|
|||||||
import { useEffect, useState, forwardRef, useImperativeHandle } from 'react'
|
import { useEffect, useState, forwardRef, useImperativeHandle } 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 { Skeleton } from 'antd';
|
import { Skeleton, Divider } from 'antd';
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import Empty from '@/components/Empty';
|
import Empty from '@/components/Empty';
|
||||||
@@ -33,7 +34,7 @@ interface Data {
|
|||||||
one_sentence: string;
|
one_sentence: string;
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
const AboutMe = forwardRef<AboutMeRef>((_props, ref) => {
|
const AboutMe = forwardRef<AboutMeRef, { className?: string; }>(({ className }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
@@ -64,7 +65,9 @@ const AboutMe = forwardRef<AboutMeRef>((_props, ref) => {
|
|||||||
return (
|
return (
|
||||||
<RbCard
|
<RbCard
|
||||||
title={t('userMemory.aboutMe')}
|
title={t('userMemory.aboutMe')}
|
||||||
headerClassName="rb:min-h-[46px]!"
|
headerClassName="rb:min-h-[46px]!! rb:font-medium!"
|
||||||
|
className={clsx("rb:bg-[#FFFFFF]! rb:shadow-[0px_2px_6px_0px_rgba(33,35,50,0.13)]! rb:absolute! rb:w-100 rb:top-29 rb:left-26", className)}
|
||||||
|
bodyClassName="rb:px-5! rb:pb-5! rb:pt-3.75! rb:max-h-[calc(100vh-176px)] rb:overflow-y-auto!"
|
||||||
>
|
>
|
||||||
{loading
|
{loading
|
||||||
? <Skeleton className="rb:mt-4" />
|
? <Skeleton className="rb:mt-4" />
|
||||||
@@ -76,19 +79,21 @@ const AboutMe = forwardRef<AboutMeRef>((_props, ref) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{data.personality && <>
|
{data.personality && <>
|
||||||
<div className="rb:pt-4 rb:font-medium rb:leading-5 rb:mb-2">{t('userMemory.personality')}</div>
|
<Divider className="rb:my-4!" />
|
||||||
<div className="rb:font-regular rb:leading-5 rb:text-[#5B6167]">
|
<div className="rb:font-medium rb:leading-5">{t('userMemory.personality')}</div>
|
||||||
|
<div className="rb:font-regular rb:leading-5 rb:text-[#5B6167] rb:mt-2">
|
||||||
{data.personality}
|
{data.personality}
|
||||||
</div>
|
</div>
|
||||||
</>}
|
</>}
|
||||||
{data.core_values && <>
|
{data.core_values && <>
|
||||||
<div className="rb:pt-4 rb:font-medium rb:leading-5 rb:mb-2">{t('userMemory.core_values')}</div>
|
<Divider className="rb:my-4!" />
|
||||||
<div className="rb:font-regular rb:leading-5 rb:text-[#5B6167]">
|
<div className="rb:font-medium rb:leading-5">{t('userMemory.core_values')}</div>
|
||||||
|
<div className="rb:font-regular rb:leading-5 rb:text-[#5B6167] rb:mt-2">
|
||||||
{data.core_values}
|
{data.core_values}
|
||||||
</div>
|
</div>
|
||||||
</>}
|
</>}
|
||||||
{data.one_sentence &&
|
{data.one_sentence &&
|
||||||
<RbAlert className="rb:mt-4">{data.one_sentence}</RbAlert>
|
<RbAlert className="rb:mt-4! rb:text-[14px]!">{data.one_sentence}</RbAlert>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
: <Empty size={88} className="rb:mt-12 rb:mb-20.25" />
|
: <Empty size={88} className="rb:mt-12 rb:mb-20.25" />
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ interface CardProps {
|
|||||||
|
|
||||||
const Card: FC<CardProps> = ({ title, children, theme = 'default', className }) => {
|
const Card: FC<CardProps> = ({ title, children, theme = 'default', className }) => {
|
||||||
return (
|
return (
|
||||||
<div className={clsx('rb:h-full rb:border rb:rounded-xl rb:p-4 rb:border-[#DFE4ED]', {
|
<div className={clsx('rb:h-full rb:rounded-xl rb:p-4 rb-border', {
|
||||||
'rb:bg-[#FBFDFF]': theme === 'default',
|
'rb:bg-[#FBFDFF]': theme === 'default',
|
||||||
'rb:bg-[linear-gradient(180deg,#F1F9FE_0%,#FBFCFF_100%)]': theme === 'custom',
|
'rb:bg-[linear-gradient(180deg,#F1F9FE_0%,#FBFCFF_100%)]': theme === 'custom',
|
||||||
}, className)}>
|
}, className)}>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const ConversationMemory:FC = () => {
|
|||||||
<List.Item>
|
<List.Item>
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="rb:rounded-lg rb:border rb:border-[#DFE4ED] rb:px-4 rb:py-3 rb:bg-[#F0F3F8] rb:mt-2 rb:text-gray-800 rb:text-sm"
|
className="rb:rounded-lg rb-border rb:px-4 rb:py-3 rb:bg-[#F0F3F8] rb:mt-2 rb:text-[#212332] rb:text-sm"
|
||||||
>
|
>
|
||||||
<Markdown content={item} />
|
<Markdown content={item} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 18:33:30
|
* @Date: 2026-02-03 18:33:30
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-03 18:33:30
|
* @Last Modified time: 2026-02-11 14:51:00
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* End User Profile Component
|
* End User Profile Component
|
||||||
@@ -12,8 +12,9 @@
|
|||||||
import { forwardRef, useImperativeHandle, useEffect, useState, useRef, useCallback } from 'react'
|
import { forwardRef, useImperativeHandle, 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 { Skeleton } from 'antd';
|
import { Skeleton, Flex } from 'antd';
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import {
|
import {
|
||||||
@@ -26,10 +27,11 @@ import type { EndUser, EndUserProfileModalRef, EndUserProfileRef } from '../type
|
|||||||
* Component props
|
* Component props
|
||||||
*/
|
*/
|
||||||
interface EndUserProfileProps {
|
interface EndUserProfileProps {
|
||||||
onDataLoaded?: (data: { other_name?: string; id: string }) => void
|
onDataLoaded?: (data: { other_name?: string; id: string }) => void;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EndUserProfile = forwardRef<EndUserProfileRef, EndUserProfileProps>(({ onDataLoaded }, ref) => {
|
const EndUserProfile = forwardRef<EndUserProfileRef, EndUserProfileProps>(({ onDataLoaded, className }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const endUserProfileModalRef = useRef<EndUserProfileModalRef>(null)
|
const endUserProfileModalRef = useRef<EndUserProfileModalRef>(null)
|
||||||
@@ -85,22 +87,24 @@ const EndUserProfile = forwardRef<EndUserProfileRef, EndUserProfileProps>(({ onD
|
|||||||
onClick={handleEdit}
|
onClick={handleEdit}
|
||||||
></div>
|
></div>
|
||||||
}
|
}
|
||||||
headerClassName="rb:min-h-[46px]!"
|
headerClassName="rb:min-h-[46px]!! rb:font-medium!"
|
||||||
|
className={clsx("rb:bg-[#FFFFFF]! rb:shadow-[0px_2px_6px_0px_rgba(33,35,50,0.13)]! rb:absolute! rb:w-80 rb:top-29 rb:left-26", className)}
|
||||||
|
bodyClassName="rb:px-5! rb:pb-5! rb:pt-3.75! rb:max-h-[calc(100vh-176px)] rb:overflow-auto"
|
||||||
>
|
>
|
||||||
{loading
|
{loading
|
||||||
? <Skeleton />
|
? <Skeleton />
|
||||||
: <div className="rb:flex rb:flex-col rb:justify-between rb:gap-3 rb:h-full">
|
: <Flex vertical gap={20}>
|
||||||
{formatItems().map(vo => (
|
{formatItems().map(vo => (
|
||||||
<div key={vo.key} className="rb:flex rb:justify-between rb:items-center rb:gap-3 rb:leading-5">
|
<div key={vo.key} className="rb:leading-5">
|
||||||
<div className="rb:text-[#5B6167]">{vo.label}</div>
|
<div className="rb:text-[#7B8085]">{vo.label}</div>
|
||||||
<div className="">{vo.children}</div>
|
<div className="rb:mt-0.5">{vo.children}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="rb:border-t rb:border-t-[#DFE4ED] rb:pt-4 rb:text-[#5B6167] rb:text-[12px] rb:leading-4">
|
<div className="rb:text-[#7B8085] rb:text-[12px] rb:leading-4.5">
|
||||||
{t('userMemory.updated_at')}: {data?.updatetime_profile ? dayjs(data?.updatetime_profile).format('YYYY/MM/DD HH:mm:ss') : ''}
|
{t('userMemory.updated_at')}: {data?.updatetime_profile ? dayjs(data?.updatetime_profile).format('YYYY/MM/DD HH:mm:ss') : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Flex>
|
||||||
}
|
}
|
||||||
<EndUserProfileModal
|
<EndUserProfileModal
|
||||||
ref={endUserProfileModalRef}
|
ref={endUserProfileModalRef}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 18:32:47
|
* @Date: 2026-02-03 18:32:47
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-03 18:32:47
|
* @Last Modified time: 2026-02-05 18:29:29
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Interest Distribution Component
|
* Interest Distribution Component
|
||||||
@@ -13,7 +13,7 @@ import { type FC, useRef, useEffect, useState } 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 ReactEcharts from 'echarts-for-react';
|
import ReactEcharts from 'echarts-for-react';
|
||||||
import { Space } from 'antd'
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import { getInterestDistributionByUser } from '@/api/memory';
|
import { getInterestDistributionByUser } from '@/api/memory';
|
||||||
import Empty from '@/components/Empty';
|
import Empty from '@/components/Empty';
|
||||||
@@ -21,16 +21,15 @@ import Loading from '@/components/Empty/Loading';
|
|||||||
import RbCard from '@/components/RbCard/Card';
|
import RbCard from '@/components/RbCard/Card';
|
||||||
|
|
||||||
/** Chart color palette */
|
/** Chart color palette */
|
||||||
const Colors = ['#155EEF', '#4DA8FF', '#03BDFF', '#31E8FF', '#AD88FF', '#FFB048']
|
const Colors = ['#171719', '#155EEF', '#4DA8FF', '#9C6FFF', '#ABEBFF', '#DFE4ED']
|
||||||
|
|
||||||
const InterestDistribution: FC = () => {
|
const InterestDistribution: FC<{ className?: string; }> = ({ className }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const chartRef = useRef<ReactEcharts>(null);
|
const chartRef = useRef<ReactEcharts>(null);
|
||||||
const resizeScheduledRef = useRef(false)
|
const resizeScheduledRef = useRef(false)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [data, setData] = useState<Array<Record<string, string | number>>>([])
|
const [data, setData] = useState<Array<Record<string, string | number>>>([])
|
||||||
const totalValue = data.reduce((sum, item) => sum + Number(item.value), 0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getData()
|
getData()
|
||||||
@@ -76,7 +75,9 @@ const InterestDistribution: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<RbCard
|
<RbCard
|
||||||
title={t('userMemory.interestDistribution')}
|
title={t('userMemory.interestDistribution')}
|
||||||
headerClassName="rb:min-h-[46px]!"
|
headerClassName="rb:min-h-[46px]!! rb:font-medium!"
|
||||||
|
className={clsx("rb:bg-[#FFFFFF]! rb:shadow-[0px_2px_6px_0px_rgba(33,35,50,0.13)]! rb:absolute! rb:w-100 rb:top-29 rb:left-26", className)}
|
||||||
|
bodyClassName="rb:px-5! rb:pb-5! rb:pt-3.75! rb:max-h-[calc(100vh-176px)] rb:overflow-auto"
|
||||||
>
|
>
|
||||||
{loading
|
{loading
|
||||||
? <Loading size={249} />
|
? <Loading size={249} />
|
||||||
@@ -102,61 +103,55 @@ const InterestDistribution: FC = () => {
|
|||||||
extraCssText: 'width: 36px; height: 36px; box-shadow: 0px 2px 4px 0px rgba(33,35,50,0.12);border-radius: 36px;'
|
extraCssText: 'width: 36px; height: 36px; box-shadow: 0px 2px 4px 0px rgba(33,35,50,0.12);border-radius: 36px;'
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
show: false
|
bottom: 0,
|
||||||
|
padding: 0,
|
||||||
|
itemWidth: 12,
|
||||||
|
itemHeight: 12,
|
||||||
|
borderRadius: 2,
|
||||||
|
orient: 'horizontal',
|
||||||
|
textStyle: {
|
||||||
|
color: '#5B6167',
|
||||||
|
fontFamily: 'PingFangSC, PingFang SC',
|
||||||
|
lineHeight: 16,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: 'Access From',
|
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
radius: ['60%', '100%'],
|
radius: ['60%', '100%'],
|
||||||
avoidLabelOverlap: false,
|
avoidLabelOverlap: false,
|
||||||
percentPrecision: 0,
|
percentPrecision: 0,
|
||||||
padAngle: 0,
|
padAngle: 1,
|
||||||
width: 200,
|
width: 180,
|
||||||
height: 200,
|
height: 180,
|
||||||
top: 18,
|
|
||||||
left: 'center',
|
left: 'center',
|
||||||
|
top: 24,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderRadius: 0
|
borderRadius: 2,
|
||||||
|
shadowBlur: 4,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowOffsetY: 2,
|
||||||
|
shadowColor: 'rgba(0,0,0,0.25)',
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
show: false,
|
fontWeight: 'bold',
|
||||||
position: 'center'
|
color: '#171719',
|
||||||
},
|
formatter: '{d}%',
|
||||||
emphasis: {
|
fontFamily: 'MiSans-Demibold',
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: '#212332',
|
|
||||||
formatter: '{d}%\n{b}',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
labelLine: {
|
labelLine: {
|
||||||
show: false
|
lineStyle: {
|
||||||
|
color: '#DFE4ED'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data: data
|
data: data
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
style={{ height: '250px', width: '100%' }}
|
style={{ height: '320px', width: '100%' }}
|
||||||
notMerge={true}
|
notMerge={true}
|
||||||
lazyUpdate={true}
|
lazyUpdate={true}
|
||||||
/>
|
/>
|
||||||
<Space size={12} direction="vertical" className="rb:w-full">
|
|
||||||
{data.map((item, index) => (
|
|
||||||
<div key={index} className="rb:relative rb:flex rb:items-center rb:justify-between rb:px-4 rb:py-2.5 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:font-regular rb:leading-5 rb:rounded-md">
|
|
||||||
<div className="rb:pl-3.5 rb:relative">
|
|
||||||
<span
|
|
||||||
className="rb:absolute rb:left-0 rb:top-[calc(50%-4px)] rb:w-2 rb:h-2 rb:rounded-full"
|
|
||||||
style={{ backgroundColor: Colors[index % Colors.length] }}
|
|
||||||
/>
|
|
||||||
{item.name}
|
|
||||||
</div>
|
|
||||||
<div className="rb:font-medium">{totalValue > 0 ? Math.round((Number(item.value) / totalValue) * 100) : 0}%</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Space>
|
|
||||||
</>}
|
</>}
|
||||||
</RbCard>
|
</RbCard>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 18:32:41
|
* @Date: 2026-02-03 18:32:41
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-03 18:32:41
|
* @Last Modified time: 2026-02-05 18:35:01
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Memory Insight Component
|
* Memory Insight Component
|
||||||
@@ -10,10 +10,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState, forwardRef, useImperativeHandle } from 'react'
|
import { useEffect, useState, forwardRef, useImperativeHandle } from 'react'
|
||||||
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 { Skeleton, Space } from 'antd';
|
import { Skeleton, Divider } from 'antd';
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import Empty from '@/components/Empty';
|
import Empty from '@/components/Empty';
|
||||||
@@ -34,7 +34,7 @@ interface Data {
|
|||||||
is_cached: boolean;
|
is_cached: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MemoryInsight = forwardRef<MemoryInsightRef>((_props, ref) => {
|
const MemoryInsight = forwardRef<MemoryInsightRef, { className?: string; }>(({ className }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
@@ -63,24 +63,25 @@ const MemoryInsight = forwardRef<MemoryInsightRef>((_props, ref) => {
|
|||||||
}));
|
}));
|
||||||
return (
|
return (
|
||||||
<RbCard
|
<RbCard
|
||||||
title={t('userMemory.memoryInsight')}
|
title={t('userMemory.memoryInsight')}
|
||||||
headerType="borderless"
|
headerClassName="rb:min-h-[46px]!! rb:font-medium!"
|
||||||
headerClassName="rb:min-h-[46px]!"
|
className={clsx("rb:bg-[#FFFFFF]! rb:shadow-[0px_2px_6px_0px_rgba(33,35,50,0.13)]! rb:absolute! rb:w-100 rb:top-29 rb:left-26", className)}
|
||||||
|
bodyClassName="rb:px-5! rb:pb-5! rb:pt-3.75! rb:max-h-[calc(100vh-176px)] rb:overflow-auto"
|
||||||
>
|
>
|
||||||
{loading
|
{loading
|
||||||
? <Skeleton />
|
? <Skeleton />
|
||||||
: Object.keys(data).length > 0
|
: Object.keys(data).length > 0
|
||||||
? <Space size={16} direction="vertical" className="rb:w-full">
|
? <div>
|
||||||
{['memory_insight', 'key_findings', 'behavior_pattern', 'growth_trajectory'].map(key => {
|
{['memory_insight', 'key_findings', 'behavior_pattern', 'growth_trajectory'].map((key, index) => {
|
||||||
const value = data[key as keyof Data];
|
const value = data[key as keyof Data];
|
||||||
if (Array.isArray(value) && value.length > 0 || (!Array.isArray(value) && value)) {
|
if (Array.isArray(value) && value.length > 0 || (!Array.isArray(value) && value)) {
|
||||||
return (
|
return (
|
||||||
<div key={key} className="rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:py-3 rb:text-[#5B6167] rb:leading-5">
|
<div key={key}>
|
||||||
<div className={clsx(`rb:relative rb:before:content-[''] rb:before:block rb:before:h-4 rb:before:absolute rb:before:top-0.5 rb:before:left-0 rb:before:w-1 rb:pl-4 rb:mb-2 rb:font-medium rb:leading-5`, {
|
{index > 0 && <Divider className="rb:my-4! rb:border-t-[0.5px]!" />}
|
||||||
'rb:before:bg-[#155EEF]': key === 'memory_insight',
|
<div className="rb:font-medium rb:leading-5">
|
||||||
'rb:before:bg-[#369F21]': key !== 'memory_insight'
|
{t(`userMemory.${key}`)}
|
||||||
})}>{t(`userMemory.${key}`)}</div>
|
</div>
|
||||||
<div className="rb:px-4">
|
<div className="rb:font-regular rb:leading-5 rb:text-[#5B6167] rb:mt-2">
|
||||||
{Array.isArray(data[key as keyof Data])
|
{Array.isArray(data[key as keyof Data])
|
||||||
? <>
|
? <>
|
||||||
{(data[key as keyof Data] as string[])?.map((item: string, index: number) => (
|
{(data[key as keyof Data] as string[])?.map((item: string, index: number) => (
|
||||||
@@ -98,7 +99,7 @@ const MemoryInsight = forwardRef<MemoryInsightRef>((_props, ref) => {
|
|||||||
return null
|
return null
|
||||||
})}
|
})}
|
||||||
|
|
||||||
</Space>
|
</div>
|
||||||
: <Empty size={80} />
|
: <Empty size={80} />
|
||||||
}
|
}
|
||||||
</RbCard>
|
</RbCard>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 18:32:35
|
* @Date: 2026-02-03 18:32:35
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-03 18:32:35
|
* @Last Modified time: 2026-02-05 19:07:07
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Node Statistics Component
|
* Node Statistics Component
|
||||||
@@ -13,30 +13,20 @@ import { type FC, useEffect, useState } from 'react'
|
|||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useParams, useNavigate } from 'react-router-dom'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import { Skeleton } from 'antd';
|
import { Skeleton, Flex, Divider } from 'antd';
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
|
||||||
import {
|
import {
|
||||||
getNodeStatistics,
|
getNodeStatistics,
|
||||||
} from '@/api/memory'
|
} from '@/api/memory'
|
||||||
import type { NodeStatisticsItem } from '../types'
|
import type { NodeStatisticsItem } from '../types'
|
||||||
|
|
||||||
|
|
||||||
/** Background gradient list */
|
|
||||||
const BG_LIST = [
|
|
||||||
'rb:bg-[linear-gradient(316deg,rgba(21,94,239,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
|
||||||
'rb:bg-[linear-gradient(316deg,rgba(54,159,33,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
|
||||||
'rb:bg-[linear-gradient(314deg,rgba(156,111,255,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
|
||||||
'rb:bg-[linear-gradient(332deg,rgba(255,93,52,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
|
||||||
'rb:bg-[linear-gradient(313deg,rgba(156,111,255,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
|
||||||
'rb:bg-[linear-gradient(332deg,rgba(54,159,33,0.06)_0%,rgba(251,253,255,0)_100%)]',
|
|
||||||
]
|
|
||||||
/** Memory type configuration */
|
/** Memory type configuration */
|
||||||
const typeList = [
|
const typeList = [
|
||||||
{ key: 'PERCEPTUAL_MEMORY', bg: 0 },
|
{ key: 'PERCEPTUAL_MEMORY', bg: 0 },
|
||||||
{ key: 'WORKING_MEMORY', bg: 1 },
|
{ key: 'WORKING_MEMORY', bg: 1 },
|
||||||
{ key: 'EMOTIONAL_MEMORY', bg: 2 },
|
{ key: 'EMOTIONAL_MEMORY', bg: 2 },
|
||||||
{ key: 'SHORT_TERM_MEMORY', bg: 3 },
|
{ key: 'SHORT_TERM_MEMORY', bg: 3 },
|
||||||
|
{ key: 'FORGET_MEMORY', bg: 5 },
|
||||||
{
|
{
|
||||||
key: 'LONG_TERM_MEMORY',
|
key: 'LONG_TERM_MEMORY',
|
||||||
bg: 4,
|
bg: 4,
|
||||||
@@ -46,7 +36,6 @@ const typeList = [
|
|||||||
{ key: 'EXPLICIT_MEMORY' }
|
{ key: 'EXPLICIT_MEMORY' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ key: 'FORGET_MEMORY', bg: 5 },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const NodeStatistics: FC = () => {
|
const NodeStatistics: FC = () => {
|
||||||
@@ -54,7 +43,6 @@ const NodeStatistics: 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)
|
||||||
const [total, setTotal] = useState<number>(0)
|
|
||||||
const [data, setData] = useState<NodeStatisticsItem[]>([])
|
const [data, setData] = useState<NodeStatisticsItem[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -69,9 +57,6 @@ const NodeStatistics: FC = () => {
|
|||||||
getNodeStatistics(id).then((res) => {
|
getNodeStatistics(id).then((res) => {
|
||||||
const response = res as NodeStatisticsItem[]
|
const response = res as NodeStatisticsItem[]
|
||||||
setData(response)
|
setData(response)
|
||||||
// Calculate total count
|
|
||||||
const totalCount = response.reduce((sum, item) => sum + (item.count || 0), 0)
|
|
||||||
setTotal(totalCount)
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -83,59 +68,57 @@ const NodeStatistics: FC = () => {
|
|||||||
navigate(`/user-memory/detail/${id}/${type}`)
|
navigate(`/user-memory/detail/${id}/${type}`)
|
||||||
}
|
}
|
||||||
/** Render statistics card */
|
/** Render statistics card */
|
||||||
const renderCard = (key: string, bgIndex: number | null, isChild: boolean = false) => {
|
const renderCard = (key: string, isChild: boolean = false) => {
|
||||||
const item = data.find((item) => item.type === key)
|
const item = data.find((item) => item.type === key)
|
||||||
return (
|
return (
|
||||||
<div
|
<Flex
|
||||||
|
vertical
|
||||||
|
justify="space-between"
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"rb:flex rb:flex-col rb:justify-between rb:group rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:pt-3 rb:px-4 rb:pb-5 rb:cursor-pointer",
|
"rb:h-full rb:group rb:cursor-pointer rb:bg-[#FFFFFF]",{
|
||||||
{
|
'rb:rounded-xl rb:shaodow-[0px_2px_6px_0px_rgba(33,35,50,0.08)] rb:p-3!': !isChild,
|
||||||
'rb:h-45': !isChild,
|
'rb:px-3! rb:pt-2! rb:pb-2.5! rb:w-full': isChild
|
||||||
'rb:h-31': isChild
|
}
|
||||||
},
|
|
||||||
typeof bgIndex === 'number' ? BG_LIST[bgIndex] : 'rb:bg-[#FBFDFF]'
|
|
||||||
)}
|
)}
|
||||||
onClick={() => handleViewDetail(key)}
|
onClick={() => handleViewDetail(key)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div className={clsx("rb:text-[#5B6167] rb:leading-5 rb:font-regular", {
|
<div className={clsx("rb:text-[#5B6167] rb:leading-5 rb:font-regular")}>
|
||||||
'rb:mb-2': !isChild,
|
|
||||||
'rb:mb-1': isChild
|
|
||||||
})}>
|
|
||||||
{t(`userMemory.${key}`)}
|
{t(`userMemory.${key}`)}
|
||||||
</div>
|
</div>
|
||||||
<div className="rb:w-3 rb:h-3 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/userMemory/arrow_right.svg')] rb:group-hover:bg-[url('@/assets/images/userMemory/arrow_right_hover.svg')]"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="rb:text-[28px] rb:leading-8.75 rb:font-extrabold">{item?.count ?? 0}</div>
|
<Flex justify="space-between" align="center">
|
||||||
</div>
|
<div className="rb:text-[24px] rb:leading-8 rb:font-extrabold rb:font-[MiSans-Heavy]">{item?.count ?? 0}</div>
|
||||||
|
<div className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/userMemory/arrow_right.svg')] rb:group-hover:bg-[url('@/assets/images/userMemory/arrow_right_hover.svg')]"></div>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RbCard
|
<div className="rb:h-22">
|
||||||
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
|
{loading
|
||||||
? <Skeleton active />
|
? <Skeleton active />
|
||||||
: <div className="rb:w-full rb:grid rb:grid-cols-8 rb:gap-3">
|
: <div className="rb:w-full rb:grid rb:grid-cols-8 rb:gap-3 rb:h-full">
|
||||||
{typeList.map((vo) => {
|
{typeList.map((vo) => {
|
||||||
if (!vo.children) {
|
if (!vo.children) {
|
||||||
return <div key={vo.key}>{renderCard(vo.key, vo.bg)}</div>
|
return <div key={vo.key} className="rb:h-full">{renderCard(vo.key)}</div>
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div key={vo.key} className={clsx("rb:col-span-3 rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:p-3", BG_LIST[vo.bg])}>
|
<div key={vo.key} className={clsx("rb:col-span-3 rb:shaodow-[0px_2px_6px_0px_rgba(33,35,50,0.08)] rb:rounded-xl rb:bg-[#FFFFFF] rb:overflow-hidden")}>
|
||||||
<div className="rb:text-[#5B6167] rb:leading-5 rb:font-regular rb:mb-3">{t(`userMemory.${vo.key}`)}</div>
|
<div className="rb:bg-[#171719] rb:text-[12px] rb:text-[#FFFFFF] rb:font-medium rb:text-center rb:leading-4 rb:py-px rb:rounded-tl-xl rb:rounded-tr-xl">{t(`userMemory.${vo.key}`)}</div>
|
||||||
<div className="rb:grid rb:grid-cols-3 rb:gap-3">
|
<div className="rb:grid rb:grid-cols-3">
|
||||||
{vo.children.map((child) => <div key={child.key}>{renderCard(child.key, null, true)}</div>)}
|
{vo.children.map((child, index) => <Flex key={child.key} align="center">
|
||||||
|
{index > 0 && <Divider type="vertical" className="rb:h-12! rb:mx-0!" />}
|
||||||
|
{renderCard(child.key, true)}
|
||||||
|
</Flex>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</RbCard>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default NodeStatistics
|
export default NodeStatistics
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 18:32:00
|
* @Date: 2026-02-03 18:32:00
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-03 18:32:00
|
* @Last Modified time: 2026-02-11 15:06:05
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Relationship Network Component
|
* Relationship Network Component
|
||||||
@@ -10,37 +10,29 @@
|
|||||||
* Interactive force-directed graph visualization
|
* Interactive force-directed graph visualization
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { type FC, useEffect, useState, useRef, useCallback } from 'react'
|
import React, { type FC, useEffect, useState, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useParams, useNavigate } from 'react-router-dom'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import { Col, Row, Space, Button } from 'antd'
|
import { Space, Flex } from 'antd'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import ReactEcharts from 'echarts-for-react'
|
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import detailEmpty from '@/assets/images/userMemory/detail_empty.png'
|
import type { GraphData, StatementNodeProperties, ExtractedEntityNodeProperties } 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 Tag from '@/components/Tag'
|
import Tag from '@/components/Tag'
|
||||||
|
import GraphNetworkChart, { type Node, type Edge } from '@/components/Charts/GraphNetworkChart'
|
||||||
|
|
||||||
/** Node color palette */
|
|
||||||
const colors = ['#155EEF', '#369F21', '#4DA8FF', '#FF5D34', '#9C6FFF', '#FF8A4C', '#8BAEF7', '#FFB048']
|
|
||||||
const RelationshipNetwork:FC = () => {
|
const RelationshipNetwork:FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const chartRef = useRef<ReactEcharts>(null)
|
|
||||||
const resizeScheduledRef = useRef(false)
|
|
||||||
const [nodes, setNodes] = useState<Node[]>([])
|
const [nodes, setNodes] = useState<Node[]>([])
|
||||||
const [links, setLinks] = useState<Edge[]>([])
|
const [links, setLinks] = useState<Edge[]>([])
|
||||||
const [categories, setCategories] = useState<{ name: string }[]>([])
|
const [categories, setCategories] = useState<{ name: string }[]>([])
|
||||||
const [selectedNode, setSelectedNode] = useState<Node | null>(null)
|
const [selectedNode, setSelectedNode] = useState<Node | null>(null)
|
||||||
// const [fullScreen, setFullScreen] = useState<boolean>(false)
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
console.log('categories', categories)
|
|
||||||
/** Fetch relationship network data */
|
/** Fetch relationship network data */
|
||||||
const getEdgeData = useCallback(() => {
|
const getEdgeData = useCallback(() => {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
@@ -124,28 +116,6 @@ const RelationshipNetwork:FC = () => {
|
|||||||
if (!id) return
|
if (!id) return
|
||||||
getEdgeData()
|
getEdgeData()
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleResize = () => {
|
|
||||||
if (chartRef.current && !resizeScheduledRef.current) {
|
|
||||||
resizeScheduledRef.current = true
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
chartRef.current?.getEchartsInstance().resize();
|
|
||||||
resizeScheduledRef.current = false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(handleResize)
|
|
||||||
const chartElement = chartRef.current?.getEchartsInstance().getDom().parentElement
|
|
||||||
if (chartElement) {
|
|
||||||
resizeObserver.observe(chartElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
resizeObserver.disconnect()
|
|
||||||
}
|
|
||||||
}, [nodes])
|
|
||||||
|
|
||||||
/** Navigate to full graph view */
|
/** Navigate to full graph view */
|
||||||
const handleViewAll = () => {
|
const handleViewAll = () => {
|
||||||
@@ -157,204 +127,111 @@ const RelationshipNetwork:FC = () => {
|
|||||||
})
|
})
|
||||||
navigate(`/user-memory/detail/${id}/GRAPH?${params.toString()}`)
|
navigate(`/user-memory/detail/${id}/GRAPH?${params.toString()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={16}>
|
<div className="rb:flex-1 rb:relative">
|
||||||
{/* Relationship Network */}
|
<GraphNetworkChart
|
||||||
<Col span={16}>
|
nodes={nodes}
|
||||||
<RbCard
|
links={links}
|
||||||
title={t('userMemory.relationshipNetwork')}
|
categories={categories.map(vo => ({
|
||||||
headerType="borderless"
|
name: t(`userMemory.${vo.name}`)
|
||||||
headerClassName="rb:min-h-[46px]!"
|
})) || []}
|
||||||
// extra={
|
onNodeClick={setSelectedNode}
|
||||||
// <div
|
/>
|
||||||
// onClick={handleFullScreen}
|
|
||||||
// className="rb:group rb:cursor-pointer rb:hover:text-[#212332] rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:flex rb:items-center rb:gap-1"
|
{selectedNode &&
|
||||||
// >
|
<RbCard
|
||||||
// <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/fullScreen.svg')] rb:hover:bg-[url('@/assets/images/fullScreen_hover.svg')]"></div>
|
|
||||||
// {t('userMemory.fullScreen')}
|
|
||||||
// </div>
|
|
||||||
// }
|
|
||||||
>
|
|
||||||
<div className="rb:h-129.5 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-sm">
|
|
||||||
{nodes.length === 0 ? (
|
|
||||||
<Empty className="rb:h-full" />
|
|
||||||
) : (
|
|
||||||
<ReactEcharts
|
|
||||||
option={{
|
|
||||||
colors: colors,
|
|
||||||
tooltip: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: true,
|
|
||||||
bottom: 12,
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'graph',
|
|
||||||
layout: 'force',
|
|
||||||
data: nodes || [],
|
|
||||||
links: links || [],
|
|
||||||
categories: categories.map(vo => ({
|
|
||||||
name: t(`userMemory.${vo.name}`)
|
|
||||||
})) || [],
|
|
||||||
roam: true,
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
position: 'right',
|
|
||||||
formatter: '{b}',
|
|
||||||
},
|
|
||||||
lineStyle: {
|
|
||||||
color: '#5B6167',
|
|
||||||
curveness: 0.3
|
|
||||||
},
|
|
||||||
force: {
|
|
||||||
repulsion: 100,
|
|
||||||
// Enable category aggregation
|
|
||||||
edgeLength: 80,
|
|
||||||
gravity: 0.3,
|
|
||||||
// Nodes of the same category attract each other
|
|
||||||
layoutAnimation: true,
|
|
||||||
// Prevent layout recalculation on click
|
|
||||||
preventOverlap: true,
|
|
||||||
// Keep layout stable after node click
|
|
||||||
edgeSymbol: ['none', 'arrow'],
|
|
||||||
edgeSymbolSize: [4, 10],
|
|
||||||
// Disable force-directed after initial layout
|
|
||||||
initLayout: 'force'
|
|
||||||
},
|
|
||||||
selectedMode: 'single',
|
|
||||||
draggable: true,
|
|
||||||
// Prevent layout recalculation on data update
|
|
||||||
animationDurationUpdate: 0,
|
|
||||||
select: {
|
|
||||||
itemStyle: {
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: '#ffffff',
|
|
||||||
shadowBlur: 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}}
|
|
||||||
style={{ height: '518px', width: '100%' }}
|
|
||||||
notMerge={false}
|
|
||||||
lazyUpdate={true}
|
|
||||||
onEvents={{
|
|
||||||
// Node click event handler
|
|
||||||
click: (params: { dataType: string; data: Node; name: string }) => {
|
|
||||||
if (params.dataType === 'node') {
|
|
||||||
// Handle node click event
|
|
||||||
console.log('Node clicked:', params.data);
|
|
||||||
// Use functional update to avoid state dependency issues
|
|
||||||
setSelectedNode(params.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</RbCard>
|
|
||||||
</Col>
|
|
||||||
{/* Memory Details */}
|
|
||||||
<Col span={8}>
|
|
||||||
<RbCard
|
|
||||||
title={t('userMemory.memoryDetails')}
|
title={t('userMemory.memoryDetails')}
|
||||||
|
className="rb:absolute! rb:top-4 rb:right-0 rb:w-100! rb:bg-white!"
|
||||||
headerType="borderless"
|
headerType="borderless"
|
||||||
headerClassName="rb:min-h-[46px]!"
|
headerClassName="rb:min-h-[60px]!"
|
||||||
bodyClassName='rb:p-0!'
|
bodyClassName='rb:px-5! rb:pb-[76px]! rb:pt-0! rb:h-auto!'
|
||||||
extra={selectedNode && <Button type="text" onClick={handleViewAll}>
|
extra={<div className="rb:cursor-pointer rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/userMemory/close.svg')]" onClick={() => setSelectedNode(null)}></div>}
|
||||||
<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 rb:overflow-y-auto">
|
<div className="rb:max-h-[calc(100vh-269px)] rb:overflow-auto">
|
||||||
{!selectedNode
|
{selectedNode.name &&
|
||||||
? <Empty
|
<div className="rb:font-medium rb:text-[16px] rb:text-[#212332] rb:leading-5.5 rb:mb-3">
|
||||||
url={detailEmpty}
|
{selectedNode.name}
|
||||||
subTitle={t('userMemory.memoryDetailEmptyDesc')}
|
</div>
|
||||||
className="rb:h-full rb:mx-10 rb:text-center"
|
}
|
||||||
size={[197.81, 150]}
|
<Flex vertical gap={24}>
|
||||||
/>
|
<div>
|
||||||
: <>
|
<div className="rb:font-medium rb:leading-5">{t('userMemory.memoryContent')}</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:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-2">
|
||||||
<div className="rb:p-4">
|
{['Chunk', 'Dialogue', 'MemorySummary'].includes(selectedNode.label) && 'content' in selectedNode.properties
|
||||||
<>
|
? selectedNode.properties.content
|
||||||
<div className="rb:font-medium rb:leading-5">{t('userMemory.memoryContent')}</div>
|
: selectedNode.label === 'ExtractedEntity' && 'description' in selectedNode.properties
|
||||||
<div className="rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-1 rb:pb-4 rb:border-b rb:border-[#DFE4ED]">
|
? selectedNode.properties.description
|
||||||
{['Chunk', 'Dialogue', 'MemorySummary'].includes(selectedNode.label) && 'content' in selectedNode.properties
|
: selectedNode.label === 'Statement' && 'statement' in selectedNode.properties
|
||||||
? selectedNode.properties.content
|
|
||||||
: selectedNode.label === 'ExtractedEntity' && 'description' in selectedNode.properties
|
|
||||||
? selectedNode.properties.description
|
|
||||||
: selectedNode.label === 'Statement' && 'statement' in selectedNode.properties
|
|
||||||
? selectedNode.properties.statement
|
? selectedNode.properties.statement
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
<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: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')}
|
|
||||||
</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) || typeof statementProps[key] === 'string') {
|
|
||||||
console.log('statementProps[key]', 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]">
|
|
||||||
{Array.isArray(entityProps[key]) && entityProps[key].length > 0
|
|
||||||
? entityProps[key].map((vo, index) => <div key={index}>- {vo}</div>)
|
|
||||||
: entityProps[key]
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
})}
|
|
||||||
</>}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
}
|
|
||||||
|
<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-2">
|
||||||
|
{dayjs(selectedNode?.properties.created_at).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedNode?.properties.associative_memory > 0 && <div>
|
||||||
|
<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-2">
|
||||||
|
<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) || typeof statementProps[key] === 'string') {
|
||||||
|
return (
|
||||||
|
<div key={key}>
|
||||||
|
<div className="rb:font-medium rb:leading-5">{t(`userMemory.Statement_${key}`)}</div>
|
||||||
|
<div className="rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-2">
|
||||||
|
{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 key={key}>
|
||||||
|
<div className="rb:font-medium rb:leading-5">{t(`userMemory.ExtractedEntity_${key}`)}</div>
|
||||||
|
<div className="rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-2">
|
||||||
|
{Array.isArray(entityProps[key]) && entityProps[key].length > 0
|
||||||
|
? entityProps[key].map((vo, index) => <div key={index}>- {vo}</div>)
|
||||||
|
: entityProps[key]
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
</>}
|
||||||
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Flex align="center" justify="center" className="rb:absolute rb:bottom-3 rb:left-6 rb:right-6 rb:border rb:border-[#171719] rb:rounded-xl rb:h-11 rb:font-medium rb:leading-5 rb:cursor-pointer" onClick={handleViewAll}>
|
||||||
|
{t('userMemory.completeMemory')}
|
||||||
|
</Flex>
|
||||||
</RbCard>
|
</RbCard>
|
||||||
</Col>
|
}
|
||||||
</Row>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/** Use React.memo to avoid unnecessary renders */
|
/** Use React.memo to avoid unnecessary renders */
|
||||||
|
|||||||
Reference in New Issue
Block a user