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)
This commit is contained in:
chanx
2026-01-07 10:03:54 +08:00
committed by GitHub
parent 6814ace1aa
commit 2a4627d9a0
25 changed files with 239 additions and 51 deletions

View File

@ -1,3 +0,0 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" transform="scale(64)" fill="#1B1F23"/>
</svg>

Before

Width:  |  Height:  |  Size: 968 B

View File

@ -1,7 +0,0 @@
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24"
stroke-linecap="round" stroke-linejoin="round"
class="text-text-04" height="32" width="32"
xmlns="http://www.w3.org/2000/svg">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
<polyline points="22,6 12,13 2,6"></polyline>
</svg>

Before

Width:  |  Height:  |  Size: 360 B

View File

@ -97,6 +97,7 @@ export interface FormFieldConfig {
schema?: ZodSchema;
shouldRender?: (formValues: any) => boolean;
labelClassName?: string;
className?: string;
disabled?: boolean;
}

View File

@ -10,7 +10,10 @@ export const MoreButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
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}
>
<Ellipsis />

View File

@ -45,7 +45,7 @@ export function RAGFlowFormItem({
<FormItem
className={cn(
{
'flex items-center w-full': horizontal,
'flex items-center w-full space-y-0': horizontal,
},
className,
)}

View File

@ -3,9 +3,12 @@ import * as React from 'react';
import { cn } from '@/lib/utils';
import { Eye, EyeOff, Search } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
export interface InputProps
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'prefix'> {
export interface InputProps extends Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'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 (
<Input {...props} prefix={<Search className="ml-2 mr-1 size-[1em]" />} />
<Input
placeholder={t('common.search')}
{...props}
prefix={<Search className="ml-2 mr-1 size-[1em]" />}
/>
);
};

View File

@ -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.',

View File

@ -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: {

View File

@ -109,7 +109,7 @@ export default function Agents() {
<DropdownMenu>
<DropdownMenuTrigger>
<Button>
<Plus className="mr-2 h-4 w-4" />
<Plus className="h-4 w-4" />
{t('flow.createGraph')}
</Button>
</DropdownMenuTrigger>

View File

@ -515,7 +515,6 @@ export function LLMModelItem({ line = 1, isEdit, label, name }: IProps) {
})}
>
<FormLabel
required
tooltip={t('globalIndexModelTip')}
className={cn('text-sm whitespace-wrap ', {
'w-1/4': line === 1,

View File

@ -96,7 +96,7 @@ export const formSchema = z
)
.optional(),
enable_metadata: z.boolean().optional(),
llm_id: z.string().min(1, { message: 'Indexing model is required' }),
llm_id: z.string().optional(),
})
.optional(),
pagerank: z.number(),

View File

@ -95,7 +95,7 @@ export default function Datasets() {
icon={'datasets'}
>
<Button onClick={showModal}>
<Plus className=" size-2.5" />
<Plus className="h-4 w-4" />
{t('knowledgeList.createKnowledgeBase')}
</Button>
</ListFilterBar>

View File

@ -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<string, any> = {
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 };
}

View File

@ -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() {
<ListFilterBar
icon="memory"
title={t('memory')}
showFilter={false}
onSearchChange={handleInputChange}
searchString={searchString}
filters={filters}
onChange={handleFilterSubmit}
value={filterValue}
>
<Button
variant={'default'}
@ -97,7 +102,7 @@ export default function MemoryList() {
openCreateModalFun();
}}
>
<Plus className="mr-2 h-4 w-4" />
<Plus className=" h-4 w-4" />
{t('createMemory')}
</Button>
</ListFilterBar>

View File

@ -4,7 +4,7 @@ import { SideBar } from './sidebar';
export default function DatasetWrapper() {
return (
<section className="flex h-full flex-col w-full">
<section className="flex h-full flex-col w-full pt-3">
<div className="flex flex-1 min-h-0">
<SideBar></SideBar>
<div className=" relative flex-1 overflow-auto border-[0.5px] border-border-button p-5 rounded-md mr-5 mb-5">

View File

@ -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<IMessageTableProps>({
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 };
}

View File

@ -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 (
<div className="flex flex-col gap-2">
<ListFilterBar
title="Dataset"
onSearchChange={handleInputChange}
searchString={searchString}
showFilter={false}
// showFilter={false}
// value={filterValue}
// onChange={handleFilterSubmit}
// onOpenChange={onOpenChange}
// filters={filters}
filters={filters}
onChange={handleFilterSubmit}
value={filterValue}
leftPanel={
<div className="items-start">
<div className="pb-1">{t('memory.sideBar.messages')}</div>
<div className="text-text-secondary text-sm">
<div className="text-text-secondary text-sm font-normal">
{t('memory.messages.messageDescription')}
</div>
</div>

View File

@ -210,7 +210,7 @@ export function MemoryTable({
return (
<div className="w-full">
<Table rootClassName="max-h-[calc(100vh-282px)]">
<Table rootClassName="max-h-[calc(100vh-292px)]">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
@ -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: (

View File

@ -30,7 +30,9 @@ export const AdvancedSettingsForm = () => {
return (
<>
<div
className="flex items-center gap-1 w-full cursor-pointer"
className={cn('flex items-center gap-1 w-full cursor-pointer', {
'text-primary': showAdvancedSettings,
})}
onClick={() => setShowAdvancedSettings(!showAdvancedSettings)}
>
{showAdvancedSettings ? (
@ -134,6 +136,7 @@ export const AdvancedSettingsForm = () => {
/>
<RenderField
field={{
className: '!items-start',
name: 'system_prompt',
label: t('memory.config.systemPrompt'),
type: FormFieldType.Textarea,
@ -144,6 +147,7 @@ export const AdvancedSettingsForm = () => {
/>
<RenderField
field={{
className: '!items-start',
name: 'user_prompt',
label: t('memory.config.userPrompt'),
type: FormFieldType.Textarea,

View File

@ -41,6 +41,7 @@ export const BasicInfo = () => {
label={t('memory.config.description')}
required={false}
horizontal={true}
className="!items-start"
// tooltip={field.tooltip}
// labelClassName={labelClassName || field.labelClassName}
>

View File

@ -72,7 +72,7 @@ export default function ChatList() {
searchString={searchString}
>
<Button onClick={handleShowCreateModal}>
<Plus className="size-2.5" />
<Plus className="h-4 w-4" />
{t('chat.createChat')}
</Button>
</ListFilterBar>

View File

@ -96,7 +96,7 @@ export default function SearchList() {
openCreateModalFun();
}}
>
<Plus className="mr-2 h-4 w-4" />
<Plus className="h-4 w-4" />
{t('createSearch')}
</Button>
</ListFilterBar>

View File

@ -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: <SvgIcon name={'data-source/github'} width={38} />,
icon: (
<IconFontFill
// name="a-DiscordIconSVGVectorIcon"
name="GitHub"
className="text-text-primary size-6"
></IconFontFill>
),
},
[DataSourceKey.IMAP]: {
name: 'IMAP',
description: t(`setting.${DataSourceKey.IMAP}Description`),
icon: <SvgIcon name={'data-source/imap'} width={38} />,
icon: <Mail className="text-text-primary" size={22} />,
},
[DataSourceKey.BITBUCKET]: {
name: 'Bitbucket',

View File

@ -22,8 +22,32 @@ export function groupListByType<T extends Record<string, any>>(
return fileTypeList;
}
export function buildOwnersFilter<T extends Record<string, any>>(list: T[]) {
const owners = groupListByType(list, 'tenant_id', 'nickname');
export function groupListByArray<T extends Record<string, any>>(
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<T extends Record<string, any>>(
list: T[],
nickName?: string,
) {
const owners = groupListByType(list, 'tenant_id', nickName || 'nickname');
return { field: 'owner', list: owners, label: 'Owner' };
}

View File

@ -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'],
};
});