From 2a4627d9a04ad81c085c5e2713d85f821957230b Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Wed, 7 Jan 2026 10:03:54 +0800 Subject: [PATCH] Fix: Issues and style fixes related to the 'Memory' page (#12469) ### What problem does this PR solve? Fix: Some bugs - Issues and style fixes related to the 'Memory' page - Data source icon replacement - Build optimization ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --- web/src/assets/svg/data-source/github.svg | 3 - web/src/assets/svg/data-source/imap.svg | 7 -- web/src/components/dynamic-form.tsx | 1 + web/src/components/more-button.tsx | 5 +- web/src/components/ragflow-form.tsx | 2 +- web/src/components/ui/input.tsx | 14 +++- web/src/locales/en.ts | 1 + web/src/locales/zh.ts | 8 ++- web/src/pages/agents/index.tsx | 2 +- .../configuration/common-item.tsx | 1 - .../dataset/dataset-setting/form-schema.ts | 2 +- web/src/pages/datasets/index.tsx | 2 +- web/src/pages/memories/hooks.ts | 72 +++++++++++++++++-- web/src/pages/memories/index.tsx | 11 ++- web/src/pages/memory/index.tsx | 2 +- web/src/pages/memory/memory-message/hook.ts | 37 ++++++++-- web/src/pages/memory/memory-message/index.tsx | 14 ++-- .../memory/memory-message/message-table.tsx | 4 +- .../memory-setting/advanced-settings-form.tsx | 6 +- .../memory/memory-setting/basic-form.tsx | 1 + web/src/pages/next-chats/index.tsx | 2 +- web/src/pages/next-searches/index.tsx | 2 +- .../data-source/constant/index.tsx | 12 +++- web/src/utils/list-filter-util.ts | 28 +++++++- web/vite.config.ts | 51 +++++++++++++ 25 files changed, 239 insertions(+), 51 deletions(-) delete mode 100644 web/src/assets/svg/data-source/github.svg delete mode 100644 web/src/assets/svg/data-source/imap.svg diff --git a/web/src/assets/svg/data-source/github.svg b/web/src/assets/svg/data-source/github.svg deleted file mode 100644 index a8d117404..000000000 --- a/web/src/assets/svg/data-source/github.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/src/assets/svg/data-source/imap.svg b/web/src/assets/svg/data-source/imap.svg deleted file mode 100644 index 82a815425..000000000 --- a/web/src/assets/svg/data-source/imap.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/web/src/components/dynamic-form.tsx b/web/src/components/dynamic-form.tsx index 6ef736a40..1900d5794 100644 --- a/web/src/components/dynamic-form.tsx +++ b/web/src/components/dynamic-form.tsx @@ -97,6 +97,7 @@ export interface FormFieldConfig { schema?: ZodSchema; shouldRender?: (formValues: any) => boolean; labelClassName?: string; + className?: string; disabled?: boolean; } diff --git a/web/src/components/more-button.tsx b/web/src/components/more-button.tsx index f8d2d75dc..45953a4b1 100644 --- a/web/src/components/more-button.tsx +++ b/web/src/components/more-button.tsx @@ -10,7 +10,10 @@ export const MoreButton = React.forwardRef( ref={ref} variant="ghost" size={size || 'icon'} - className={cn('invisible group-hover:visible size-3.5', className)} + className={cn( + 'invisible group-hover:visible size-3.5 bg-transparent group-hover:bg-transparent', + className, + )} {...props} > diff --git a/web/src/components/ragflow-form.tsx b/web/src/components/ragflow-form.tsx index 758038bfd..5d10124ff 100644 --- a/web/src/components/ragflow-form.tsx +++ b/web/src/components/ragflow-form.tsx @@ -45,7 +45,7 @@ export function RAGFlowFormItem({ , 'prefix'> { +export interface InputProps extends Omit< + React.InputHTMLAttributes, + 'prefix' +> { value?: string | number | readonly string[] | undefined; prefix?: React.ReactNode; suffix?: React.ReactNode; @@ -157,8 +160,13 @@ export interface ExpandedInputProps extends InputProps {} const ExpandedInput = Input; const SearchInput = (props: InputProps) => { + const { t } = useTranslation(); return ( - } /> + } + /> ); }; diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 385dae580..42458e616 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -129,6 +129,7 @@ Procedural Memory: Learned skills, habits, and automated procedures.`, }, memory: { messages: { + forget: 'Forget', forgetMessageTip: 'Are you sure you want to forget?', messageDescription: 'Memory extract is configured with Prompts and Temperature from Advanced Settings.', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 96d36a312..18e9273b1 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -101,7 +101,7 @@ export default { embeddingModelTooltip: '将文本转换为数值向量,用于语义相似度搜索和记忆检索。', embeddingModelError: '记忆类型为必填项,且"原始"类型不可删除。', - memoryTypeTooltip: `原始: 用户与代理之间的原始对话内容(默认必需)。 + memoryTypeTooltip: `原始: 用户与智能体之间的原始对话内容(默认必需)。 语义记忆: 关于用户和世界的通用知识和事实。 情景记忆: 带时间戳的特定事件和经历记录。 程序记忆: 学习的技能、习惯和自动化程序。`, @@ -118,15 +118,16 @@ export default { embeddingModel: '嵌入模型', selectModel: '选择模型', llm: '大语言模型', - delMemoryWarn: `删除后,此记忆中的所有消息都将被删除,代理将无法检索。`, + delMemoryWarn: `删除后,此记忆中的所有消息都将被删除,智能体将无法检索。`, }, memory: { messages: { + forget: '遗忘', forgetMessageTip: '确定遗忘吗?', messageDescription: '记忆提取使用高级设置中的提示词和温度值进行配置。', copied: '已复制!', content: '内容', - delMessageWarn: `遗忘后,代理将无法检索此消息。`, + delMessageWarn: `遗忘后,智能体将无法检索此消息。`, forgetMessage: '遗忘消息', sessionId: '会话ID', agent: '智能体', @@ -2138,6 +2139,7 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`, delFilesContent: '已选择 {{count}} 个文件', delChat: '删除聊天', delMember: '删除成员', + delMemory: '删除记忆', }, empty: { diff --git a/web/src/pages/agents/index.tsx b/web/src/pages/agents/index.tsx index 52815392f..82308f309 100644 --- a/web/src/pages/agents/index.tsx +++ b/web/src/pages/agents/index.tsx @@ -109,7 +109,7 @@ export default function Agents() { diff --git a/web/src/pages/dataset/dataset-setting/configuration/common-item.tsx b/web/src/pages/dataset/dataset-setting/configuration/common-item.tsx index 3f90d64a4..8a8d6e1a8 100644 --- a/web/src/pages/dataset/dataset-setting/configuration/common-item.tsx +++ b/web/src/pages/dataset/dataset-setting/configuration/common-item.tsx @@ -515,7 +515,6 @@ export function LLMModelItem({ line = 1, isEdit, label, name }: IProps) { })} > diff --git a/web/src/pages/memories/hooks.ts b/web/src/pages/memories/hooks.ts index 9a50d337e..f2bd4b64d 100644 --- a/web/src/pages/memories/hooks.ts +++ b/web/src/pages/memories/hooks.ts @@ -1,14 +1,21 @@ // src/pages/next-memoryes/hooks.ts +import { FilterCollection } from '@/components/list-filter-bar/interface'; +import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit'; import message from '@/components/ui/message'; import { useSetModalState } from '@/hooks/common-hooks'; import { useHandleSearchChange } from '@/hooks/logic-hooks'; import { useFetchTenantInfo } from '@/hooks/use-user-setting-request'; import memoryService, { updateMemoryById } from '@/services/memory-service'; +import { + buildOwnersFilter, + groupListByArray, + groupListByType, +} from '@/utils/list-filter-util'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useDebounce } from 'ahooks'; import { omit } from 'lodash'; -import { useCallback, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams, useSearchParams } from 'react-router'; import { @@ -45,7 +52,27 @@ export const useCreateMemory = () => { export const useFetchMemoryList = () => { const { handleInputChange, searchString, pagination, setPagination } = useHandleSearchChange(); + const { filterValue, handleFilterSubmit } = useHandleFilterSubmit(); const debouncedSearchString = useDebounce(searchString, { wait: 500 }); + + const memoryType = Array.isArray(filterValue.memoryType) + ? filterValue.memoryType + : []; + const storageType = Array.isArray(filterValue.storageType) + ? filterValue.storageType + : []; + const owner = filterValue.owner; + const requestParams: Record = { + keywords: debouncedSearchString, + page_size: pagination.pageSize, + page: pagination.current, + memory_type: memoryType.length > 0 ? memoryType.join(',') : undefined, + storage_type: storageType.length === 1 ? storageType[0] : undefined, + }; + + if (Array.isArray(owner) && owner.length > 0) { + requestParams.owner_ids = owner.join(','); + } const { data, isLoading, isError, refetch } = useQuery< MemoryListResponse, Error @@ -56,16 +83,13 @@ export const useFetchMemoryList = () => { debouncedSearchString, ...pagination, }, + filterValue, ], queryFn: async () => { const { data: response } = await memoryService.getMemoryList( { - params: { - keywords: debouncedSearchString, - page_size: pagination.pageSize, - page: pagination.current, - }, - data: {}, + params: requestParams, + data: { memory_type: memoryType }, }, true, ); @@ -93,6 +117,8 @@ export const useFetchMemoryList = () => { handleInputChange, setPagination, refetch, + filterValue, + handleFilterSubmit, }; }; @@ -275,3 +301,35 @@ export const useRenameMemory = () => { showMemoryRenameModal: handleShowChatRenameModal, }; }; + +export function useSelectFilters() { + const { data: res } = useFetchMemoryList(); + const data = res?.data; + + const memoryType = useMemo(() => { + return groupListByArray(data?.memory_list ?? [], 'memory_type'); + }, [data?.memory_list]); + const storageType = useMemo(() => { + return groupListByType( + data?.memory_list ?? [], + 'storage_type', + 'storage_type', + ); + }, [data?.memory_list]); + + const filters: FilterCollection[] = [ + buildOwnersFilter(data?.memory_list ?? [], 'owner_name'), + { + field: 'memoryType', + list: memoryType, + label: 'Memory Type', + }, + { + field: 'storageType', + list: storageType, + label: 'Storage Type', + }, + ]; + + return { filters }; +} diff --git a/web/src/pages/memories/index.tsx b/web/src/pages/memories/index.tsx index a29c4f3bf..811102a8e 100644 --- a/web/src/pages/memories/index.tsx +++ b/web/src/pages/memories/index.tsx @@ -11,7 +11,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useSearchParams } from 'react-router'; import { AddOrEditModal } from './add-or-edit-modal'; import { defaultMemoryFields } from './constants'; -import { useFetchMemoryList, useRenameMemory } from './hooks'; +import { useFetchMemoryList, useRenameMemory, useSelectFilters } from './hooks'; import { ICreateMemoryProps, IMemory } from './interface'; import { MemoryCard } from './memory-card'; @@ -27,6 +27,8 @@ export default function MemoryList() { handleInputChange, setPagination, refetch: refetchList, + filterValue, + handleFilterSubmit, } = useFetchMemoryList(); const { @@ -56,6 +58,7 @@ export default function MemoryList() { ); const [searchUrl, setMemoryUrl] = useSearchParams(); + const { filters } = useSelectFilters(); const isCreate = searchUrl.get('isCreate') === 'true'; useEffect(() => { if (isCreate) { @@ -87,9 +90,11 @@ export default function MemoryList() { diff --git a/web/src/pages/memory/index.tsx b/web/src/pages/memory/index.tsx index 77dde215c..89a964bfc 100644 --- a/web/src/pages/memory/index.tsx +++ b/web/src/pages/memory/index.tsx @@ -4,7 +4,7 @@ import { SideBar } from './sidebar'; export default function DatasetWrapper() { return ( -
+
diff --git a/web/src/pages/memory/memory-message/hook.ts b/web/src/pages/memory/memory-message/hook.ts index 7cf05a5d2..0273b581a 100644 --- a/web/src/pages/memory/memory-message/hook.ts +++ b/web/src/pages/memory/memory-message/hook.ts @@ -1,9 +1,12 @@ +import { FilterCollection } from '@/components/list-filter-bar/interface'; +import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit'; import message from '@/components/ui/message'; import { useHandleSearchChange } from '@/hooks/logic-hooks'; import memoryService, { getMemoryDetailById } from '@/services/memory-service'; +import { groupListByType } from '@/utils/list-filter-util'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { t } from 'i18next'; -import { useCallback, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { useParams, useSearchParams } from 'react-router'; import { MemoryApiAction } from '../constant'; import { @@ -18,13 +21,15 @@ export const useFetchMemoryMessageList = () => { const memoryBaseId = searchParams.get('id') || id; const { handleInputChange, searchString, pagination, setPagination } = useHandleSearchChange(); - + const { filterValue, handleFilterSubmit } = useHandleFilterSubmit(); let queryKey: (MemoryApiAction | number)[] = [ MemoryApiAction.FetchMemoryMessage, ]; - + const agentIds = Array.isArray(filterValue.agentId) + ? filterValue.agentId + : []; const { data, isFetching: loading } = useQuery({ - queryKey: [...queryKey, searchString, pagination], + queryKey: [...queryKey, searchString, pagination, filterValue], initialData: {} as IMessageTableProps, gcTime: 0, queryFn: async () => { @@ -33,6 +38,7 @@ export const useFetchMemoryMessageList = () => { keywords: searchString, page: pagination.current, page_size: pagination.pageSize, + agentId: agentIds.length > 0 ? agentIds.join(',') : undefined, }); return data?.data ?? {}; } else { @@ -48,6 +54,8 @@ export const useFetchMemoryMessageList = () => { searchString, pagination, setPagination, + filterValue, + handleFilterSubmit, }; }; @@ -164,3 +172,24 @@ export const useMessageAction = () => { handleClickUpdateMessageState, }; }; + +export function useSelectFilters() { + const { data } = useFetchMemoryMessageList(); + const agentId = useMemo(() => { + return groupListByType( + data?.messages?.message_list ?? [], + 'agent_id', + 'agent_name', + ); + }, [data?.messages?.message_list]); + + const filters: FilterCollection[] = [ + { + field: 'agentId', + list: agentId, + label: 'Agent', + }, + ]; + + return { filters }; +} diff --git a/web/src/pages/memory/memory-message/index.tsx b/web/src/pages/memory/memory-message/index.tsx index 943fad706..62e27678d 100644 --- a/web/src/pages/memory/memory-message/index.tsx +++ b/web/src/pages/memory/memory-message/index.tsx @@ -1,6 +1,6 @@ import ListFilterBar from '@/components/list-filter-bar'; import { t } from 'i18next'; -import { useFetchMemoryMessageList } from './hook'; +import { useFetchMemoryMessageList, useSelectFilters } from './hook'; import { MemoryTable } from './message-table'; export default function MemoryMessage() { @@ -11,25 +11,29 @@ export default function MemoryMessage() { pagination, handleInputChange, setPagination, - // filterValue, - // handleFilterSubmit, + filterValue, + handleFilterSubmit, loading, } = useFetchMemoryMessageList(); + const { filters } = useSelectFilters(); return (
{t('memory.sideBar.messages')}
-
+
{t('memory.messages.messageDescription')}
diff --git a/web/src/pages/memory/memory-message/message-table.tsx b/web/src/pages/memory/memory-message/message-table.tsx index 9557c0190..1a8d6c946 100644 --- a/web/src/pages/memory/memory-message/message-table.tsx +++ b/web/src/pages/memory/memory-message/message-table.tsx @@ -210,7 +210,7 @@ export function MemoryTable({ return (
- +
{table.getHeaderGroups().map((headerGroup) => ( @@ -257,7 +257,7 @@ export function MemoryTable({ title={t('memory.messages.forgetMessage')} open={showDeleteDialog} onOpenChange={setShowDeleteDialog} - okButtonText={t('common.confirm')} + okButtonText={t('memory.messages.forget')} content={{ title: t('memory.messages.forgetMessageTip'), node: ( diff --git a/web/src/pages/memory/memory-setting/advanced-settings-form.tsx b/web/src/pages/memory/memory-setting/advanced-settings-form.tsx index 8ff2918ea..2776e29f8 100644 --- a/web/src/pages/memory/memory-setting/advanced-settings-form.tsx +++ b/web/src/pages/memory/memory-setting/advanced-settings-form.tsx @@ -30,7 +30,9 @@ export const AdvancedSettingsForm = () => { return ( <>
setShowAdvancedSettings(!showAdvancedSettings)} > {showAdvancedSettings ? ( @@ -134,6 +136,7 @@ export const AdvancedSettingsForm = () => { /> { /> { label={t('memory.config.description')} required={false} horizontal={true} + className="!items-start" // tooltip={field.tooltip} // labelClassName={labelClassName || field.labelClassName} > diff --git a/web/src/pages/next-chats/index.tsx b/web/src/pages/next-chats/index.tsx index 2f45ff942..00077b678 100644 --- a/web/src/pages/next-chats/index.tsx +++ b/web/src/pages/next-chats/index.tsx @@ -72,7 +72,7 @@ export default function ChatList() { searchString={searchString} > diff --git a/web/src/pages/next-searches/index.tsx b/web/src/pages/next-searches/index.tsx index 39482c0fe..7656c50af 100644 --- a/web/src/pages/next-searches/index.tsx +++ b/web/src/pages/next-searches/index.tsx @@ -96,7 +96,7 @@ export default function SearchList() { openCreateModalFun(); }} > - + {t('createSearch')} diff --git a/web/src/pages/user-setting/data-source/constant/index.tsx b/web/src/pages/user-setting/data-source/constant/index.tsx index 8b9193c18..199443178 100644 --- a/web/src/pages/user-setting/data-source/constant/index.tsx +++ b/web/src/pages/user-setting/data-source/constant/index.tsx @@ -1,6 +1,8 @@ import { FormFieldType } from '@/components/dynamic-form'; +import { IconFontFill } from '@/components/icon-font'; import SvgIcon from '@/components/svg-icon'; import { t, TFunction } from 'i18next'; +import { Mail } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import BoxTokenField from '../component/box-token-field'; @@ -130,12 +132,18 @@ export const generateDataSourceInfo = (t: TFunction) => { [DataSourceKey.GITHUB]: { name: 'GitHub', description: t(`setting.${DataSourceKey.GITHUB}Description`), - icon: , + icon: ( + + ), }, [DataSourceKey.IMAP]: { name: 'IMAP', description: t(`setting.${DataSourceKey.IMAP}Description`), - icon: , + icon: , }, [DataSourceKey.BITBUCKET]: { name: 'Bitbucket', diff --git a/web/src/utils/list-filter-util.ts b/web/src/utils/list-filter-util.ts index 727f55e9b..06fcbc733 100644 --- a/web/src/utils/list-filter-util.ts +++ b/web/src/utils/list-filter-util.ts @@ -22,8 +22,32 @@ export function groupListByType>( return fileTypeList; } -export function buildOwnersFilter>(list: T[]) { - const owners = groupListByType(list, 'tenant_id', 'nickname'); +export function groupListByArray>( + list: T[], + idField: string, +) { + const fileTypeList: FilterType[] = []; + list.forEach((x) => { + if (Array.isArray(x[idField])) { + x[idField].forEach((j) => { + const item = fileTypeList.find((i) => i.id === j); + if (!item) { + fileTypeList.push({ id: j, label: j, count: 1 }); + } else { + item.count += 1; + } + }); + } + }); + + return fileTypeList; +} + +export function buildOwnersFilter>( + list: T[], + nickName?: string, +) { + const owners = groupListByType(list, 'tenant_id', nickName || 'nickname'); return { field: 'owner', list: owners, label: 'Owner' }; } diff --git a/web/vite.config.ts b/web/vite.config.ts index 25f1c5b01..7ef0e5c71 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -61,6 +61,9 @@ export default defineConfig(({ mode, command }) => { server: { port: 9222, strictPort: false, + hmr: { + overlay: false, + }, proxy: { '/api/v1/admin': { target: 'http://127.0.0.1:9381/', @@ -77,18 +80,63 @@ export default defineConfig(({ mode, command }) => { assetsInclude: ['**/*.md'], base: env.VITE_BASE_URL, publicDir: 'public', + cacheDir: './node_modules/.vite-cache', + optimizeDeps: { + include: [ + 'react', + 'react-dom', + 'react-router', + 'antd', + 'axios', + 'lodash', + 'dayjs', + ], + exclude: [], + force: false, + }, build: { outDir: 'dist', assetsDir: 'assets', assetsInlineLimit: 4096, experimentalMinChunkSize: 30 * 1024, + chunkSizeWarningLimit: 1000, rollupOptions: { output: { + manualChunks(id) { + // if (id.includes('src/components')) { + // return 'components'; + // } + + if (id.includes('node_modules')) { + if (id.includes('node_modules/d3')) { + return 'd3'; + } + if (id.includes('node_modules/ajv')) { + return 'ajv'; + } + if (id.includes('node_modules/@antv')) { + return 'antv'; + } + const name = id + .toString() + .split('node_modules/')[1] + .split('/')[0] + .toString(); + if (['lodash', 'dayjs', 'date-fns', 'axios'].includes(name)) { + return 'utils'; + } + if (['@xmldom', 'xmlbuilder '].includes(name)) { + return 'xml-js'; + } + return name; + } + }, chunkFileNames: 'chunk/js/[name]-[hash].js', entryFileNames: 'entry/js/[name]-[hash].js', assetFileNames: 'assets/[ext]/[name]-[hash].[ext]', }, plugins: [], + treeshake: true, }, minify: 'terser', terserOptions: { @@ -108,6 +156,8 @@ export default defineConfig(({ mode, command }) => { }, }, sourcemap: true, + cssCodeSplit: true, + target: 'es2015', }, esbuild: { tsconfigRaw: { @@ -118,5 +168,6 @@ export default defineConfig(({ mode, command }) => { }, }, }, + entries: ['./src/main.tsx'], }; });