diff --git a/web/src/api/memory.ts b/web/src/api/memory.ts index 3c0fe6fa..2ecc077f 100644 --- a/web/src/api/memory.ts +++ b/web/src/api/memory.ts @@ -176,10 +176,10 @@ export const getPerceptualTimeline = (end_user: string) => { } // 情景记忆-总览 export const getEpisodicOverview = (data: { end_user_id: string; time_range: string; episodic_type: string; } ) => { - return request.post(`/memory-storage/classifications/episodic-memory`, data) + return request.post(`/memory/episodic-memory/overview`, data) } export const getEpisodicDetail = (data: { end_user_id: string; summary_id: string; } ) => { - return request.post(`/memory-storage/classifications/episodic-memory-details`, data) + return request.post(`/memory/episodic-memory/details`, data) } // 关系演化 export const getRelationshipEvolution = (data: { id: string; label: string; } ) => { @@ -190,10 +190,10 @@ export const getTimelineMemories = (data: { id: string; label: string; }) => { return request.get(`/memory-storage/memory_space/timeline_memories`, data) } export const getExplicitMemory = (end_user_id: string) => { - return request.post(`/memory-storage/classifications/explicit-memory`, { end_user_id }) + return request.post(`/memory/explicit-memory/overview`, { end_user_id }) } export const getExplicitMemoryDetails = (data: { end_user_id: string, memory_id: string; }) => { - return request.post(`/memory-storage/classifications/explicit-memory-details`, data) + return request.post(`/memory/explicit-memory/details`, data) } export const getConversations = (end_user: string) => { return request.get(`/memory/work/${end_user}/conversations`) diff --git a/web/src/assets/images/menu/spaceConfig.svg b/web/src/assets/images/menu/spaceConfig.svg new file mode 100644 index 00000000..bcfeae12 --- /dev/null +++ b/web/src/assets/images/menu/spaceConfig.svg @@ -0,0 +1,17 @@ + + + 模型 (1) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/menu/spaceConfig_active.svg b/web/src/assets/images/menu/spaceConfig_active.svg new file mode 100644 index 00000000..41b25689 --- /dev/null +++ b/web/src/assets/images/menu/spaceConfig_active.svg @@ -0,0 +1,17 @@ + + + 模型 (1) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/userMemory/goto.svg b/web/src/assets/images/userMemory/goto.svg new file mode 100644 index 00000000..a66e2011 --- /dev/null +++ b/web/src/assets/images/userMemory/goto.svg @@ -0,0 +1,19 @@ + + + 编组 13备份 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/components/CustomSelect/index.tsx b/web/src/components/CustomSelect/index.tsx index 97ca4e4b..e9ccce74 100644 --- a/web/src/components/CustomSelect/index.tsx +++ b/web/src/components/CustomSelect/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useCallback, useRef, type FC, type Key } from 'react'; +import { useEffect, useState, type FC, type Key } from 'react'; import { Select } from 'antd' import type { SelectProps, DefaultOptionType } from 'antd/es/select' import { useTranslation } from 'react-i18next'; @@ -26,7 +26,7 @@ interface CustomSelectProps extends Omit { disabled?: boolean; style?: React.CSSProperties; className?: string; - filterOption?: (inputValue: string, option: DefaultOptionType) => boolean; + filterOption?: (inputValue: string, option?: DefaultOptionType) => boolean; } interface OptionType { [key: string]: Key | string | number; @@ -48,44 +48,27 @@ const CustomSelect: FC = ({ }) => { const { t } = useTranslation(); const [options, setOptions] = useState([]); - // 创建防抖定时器引用 - const debounceRef = useRef(); - - // 防抖搜索函数 - const handleSearch = useCallback((value?: string) => { - // 清除之前的定时器 - if (debounceRef.current) { - clearTimeout(debounceRef.current); - } - - // 设置新的定时器 - debounceRef.current = window.setTimeout(() => { - request.get>(url, {...params, [optionFilterProp]: value}).then((res) => { - const data = res; - setOptions(Array.isArray(data) ? data || [] : Array.isArray(data?.items) ? data.items || [] : []); - }); - }, 300); // 300毫秒防抖延迟 - }, [url, params, optionFilterProp]); + // 默认模糊搜索函数 + const defaultFilterOption = (inputValue: string, option?: DefaultOptionType) => { + if (!option || !inputValue) return true; + const label = String(option.children || option.label || ''); + return label.toLowerCase().includes(inputValue.toLowerCase()); + }; // 组件挂载时获取初始数据 useEffect(() => { - handleSearch(); - - // 组件卸载时清除定时器 - return () => { - if (debounceRef.current) { - clearTimeout(debounceRef.current); - } - }; - }, [url, handleSearch]); + request.get>(url, params).then((res) => { + const data = res; + setOptions(Array.isArray(data) ? data || [] : Array.isArray(data?.items) ? data.items || [] : []); + }); + }, []); return ( {hasAll && ({allTitle || t('common.all')})} diff --git a/web/src/components/SiderMenu/index.tsx b/web/src/components/SiderMenu/index.tsx index a39a0b4e..fd9bfea9 100644 --- a/web/src/components/SiderMenu/index.tsx +++ b/web/src/components/SiderMenu/index.tsx @@ -40,6 +40,8 @@ import apiKeyIcon from '@/assets/images/menu/apiKey.png'; import apiKeyActiveIcon from '@/assets/images/menu/apiKey_active.png'; import pricingIcon from '@/assets/images/menu/pricing.svg' import pricingActiveIcon from '@/assets/images/menu/pricing_active.svg' +import spaceConfigIcon from '@/assets/images/menu/spaceConfig.svg' +import spaceConfigActiveIcon from '@/assets/images/menu/spaceConfig_active.svg' // 图标路径映射表 const iconPathMap: Record = { @@ -68,7 +70,9 @@ const iconPathMap: Record = { 'apiKey': apiKeyIcon, 'apiKeyActive': apiKeyActiveIcon, 'pricing': pricingIcon, - 'pricingActive': pricingActiveIcon + 'pricingActive': pricingActiveIcon, + 'spaceConfig': spaceConfigIcon, + 'spaceConfigActive': spaceConfigActiveIcon, }; const { Sider } = Layout; diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 40510a8b..68da328d 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -95,6 +95,7 @@ export const en = { pricing: 'Pricing Management', orderPayment: 'Order Payment', orderHistory: 'Order History', + spaceConfig: 'Space Configuration' }, dashboard: { total_models: 'Total number of available models', @@ -1250,6 +1251,12 @@ export const en = { negative: 'Negative Emotion', neutral: 'Neutral Emotion', interactionCountData: 'Interaction Count', + capacity: 'Capacity', + type: 'Type', + person: 'Personal', + memoryNum: 'memories', + memory_config_name: 'Memory Engine', + searchPlaceholder: 'Search memory store name', }, space: { createSpace: 'Create Space', @@ -1265,7 +1272,8 @@ export const en = { neo4jDesc: 'Based on knowledge graph, suitable for relational reasoning and path query', llmModel: 'LLM Model', embeddingModel: 'Embedding Model', - rerankModel: 'Rerank Model' + rerankModel: 'Rerank Model', + configAlert: 'Space model configuration ensures that the space can correctly call the corresponding models to process business data during runtime.', }, memoryExtractionEngine: { title: 'Memory Engine Module Configuration Center', @@ -1594,10 +1602,10 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re JsonTool_desc: 'Data Format Conversion', JsonTool_features: 'JSON formatting, compression, validation and conversion functions', - jsonFormat: 'JSON Formatting', - jsonGzip: 'JSON Compression', - jsonCheck: 'JSON Validation', - jsonConversion: 'Format Conversion', + jsonParse: 'JSON Parse', + jsonInsert: 'JSON Insert', + jsonReplace: 'JSON Validation', + jsonDelete: 'JSON Delete', jsonEg: 'Example JSON', enterJson: 'Enter JSON', jsonPlaceholder: 'Enter JSON data, e.g.: {"name": "test", "value": 123}', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index e1d87cf1..8697818e 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -95,6 +95,7 @@ export const zh = { pricing: '收费管理', orderPayment: '订单支付', orderHistory: '订单记录', + spaceConfig: '空间配置' }, knowledgeBase: { home: '首页', @@ -1328,6 +1329,12 @@ export const zh = { negative: '负向情绪', neutral: '中性情绪', interactionCountData: '互动次数', + capacity: '容量', + type: '类型', + person: '个人', + memoryNum: '条记忆', + memory_config_name: '记忆引擎', + searchPlaceholder: '搜索记忆库名称', }, space: { createSpace: '创建空间', @@ -1343,7 +1350,8 @@ export const zh = { neo4jDesc: '基于知识图谱,适合关系推理和路径查询', llmModel: 'LLM 模型', embeddingModel: 'Embedding 模型', - rerankModel: 'Rerank 模型' + rerankModel: 'Rerank 模型', + configAlert: '空间模型配置为空间的模型模型,保障空间运行时能正确的调用到相应的模型来处理业务数据。', }, memoryExtractionEngine: { title: '记忆引擎模块配置中心', @@ -1690,10 +1698,10 @@ export const zh = { JsonTool_desc: '数据格式转换', JsonTool_features: 'JSON格式化、压缩、验证和转换功能', - jsonFormat: 'JSON格式化', - jsonGzip: 'JSON压缩', - jsonCheck: 'JSON验证', - jsonConversion: '格式转换', + jsonParse: 'JSON解析', + jsonInsert: 'JSON插入', + jsonReplace: 'JSON验证', + jsonDelete: 'JSON删除', jsonEg: '示例JSON', enterJson: '输入JSON', jsonPlaceholder: '输入JSON数据,例如:{"name": "测试", "value": 123}', diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index f3bd9c2d..5c302565 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -66,6 +66,7 @@ const componentMap: Record>> = OrderHistory: lazy(() => import('@/views/OrderHistory')), Pricing: lazy(() => import('@/views/Pricing')), ToolManagement: lazy(() => import('@/views/ToolManagement')), + SpaceConfig: lazy(() => import('@/views/SpaceConfig')), Login: lazy(() => import('@/views/Login')), InviteRegister: lazy(() => import('@/views/InviteRegister')), NoPermission: lazy(() => import('@/views/NoPermission')), diff --git a/web/src/routes/routes.json b/web/src/routes/routes.json index ca6a3271..db0c1b7d 100644 --- a/web/src/routes/routes.json +++ b/web/src/routes/routes.json @@ -33,6 +33,7 @@ { "path": "/api-key", "element": "ApiKeyManagement" }, { "path": "/emotion-engine/:id", "element": "EmotionEngine" }, { "path": "/reflection-engine/:id", "element": "SelfReflectionEngine" }, + { "path": "/space-config", "element": "SpaceConfig" }, { "path": "/no-permission", "element": "NoPermission" }, { "path": "/*", "element": "NotFound" } ] diff --git a/web/src/store/menu.json b/web/src/store/menu.json index 820aaa14..b49788a8 100644 --- a/web/src/store/menu.json +++ b/web/src/store/menu.json @@ -376,6 +376,21 @@ "icon": null, "iconActive": null, "subs": null + }, + { + "id": 12, + "parent": 0, + "code": "spaceConfig", + "label": "空间配置", + "i18nKey": "menu.spaceConfig", + "path": "/space-config", + "enable": true, + "display": true, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": null } ] } \ No newline at end of file diff --git a/web/src/views/SpaceConfig/index.tsx b/web/src/views/SpaceConfig/index.tsx new file mode 100644 index 00000000..ad99e220 --- /dev/null +++ b/web/src/views/SpaceConfig/index.tsx @@ -0,0 +1,118 @@ +import { type FC, useEffect, useState } from 'react'; +import { Form, App, Button, Skeleton } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import type { SpaceConfigData } from './types' +import { getWorkspaceModels, updateWorkspaceModels } from '@/api/workspaces' +import { getModelListUrl } from '@/api/models' +import CustomSelect from '@/components/CustomSelect' +import RbAlert from '@/components/RbAlert'; + +const SpaceConfig: FC = () => { + const { t } = useTranslation(); + const { message } = App.useApp(); + const [pageLoading, setPageLoding] = useState(false) + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false) + + const values = Form.useWatch([], form); + + useEffect(() => { + setPageLoding(true) + getWorkspaceModels().then((res) => { + const { llm, embedding, rerank } = res as SpaceConfigData + form.setFieldsValue({ + llm, + embedding, + rerank + }) + }) + .finally(() => { + setPageLoding(false) + }) + }, []) + // 封装保存方法,添加提交逻辑 + const handleSave = () => { + form + .validateFields() + .then(() => { + setLoading(true) + updateWorkspaceModels(values) + .then(() => { + setLoading(false) + message.success(t('common.updateSuccess')) + }) + .catch(() => { + setLoading(false) + }); + }) + .catch((err) => { + console.log('err', err) + }); + } + + return ( + + {pageLoading + ? + : + + + + + + + + + + + {t('space.configAlert')} + + + + {t('common.save')} + + + + } + + ); +}; + +export default SpaceConfig; \ No newline at end of file diff --git a/web/src/views/SpaceConfig/types.ts b/web/src/views/SpaceConfig/types.ts new file mode 100644 index 00000000..15dfbc05 --- /dev/null +++ b/web/src/views/SpaceConfig/types.ts @@ -0,0 +1,8 @@ +export interface SpaceConfigData { + llm: string; + embedding: string; + rerank: string; +} +export interface SpaceConfigRef { + handleOpen: () => void; +} \ No newline at end of file diff --git a/web/src/views/ToolManagement/constant.ts b/web/src/views/ToolManagement/constant.ts index 1e30bafa..6763a140 100644 --- a/web/src/views/ToolManagement/constant.ts +++ b/web/src/views/ToolManagement/constant.ts @@ -10,10 +10,10 @@ export const InnerConfigData: Record = { }, JsonTool: { features: [ - 'jsonFormat', - 'jsonGzip', - 'jsonCheck', - 'jsonConversion' + 'jsonParse', + 'jsonInsert', + 'jsonReplace', + 'jsonDelete' ], eg: '{"name":"工具","tool_class":"内置"}' }, diff --git a/web/src/views/UserMemory/components/ConfigModal.tsx b/web/src/views/UserMemory/components/ConfigModal.tsx deleted file mode 100644 index 86ea8f19..00000000 --- a/web/src/views/UserMemory/components/ConfigModal.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { forwardRef, useImperativeHandle, useState } from 'react'; -import { Form, App } from 'antd'; -import { useTranslation } from 'react-i18next'; - -import type { ConfigModalData, ConfigModalRef } from '../types' -import { getWorkspaceModels, updateWorkspaceModels } from '@/api/workspaces' -import { getModelListUrl } from '@/api/models' -import CustomSelect from '@/components/CustomSelect' -import RbModal from '@/components/RbModal' - -const ConfigModal = forwardRef((_props, ref) => { - const { t } = useTranslation(); - const { message } = App.useApp(); - const [visible, setVisible] = useState(false); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false) - - const values = Form.useWatch([], form); - - // 封装取消方法,添加关闭弹窗逻辑 - const handleClose = () => { - setVisible(false); - form.resetFields(); - setLoading(false) - }; - - const handleOpen = () => { - getWorkspaceModels().then((res) => { - const { llm, embedding, rerank } = res as ConfigModalData - form.setFieldsValue({ - llm, - embedding, - rerank - }) - }) - setVisible(true); - }; - // 封装保存方法,添加提交逻辑 - const handleSave = () => { - form - .validateFields() - .then(() => { - setLoading(true) - updateWorkspaceModels(values) - .then(() => { - setLoading(false) - handleClose() - message.success(t('common.updateSuccess')) - }) - .catch(() => { - setLoading(false) - }); - - handleClose() - }) - .catch((err) => { - console.log('err', err) - }); - } - - // 暴露给父组件的方法 - useImperativeHandle(ref, () => ({ - handleOpen, - handleClose - })); - - return ( - - - - - - - - - - - - - - ); -}); - -export default ConfigModal; \ No newline at end of file diff --git a/web/src/views/UserMemory/index.tsx b/web/src/views/UserMemory/index.tsx index 7065f036..064b55be 100644 --- a/web/src/views/UserMemory/index.tsx +++ b/web/src/views/UserMemory/index.tsx @@ -1,56 +1,28 @@ -import { useEffect, useState, useRef } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom' -import { Row, Col, Radio, Button, List, Skeleton, Space } from 'antd'; -import type { ColumnsType } from 'antd/es/table'; -import type { RadioChangeEvent } from 'antd'; -import { AppstoreOutlined, MenuOutlined } from '@ant-design/icons'; +import { Row, Col, List, Skeleton } from 'antd'; import Empty from '@/components/Empty' -import type { Data, ConfigModalRef } from './types' -import totalNum from '@/assets/images/memory/totalNum.svg' -import onlineNum from '@/assets/images/memory/onlineNum.svg' -import Table from '@/components/Table' -import { getTotalEndUsers, userMemoryListUrl, getUserMemoryList } from '@/api/memory'; -import ConfigModal from './components/ConfigModal'; +import type { Data } from './types' +import { getUserMemoryList } from '@/api/memory'; import { useUser } from '@/store/user' +import RbCard from '@/components/RbCard/Card' +import SearchInput from '@/components/SearchInput'; -const bgList = [ - 'linear-gradient( 180deg, #F1F6FE 0%, #FBFDFF 100%)', - 'linear-gradient( 180deg, #F1F9FE 0%, #FBFDFF 100%)', - 'linear-gradient( 180deg, #FEFBF7 0%, #FBFDFF 100%)', - 'linear-gradient( 180deg, #F1F9FE 0%, #FBFDFF 100%)', -] - -const countList = [ - 'total_num', 'online_num', -] -const IconList: Record = { - total_num: totalNum, - online_num: onlineNum, -} export default function UserMemory() { const { t } = useTranslation(); const navigate = useNavigate() const { storageType } = useUser() - const configModalRef = useRef(null) const [loading, setLoading] = useState(false); const [data, setData] = useState([]); - const [countData, setCountData] = useState>({}); - const [layout, setLayout] = useState<'card' | 'list'>('card'); + const [search, setSearch] = useState(undefined); // 获取数据 useEffect(() => { - getCountData() getData() }, []); - // 用户记忆统计 - const getCountData = () => { - getTotalEndUsers().then((res) => { - setCountData(res as Record || {}) - }) - } const getData = () => { setLoading(true) getUserMemoryList().then((res) => { @@ -60,7 +32,6 @@ export default function UserMemory() { setLoading(false) }) } - console.log('storageType', storageType) const handleViewDetail = (id: string | number) => { switch (storageType) { case 'neo4j': @@ -70,112 +41,77 @@ export default function UserMemory() { navigate(`/user-memory/${id}`) } } - const handleChangeLayout = (e: RadioChangeEvent) => { - const type = e.target.value - setLayout(type) + const handleViewMemoryConfig = () => { + navigate(`/memory`) } - // 表格列配置 - const columns: ColumnsType = [ - { - title: t('userMemory.user'), - dataIndex: 'end_user', - key: 'end_user', - render: (value) => value?.other_name && value?.other_name !== '' ? value?.other_name : value?.id || '-' - }, - { - title: t('userMemory.knowledgeEntryCount'), - dataIndex: 'memory_num', - key: 'memory_num', - render: (value) => value?.total || 0 - }, - { - title: t('common.operation'), - key: 'action', - render: (_, record) => ( - handleViewDetail(record.end_user?.id)} - > - {t('common.viewDetail')} - - ), - }, - ]; + + const filterData = useMemo(() => { + if (search && search.trim() !== '') { + return data.filter((item) => { + const { end_user } = item as Data; + const name = end_user?.other_name && end_user?.other_name !== '' ? end_user?.other_name : end_user?.id + return name?.includes(search) + }) + } + + return data + }, [search, data]) return ( - {countList.map(key => ( - - - - {countData[key] || 0}{key === 'avgInteractionTime' ? 's' : ''} - - - {t(`userMemory.${key}`)} - - - ))} - - - configModalRef?.current?.handleOpen()}>{t('userMemory.chooseModel')} - - - - - + + setSearch(value)} + style={{ width: '100%' }} + /> - {layout === 'card' && - <> - {loading ? - - : data.length > 0 ? ( - { - const { end_user, memory_num } = item as Data; - const name = end_user?.other_name && end_user?.other_name !== '' ? end_user?.other_name : end_user?.id - return ( - - + : filterData.length > 0 ? ( + { + 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 + return ( + + {name[0]}} + title={name || '-'} + extra={ handleViewDetail(end_user.id)} - > - - {name[0]} - - {name || '-'} - - - - - {memory_num.total || 0} - {t(`userMemory.knowledgeEntryCount`)} - - + >} + > + + {t('userMemory.capacity')} + {memory_num?.total || 0} {t('userMemory.memoryNum')} + + + {t('userMemory.type')} + {t(`userMemory.${item.type || 'person'}`)} - - ) - }} - /> - ) : } - > - } - {layout === 'list' && - + + + {t('userMemory.memory_config_name')} + + + {memory_config?.memory_config_name || '-'} + + + + ) + }} + /> + ) : } - ); } \ No newline at end of file diff --git a/web/src/views/UserMemory/types.ts b/web/src/views/UserMemory/types.ts index 696b1694..927cf778 100644 --- a/web/src/views/UserMemory/types.ts +++ b/web/src/views/UserMemory/types.ts @@ -17,13 +17,10 @@ export interface Data { entity: number; } }, + memory_config: { + memory_config_id: string; + memory_config_name: string; + }, + type: string; name?: string; -} -export interface ConfigModalData { - llm: string; - embedding: string; - rerank: string; -} -export interface ConfigModalRef { - handleOpen: () => void; } \ No newline at end of file