Merge pull request #893 from SuanmoSuanyangTechnology/feature/app_zy

Feature/app zy
This commit is contained in:
yingzhao
2026-04-14 17:04:51 +08:00
committed by GitHub
14 changed files with 159 additions and 30 deletions

View File

@@ -16,7 +16,7 @@ import {
ConfigProvider, ConfigProvider,
App as AntdApp App as AntdApp
} from 'antd'; } from 'antd';
import { useTranslation } from 'react-i18next'; import i18n from 'i18next';
import { lightTheme } from './styles/antdThemeConfig.ts' import { lightTheme } from './styles/antdThemeConfig.ts'
import router from './routes'; import router from './routes';
@@ -29,11 +29,58 @@ import 'dayjs/plugin/utc'
import { cookieUtils } from './utils/request'; import { cookieUtils } from './utils/request';
import { useUser } from '@/store/user'; import { useUser } from '@/store/user';
import menuJson from '@/store/menu.json';
type MenuEntry = { path: string; i18nKey: string };
function flattenMenuEntries(list: any[]): MenuEntry[] {
const result: MenuEntry[] = [];
for (const item of list) {
if (item.path && item.i18nKey && item.type !== 'group') result.push({ path: item.path, i18nKey: item.i18nKey });
if (item.subs?.length) result.push(...flattenMenuEntries(item.subs));
}
return result;
}
const menuEntries: MenuEntry[] = flattenMenuEntries([...menuJson.manage, ...menuJson.space]);
function pathMatches(pattern: string, path: string): boolean {
if (pattern === path) return true;
if (pattern.includes(':')) {
return new RegExp('^' + pattern.replace(/:[\w-]+/g, '[^/]+') + '$').test(path);
}
return false;
}
function getPageTitle(pathname: string): string {
const appName = i18n.t('memoryBear');
const entry = menuEntries.find(e => pathMatches(e.path, pathname));
if (!entry) return appName;
return `${i18n.t(entry.i18nKey)} - ${appName}`;
}
const SKIP_TITLE_PATTERNS = [
'/user-memory/detail/:id/:type',
'/forgetting-engine/:id',
'/memory-extraction-engine/:id',
'/emotion-engine/:id',
'/reflection-engine/:id',
];
function App() { function App() {
const { t } = useTranslation();
const { locale, language, timeZone } = useI18n() const { locale, language, timeZone } = useI18n()
const { checkJump } = useUser(); const { checkJump } = useUser();
useEffect(() => {
const unsubscribe = router.subscribe(({ location }) => {
if (SKIP_TITLE_PATTERNS.some(p => pathMatches(p, location.pathname))) return;
document.title = getPageTitle(location.pathname);
});
return () => unsubscribe();
}, [])
useEffect(() => { useEffect(() => {
const authToken = cookieUtils.get('authToken') const authToken = cookieUtils.get('authToken')
if (!authToken && !window.location.hash.includes('#/login') && !window.location.hash.includes('#/conversation/') && !window.location.hash.includes('#/jump') && !window.location.hash.includes('#/invite-register')) { if (!authToken && !window.location.hash.includes('#/login') && !window.location.hash.includes('#/conversation/') && !window.location.hash.includes('#/jump') && !window.location.hash.includes('#/invite-register')) {
@@ -44,7 +91,9 @@ function App() {
}, []) }, [])
useEffect(() => { useEffect(() => {
document.title = t('memoryBear') if (!SKIP_TITLE_PATTERNS.some(p => pathMatches(p, router.state.location.pathname))) {
document.title = getPageTitle(router.state.location.pathname)
}
dayjs.locale(language) dayjs.locale(language)
localStorage.setItem('language', language) localStorage.setItem('language', language)
}, [language]) }, [language])

View File

@@ -1,8 +1,8 @@
/* /*
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-02 16:24:44 * @Date: 2026-02-02 16:24:44
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 16:24:44 * @Last Modified time: 2026-04-14 16:52:43
*/ */
/** /**
* useBreadcrumbManager Hook * useBreadcrumbManager Hook
@@ -18,8 +18,10 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next'
import { useMenu } from '@/store/menu'; import { useMenu } from '@/store/menu';
import type { MenuItem } from '@/store/menu'; import type { MenuItem } from '@/store/menu';
import { useI18n } from '@/store/locale'
/** Breadcrumb item interface */ /** Breadcrumb item interface */
export interface BreadcrumbItem { export interface BreadcrumbItem {
@@ -53,6 +55,8 @@ export interface BreadcrumbOptions {
export const useBreadcrumbManager = (options?: BreadcrumbOptions) => { export const useBreadcrumbManager = (options?: BreadcrumbOptions) => {
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu(); const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation()
const { language } = useI18n()
/** Update breadcrumbs based on current path and type */ /** Update breadcrumbs based on current path and type */
const updateBreadcrumbs = useCallback((breadcrumbPath: BreadcrumbPath) => { const updateBreadcrumbs = useCallback((breadcrumbPath: BreadcrumbPath) => {
@@ -336,10 +340,10 @@ export const useBreadcrumbManager = (options?: BreadcrumbOptions) => {
/** Use different keys based on breadcrumb type to implement independent breadcrumb paths */ /** Use different keys based on breadcrumb type to implement independent breadcrumb paths */
const breadcrumbKey = breadcrumbType === 'list' ? 'space' : 'space-detail'; const breadcrumbKey = breadcrumbType === 'list' ? 'space' : 'space-detail';
const lastMenu = customBreadcrumbs[customBreadcrumbs.length - 1]
document.title = `${lastMenu.i18nKey ? t(lastMenu.i18nKey) : lastMenu.label} - ${t('memoryBear') }`;
setCustomBreadcrumbs(customBreadcrumbs, breadcrumbKey); setCustomBreadcrumbs(customBreadcrumbs, breadcrumbKey);
}, [setCustomBreadcrumbs, navigate, options?.breadcrumbType, options?.onKnowledgeBaseMenuClick, options?.onKnowledgeBaseFolderClick]); }, [setCustomBreadcrumbs, navigate, options?.breadcrumbType, options?.onKnowledgeBaseMenuClick, options?.onKnowledgeBaseFolderClick, language]);
return { return {
updateBreadcrumbs, updateBreadcrumbs,

View File

@@ -6,7 +6,7 @@
"code": "workbench", "code": "workbench",
"label": "workbench", "label": "workbench",
"i18nKey": "menu.workbench", "i18nKey": "menu.workbench",
"path": "/", "path": null,
"enable": true, "enable": true,
"display": true, "display": true,
"level": 1, "level": 1,
@@ -174,7 +174,7 @@
"code": "workbench", "code": "workbench",
"label": "workbench", "label": "workbench",
"i18nKey": "menu.workbench", "i18nKey": "menu.workbench",
"path": "/", "path": null,
"enable": true, "enable": true,
"display": true, "display": true,
"level": 1, "level": 1,
@@ -421,7 +421,20 @@
"display": false, "display": false,
"level": 3, "level": 3,
"sort": 0, "sort": 0,
"subs": [] "subs": [
{
"id": 2211,
"parent": 221,
"code": "userMemoryDetail",
"label": "记忆详情",
"i18nKey": "menu.userMemoryDetail",
"path": "/user-memory/detail/:id/:type",
"enable": true,
"display": false,
"level": 3,
"sort": 0
}
]
}, },
{ {
"id": 222, "id": 222,

View File

@@ -2,11 +2,12 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:29:37 * @Date: 2026-02-03 16:29:37
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-26 15:37:18 * @Last Modified time: 2026-04-14 16:53:27
*/ */
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { Flex } from 'antd' import { Flex } from 'antd'
import { useTranslation } from 'react-i18next'
import ConfigHeader from './components/ConfigHeader' import ConfigHeader from './components/ConfigHeader'
import type { AgentRef, ClusterRef, WorkflowRef, Config } from './types' import type { AgentRef, ClusterRef, WorkflowRef, Config } from './types'
@@ -21,6 +22,7 @@ import Statistics from './Statistics'
import TestChat from './TestChat' import TestChat from './TestChat'
import type { WorkflowConfig } from '@/views/Workflow/types'; import type { WorkflowConfig } from '@/views/Workflow/types';
import Logs from './Logs'; import Logs from './Logs';
import { useI18n } from '@/store/locale'
/** /**
* Application configuration page component * Application configuration page component
@@ -30,6 +32,8 @@ import Logs from './Logs';
const ApplicationConfig: React.FC = () => { const ApplicationConfig: React.FC = () => {
// Hooks // Hooks
const { id, source } = useParams(); const { id, source } = useParams();
const { t } = useTranslation()
const { language } = useI18n()
// Refs for different application types // Refs for different application types
const agentRef = useRef<AgentRef>(null) const agentRef = useRef<AgentRef>(null)
@@ -95,6 +99,13 @@ const ApplicationConfig: React.FC = () => {
getApplicationInfo() getApplicationInfo()
}, [id]) }, [id])
useEffect(() => {
if (application?.name) {
const appName = t('memoryBear');
document.title = `${application.name} - ${appName}`;
}
}, [application?.name, language])
/** /**
* Fetch application information * Fetch application information
*/ */

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:56:54 * @Date: 2026-02-03 16:56:54
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-26 15:43:29 * @Last Modified time: 2026-04-14 16:59:16
*/ */
/** /**
* Emotion Engine Configuration Page * Emotion Engine Configuration Page
@@ -25,6 +25,7 @@ import DescWrapper from '@/components/FormItem/DescWrapper'
import RbSlider from '@/components/RbSlider'; import RbSlider from '@/components/RbSlider';
import RbAlert from '@/components/RbAlert'; import RbAlert from '@/components/RbAlert';
import ModelSelect from '@/components/ModelSelect'; import ModelSelect from '@/components/ModelSelect';
import { useI18n } from '@/store/locale'
/** /**
* Configuration field definitions * Configuration field definitions
@@ -69,9 +70,14 @@ const EmotionEngine: React.FC = () => {
const [form] = Form.useForm<ConfigForm>(); const [form] = Form.useForm<ConfigForm>();
const { message: messageApi } = App.useApp(); const { message: messageApi } = App.useApp();
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const { language } = useI18n()
const values = Form.useWatch([], form); const values = Form.useWatch([], form);
useEffect(() => {
document.title = [document.title.split(' - ')[0], t('memoryBear')].join(' - ')
}, [language])
useEffect(() => { useEffect(() => {
getConfigData() getConfigData()
}, [id]) }, [id])

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 17:00:12 * @Date: 2026-02-03 17:00:12
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-26 15:47:37 * @Last Modified time: 2026-04-14 16:54:38
*/ */
/** /**
* Forgetting Engine Configuration Page * Forgetting Engine Configuration Page
@@ -22,6 +22,7 @@ import type { ConfigForm } from './types'
import SwitchFormItem from '@/components/FormItem/SwitchFormItem' import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
import RbSlider from '@/components/RbSlider'; import RbSlider from '@/components/RbSlider';
import DescWrapper from '@/components/FormItem/DescWrapper' import DescWrapper from '@/components/FormItem/DescWrapper'
import { useI18n } from '@/store/locale'
/** /**
* Configuration field definitions * Configuration field definitions
@@ -109,9 +110,14 @@ const ForgettingEngine: React.FC = () => {
const [form] = Form.useForm<ConfigForm>(); const [form] = Form.useForm<ConfigForm>();
const { message: messageApi } = App.useApp(); const { message: messageApi } = App.useApp();
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const { language } = useI18n()
const values = Form.useWatch([], form); const values = Form.useWatch([], form);
useEffect(() => {
document.title = [document.title.split(' - ')[0], t('memoryBear')].join(' - ')
}, [language])
useEffect(() => { useEffect(() => {
getConfigData() getConfigData()
}, []) }, [])
@@ -182,6 +188,7 @@ const ForgettingEngine: React.FC = () => {
if (config.type === 'button') { if (config.type === 'button') {
return ( return (
<SwitchFormItem <SwitchFormItem
key={config.key}
title={t(`forgettingEngine.${config.key}`)} title={t(`forgettingEngine.${config.key}`)}
name={config.name} name={config.name}
desc={config.type && <span>{t(`forgettingEngine.type`)}: {config.type}</span>} desc={config.type && <span>{t(`forgettingEngine.type`)}: {config.type}</span>}

View File

@@ -328,6 +328,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
<Space size={24} className="rb:mt-4! rb:mb-3!"> <Space size={24} className="rb:mt-4! rb:mb-3!">
{['processData', 'finalResult'].map(tab => ( {['processData', 'finalResult'].map(tab => (
<div <div
key={tab}
className={clsx('rb:font-[MiSans-Bold] rb:font-bold rb:leading-5 rb:cursor-pointer', { className={clsx('rb:font-[MiSans-Bold] rb:font-bold rb:leading-5 rb:cursor-pointer', {
'rb:text-[#212332]': activeTab === tab, 'rb:text-[#212332]': activeTab === tab,
'rb:text-[#A8A9AA]': activeTab !== tab, 'rb:text-[#A8A9AA]': activeTab !== tab,

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 17:30:02 * @Date: 2026-02-03 17:30:02
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-26 15:45:42 * @Last Modified time: 2026-04-14 16:54:40
*/ */
/** /**
* Memory Extraction Engine Configuration Page * Memory Extraction Engine Configuration Page
@@ -27,6 +27,7 @@ import ModelSelect from '@/components/ModelSelect'
import RbSlider from '@/components/RbSlider'; import RbSlider from '@/components/RbSlider';
import DescWrapper from '@/components/FormItem/DescWrapper' import DescWrapper from '@/components/FormItem/DescWrapper'
import LabelWrapper from '@/components/FormItem/LabelWrapper' import LabelWrapper from '@/components/FormItem/LabelWrapper'
import { useI18n } from '@/store/locale'
/** Available configuration section keys */ /** Available configuration section keys */
const keys = [ const keys = [
@@ -54,12 +55,17 @@ const MemoryExtractionEngine: FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { message } = App.useApp(); const { message } = App.useApp();
const { id } = useParams() const { id } = useParams()
const { language } = useI18n()
const [expandedKeys, setExpandedKeys] = useState<string[]>(keys) const [expandedKeys, setExpandedKeys] = useState<string[]>(keys)
const [form] = Form.useForm<ConfigForm>() const [form] = Form.useForm<ConfigForm>()
const values = Form.useWatch<ConfigForm>([], form) const values = Form.useWatch<ConfigForm>([], form)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [iterationPeriodDisabled, setIterationPeriodDisabled] = useState(false) const [iterationPeriodDisabled, setIterationPeriodDisabled] = useState(false)
useEffect(() => {
document.title = [document.title.split(' - ')[0], t('memoryBear')].join(' - ')
}, [language])
useEffect(() => { useEffect(() => {
if (values?.reflexion_range === 'database') { if (values?.reflexion_range === 'database') {
form.setFieldValue('iteration_period', 24) form.setFieldValue('iteration_period', 24)

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 17:33:15 * @Date: 2026-02-03 17:33:15
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-26 14:56:00 * @Last Modified time: 2026-04-14 16:17:29
*/ */
/** /**
* Memory Management Page * Memory Management Page
@@ -74,7 +74,8 @@ const MemoryManagement: React.FC = () => {
}; };
/** Navigate to engine configuration page */ /** Navigate to engine configuration page */
const handleClick = (id: number, type: string) => { const handleClick = (id: number, type: string, config_name: string) => {
document.title = `${config_name} - ${t('memoryBear')}`;
switch (type) { switch (type) {
case 'memoryExtractionEngine': case 'memoryExtractionEngine':
navigate(`/memory-extraction-engine/${id}`) navigate(`/memory-extraction-engine/${id}`)
@@ -130,7 +131,7 @@ const MemoryManagement: React.FC = () => {
align="center" align="center"
justify="space-between" justify="space-between"
className="rb:cursor-pointer rb:bg-[#F6F6F6] rb:h-8 rb:rounded-lg rb:font-medium rb:leading-5 rb:pl-2! rb:pr-1! rb:hover:shadow-[0px_2px_8px_0px_rgba(23,23,25,0.16)]" className="rb:cursor-pointer rb:bg-[#F6F6F6] rb:h-8 rb:rounded-lg rb:font-medium rb:leading-5 rb:pl-2! rb:pr-1! rb:hover:shadow-[0px_2px_8px_0px_rgba(23,23,25,0.16)]"
onClick={() => handleClick(item.config_id, key)} onClick={() => handleClick(item.config_id, key, item.config_name)}
> >
{t(`memory.${key}`)} {t(`memory.${key}`)}
<div <div

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 14:10:20 * @Date: 2026-02-03 14:10:20
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-26 18:55:37 * @Last Modified time: 2026-04-14 16:55:26
*/ */
import { type FC, useEffect, useState, useRef } from 'react' import { type FC, useEffect, useState, useRef } from 'react'
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
@@ -18,6 +18,7 @@ import SearchInput from '@/components/SearchInput';
import OntologyClassExtractModal from '../components/OntologyClassExtractModal' import OntologyClassExtractModal from '../components/OntologyClassExtractModal'
import BodyWrapper from '@/components/Empty/BodyWrapper' import BodyWrapper from '@/components/Empty/BodyWrapper'
import Tag from '@/components/Tag' import Tag from '@/components/Tag'
import { useI18n } from '@/store/locale'
/** /**
* Ontology detail page component * Ontology detail page component
@@ -29,6 +30,7 @@ const Detail: FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const { id } = useParams() const { id } = useParams()
const { modal, message } = App.useApp() const { modal, message } = App.useApp()
const { language } = useI18n()
// Refs // Refs
const ontologyClassModalRef = useRef<OntologyClassModalRef>(null) const ontologyClassModalRef = useRef<OntologyClassModalRef>(null)
@@ -46,6 +48,10 @@ const Detail: FC = () => {
getData() getData()
}, [id, query]) }, [id, query])
useEffect(() => {
document.title = `${data.scene_name} - ${t('memoryBear')}`;
}, [data.scene_name, language])
/** /**
* Fetch ontology class list data * Fetch ontology class list data
*/ */

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 17:46:47 * @Date: 2026-02-03 17:46:47
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-26 18:57:08 * @Last Modified time: 2026-04-14 16:59:56
*/ */
/** /**
* Self Reflection Engine Configuration Page * Self Reflection Engine Configuration Page
@@ -99,6 +99,10 @@ const SelfReflectionEngine: React.FC = () => {
const values = Form.useWatch([], form); const values = Form.useWatch([], form);
useEffect(() => {
document.title = [document.title.split(' - ')[0], t('memoryBear')].join(' - ')
}, [language])
useEffect(() => { useEffect(() => {
getConfigData() getConfigData()
}, [id]) }, [id])
@@ -242,6 +246,7 @@ const SelfReflectionEngine: React.FC = () => {
return ( return (
<SwitchFormItem <SwitchFormItem
key={config.key}
title={t(`reflectionEngine.${config.key}`)} title={t(`reflectionEngine.${config.key}`)}
name={config.key} name={config.key}
desc={<> desc={<>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-05 10:44:08 * @Date: 2026-02-05 10:44:08
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-05 10:56:28 * @Last Modified time: 2026-04-14 16:57:52
*/ */
import { type FC, useEffect, useRef, useState } from "react"; import { type FC, useEffect, useRef, useState } from "react";
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -17,6 +17,7 @@ import type { SkillFormData } from '../types'
import { getSkillDetail, createSkill, updateSkill } from '@/api/skill' import { getSkillDetail, createSkill, updateSkill } from '@/api/skill'
import { stringRegExp } from '@/utils/validator'; import { stringRegExp } from '@/utils/validator';
import PageHeader from '@/components/Layout/PageHeader' import PageHeader from '@/components/Layout/PageHeader'
import { useI18n } from '@/store/locale'
/** /**
* Skill Configuration Page Component * Skill Configuration Page Component
@@ -43,6 +44,7 @@ const SkillConfig: FC = () => {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [form] = Form.useForm<SkillFormData>(); const [form] = Form.useForm<SkillFormData>();
const [data, setData] = useState<SkillFormData | null>(null) const [data, setData] = useState<SkillFormData | null>(null)
const { language } = useI18n()
/** /**
* Effect: Load skill data if editing existing skill * Effect: Load skill data if editing existing skill
@@ -77,6 +79,11 @@ const SkillConfig: FC = () => {
}) })
} }
useEffect(() => {
if (!data) return;
document.title = `${data?.name} - ${t('memoryBear')}`;
}, [language, data?.name])
const aiPromptModalRef = useRef<AiPromptModalRef>(null) const aiPromptModalRef = useRef<AiPromptModalRef>(null)
/** /**

View File

@@ -2,7 +2,7 @@
* @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-03-26 18:59:53 * @Last Modified time: 2026-04-14 16:57:59
*/ */
/** /**
* Neo4j User Memory Detail View * Neo4j User Memory Detail View
@@ -10,7 +10,7 @@
* Shows profile, interests, node statistics, relationships, and insights * Shows profile, interests, node statistics, relationships, and insights
*/ */
import { type FC, useRef, useState, type MouseEvent } from 'react' import { type FC, useRef, useState, type MouseEvent, useEffect } from 'react'
import clsx from 'clsx' import clsx from 'clsx'
import { useParams, useNavigate } from 'react-router-dom' import { useParams, useNavigate } from 'react-router-dom'
import { Flex, Popover } from 'antd' import { Flex, Popover } from 'antd'
@@ -22,10 +22,11 @@ import InterestDistribution from './components/InterestDistribution'
import NodeStatistics from './components/NodeStatistics' import NodeStatistics from './components/NodeStatistics'
import RelationshipNetwork from './components/RelationshipNetwork' import RelationshipNetwork from './components/RelationshipNetwork'
import MemoryInsight from './components/MemoryInsight' import MemoryInsight from './components/MemoryInsight'
import type { EndUserProfileRef, MemoryInsightRef, AboutMeRef } from './types' import type { EndUserProfileRef, MemoryInsightRef, AboutMeRef, EndUser } from './types'
import { import {
analyticsRefresh, analyticsRefresh,
} from '@/api/memory' } from '@/api/memory'
import { useI18n } from '@/store/locale'
const Neo4j: FC = () => { const Neo4j: FC = () => {
const { id } = useParams() const { id } = useParams()
@@ -33,15 +34,21 @@ const Neo4j: FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [name, setName] = useState('') const [name, setName] = useState('')
const { language } = useI18n()
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) const [selectedKey, setSelectedKey] = useState<string | null>(null)
/** Update displayed name */ /** Update displayed name */
const handleNameUpdate = (data: { other_name?: string; id: string }) => { const handleNameUpdate = (data?: EndUser) => {
setName(data.other_name && data.other_name !== '' ? data.other_name : data.id) if (!data) return
let name = data.other_name && data.other_name !== '' ? data.other_name : data.id || data.end_user_id
setName(name)
} }
useEffect(() => {
document.title = `${name} - ${t('memoryBear')}`;
}, [name, language])
/** Navigate back */ /** Navigate back */
const goBack = () => { const goBack = () => {

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 17:57:11 * @Date: 2026-02-03 17:57:11
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-31 15:29:45 * @Last Modified time: 2026-04-14 16:56:36
*/ */
/** /**
* RAG User Memory Detail View * RAG User Memory Detail View
@@ -26,6 +26,7 @@ import {
} from '@/api/memory' } from '@/api/memory'
import Empty from '@/components/Empty' import Empty from '@/components/Empty'
import ConversationMemory from './components/ConversationMemory' import ConversationMemory from './components/ConversationMemory'
import { useI18n } from '@/store/locale'
/** /**
* Title component props * Title component props
@@ -45,6 +46,7 @@ const Title: FC<TitleProps> = ({ title, iconClassName }) => (
const Rag: FC = () => { const Rag: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { id } = useParams() const { id } = useParams()
const { language } = useI18n()
const [data, setData] = useState<Data | null>(null) const [data, setData] = useState<Data | null>(null)
const [summary, setSummary] = useState<string | null>('') const [summary, setSummary] = useState<string | null>('')
const [loading, setLoading] = useState<Record<string, boolean>>({ const [loading, setLoading] = useState<Record<string, boolean>>({
@@ -97,6 +99,10 @@ const Rag: FC = () => {
} }
const name = loading.detail ? '' : data?.name && data?.name !== '' ? data.name : id const name = loading.detail ? '' : data?.name && data?.name !== '' ? data.name : id
useEffect(() => {
document.title = `${name} - ${t('memoryBear')}`;
}, [name, language])
const [refreshLoading, setRefreshLoading] = useState(false) const [refreshLoading, setRefreshLoading] = useState(false)
const handleRefresh = () => { const handleRefresh = () => {
if (refreshLoading || !id) return if (refreshLoading || !id) return