Feature: Memory interface integration testing (#11833)

### What problem does this PR solve?

Feature: Memory interface integration testing

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
chanx
2025-12-09 14:52:58 +08:00
committed by GitHub
parent c51e6b2a58
commit 28bc87c5e2
32 changed files with 1168 additions and 501 deletions

View File

@ -1,3 +1,5 @@
export enum MemoryApiAction {
FetchMemoryDetail = 'fetchMemoryDetail',
FetchMemoryMessage = 'fetchMemoryMessage',
FetchMessageContent = 'fetchMessageContent',
}

View File

@ -1,59 +0,0 @@
import { useHandleSearchChange } from '@/hooks/logic-hooks';
import { getMemoryDetailById } from '@/services/memory-service';
import { useQuery } from '@tanstack/react-query';
import { useParams, useSearchParams } from 'umi';
import { MemoryApiAction } from '../constant';
import { IMessageTableProps } from '../memory-message/interface';
export const useFetchMemoryMessageList = (props?: {
refreshCount?: number;
}) => {
const { refreshCount } = props || {};
const { id } = useParams();
const [searchParams] = useSearchParams();
const memoryBaseId = searchParams.get('id') || id;
const { handleInputChange, searchString, pagination, setPagination } =
useHandleSearchChange();
let queryKey: (MemoryApiAction | number)[] = [
MemoryApiAction.FetchMemoryDetail,
];
if (typeof refreshCount === 'number') {
queryKey = [MemoryApiAction.FetchMemoryDetail, refreshCount];
}
const { data, isFetching: loading } = useQuery<IMessageTableProps>({
queryKey: [...queryKey, searchString, pagination],
initialData: {} as IMessageTableProps,
gcTime: 0,
queryFn: async () => {
if (memoryBaseId) {
const { data } = await getMemoryDetailById(memoryBaseId as string, {
// filter: {
// agent_id: '',
// },
keyword: searchString,
page: pagination.current,
page_size: pagination.pageSize,
});
// setPagination({
// page: data?.page ?? 1,
// pageSize: data?.page_size ?? 10,
// total: data?.total ?? 0,
// });
return data?.data ?? {};
} else {
return {};
}
},
});
return {
data,
loading,
handleInputChange,
searchString,
pagination,
setPagination,
};
};

View File

@ -1,14 +1,11 @@
import { useHandleSearchChange } from '@/hooks/logic-hooks';
import { IMemory } from '@/pages/memories/interface';
import { getMemoryDetailById } from '@/services/memory-service';
import memoryService from '@/services/memory-service';
import { useQuery } from '@tanstack/react-query';
import { useParams, useSearchParams } from 'umi';
import { MemoryApiAction } from '../constant';
export const useFetchMemoryBaseConfiguration = (props?: {
refreshCount?: number;
}) => {
const { refreshCount } = props || {};
export const useFetchMemoryBaseConfiguration = () => {
const { id } = useParams();
const [searchParams] = useSearchParams();
const memoryBaseId = searchParams.get('id') || id;
@ -18,9 +15,6 @@ export const useFetchMemoryBaseConfiguration = (props?: {
let queryKey: (MemoryApiAction | number)[] = [
MemoryApiAction.FetchMemoryDetail,
];
if (typeof refreshCount === 'number') {
queryKey = [MemoryApiAction.FetchMemoryDetail, refreshCount];
}
const { data, isFetching: loading } = useQuery<IMemory>({
queryKey: [...queryKey, searchString, pagination],
@ -28,19 +22,9 @@ export const useFetchMemoryBaseConfiguration = (props?: {
gcTime: 0,
queryFn: async () => {
if (memoryBaseId) {
const { data } = await getMemoryDetailById(memoryBaseId as string, {
// filter: {
// agent_id: '',
// },
keyword: searchString,
page: pagination.current,
page_size: pagination.size,
});
// setPagination({
// page: data?.page ?? 1,
// pageSize: data?.page_size ?? 10,
// total: data?.total ?? 0,
// });
const { data } = await memoryService.getMemoryConfig(
memoryBaseId as string,
);
return data?.data ?? {};
} else {
return {};

View File

@ -0,0 +1,128 @@
import message from '@/components/ui/message';
import { useHandleSearchChange } from '@/hooks/logic-hooks';
import memoryService, { getMemoryDetailById } from '@/services/memory-service';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { t } from 'i18next';
import { useCallback, useState } from 'react';
import { useParams, useSearchParams } from 'umi';
import { MemoryApiAction } from '../constant';
import {
IMessageContentProps,
IMessageTableProps,
} from '../memory-message/interface';
import { IMessageInfo } from './interface';
export const useFetchMemoryMessageList = () => {
const { id } = useParams();
const [searchParams] = useSearchParams();
const memoryBaseId = searchParams.get('id') || id;
const { handleInputChange, searchString, pagination, setPagination } =
useHandleSearchChange();
let queryKey: (MemoryApiAction | number)[] = [
MemoryApiAction.FetchMemoryMessage,
];
const { data, isFetching: loading } = useQuery<IMessageTableProps>({
queryKey: [...queryKey, searchString, pagination],
initialData: {} as IMessageTableProps,
gcTime: 0,
queryFn: async () => {
if (memoryBaseId) {
const { data } = await getMemoryDetailById(memoryBaseId as string, {
keyword: searchString,
page: pagination.current,
page_size: pagination.pageSize,
});
return data?.data ?? {};
} else {
return {};
}
},
});
return {
data,
loading,
handleInputChange,
searchString,
pagination,
setPagination,
};
};
export const useMessageAction = () => {
const queryClient = useQueryClient();
const [selectedMessage, setSelectedMessage] = useState<IMessageInfo>(
{} as IMessageInfo,
);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const handleClickDeleteMessage = useCallback((message: IMessageInfo) => {
console.log('handleClickDeleteMessage', message);
setSelectedMessage(message);
setShowDeleteDialog(true);
}, []);
const handleDeleteMessage = useCallback(() => {
// delete message
memoryService.deleteMemoryMessage(selectedMessage.message_id).then(() => {
message.success(t('message.deleted'));
queryClient.invalidateQueries({
queryKey: [MemoryApiAction.FetchMemoryMessage],
});
});
setShowDeleteDialog(false);
}, [selectedMessage.message_id, queryClient]);
const [showMessageContentDialog, setShowMessageContentDialog] =
useState(false);
const [selectedMessageContent, setSelectedMessageContent] =
useState<IMessageContentProps>({} as IMessageContentProps);
const {
data: messageContent,
isPending: fetchMessageContentLoading,
mutateAsync: fetchMessageContent,
} = useMutation<IMessageContentProps>({
mutationKey: [
MemoryApiAction.FetchMessageContent,
selectedMessage.message_id,
],
mutationFn: async () => {
setShowMessageContentDialog(true);
const res = await memoryService.getMessageContent(
selectedMessage.message_id,
);
if (res.data.code === 0) {
setSelectedMessageContent(res.data.data);
} else {
message.error(res.data.message);
}
return res.data.data;
},
});
const handleClickMessageContentDialog = useCallback(
(message: IMessageInfo) => {
setSelectedMessage(message);
fetchMessageContent();
},
[fetchMessageContent],
);
return {
selectedMessage,
setSelectedMessage,
showDeleteDialog,
setShowDeleteDialog,
handleClickDeleteMessage,
handleDeleteMessage,
messageContent,
fetchMessageContentLoading,
fetchMessageContent,
selectedMessageContent,
showMessageContentDialog,
setShowMessageContentDialog,
handleClickMessageContentDialog,
};
};

View File

@ -1,6 +1,6 @@
import ListFilterBar from '@/components/list-filter-bar';
import { t } from 'i18next';
import { useFetchMemoryMessageList } from '../hooks/use-memory-messages';
import { useFetchMemoryMessageList } from './hook';
import { MemoryTable } from './message-table';
export default function MemoryMessage() {

View File

@ -17,3 +17,8 @@ export interface IMessageTableProps {
messages: { message_list: Array<IMessageInfo>; total: number };
storage_type: string;
}
export interface IMessageContentProps {
content: string;
content_embed: string;
}

View File

@ -1,3 +1,23 @@
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogNode,
} from '@/components/confirm-delete-dialog';
import { EmptyType } from '@/components/empty/constant';
import Empty from '@/components/empty/empty';
import { Button } from '@/components/ui/button';
import { Modal } from '@/components/ui/modal/modal';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { Switch } from '@/components/ui/switch';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Pagination } from '@/interfaces/common';
import { replaceText } from '@/pages/dataset/process-log-modal';
import {
ColumnDef,
ColumnFiltersState,
@ -10,26 +30,13 @@ import {
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import * as React from 'react';
import { EmptyType } from '@/components/empty/constant';
import Empty from '@/components/empty/empty';
import { Button } from '@/components/ui/button';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { Switch } from '@/components/ui/switch';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Pagination } from '@/interfaces/common';
import { t } from 'i18next';
import { pick } from 'lodash';
import { Eraser, TextSelect } from 'lucide-react';
import { useMemo } from 'react';
import { Copy, Eraser, TextSelect } from 'lucide-react';
import * as React from 'react';
import { useMemo, useState } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { useMessageAction } from './hook';
import { IMessageInfo } from './interface';
export type MemoryTableProps = {
@ -51,13 +58,27 @@ export function MemoryTable({
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [copied, setCopied] = useState(false);
const {
showDeleteDialog,
setShowDeleteDialog,
handleClickDeleteMessage,
selectedMessage,
handleDeleteMessage,
fetchMessageContent,
selectedMessageContent,
showMessageContentDialog,
setShowMessageContentDialog,
handleClickMessageContentDialog,
} = useMessageAction();
// Define columns for the memory table
const columns: ColumnDef<IMessageInfo>[] = useMemo(
() => [
{
accessorKey: 'session_id',
header: () => <span>{t('memoryDetail.messages.sessionId')}</span>,
header: () => <span>{t('memory.messages.sessionId')}</span>,
cell: ({ row }) => (
<div className="text-sm font-medium ">
{row.getValue('session_id')}
@ -66,7 +87,7 @@ export function MemoryTable({
},
{
accessorKey: 'agent_name',
header: () => <span>{t('memoryDetail.messages.agent')}</span>,
header: () => <span>{t('memory.messages.agent')}</span>,
cell: ({ row }) => (
<div className="text-sm font-medium ">
{row.getValue('agent_name')}
@ -75,7 +96,7 @@ export function MemoryTable({
},
{
accessorKey: 'message_type',
header: () => <span>{t('memoryDetail.messages.type')}</span>,
header: () => <span>{t('memory.messages.type')}</span>,
cell: ({ row }) => (
<div className="text-sm font-medium capitalize">
{row.getValue('message_type')}
@ -84,28 +105,28 @@ export function MemoryTable({
},
{
accessorKey: 'valid_at',
header: () => <span>{t('memoryDetail.messages.validDate')}</span>,
header: () => <span>{t('memory.messages.validDate')}</span>,
cell: ({ row }) => (
<div className="text-sm ">{row.getValue('valid_at')}</div>
),
},
{
accessorKey: 'forget_at',
header: () => <span>{t('memoryDetail.messages.forgetAt')}</span>,
header: () => <span>{t('memory.messages.forgetAt')}</span>,
cell: ({ row }) => (
<div className="text-sm ">{row.getValue('forget_at')}</div>
),
},
{
accessorKey: 'source_id',
header: () => <span>{t('memoryDetail.messages.source')}</span>,
header: () => <span>{t('memory.messages.source')}</span>,
cell: ({ row }) => (
<div className="text-sm ">{row.getValue('source_id')}</div>
),
},
{
accessorKey: 'status',
header: () => <span>{t('memoryDetail.messages.enable')}</span>,
header: () => <span>{t('memory.messages.enable')}</span>,
cell: ({ row }) => {
const isEnabled = row.getValue('status') as boolean;
return (
@ -117,19 +138,28 @@ export function MemoryTable({
},
{
accessorKey: 'action',
header: () => <span>{t('memoryDetail.messages.action')}</span>,
header: () => <span>{t('memory.messages.action')}</span>,
meta: {
cellClassName: 'w-12',
},
cell: () => (
cell: ({ row }) => (
<div className=" flex opacity-0 group-hover:opacity-100">
<Button variant={'ghost'} className="bg-transparent">
<Button
variant={'ghost'}
className="bg-transparent"
onClick={() => {
handleClickMessageContentDialog(row.original);
}}
>
<TextSelect />
</Button>
<Button
variant={'delete'}
className="bg-transparent"
aria-label="Edit"
onClick={() => {
handleClickDeleteMessage(row.original);
}}
>
<Eraser />
</Button>
@ -137,7 +167,7 @@ export function MemoryTable({
),
},
],
[],
[handleClickDeleteMessage],
);
const currentPagination = useMemo(() => {
@ -210,6 +240,85 @@ export function MemoryTable({
)}
</TableBody>
</Table>
{showDeleteDialog && (
<ConfirmDeleteDialog
onOk={handleDeleteMessage}
title={t('memory.messages.forgetMessage')}
open={showDeleteDialog}
onOpenChange={setShowDeleteDialog}
content={{
node: (
<ConfirmDeleteDialogNode
// avatar={{ avatar: selectedMessage.avatar, name: selectedMessage.name }}
name={
t('memory.messages.sessionId') +
': ' +
selectedMessage.session_id
}
warnText={t('memory.messages.delMessageWarn')}
/>
),
}}
/>
)}
{showMessageContentDialog && (
<Modal
title={t('memory.messages.content')}
open={showMessageContentDialog}
onOpenChange={setShowMessageContentDialog}
className="!w-[640px]"
footer={
<div className="flex justify-end gap-2">
<button
type="button"
onClick={() => setShowMessageContentDialog(false)}
className={
'px-2 py-1 border border-border-button rounded-md hover:bg-bg-card hover:text-text-primary '
}
>
{t('common.close')}
</button>
</div>
}
>
<div className="flex flex-col gap-2.5">
<div className="text-text-secondary text-sm">
{t('memory.messages.sessionId')}:&nbsp;&nbsp;
{selectedMessage.session_id}
</div>
{selectedMessageContent?.content && (
<div className="w-full bg-accent-primary-5 whitespace-pre-line text-wrap rounded-lg h-fit max-h-[350px] overflow-y-auto scrollbar-auto px-2.5 py-1">
{replaceText(selectedMessageContent?.content || '')}
</div>
)}
{selectedMessageContent?.content_embed && (
<div className="flex gap-2 items-center">
<CopyToClipboard
text={selectedMessageContent?.content_embed}
onCopy={() => {
setCopied(true);
setTimeout(() => setCopied(false), 1000);
}}
>
<Button
variant={'ghost'}
className="border border-border-button "
>
{t('memory.messages.contentEmbed')}
<Copy />
</Button>
</CopyToClipboard>
{copied && (
<span className="text-xs text-text-secondary">
{t('memory.messages.copied')}
</span>
)}
</div>
)}
</div>
</Modal>
)}
<div className="flex items-center justify-end py-4 absolute bottom-3 right-3">
<RAGFlowPagination

View File

@ -0,0 +1,159 @@
import { FormFieldType, RenderField } from '@/components/dynamic-form';
import { SingleFormSlider } from '@/components/ui/dual-range-slider';
import { NumberInput } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { cn } from '@/lib/utils';
import { t } from 'i18next';
import { ListChevronsDownUp, ListChevronsUpDown } from 'lucide-react';
import { useState } from 'react';
import { z } from 'zod';
export const advancedSettingsFormSchema = {
permission: z.string().optional(),
storage_type: z.enum(['table', 'graph']).optional(),
forget_policy: z.enum(['lru', 'fifo']).optional(),
temperature: z.number().optional(),
system_prompt: z.string().optional(),
user_prompt: z.string().optional(),
};
export const defaultAdvancedSettingsForm = {
permission: 'me',
storage_type: 'table',
forget_policy: 'fifo',
temperature: 0.7,
system_prompt: '',
user_prompt: '',
};
export const AdvancedSettingsForm = () => {
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
return (
<>
<div
className="flex items-center gap-1 w-full cursor-pointer"
onClick={() => setShowAdvancedSettings(!showAdvancedSettings)}
>
{showAdvancedSettings ? (
<ListChevronsDownUp size={14} />
) : (
<ListChevronsUpDown size={14} />
)}
{t('memory.config.advancedSettings')}
</div>
{/* {showAdvancedSettings && ( */}
<>
<RenderField
field={{
name: 'permission',
label: t('memory.config.permission'),
required: false,
horizontal: true,
// hideLabel: true,
type: FormFieldType.Custom,
render: (field) => (
<RadioGroup
defaultValue="me"
className="flex"
{...field}
onValueChange={(value) => {
console.log(value);
field.onChange(value);
}}
>
<div className="flex items-center gap-3">
<RadioGroupItem value="me" id="r1" />
<Label htmlFor="r1">{t('memory.config.onlyMe')}</Label>
</div>
<div className="flex items-center gap-3">
<RadioGroupItem value="team" id="r2" />
<Label htmlFor="r2">{t('memory.config.team')}</Label>
</div>
</RadioGroup>
),
}}
/>
<RenderField
field={{
name: 'storage_type',
label: t('memory.config.storageType'),
type: FormFieldType.Select,
horizontal: true,
placeholder: t('memory.config.storageTypePlaceholder'),
options: [
{ label: 'table', value: 'table' },
// { label: 'graph', value: 'graph' },
],
required: false,
}}
/>
<RenderField
field={{
name: 'forget_policy',
label: t('memory.config.forgetPolicy'),
type: FormFieldType.Select,
horizontal: true,
// placeholder: t('memory.config.storageTypePlaceholder'),
options: [
{ label: 'lru', value: 'lru' },
{ label: 'fifo', value: 'fifo' },
],
required: false,
}}
/>
<RenderField
field={{
name: 'temperature',
label: t('memory.config.temperature'),
type: FormFieldType.Custom,
horizontal: true,
required: false,
render: (field) => (
<div className="flex gap-2 items-center">
<SingleFormSlider
{...field}
onChange={(value: number) => {
field.onChange(value);
}}
max={1}
step={0.01}
min={0}
disabled={false}
></SingleFormSlider>
<NumberInput
className={cn(
'h-6 w-10 p-1 border border-border-button rounded-sm',
)}
max={1}
step={0.01}
min={0}
{...field}
></NumberInput>
</div>
),
}}
/>
<RenderField
field={{
name: 'system_prompt',
label: t('memory.config.systemPrompt'),
type: FormFieldType.Textarea,
horizontal: true,
placeholder: t('memory.config.systemPromptPlaceholder'),
required: false,
}}
/>
<RenderField
field={{
name: 'user_prompt',
label: t('memory.config.userPrompt'),
type: FormFieldType.Text,
horizontal: true,
placeholder: t('memory.config.userPromptPlaceholder'),
required: false,
}}
/>
</>
{/* )} */}
</>
);
};

View File

@ -0,0 +1,53 @@
import { AvatarUpload } from '@/components/avatar-upload';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Input } from '@/components/ui/input';
import { t } from 'i18next';
import { z } from 'zod';
export const basicInfoSchema = {
name: z.string().min(1, { message: t('setting.nameRequired') }),
avatar: z.string().optional(),
description: z.string().optional(),
};
export const defaultBasicInfo = { name: '', avatar: '', description: '' };
export const BasicInfo = () => {
return (
<>
<RAGFlowFormItem
name={'name'}
label={t('memories.name')}
required={true}
horizontal={true}
// tooltip={field.tooltip}
// labelClassName={labelClassName || field.labelClassName}
>
{(field) => {
return <Input {...field}></Input>;
}}
</RAGFlowFormItem>
<RAGFlowFormItem
name={'avatar'}
label={t('memory.config.avatar')}
required={false}
horizontal={true}
// tooltip={field.tooltip}
// labelClassName={labelClassName || field.labelClassName}
>
{(field) => {
return <AvatarUpload {...field}></AvatarUpload>;
}}
</RAGFlowFormItem>
<RAGFlowFormItem
name={'description'}
label={t('memory.config.description')}
required={false}
horizontal={true}
// tooltip={field.tooltip}
// labelClassName={labelClassName || field.labelClassName}
>
{(field) => {
return <Input {...field}></Input>;
}}
</RAGFlowFormItem>
</>
);
};

View File

@ -0,0 +1,42 @@
import { useUpdateMemory } from '@/pages/memories/hooks';
import { IMemory, IMemoryAppDetailProps } from '@/pages/memories/interface';
import { omit } from 'lodash';
import { useCallback, useState } from 'react';
export const useUpdateMemoryConfig = () => {
const { updateMemory } = useUpdateMemory();
const [loading, setLoading] = useState(false);
const onMemoryRenameOk = useCallback(
async (data: IMemory) => {
let res;
setLoading(true);
if (data?.id) {
// console.log('memory-->', memory, data);
try {
const params = omit(data, [
'id',
'memory_type',
'embd_id',
'storage_type',
]);
res = await updateMemory({
// ...memoryDataTemp,
// data: data,
id: data.id,
...params,
} as unknown as IMemoryAppDetailProps);
// if (res && res.data.code === 0) {
// message.success(t('message.update_success'));
// } else {
// message.error(t('message.update_fail'));
// }
} catch (e) {
console.error('error', e);
}
}
setLoading(false);
},
[updateMemory],
);
return { onMemoryRenameOk, loading };
};

View File

@ -1,13 +1,110 @@
import { DynamicForm } from '@/components/dynamic-form';
import { Button } from '@/components/ui/button';
import Divider from '@/components/ui/divider';
import { Form } from '@/components/ui/form';
import { MainContainer } from '@/pages/dataset/dataset-setting/configuration-form-container';
import { TopTitle } from '@/pages/dataset/dataset-title';
import { IMemory } from '@/pages/memories/interface';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { useFetchMemoryBaseConfiguration } from '../hooks/use-memory-setting';
import {
AdvancedSettingsForm,
advancedSettingsFormSchema,
defaultAdvancedSettingsForm,
} from './advanced-settings-form';
import { BasicInfo, basicInfoSchema, defaultBasicInfo } from './basic-form';
import { useUpdateMemoryConfig } from './hook';
import {
MemoryModelForm,
defaultMemoryModelForm,
memoryModelFormSchema,
} from './memory-model-form';
const MemoryMessageSchema = z.object({
id: z.string(),
...basicInfoSchema,
...memoryModelFormSchema,
...advancedSettingsFormSchema,
});
// type MemoryMessageForm = z.infer<typeof MemoryMessageSchema>;
export default function MemoryMessage() {
const form = useForm<IMemory>({
resolver: zodResolver(MemoryMessageSchema),
defaultValues: {
id: '',
...defaultBasicInfo,
...defaultMemoryModelForm,
...defaultAdvancedSettingsForm,
} as unknown as IMemory,
});
const { data } = useFetchMemoryBaseConfiguration();
const { onMemoryRenameOk, loading } = useUpdateMemoryConfig();
useEffect(() => {
form.reset({
id: data?.id,
embd_id: data?.embd_id,
llm_id: data?.llm_id,
name: data?.name || '',
description: data?.description || '',
avatar: data?.avatar || '',
memory_size: data?.memory_size,
memory_type: data?.memory_type,
temperature: data?.temperature,
system_prompt: data?.system_prompt || '',
user_prompt: data?.user_prompt || '',
forgetting_policy: data?.forgetting_policy || 'fifo',
permissions: data?.permissions || 'me',
});
}, [data, form]);
const onSubmit = (data: IMemory) => {
onMemoryRenameOk(data);
};
return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<div className="h-4 w-4 rounded-full bg-text-secondary">11</div>
<div className="h-4 w-4 rounded-full bg-text-secondary">11</div>
<section className="h-full flex flex-col">
<TopTitle
title={t('knowledgeDetails.configuration')}
description={t('knowledgeConfiguration.titleDescription')}
></TopTitle>
<div className="flex gap-14 flex-1 min-h-0">
<Form {...form}>
<form onSubmit={form.handleSubmit(() => {})} className="space-y-6 ">
<div className="w-[768px] h-[calc(100vh-300px)] pr-1 overflow-y-auto scrollbar-auto">
<MainContainer className="text-text-secondary !space-y-10">
<div className="text-base font-medium text-text-primary">
{t('knowledgeConfiguration.baseInfo')}
</div>
<BasicInfo></BasicInfo>
<Divider />
<MemoryModelForm />
<AdvancedSettingsForm />
</MainContainer>
</div>
<div className="text-right items-center flex justify-end gap-3 w-[768px]">
<Button
type="reset"
className="bg-transparent text-color-white hover:bg-transparent border-border-button border"
onClick={() => {
form.reset();
}}
>
{t('knowledgeConfiguration.cancel')}
</Button>
<DynamicForm.SavingButton
submitLoading={loading}
submitFunc={(value) => {
console.log('form-value', value);
onSubmit(value as IMemory);
}}
></DynamicForm.SavingButton>
</div>
</form>
</Form>
</div>
<div className="flex items-center gap-2">
<div className="h-4 w-4 rounded-full bg-text ">setting</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,74 @@
import { FormFieldType, RenderField } from '@/components/dynamic-form';
import { useModelOptions } from '@/components/llm-setting-items/llm-form-field';
import { EmbeddingSelect } from '@/pages/dataset/dataset-setting/configuration/common-item';
import { t } from 'i18next';
import { z } from 'zod';
export const memoryModelFormSchema = {
embd_id: z.string(),
llm_id: z.string(),
memory_type: z.array(z.string()).optional(),
memory_size: z.number().optional(),
};
export const defaultMemoryModelForm = {
embd_id: '',
llm_id: '',
memory_type: [],
memory_size: 0,
};
export const MemoryModelForm = () => {
const { modelOptions } = useModelOptions();
return (
<>
<RenderField
field={{
name: 'embd_id',
label: t('memories.embeddingModel'),
placeholder: t('memories.selectModel'),
required: true,
horizontal: true,
// hideLabel: true,
type: FormFieldType.Custom,
render: (field) => <EmbeddingSelect field={field} isEdit={false} />,
}}
/>
<RenderField
field={{
name: 'llm_id',
label: t('memories.llm'),
placeholder: t('memories.selectModel'),
required: true,
horizontal: true,
type: FormFieldType.Select,
options: modelOptions as { value: string; label: string }[],
}}
/>
<RenderField
field={{
name: 'memory_type',
label: t('memories.memoryType'),
type: FormFieldType.MultiSelect,
horizontal: true,
placeholder: t('memories.memoryTypePlaceholder'),
options: [
{ label: 'Raw', value: 'raw' },
{ label: 'Semantic', value: 'semantic' },
{ label: 'Episodic', value: 'episodic' },
{ label: 'Procedural', value: 'procedural' },
],
required: true,
}}
/>
<RenderField
field={{
name: 'memory_size',
label: t('memory.config.memorySize'),
type: FormFieldType.Number,
horizontal: true,
// placeholder: t('memory.config.memorySizePlaceholder'),
required: false,
}}
/>
</>
);
};

View File

@ -1,36 +1,31 @@
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Button } from '@/components/ui/button';
import { useSecondPathName } from '@/hooks/route-hook';
import { cn, formatBytes } from '@/lib/utils';
import { cn } from '@/lib/utils';
import { Routes } from '@/routes';
import { formatPureDate } from '@/utils/date';
import { Banknote, Logs } from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useFetchMemoryBaseConfiguration } from '../hooks/use-memory-setting';
import { useHandleMenuClick } from './hooks';
type PropType = {
refreshCount?: number;
};
export function SideBar({ refreshCount }: PropType) {
export function SideBar() {
const pathName = useSecondPathName();
const { handleMenuClick } = useHandleMenuClick();
// refreshCount: be for avatar img sync update on top left
const { data } = useFetchMemoryBaseConfiguration({ refreshCount });
const { data } = useFetchMemoryBaseConfiguration();
const { t } = useTranslation();
const items = useMemo(() => {
const list = [
{
icon: <Logs className="size-4" />,
label: t(`knowledgeDetails.overview`),
label: t(`memory.sideBar.messages`),
key: Routes.MemoryMessage,
},
{
icon: <Banknote className="size-4" />,
label: t(`knowledgeDetails.configuration`),
label: t(`memory.sideBar.configuration`),
key: Routes.MemorySetting,
},
];
@ -49,15 +44,15 @@ export function SideBar({ refreshCount }: PropType) {
<h3 className="text-lg font-semibold line-clamp-1 text-text-primary text-ellipsis overflow-hidden">
{data.name}
</h3>
<div className="flex justify-between">
{/* <div className="flex justify-between">
<span>
{data.doc_num} {t('knowledgeDetails.files')}
</span>
<span>{formatBytes(data.size)}</span>
</div>
<div>
{t('knowledgeDetails.created')} {formatPureDate(data.create_time)}
</div>
{t('knowledgeDetails.created')} {formatPureDate(data.)}
</div> */}
</div>
</div>