mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-19 03:56:42 +08:00
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:
@ -45,7 +45,7 @@ export function ConfirmDeleteDialog({
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (hidden) {
|
||||
return children;
|
||||
return children || <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -54,7 +54,7 @@ export function ConfirmDeleteDialog({
|
||||
open={open}
|
||||
defaultOpen={defaultOpen}
|
||||
>
|
||||
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
||||
{children && <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>}
|
||||
<AlertDialogOverlay
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@ -109,23 +109,28 @@ export function ConfirmDeleteDialog({
|
||||
export const ConfirmDeleteDialogNode = ({
|
||||
avatar,
|
||||
name,
|
||||
warnText,
|
||||
children,
|
||||
}: {
|
||||
avatar?: { avatar?: string; name?: string; isPerson?: boolean };
|
||||
name?: string;
|
||||
warnText?: string;
|
||||
children?: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center border-0.5 text-text-secondary border-border-button rounded-lg px-3 py-4">
|
||||
{avatar && (
|
||||
<RAGFlowAvatar
|
||||
className="w-8 h-8"
|
||||
avatar={avatar.avatar}
|
||||
isPerson={avatar.isPerson}
|
||||
name={avatar.name}
|
||||
/>
|
||||
)}
|
||||
{name && <div className="ml-3">{name}</div>}
|
||||
<div className="flex flex-col gap-2.5">
|
||||
<div className="flex items-center border-0.5 text-text-secondary border-border-button rounded-lg px-3 py-4">
|
||||
{avatar && (
|
||||
<RAGFlowAvatar
|
||||
className="w-8 h-8"
|
||||
avatar={avatar.avatar}
|
||||
isPerson={avatar.isPerson}
|
||||
name={avatar.name}
|
||||
/>
|
||||
)}
|
||||
{name && <div className="ml-3">{name}</div>}
|
||||
</div>
|
||||
{warnText && <div className="text-state-error text-xs">{warnText}</div>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -110,7 +110,7 @@ export interface DynamicFormRef {
|
||||
}
|
||||
|
||||
// Generate Zod validation schema based on field configurations
|
||||
const generateSchema = (fields: FormFieldConfig[]): ZodSchema<any> => {
|
||||
export const generateSchema = (fields: FormFieldConfig[]): ZodSchema<any> => {
|
||||
const schema: Record<string, ZodSchema> = {};
|
||||
const nestedSchemas: Record<string, Record<string, ZodSchema>> = {};
|
||||
|
||||
@ -311,6 +311,271 @@ const generateDefaultValues = <T extends FieldValues>(
|
||||
|
||||
return defaultValues as DefaultValues<T>;
|
||||
};
|
||||
// Render form fields
|
||||
export const RenderField = ({
|
||||
field,
|
||||
labelClassName,
|
||||
}: {
|
||||
field: FormFieldConfig;
|
||||
labelClassName?: string;
|
||||
}) => {
|
||||
const form = useFormContext();
|
||||
if (field.render) {
|
||||
if (field.type === FormFieldType.Custom && field.hideLabel) {
|
||||
return <div className="w-full">{field.render({})}</div>;
|
||||
}
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (e: any) => {
|
||||
fieldProps.onChange(e);
|
||||
field.onChange?.(e.target?.value ?? e);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return field.render?.(finalFieldProps);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
switch (field.type) {
|
||||
case FormFieldType.Textarea:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (e: any) => {
|
||||
fieldProps.onChange(e);
|
||||
field.onChange?.(e.target.value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
<Textarea
|
||||
{...finalFieldProps}
|
||||
placeholder={field.placeholder}
|
||||
// className="resize-none"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
case FormFieldType.Select:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (value: string) => {
|
||||
console.log('select value', value);
|
||||
if (fieldProps.onChange) {
|
||||
fieldProps.onChange(value);
|
||||
}
|
||||
field.onChange?.(value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
<SelectWithSearch
|
||||
triggerClassName="!shrink"
|
||||
{...finalFieldProps}
|
||||
options={field.options}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
case FormFieldType.MultiSelect:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
console.log('multi select value', fieldProps);
|
||||
const finalFieldProps = {
|
||||
...fieldProps,
|
||||
onValueChange: (value: string[]) => {
|
||||
if (fieldProps.onChange) {
|
||||
fieldProps.onChange(value);
|
||||
}
|
||||
field.onChange?.(value);
|
||||
},
|
||||
};
|
||||
return (
|
||||
<MultiSelect
|
||||
variant="inverted"
|
||||
maxCount={100}
|
||||
{...finalFieldProps}
|
||||
// onValueChange={(data) => {
|
||||
// console.log(data);
|
||||
// field.onChange?.(data);
|
||||
// }}
|
||||
options={field.options as MultiSelectOptionType[]}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
case FormFieldType.Checkbox:
|
||||
return (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={field.name as any}
|
||||
render={({ field: formField }) => (
|
||||
<FormItem
|
||||
className={cn('flex items-center w-full', {
|
||||
'flex-row items-center space-x-3 space-y-0': !field.horizontal,
|
||||
})}
|
||||
>
|
||||
{field.label && !field.horizontal && (
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel
|
||||
className={cn(
|
||||
'font-medium',
|
||||
labelClassName || field.labelClassName,
|
||||
)}
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{field.label}{' '}
|
||||
{field.required && (
|
||||
<span className="text-destructive">*</span>
|
||||
)}
|
||||
</FormLabel>
|
||||
</div>
|
||||
)}
|
||||
{field.label && field.horizontal && (
|
||||
<div className="space-y-1 leading-none w-1/4">
|
||||
<FormLabel
|
||||
className={cn(
|
||||
'font-medium',
|
||||
labelClassName || field.labelClassName,
|
||||
)}
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{field.label}{' '}
|
||||
{field.required && (
|
||||
<span className="text-destructive">*</span>
|
||||
)}
|
||||
</FormLabel>
|
||||
</div>
|
||||
)}
|
||||
<FormControl>
|
||||
<div className={cn({ 'w-full': field.horizontal })}>
|
||||
<Checkbox
|
||||
checked={formField.value}
|
||||
onCheckedChange={(checked) => {
|
||||
formField.onChange(checked);
|
||||
field.onChange?.(checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
case FormFieldType.Tag:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (value: string[]) => {
|
||||
fieldProps.onChange(value);
|
||||
field.onChange?.(value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
// <TagInput {...fieldProps} placeholder={field.placeholder} />
|
||||
<div className="w-full">
|
||||
<EditTag {...finalFieldProps}></EditTag>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (e: any) => {
|
||||
fieldProps.onChange(e);
|
||||
field.onChange?.(e.target.value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Input
|
||||
{...finalFieldProps}
|
||||
type={field.type}
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Dynamic form component
|
||||
const DynamicForm = {
|
||||
@ -497,266 +762,6 @@ const DynamicForm = {
|
||||
// Submit handler
|
||||
// const handleSubmit = form.handleSubmit(onSubmit);
|
||||
|
||||
// Render form fields
|
||||
const renderField = (field: FormFieldConfig) => {
|
||||
if (field.render) {
|
||||
if (field.type === FormFieldType.Custom && field.hideLabel) {
|
||||
return <div className="w-full">{field.render({})}</div>;
|
||||
}
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (e: any) => {
|
||||
fieldProps.onChange(e);
|
||||
field.onChange?.(e.target?.value ?? e);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return field.render?.(finalFieldProps);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
switch (field.type) {
|
||||
case FormFieldType.Textarea:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (e: any) => {
|
||||
fieldProps.onChange(e);
|
||||
field.onChange?.(e.target.value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
<Textarea
|
||||
{...finalFieldProps}
|
||||
placeholder={field.placeholder}
|
||||
// className="resize-none"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
case FormFieldType.Select:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (value: string) => {
|
||||
console.log('select value', value);
|
||||
if (fieldProps.onChange) {
|
||||
fieldProps.onChange(value);
|
||||
}
|
||||
field.onChange?.(value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
<SelectWithSearch
|
||||
triggerClassName="!shrink"
|
||||
{...finalFieldProps}
|
||||
options={field.options}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
case FormFieldType.MultiSelect:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
console.log('multi select value', fieldProps);
|
||||
const finalFieldProps = {
|
||||
...fieldProps,
|
||||
onValueChange: (value: string[]) => {
|
||||
if (fieldProps.onChange) {
|
||||
fieldProps.onChange(value);
|
||||
}
|
||||
field.onChange?.(value);
|
||||
},
|
||||
};
|
||||
return (
|
||||
<MultiSelect
|
||||
variant="inverted"
|
||||
maxCount={100}
|
||||
{...finalFieldProps}
|
||||
// onValueChange={(data) => {
|
||||
// console.log(data);
|
||||
// field.onChange?.(data);
|
||||
// }}
|
||||
options={field.options as MultiSelectOptionType[]}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
case FormFieldType.Checkbox:
|
||||
return (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={field.name as any}
|
||||
render={({ field: formField }) => (
|
||||
<FormItem
|
||||
className={cn('flex items-center w-full', {
|
||||
'flex-row items-center space-x-3 space-y-0':
|
||||
!field.horizontal,
|
||||
})}
|
||||
>
|
||||
{field.label && !field.horizontal && (
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel
|
||||
className={cn(
|
||||
'font-medium',
|
||||
labelClassName || field.labelClassName,
|
||||
)}
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{field.label}{' '}
|
||||
{field.required && (
|
||||
<span className="text-destructive">*</span>
|
||||
)}
|
||||
</FormLabel>
|
||||
</div>
|
||||
)}
|
||||
{field.label && field.horizontal && (
|
||||
<div className="space-y-1 leading-none w-1/4">
|
||||
<FormLabel
|
||||
className={cn(
|
||||
'font-medium',
|
||||
labelClassName || field.labelClassName,
|
||||
)}
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{field.label}{' '}
|
||||
{field.required && (
|
||||
<span className="text-destructive">*</span>
|
||||
)}
|
||||
</FormLabel>
|
||||
</div>
|
||||
)}
|
||||
<FormControl>
|
||||
<div className={cn({ 'w-full': field.horizontal })}>
|
||||
<Checkbox
|
||||
checked={formField.value}
|
||||
onCheckedChange={(checked) => {
|
||||
formField.onChange(checked);
|
||||
field.onChange?.(checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
case FormFieldType.Tag:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (value: string[]) => {
|
||||
fieldProps.onChange(value);
|
||||
field.onChange?.(value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
// <TagInput {...fieldProps} placeholder={field.placeholder} />
|
||||
<div className="w-full">
|
||||
<EditTag {...finalFieldProps}></EditTag>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (e: any) => {
|
||||
fieldProps.onChange(e);
|
||||
field.onChange?.(e.target.value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Input
|
||||
{...finalFieldProps}
|
||||
type={field.type}
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Watch all form values to re-render when they change (for shouldRender checks)
|
||||
const formValues = form.watch();
|
||||
|
||||
@ -779,7 +784,10 @@ const DynamicForm = {
|
||||
key={field.name}
|
||||
className={cn({ hidden: field.hidden || !shouldShow })}
|
||||
>
|
||||
{renderField(field)}
|
||||
<RenderField
|
||||
field={field}
|
||||
labelClassName={labelClassName}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@ -798,7 +806,7 @@ const DynamicForm = {
|
||||
buttonText,
|
||||
submitFunc,
|
||||
}: {
|
||||
submitLoading: boolean;
|
||||
submitLoading?: boolean;
|
||||
buttonText?: string;
|
||||
submitFunc?: (values: FieldValues) => void;
|
||||
}) => {
|
||||
|
||||
@ -53,7 +53,12 @@ export function RAGFlowFormItem({
|
||||
{label}
|
||||
</FormLabel>
|
||||
)}
|
||||
<div className="w-full flex flex-col">
|
||||
<div
|
||||
className={cn('flex flex-col', {
|
||||
'w-3/4': horizontal,
|
||||
'w-full': !horizontal,
|
||||
})}
|
||||
>
|
||||
<FormControl>
|
||||
{typeof children === 'function'
|
||||
? children(field)
|
||||
|
||||
@ -70,6 +70,7 @@ export function Header() {
|
||||
{ path: Routes.Chats, name: t('header.chat'), icon: MessageSquareText },
|
||||
{ path: Routes.Searches, name: t('header.search'), icon: Search },
|
||||
{ path: Routes.Agents, name: t('header.flow'), icon: Cpu },
|
||||
// { path: Routes.Memories, name: t('header.Memories'), icon: Cpu },
|
||||
{ path: Routes.Files, name: t('header.fileManager'), icon: File },
|
||||
],
|
||||
[t],
|
||||
|
||||
@ -101,7 +101,7 @@ export default {
|
||||
dataset: 'Dataset',
|
||||
Memories: 'Memory',
|
||||
},
|
||||
memory: {
|
||||
memories: {
|
||||
memory: 'Memory',
|
||||
createMemory: 'Create Memory',
|
||||
name: 'Name',
|
||||
@ -110,9 +110,15 @@ export default {
|
||||
embeddingModel: 'Embedding model',
|
||||
selectModel: 'Select model',
|
||||
llm: 'LLM',
|
||||
delMemoryWarn: `After deletion, all messages in this memory will be deleted and cannot be retrieved by agents.`,
|
||||
},
|
||||
memoryDetail: {
|
||||
memory: {
|
||||
messages: {
|
||||
copied: 'Copied!',
|
||||
contentEmbed: 'Content embed',
|
||||
content: 'Content',
|
||||
delMessageWarn: `After forgetting, this message will not be retrieved by agents.`,
|
||||
forgetMessage: 'Forget message',
|
||||
sessionId: 'Session ID',
|
||||
agent: 'Agent',
|
||||
type: 'Type',
|
||||
@ -122,6 +128,27 @@ export default {
|
||||
enable: 'Enable',
|
||||
action: 'Action',
|
||||
},
|
||||
config: {
|
||||
avatar: 'Avatar',
|
||||
description: 'Description',
|
||||
memorySize: 'Memory size',
|
||||
advancedSettings: 'Advanced Settings',
|
||||
permission: 'Permission',
|
||||
onlyMe: 'Only Me',
|
||||
team: 'Team',
|
||||
storageType: 'Storage Type',
|
||||
storageTypePlaceholder: 'Please select storage type',
|
||||
forgetPolicy: 'Forget Policy',
|
||||
temperature: 'Temperature',
|
||||
systemPrompt: 'System Prompt',
|
||||
systemPromptPlaceholder: 'Please enter system prompt',
|
||||
userPrompt: 'User Prompt',
|
||||
userPromptPlaceholder: 'Please enter user prompt',
|
||||
},
|
||||
sideBar: {
|
||||
messages: 'Messages',
|
||||
configuration: 'Configuration',
|
||||
},
|
||||
},
|
||||
knowledgeList: {
|
||||
welcome: 'Welcome back',
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
@ -11,6 +12,7 @@ import { Spin } from '@/components/ui/spin';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { t } from 'i18next';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { FieldValues, useFormContext } from 'react-hook-form';
|
||||
import {
|
||||
@ -285,3 +287,14 @@ export function EnableTocToggle() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function OverlappedPercent() {
|
||||
return (
|
||||
<SliderInputFormField
|
||||
name="parser_config.overlapped_percent"
|
||||
label={t('flow.filenameEmbeddingWeight')}
|
||||
max={0.5}
|
||||
step={0.01}
|
||||
></SliderInputFormField>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
ConfigurationFormContainer,
|
||||
MainContainer,
|
||||
} from '../configuration-form-container';
|
||||
import { EnableTocToggle } from './common-item';
|
||||
import { EnableTocToggle, OverlappedPercent } from './common-item';
|
||||
|
||||
export function NaiveConfiguration() {
|
||||
return (
|
||||
@ -20,6 +20,7 @@ export function NaiveConfiguration() {
|
||||
<MaxTokenNumberFormField initialValue={512}></MaxTokenNumberFormField>
|
||||
<DelimiterFormField></DelimiterFormField>
|
||||
<EnableTocToggle />
|
||||
<OverlappedPercent />
|
||||
</ConfigurationFormContainer>
|
||||
<ConfigurationFormContainer>
|
||||
<AutoKeywordsFormField></AutoKeywordsFormField>
|
||||
|
||||
@ -29,6 +29,7 @@ export const formSchema = z
|
||||
tag_kb_ids: z.array(z.string()).nullish(),
|
||||
topn_tags: z.number().optional(),
|
||||
toc_extraction: z.boolean().optional(),
|
||||
overlapped_percent: z.number().optional(),
|
||||
raptor: z
|
||||
.object({
|
||||
use_raptor: z.boolean().optional(),
|
||||
|
||||
@ -67,6 +67,7 @@ export default function DatasetSettings() {
|
||||
html4excel: false,
|
||||
topn_tags: 3,
|
||||
toc_extraction: false,
|
||||
overlapped_percent: 0,
|
||||
raptor: {
|
||||
use_raptor: true,
|
||||
max_token: 256,
|
||||
|
||||
@ -14,16 +14,18 @@ export function PermissionFormField() {
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name="permission"
|
||||
label={t('knowledgeConfiguration.permissions')}
|
||||
tooltip={t('knowledgeConfiguration.permissionsTip')}
|
||||
horizontal
|
||||
>
|
||||
<SelectWithSearch
|
||||
options={teamOptions}
|
||||
triggerClassName="w-3/4"
|
||||
></SelectWithSearch>
|
||||
</RAGFlowFormItem>
|
||||
<div className="items-center">
|
||||
<RAGFlowFormItem
|
||||
name="permission"
|
||||
label={t('knowledgeConfiguration.permissions')}
|
||||
tooltip={t('knowledgeConfiguration.permissionsTip')}
|
||||
horizontal={true}
|
||||
>
|
||||
<SelectWithSearch
|
||||
options={teamOptions}
|
||||
triggerClassName="w-full"
|
||||
></SelectWithSearch>
|
||||
</RAGFlowFormItem>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { useModelOptions } from '@/components/llm-setting-items/llm-form-field';
|
||||
import { HomeIcon } from '@/components/svg-icon';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { t } from 'i18next';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { createMemoryFields } from './constants';
|
||||
import { IMemory } from './interface';
|
||||
|
||||
@ -13,11 +13,10 @@ type IProps = {
|
||||
onSubmit?: (data: any) => void;
|
||||
initialMemory: IMemory;
|
||||
loading?: boolean;
|
||||
isCreate?: boolean;
|
||||
};
|
||||
export const AddOrEditModal = (props: IProps) => {
|
||||
const { open, onClose, onSubmit, initialMemory } = props;
|
||||
// const [fields, setFields] = useState<FormFieldConfig[]>(createMemoryFields);
|
||||
// const formRef = useRef<DynamicFormRef>(null);
|
||||
export const AddOrEditModal = memo((props: IProps) => {
|
||||
const { open, onClose, onSubmit, initialMemory, isCreate } = props;
|
||||
const [formInstance, setFormInstance] = useState<DynamicFormRef | null>(null);
|
||||
|
||||
const formCallbackRef = useCallback((node: DynamicFormRef | null) => {
|
||||
@ -28,15 +27,25 @@ export const AddOrEditModal = (props: IProps) => {
|
||||
}, []);
|
||||
const { modelOptions } = useModelOptions();
|
||||
|
||||
useEffect(() => {
|
||||
if (initialMemory && initialMemory.id) {
|
||||
formInstance?.onFieldUpdate('memory_type', { hidden: true });
|
||||
formInstance?.onFieldUpdate('embedding', { hidden: true });
|
||||
formInstance?.onFieldUpdate('llm', { hidden: true });
|
||||
const fields = useMemo(() => {
|
||||
if (!isCreate) {
|
||||
return createMemoryFields.filter((field: any) => field.name === 'name');
|
||||
} else {
|
||||
formInstance?.onFieldUpdate('llm', { options: modelOptions as any });
|
||||
const tempFields = createMemoryFields.map((field: any) => {
|
||||
if (field.name === 'llm_id') {
|
||||
return {
|
||||
...field,
|
||||
options: modelOptions,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...field,
|
||||
};
|
||||
}
|
||||
});
|
||||
return tempFields;
|
||||
}
|
||||
}, [modelOptions, formInstance, initialMemory]);
|
||||
}, [modelOptions, isCreate]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -48,7 +57,7 @@ export const AddOrEditModal = (props: IProps) => {
|
||||
<div>
|
||||
<HomeIcon name="memory" width={'24'} />
|
||||
</div>
|
||||
{t('memory.createMemory')}
|
||||
{t('memories.createMemory')}
|
||||
</div>
|
||||
}
|
||||
showfooter={false}
|
||||
@ -56,7 +65,7 @@ export const AddOrEditModal = (props: IProps) => {
|
||||
>
|
||||
<DynamicForm.Root
|
||||
ref={formCallbackRef}
|
||||
fields={createMemoryFields}
|
||||
fields={fields}
|
||||
onSubmit={() => {}}
|
||||
defaultValues={initialMemory}
|
||||
>
|
||||
@ -72,4 +81,4 @@ export const AddOrEditModal = (props: IProps) => {
|
||||
</DynamicForm.Root>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@ -4,16 +4,16 @@ import { t } from 'i18next';
|
||||
|
||||
export const createMemoryFields = [
|
||||
{
|
||||
name: 'memory_name',
|
||||
label: t('memory.name'),
|
||||
placeholder: t('memory.memoryNamePlaceholder'),
|
||||
name: 'name',
|
||||
label: t('memories.name'),
|
||||
placeholder: t('memories.memoryNamePlaceholder'),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'memory_type',
|
||||
label: t('memory.memoryType'),
|
||||
label: t('memories.memoryType'),
|
||||
type: FormFieldType.MultiSelect,
|
||||
placeholder: t('memory.descriptionPlaceholder'),
|
||||
placeholder: t('memories.descriptionPlaceholder'),
|
||||
options: [
|
||||
{ label: 'Raw', value: 'raw' },
|
||||
{ label: 'Semantic', value: 'semantic' },
|
||||
@ -23,18 +23,18 @@ export const createMemoryFields = [
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'embedding',
|
||||
label: t('memory.embeddingModel'),
|
||||
placeholder: t('memory.selectModel'),
|
||||
name: 'embd_id',
|
||||
label: t('memories.embeddingModel'),
|
||||
placeholder: t('memories.selectModel'),
|
||||
required: true,
|
||||
// hideLabel: true,
|
||||
// type: 'custom',
|
||||
render: (field) => <EmbeddingSelect field={field} isEdit={false} />,
|
||||
},
|
||||
{
|
||||
name: 'llm',
|
||||
label: t('memory.llm'),
|
||||
placeholder: t('memory.selectModel'),
|
||||
name: 'llm_id',
|
||||
label: t('memories.llm'),
|
||||
placeholder: t('memories.selectModel'),
|
||||
required: true,
|
||||
type: FormFieldType.Select,
|
||||
},
|
||||
|
||||
@ -7,6 +7,7 @@ import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import memoryService, { updateMemoryById } from '@/services/memory-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { omit } from 'lodash';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
@ -73,12 +74,12 @@ export const useFetchMemoryList = () => {
|
||||
queryFn: async () => {
|
||||
const { data: response } = await memoryService.getMemoryList(
|
||||
{
|
||||
params: {
|
||||
data: {
|
||||
keywords: debouncedSearchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
},
|
||||
data: {},
|
||||
params: {},
|
||||
},
|
||||
true,
|
||||
);
|
||||
@ -153,7 +154,9 @@ export const useDeleteMemory = () => {
|
||||
} = useMutation<DeleteMemoryResponse, Error, DeleteMemoryProps>({
|
||||
mutationKey: ['deleteMemory'],
|
||||
mutationFn: async (props) => {
|
||||
const { data: response } = await memoryService.deleteMemory(props);
|
||||
const { data: response } = await memoryService.deleteMemory(
|
||||
props.memory_id,
|
||||
);
|
||||
if (response.code !== 0) {
|
||||
throw new Error(response.message || 'Failed to delete memory');
|
||||
}
|
||||
@ -189,7 +192,8 @@ export const useUpdateMemory = () => {
|
||||
} = useMutation<any, Error, IMemoryAppDetailProps>({
|
||||
mutationKey: ['updateMemory'],
|
||||
mutationFn: async (formData) => {
|
||||
const { data: response } = await updateMemoryById(formData.id, formData);
|
||||
const param = omit(formData, ['id']);
|
||||
const { data: response } = await updateMemoryById(formData.id, param);
|
||||
if (response.code !== 0) {
|
||||
throw new Error(response.message || 'Failed to update memory');
|
||||
}
|
||||
@ -259,7 +263,7 @@ export const useRenameMemory = () => {
|
||||
// const { id, created_by, update_time, ...memoryDataTemp } = detail;
|
||||
res = await updateMemory({
|
||||
// ...memoryDataTemp,
|
||||
name: data.memory_name,
|
||||
name: data.name,
|
||||
id: memory?.id,
|
||||
} as unknown as IMemoryAppDetailProps);
|
||||
} catch (e) {
|
||||
@ -268,9 +272,9 @@ export const useRenameMemory = () => {
|
||||
} else {
|
||||
res = await createMemory(data);
|
||||
}
|
||||
if (res && !memory?.id) {
|
||||
navigateToMemory(res?.id)();
|
||||
}
|
||||
// if (res && !memory?.id) {
|
||||
// navigateToMemory(res?.id)();
|
||||
// }
|
||||
callBack?.();
|
||||
setLoading(false);
|
||||
handleHideModal();
|
||||
|
||||
@ -7,7 +7,7 @@ import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { pick } from 'lodash';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'umi';
|
||||
import { AddOrEditModal } from './add-or-edit-modal';
|
||||
import { useFetchMemoryList, useRenameMemory } from './hooks';
|
||||
@ -16,7 +16,8 @@ import { MemoryCard } from './memory-card';
|
||||
|
||||
export default function MemoryList() {
|
||||
// const { data } = useFetchFlowList();
|
||||
const { t } = useTranslate('memory');
|
||||
const { t } = useTranslate('memories');
|
||||
const [addOrEditType, setAddOrEditType] = useState<'add' | 'edit'>('add');
|
||||
// const [isEdit, setIsEdit] = useState(false);
|
||||
const {
|
||||
data: list,
|
||||
@ -43,6 +44,7 @@ export default function MemoryList() {
|
||||
};
|
||||
const openCreateModalFun = useCallback(() => {
|
||||
// setIsEdit(false);
|
||||
setAddOrEditType('add');
|
||||
showMemoryRenameModal();
|
||||
}, [showMemoryRenameModal]);
|
||||
const handlePageChange = useCallback(
|
||||
@ -121,6 +123,7 @@ export default function MemoryList() {
|
||||
key={x.id}
|
||||
data={x}
|
||||
showMemoryRenameModal={() => {
|
||||
setAddOrEditType('edit');
|
||||
showMemoryRenameModal(x);
|
||||
}}
|
||||
></MemoryCard>
|
||||
@ -152,6 +155,7 @@ export default function MemoryList() {
|
||||
{openCreateModal && (
|
||||
<AddOrEditModal
|
||||
initialMemory={initialMemory}
|
||||
isCreate={addOrEditType === 'add'}
|
||||
open={openCreateModal}
|
||||
loading={searchRenameLoading}
|
||||
onClose={hideMemoryModal}
|
||||
|
||||
@ -1,10 +1,3 @@
|
||||
export interface ICreateMemoryProps {
|
||||
memory_name: string;
|
||||
memory_type: Array<string>;
|
||||
embedding: string;
|
||||
llm: string;
|
||||
}
|
||||
|
||||
export interface CreateMemoryResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -24,17 +17,18 @@ export type MemoryType = 'raw' | 'semantic' | 'episodic' | 'procedural';
|
||||
export type StorageType = 'table' | 'graph';
|
||||
export type Permissions = 'me' | 'team';
|
||||
export type ForgettingPolicy = 'fifo' | 'lru';
|
||||
|
||||
export interface IMemory {
|
||||
id: string;
|
||||
export interface ICreateMemoryProps {
|
||||
name: string;
|
||||
memory_type: MemoryType[];
|
||||
embd_id: string;
|
||||
llm_id: string;
|
||||
}
|
||||
export interface IMemory extends ICreateMemoryProps {
|
||||
id: string;
|
||||
avatar: string;
|
||||
tenant_id: string;
|
||||
owner_name: string;
|
||||
memory_type: MemoryType[];
|
||||
storage_type: StorageType;
|
||||
embedding: string;
|
||||
llm: string;
|
||||
permissions: Permissions;
|
||||
description: string;
|
||||
memory_size: number;
|
||||
|
||||
@ -20,7 +20,7 @@ export function MemoryCard({ data, showMemoryRenameModal }: IProps) {
|
||||
}}
|
||||
moreDropdown={
|
||||
<MemoryDropdown
|
||||
dataset={data}
|
||||
memory={data}
|
||||
showMemoryRenameModal={showMemoryRenameModal}
|
||||
>
|
||||
<MoreButton></MoreButton>
|
||||
|
||||
@ -12,15 +12,16 @@ import {
|
||||
import { PenLine, Trash2 } from 'lucide-react';
|
||||
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IMemoryAppProps, useDeleteMemory } from './hooks';
|
||||
import { useDeleteMemory } from './hooks';
|
||||
import { IMemory } from './interface';
|
||||
|
||||
export function MemoryDropdown({
|
||||
children,
|
||||
dataset,
|
||||
memory,
|
||||
showMemoryRenameModal,
|
||||
}: PropsWithChildren & {
|
||||
dataset: IMemoryAppProps;
|
||||
showMemoryRenameModal: (dataset: IMemoryAppProps) => void;
|
||||
memory: IMemory;
|
||||
showMemoryRenameModal: (memory: IMemory) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { deleteMemory } = useDeleteMemory();
|
||||
@ -28,13 +29,13 @@ export function MemoryDropdown({
|
||||
useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
showMemoryRenameModal(dataset);
|
||||
showMemoryRenameModal(memory);
|
||||
},
|
||||
[dataset, showMemoryRenameModal],
|
||||
[memory, showMemoryRenameModal],
|
||||
);
|
||||
const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => {
|
||||
deleteMemory({ search_id: dataset.id });
|
||||
}, [dataset.id, deleteMemory]);
|
||||
deleteMemory({ memory_id: memory.id });
|
||||
}, [memory, deleteMemory]);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@ -50,8 +51,9 @@ export function MemoryDropdown({
|
||||
content={{
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode
|
||||
avatar={{ avatar: dataset.avatar, name: dataset.name }}
|
||||
name={dataset.name}
|
||||
avatar={{ avatar: memory.avatar, name: memory.name }}
|
||||
name={memory.name}
|
||||
warnText={t('memories.delMemoryWarn')}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
export enum MemoryApiAction {
|
||||
FetchMemoryDetail = 'fetchMemoryDetail',
|
||||
FetchMemoryMessage = 'fetchMemoryMessage',
|
||||
FetchMessageContent = 'fetchMessageContent',
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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 {};
|
||||
|
||||
128
web/src/pages/memory/memory-message/hook.ts
Normal file
128
web/src/pages/memory/memory-message/hook.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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')}:
|
||||
{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
|
||||
|
||||
159
web/src/pages/memory/memory-setting/advanced-settings-form.tsx
Normal file
159
web/src/pages/memory/memory-setting/advanced-settings-form.tsx
Normal 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,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
{/* )} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
53
web/src/pages/memory/memory-setting/basic-form.tsx
Normal file
53
web/src/pages/memory/memory-setting/basic-form.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
42
web/src/pages/memory/memory-setting/hook.ts
Normal file
42
web/src/pages/memory/memory-setting/hook.ts
Normal 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 };
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
74
web/src/pages/memory/memory-setting/memory-model-form.tsx
Normal file
74
web/src/pages/memory/memory-setting/memory-model-form.tsx
Normal 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,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -8,6 +8,9 @@ const {
|
||||
deleteMemory,
|
||||
getMemoryDetail,
|
||||
updateMemorySetting,
|
||||
getMemoryConfig,
|
||||
deleteMemoryMessage,
|
||||
getMessageContent,
|
||||
// getMemoryDetailShare,
|
||||
} = api;
|
||||
const methods = {
|
||||
@ -17,27 +20,21 @@ const methods = {
|
||||
},
|
||||
getMemoryList: {
|
||||
url: getMemoryList,
|
||||
method: 'post',
|
||||
method: 'get',
|
||||
},
|
||||
deleteMemory: { url: deleteMemory, method: 'post' },
|
||||
// getMemoryDetail: {
|
||||
// url: getMemoryDetail,
|
||||
// method: 'get',
|
||||
// },
|
||||
// updateMemorySetting: {
|
||||
// url: updateMemorySetting,
|
||||
// method: 'post',
|
||||
// },
|
||||
// getMemoryDetailShare: {
|
||||
// url: getMemoryDetailShare,
|
||||
// method: 'get',
|
||||
// },
|
||||
deleteMemory: { url: deleteMemory, method: 'delete' },
|
||||
getMemoryConfig: {
|
||||
url: getMemoryConfig,
|
||||
method: 'get',
|
||||
},
|
||||
deleteMemoryMessage: { url: deleteMemoryMessage, method: 'delete' },
|
||||
getMessageContent: { url: getMessageContent, method: 'get' },
|
||||
} as const;
|
||||
const memoryService = registerNextServer<keyof typeof methods>(methods);
|
||||
export const updateMemoryById = (id: string, data: any) => {
|
||||
return request.post(updateMemorySetting(id), { data });
|
||||
return request.put(updateMemorySetting(id), { data });
|
||||
};
|
||||
export const getMemoryDetailById = (id: string, data: any) => {
|
||||
return request.post(getMemoryDetail(id), { data });
|
||||
return request.get(getMemoryDetail(id), { params: data });
|
||||
};
|
||||
export default memoryService;
|
||||
|
||||
@ -227,11 +227,15 @@ export default {
|
||||
retrievalTestShare: `${ExternalApi}${api_host}/searchbots/retrieval_test`,
|
||||
|
||||
// memory
|
||||
createMemory: `${api_host}/memory/create`,
|
||||
getMemoryList: `${api_host}/memory/list`,
|
||||
createMemory: `${api_host}/memories`,
|
||||
getMemoryList: `${api_host}/memories`,
|
||||
getMemoryConfig: (id: string) => `${api_host}/memories/${id}/config`,
|
||||
deleteMemory: (id: string) => `${api_host}/memory/rm/${id}`,
|
||||
getMemoryDetail: (id: string) => `${api_host}/memory/detail/${id}`,
|
||||
updateMemorySetting: (id: string) => `${api_host}/memory/update/${id}`,
|
||||
getMemoryDetail: (id: string) => `${api_host}/memories/${id}`,
|
||||
updateMemorySetting: (id: string) => `${api_host}/memories/${id}`,
|
||||
deleteMemoryMessage: (id: string) => `${api_host}/message/rm/${id}`,
|
||||
getMessageContent: (message_id: string) =>
|
||||
`${api_host}/messages/${message_id}/content`,
|
||||
|
||||
// data pipeline
|
||||
fetchDataflow: (id: string) => `${api_host}/dataflow/get/${id}`,
|
||||
|
||||
Reference in New Issue
Block a user