mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Features: Memory page rendering and other bug fixes (#11784)
### What problem does this PR solve? Features: Memory page rendering and other bug fixes - Rendering of the Memory list page - Rendering of the message list page in Memory - Fixed an issue where the empty state was incorrectly displayed when search criteria were applied - Added a web link for the API-Key - modifying the index_mode attribute of the Confluence data source. ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
16
web/src/assets/svg/home-icon/memory.svg
Normal file
16
web/src/assets/svg/home-icon/memory.svg
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="memory dark">
|
||||||
|
<g id="lucide/hard-drive">
|
||||||
|
<path id="Vector" d="M22 12H2M22 12V18C22 18.5304 21.7893 19.0391 21.4142 19.4142C21.0391 19.7893 20.5304 20 20 20H4C3.46957 20 2.96086 19.7893 2.58579 19.4142C2.21071 19.0391 2 18.5304 2 18V12M22 12L18.55 5.11C18.3844 4.77679 18.1292 4.49637 17.813 4.30028C17.4967 4.10419 17.1321 4.0002 16.76 4H7.24C6.86792 4.0002 6.50326 4.10419 6.18704 4.30028C5.87083 4.49637 5.61558 4.77679 5.45 5.11L2 12" stroke="url(#paint0_linear_1100_4836)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<g id="lucide/hard-drive_2">
|
||||||
|
<path id="Vector_2" d="M6 16H6.01M10 16H10.01" stroke="#00BEB4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_1100_4836" x1="12.5556" y1="4" x2="12.5556" y2="20" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white"/>
|
||||||
|
<stop offset="1" stop-color="#666666"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@ -1,6 +1,13 @@
|
|||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react';
|
|
||||||
import {
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import {
|
||||||
|
ControllerRenderProps,
|
||||||
DefaultValues,
|
DefaultValues,
|
||||||
FieldValues,
|
FieldValues,
|
||||||
SubmitHandler,
|
SubmitHandler,
|
||||||
@ -26,6 +33,7 @@ import { Textarea } from '@/components/ui/textarea';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
|
import { MultiSelect, MultiSelectOptionType } from './ui/multi-select';
|
||||||
|
|
||||||
// Field type enumeration
|
// Field type enumeration
|
||||||
export enum FormFieldType {
|
export enum FormFieldType {
|
||||||
@ -35,14 +43,17 @@ export enum FormFieldType {
|
|||||||
Number = 'number',
|
Number = 'number',
|
||||||
Textarea = 'textarea',
|
Textarea = 'textarea',
|
||||||
Select = 'select',
|
Select = 'select',
|
||||||
|
MultiSelect = 'multi-select',
|
||||||
Checkbox = 'checkbox',
|
Checkbox = 'checkbox',
|
||||||
Tag = 'tag',
|
Tag = 'tag',
|
||||||
|
Custom = 'custom',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field configuration interface
|
// Field configuration interface
|
||||||
export interface FormFieldConfig {
|
export interface FormFieldConfig {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
hideLabel?: boolean;
|
||||||
type: FormFieldType;
|
type: FormFieldType;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
@ -57,7 +68,7 @@ export interface FormFieldConfig {
|
|||||||
max?: number;
|
max?: number;
|
||||||
message?: string;
|
message?: string;
|
||||||
};
|
};
|
||||||
render?: (fieldProps: any) => React.ReactNode;
|
render?: (fieldProps: ControllerRenderProps) => React.ReactNode;
|
||||||
horizontal?: boolean;
|
horizontal?: boolean;
|
||||||
onChange?: (value: any) => void;
|
onChange?: (value: any) => void;
|
||||||
tooltip?: React.ReactNode;
|
tooltip?: React.ReactNode;
|
||||||
@ -78,10 +89,10 @@ interface DynamicFormProps<T extends FieldValues> {
|
|||||||
className?: string;
|
className?: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
defaultValues?: DefaultValues<T>;
|
defaultValues?: DefaultValues<T>;
|
||||||
onFieldUpdate?: (
|
// onFieldUpdate?: (
|
||||||
fieldName: string,
|
// fieldName: string,
|
||||||
updatedField: Partial<FormFieldConfig>,
|
// updatedField: Partial<FormFieldConfig>,
|
||||||
) => void;
|
// ) => void;
|
||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +103,10 @@ export interface DynamicFormRef {
|
|||||||
reset: (values?: any) => void;
|
reset: (values?: any) => void;
|
||||||
watch: (field: string, callback: (value: any) => void) => () => void;
|
watch: (field: string, callback: (value: any) => void) => () => void;
|
||||||
updateFieldType: (fieldName: string, newType: FormFieldType) => void;
|
updateFieldType: (fieldName: string, newType: FormFieldType) => void;
|
||||||
|
onFieldUpdate: (
|
||||||
|
fieldName: string,
|
||||||
|
newFieldProperties: Partial<FormFieldConfig>,
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Zod validation schema based on field configurations
|
// Generate Zod validation schema based on field configurations
|
||||||
@ -110,6 +125,14 @@ const generateSchema = (fields: FormFieldConfig[]): ZodSchema<any> => {
|
|||||||
case FormFieldType.Email:
|
case FormFieldType.Email:
|
||||||
fieldSchema = z.string().email('Please enter a valid email address');
|
fieldSchema = z.string().email('Please enter a valid email address');
|
||||||
break;
|
break;
|
||||||
|
case FormFieldType.MultiSelect:
|
||||||
|
fieldSchema = z.array(z.string()).optional();
|
||||||
|
if (field.required) {
|
||||||
|
fieldSchema = z.array(z.string()).min(1, {
|
||||||
|
message: `${field.label} is required`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
case FormFieldType.Number:
|
case FormFieldType.Number:
|
||||||
fieldSchema = z.coerce.number();
|
fieldSchema = z.coerce.number();
|
||||||
if (field.validation?.min !== undefined) {
|
if (field.validation?.min !== undefined) {
|
||||||
@ -275,7 +298,10 @@ const generateDefaultValues = <T extends FieldValues>(
|
|||||||
defaultValues[field.name] = field.defaultValue;
|
defaultValues[field.name] = field.defaultValue;
|
||||||
} else if (field.type === FormFieldType.Checkbox) {
|
} else if (field.type === FormFieldType.Checkbox) {
|
||||||
defaultValues[field.name] = false;
|
defaultValues[field.name] = false;
|
||||||
} else if (field.type === FormFieldType.Tag) {
|
} else if (
|
||||||
|
field.type === FormFieldType.Tag ||
|
||||||
|
field.type === FormFieldType.MultiSelect
|
||||||
|
) {
|
||||||
defaultValues[field.name] = [];
|
defaultValues[field.name] = [];
|
||||||
} else {
|
} else {
|
||||||
defaultValues[field.name] = '';
|
defaultValues[field.name] = '';
|
||||||
@ -291,17 +317,21 @@ const DynamicForm = {
|
|||||||
Root: forwardRef(
|
Root: forwardRef(
|
||||||
<T extends FieldValues>(
|
<T extends FieldValues>(
|
||||||
{
|
{
|
||||||
fields,
|
fields: originFields,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
className = '',
|
className = '',
|
||||||
children,
|
children,
|
||||||
defaultValues: formDefaultValues = {} as DefaultValues<T>,
|
defaultValues: formDefaultValues = {} as DefaultValues<T>,
|
||||||
onFieldUpdate,
|
// onFieldUpdate,
|
||||||
labelClassName,
|
labelClassName,
|
||||||
}: DynamicFormProps<T>,
|
}: DynamicFormProps<T>,
|
||||||
ref: React.Ref<any>,
|
ref: React.Ref<any>,
|
||||||
) => {
|
) => {
|
||||||
// Generate validation schema and default values
|
// Generate validation schema and default values
|
||||||
|
const [fields, setFields] = useState(originFields);
|
||||||
|
useMemo(() => {
|
||||||
|
setFields(originFields);
|
||||||
|
}, [originFields]);
|
||||||
const schema = useMemo(() => generateSchema(fields), [fields]);
|
const schema = useMemo(() => generateSchema(fields), [fields]);
|
||||||
|
|
||||||
const defaultValues = useMemo(() => {
|
const defaultValues = useMemo(() => {
|
||||||
@ -406,43 +436,54 @@ const DynamicForm = {
|
|||||||
}, [fields, form]);
|
}, [fields, form]);
|
||||||
|
|
||||||
// Expose form methods via ref
|
// Expose form methods via ref
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(
|
||||||
submit: () => form.handleSubmit(onSubmit)(),
|
ref,
|
||||||
getValues: () => form.getValues(),
|
() => ({
|
||||||
reset: (values?: T) => {
|
submit: () => form.handleSubmit(onSubmit)(),
|
||||||
if (values) {
|
getValues: () => form.getValues(),
|
||||||
form.reset(values);
|
reset: (values?: T) => {
|
||||||
} else {
|
if (values) {
|
||||||
form.reset();
|
form.reset(values);
|
||||||
}
|
|
||||||
},
|
|
||||||
setError: form.setError,
|
|
||||||
clearErrors: form.clearErrors,
|
|
||||||
trigger: form.trigger,
|
|
||||||
watch: (field: string, callback: (value: any) => void) => {
|
|
||||||
const { unsubscribe } = form.watch((values: any) => {
|
|
||||||
if (values && values[field] !== undefined) {
|
|
||||||
callback(values[field]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return unsubscribe;
|
|
||||||
},
|
|
||||||
|
|
||||||
onFieldUpdate: (
|
|
||||||
fieldName: string,
|
|
||||||
updatedField: Partial<FormFieldConfig>,
|
|
||||||
) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (onFieldUpdate) {
|
|
||||||
onFieldUpdate(fieldName, updatedField);
|
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
form.reset();
|
||||||
'onFieldUpdate prop is not provided. Cannot update field type.',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, 0);
|
},
|
||||||
},
|
setError: form.setError,
|
||||||
}));
|
clearErrors: form.clearErrors,
|
||||||
|
trigger: form.trigger,
|
||||||
|
watch: (field: string, callback: (value: any) => void) => {
|
||||||
|
const { unsubscribe } = form.watch((values: any) => {
|
||||||
|
if (values && values[field] !== undefined) {
|
||||||
|
callback(values[field]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return unsubscribe;
|
||||||
|
},
|
||||||
|
|
||||||
|
onFieldUpdate: (
|
||||||
|
fieldName: string,
|
||||||
|
updatedField: Partial<FormFieldConfig>,
|
||||||
|
) => {
|
||||||
|
setFields((prevFields: any) =>
|
||||||
|
prevFields.map((field: any) =>
|
||||||
|
field.name === fieldName
|
||||||
|
? { ...field, ...updatedField }
|
||||||
|
: field,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// setTimeout(() => {
|
||||||
|
// if (onFieldUpdate) {
|
||||||
|
// onFieldUpdate(fieldName, updatedField);
|
||||||
|
// } else {
|
||||||
|
// console.warn(
|
||||||
|
// 'onFieldUpdate prop is not provided. Cannot update field type.',
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }, 0);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[form],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formDefaultValues && Object.keys(formDefaultValues).length > 0) {
|
if (formDefaultValues && Object.keys(formDefaultValues).length > 0) {
|
||||||
@ -459,6 +500,9 @@ const DynamicForm = {
|
|||||||
// Render form fields
|
// Render form fields
|
||||||
const renderField = (field: FormFieldConfig) => {
|
const renderField = (field: FormFieldConfig) => {
|
||||||
if (field.render) {
|
if (field.render) {
|
||||||
|
if (field.type === FormFieldType.Custom && field.hideLabel) {
|
||||||
|
return <div className="w-full">{field.render({})}</div>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<RAGFlowFormItem
|
<RAGFlowFormItem
|
||||||
name={field.name}
|
name={field.name}
|
||||||
@ -549,6 +593,43 @@ const DynamicForm = {
|
|||||||
</RAGFlowFormItem>
|
</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:
|
case FormFieldType.Checkbox:
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
|
|||||||
@ -11,23 +11,33 @@ export enum EmptyCardType {
|
|||||||
Dataset = 'dataset',
|
Dataset = 'dataset',
|
||||||
Chat = 'chat',
|
Chat = 'chat',
|
||||||
Search = 'search',
|
Search = 'search',
|
||||||
|
Memory = 'memory',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmptyCardData = {
|
export const EmptyCardData = {
|
||||||
[EmptyCardType.Agent]: {
|
[EmptyCardType.Agent]: {
|
||||||
icon: <HomeIcon name="agents" width={'24'} />,
|
icon: <HomeIcon name="agents" width={'24'} />,
|
||||||
title: t('empty.agentTitle'),
|
title: t('empty.agentTitle'),
|
||||||
|
notFound: t('empty.notFoundAgent'),
|
||||||
},
|
},
|
||||||
[EmptyCardType.Dataset]: {
|
[EmptyCardType.Dataset]: {
|
||||||
icon: <HomeIcon name="datasets" width={'24'} />,
|
icon: <HomeIcon name="datasets" width={'24'} />,
|
||||||
title: t('empty.datasetTitle'),
|
title: t('empty.datasetTitle'),
|
||||||
|
notFound: t('empty.notFoundDataset'),
|
||||||
},
|
},
|
||||||
[EmptyCardType.Chat]: {
|
[EmptyCardType.Chat]: {
|
||||||
icon: <HomeIcon name="chats" width={'24'} />,
|
icon: <HomeIcon name="chats" width={'24'} />,
|
||||||
title: t('empty.chatTitle'),
|
title: t('empty.chatTitle'),
|
||||||
|
notFound: t('empty.notFoundChat'),
|
||||||
},
|
},
|
||||||
[EmptyCardType.Search]: {
|
[EmptyCardType.Search]: {
|
||||||
icon: <HomeIcon name="searches" width={'24'} />,
|
icon: <HomeIcon name="searches" width={'24'} />,
|
||||||
title: t('empty.searchTitle'),
|
title: t('empty.searchTitle'),
|
||||||
|
notFound: t('empty.notFoundSearch'),
|
||||||
|
},
|
||||||
|
[EmptyCardType.Memory]: {
|
||||||
|
icon: <HomeIcon name="memory" width={'24'} />,
|
||||||
|
title: t('empty.memoryTitle'),
|
||||||
|
notFound: t('empty.notFoundMemory'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -76,9 +76,10 @@ export const EmptyAppCard = (props: {
|
|||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
showIcon?: boolean;
|
showIcon?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
isSearch?: boolean;
|
||||||
size?: 'small' | 'large';
|
size?: 'small' | 'large';
|
||||||
}) => {
|
}) => {
|
||||||
const { type, showIcon, className } = props;
|
const { type, showIcon, className, isSearch } = props;
|
||||||
let defaultClass = '';
|
let defaultClass = '';
|
||||||
let style = {};
|
let style = {};
|
||||||
switch (props.size) {
|
switch (props.size) {
|
||||||
@ -95,19 +96,29 @@ export const EmptyAppCard = (props: {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className=" cursor-pointer " onClick={props.onClick}>
|
<div
|
||||||
|
className=" cursor-pointer "
|
||||||
|
onClick={isSearch ? undefined : props.onClick}
|
||||||
|
>
|
||||||
<EmptyCard
|
<EmptyCard
|
||||||
icon={showIcon ? EmptyCardData[type].icon : undefined}
|
icon={showIcon ? EmptyCardData[type].icon : undefined}
|
||||||
title={EmptyCardData[type].title}
|
title={
|
||||||
|
isSearch ? EmptyCardData[type].notFound : EmptyCardData[type].title
|
||||||
|
}
|
||||||
className={className}
|
className={className}
|
||||||
style={style}
|
style={style}
|
||||||
// description={EmptyCardData[type].description}
|
// description={EmptyCardData[type].description}
|
||||||
>
|
>
|
||||||
<div
|
{!isSearch && (
|
||||||
className={cn(defaultClass, 'flex items-center justify-start w-full')}
|
<div
|
||||||
>
|
className={cn(
|
||||||
<Plus size={24} />
|
defaultClass,
|
||||||
</div>
|
'flex items-center justify-start w-full',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Plus size={24} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</EmptyCard>
|
</EmptyCard>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,13 +9,19 @@ export type LLMFormFieldProps = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function LLMFormField({ options, name }: LLMFormFieldProps) {
|
export const useModelOptions = () => {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||||
LlmModelType.Chat,
|
LlmModelType.Chat,
|
||||||
LlmModelType.Image2text,
|
LlmModelType.Image2text,
|
||||||
]);
|
]);
|
||||||
|
return {
|
||||||
|
modelOptions,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function LLMFormField({ options, name }: LLMFormFieldProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { modelOptions } = useModelOptions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RAGFlowFormItem name={name || 'llm_id'} label={t('chat.model')}>
|
<RAGFlowFormItem name={name || 'llm_id'} label={t('chat.model')}>
|
||||||
|
|||||||
@ -53,14 +53,16 @@ export function RAGFlowFormItem({
|
|||||||
{label}
|
{label}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
)}
|
)}
|
||||||
<FormControl>
|
<div className="w-full flex flex-col">
|
||||||
{typeof children === 'function'
|
<FormControl>
|
||||||
? children(field)
|
{typeof children === 'function'
|
||||||
: isValidElement(children)
|
? children(field)
|
||||||
? cloneElement(children, { ...field })
|
: isValidElement(children)
|
||||||
: children}
|
? cloneElement(children, { ...field })
|
||||||
</FormControl>
|
: children}
|
||||||
<FormMessage />
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -126,3 +126,53 @@ export const IconMap = {
|
|||||||
[LLMFactory.JiekouAI]: 'jiekouai',
|
[LLMFactory.JiekouAI]: 'jiekouai',
|
||||||
[LLMFactory.Builtin]: 'builtin',
|
[LLMFactory.Builtin]: 'builtin',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const APIMapUrl = {
|
||||||
|
[LLMFactory.OpenAI]: 'https://platform.openai.com/api-keys',
|
||||||
|
[LLMFactory.Anthropic]: 'https://console.anthropic.com/settings/keys',
|
||||||
|
[LLMFactory.Gemini]: 'https://aistudio.google.com/app/apikey',
|
||||||
|
[LLMFactory.DeepSeek]: 'https://platform.deepseek.com/api_keys',
|
||||||
|
[LLMFactory.Moonshot]: 'https://platform.moonshot.cn/console/api-keys',
|
||||||
|
[LLMFactory.TongYiQianWen]: 'https://dashscope.console.aliyun.com/apiKey',
|
||||||
|
[LLMFactory.ZhipuAI]: 'https://open.bigmodel.cn/usercenter/apikeys',
|
||||||
|
[LLMFactory.XAI]: 'https://x.ai/api/',
|
||||||
|
[LLMFactory.HuggingFace]: 'https://huggingface.co/settings/tokens',
|
||||||
|
[LLMFactory.Mistral]: 'https://console.mistral.ai/api-keys/',
|
||||||
|
[LLMFactory.Cohere]: 'https://dashboard.cohere.com/api-keys',
|
||||||
|
[LLMFactory.BaiduYiYan]: 'https://wenxin.baidu.com/user/key',
|
||||||
|
[LLMFactory.Meituan]: 'https://longcat.chat/platform/api_keys',
|
||||||
|
[LLMFactory.Bedrock]:
|
||||||
|
'https://us-east-2.console.aws.amazon.com/bedrock/home#/api-keys',
|
||||||
|
[LLMFactory.AzureOpenAI]:
|
||||||
|
'https://portal.azure.com/#create/Microsoft.CognitiveServicesOpenAI',
|
||||||
|
[LLMFactory.OpenRouter]: 'https://openrouter.ai/keys',
|
||||||
|
[LLMFactory.XunFeiSpark]: 'https://console.xfyun.cn/services/cbm',
|
||||||
|
[LLMFactory.MiniMax]:
|
||||||
|
'https://platform.minimaxi.com/user-center/basic-information',
|
||||||
|
[LLMFactory.Groq]: 'https://console.groq.com/keys',
|
||||||
|
[LLMFactory.NVIDIA]: 'https://build.nvidia.com/settings/api-keys',
|
||||||
|
[LLMFactory.SILICONFLOW]: 'https://cloud.siliconflow.cn/account/ak',
|
||||||
|
[LLMFactory.Replicate]: 'https://replicate.com/account/api-tokens',
|
||||||
|
[LLMFactory.VolcEngine]: 'https://console.volcengine.com/ark',
|
||||||
|
[LLMFactory.Jina]: 'https://jina.ai/embeddings/',
|
||||||
|
[LLMFactory.TencentHunYuan]:
|
||||||
|
'https://console.cloud.tencent.com/hunyuan/api-key',
|
||||||
|
[LLMFactory.TencentCloud]: 'https://console.cloud.tencent.com/cam/capi',
|
||||||
|
[LLMFactory.ModelScope]: 'https://modelscope.cn/my/myaccesstoken',
|
||||||
|
[LLMFactory.GoogleCloud]: 'https://console.cloud.google.com/apis/credentials',
|
||||||
|
[LLMFactory.FishAudio]: 'https://fish.audio/app/api-keys/',
|
||||||
|
[LLMFactory.GiteeAI]:
|
||||||
|
'https://ai.gitee.com/hhxzgrjn/dashboard/settings/tokens',
|
||||||
|
[LLMFactory.StepFun]: 'https://platform.stepfun.com/interface-key',
|
||||||
|
[LLMFactory.BaiChuan]: 'https://platform.baichuan-ai.com/console/apikey',
|
||||||
|
[LLMFactory.PPIO]: 'https://ppio.com/settings/key-management',
|
||||||
|
[LLMFactory.VoyageAI]: 'https://dash.voyageai.com/api-keys',
|
||||||
|
[LLMFactory.TogetherAI]: 'https://api.together.xyz/settings/api-keys',
|
||||||
|
[LLMFactory.NovitaAI]: 'https://novita.ai/dashboard/key',
|
||||||
|
[LLMFactory.Upstage]: 'https://console.upstage.ai/api-keys',
|
||||||
|
[LLMFactory.CometAPI]: 'https://api.cometapi.com/console/token',
|
||||||
|
[LLMFactory.Ai302]: 'https://302.ai/apis/list',
|
||||||
|
[LLMFactory.DeerAPI]: 'https://api.deerapi.com/token',
|
||||||
|
[LLMFactory.TokenPony]: 'https://www.tokenpony.cn/#/user/keys',
|
||||||
|
[LLMFactory.DeepInfra]: 'https://deepinfra.com/dash/api_keys',
|
||||||
|
};
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Authorization } from '@/constants/authorization';
|
import { Authorization } from '@/constants/authorization';
|
||||||
import { MessageType } from '@/constants/chat';
|
import { MessageType } from '@/constants/chat';
|
||||||
import { LanguageTranslationMap } from '@/constants/common';
|
import { LanguageTranslationMap } from '@/constants/common';
|
||||||
|
import { Pagination } from '@/interfaces/common';
|
||||||
import { ResponseType } from '@/interfaces/database/base';
|
import { ResponseType } from '@/interfaces/database/base';
|
||||||
import {
|
import {
|
||||||
IAnswer,
|
IAnswer,
|
||||||
@ -12,7 +13,7 @@ import { IKnowledgeFile } from '@/interfaces/database/knowledge';
|
|||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { getAuthorization } from '@/utils/authorization-util';
|
import { getAuthorization } from '@/utils/authorization-util';
|
||||||
import { buildMessageUuid } from '@/utils/chat';
|
import { buildMessageUuid } from '@/utils/chat';
|
||||||
import { PaginationProps, message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { FormInstance } from 'antd/lib';
|
import { FormInstance } from 'antd/lib';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { EventSourceParserStream } from 'eventsource-parser/stream';
|
import { EventSourceParserStream } from 'eventsource-parser/stream';
|
||||||
@ -71,8 +72,8 @@ export const useGetPaginationWithRouter = () => {
|
|||||||
size: pageSize,
|
size: pageSize,
|
||||||
} = useSetPaginationParams();
|
} = useSetPaginationParams();
|
||||||
|
|
||||||
const onPageChange: PaginationProps['onChange'] = useCallback(
|
const onPageChange: Pagination['onChange'] = useCallback(
|
||||||
(pageNumber: number, pageSize: number) => {
|
(pageNumber: number, pageSize?: number) => {
|
||||||
setPaginationParams(pageNumber, pageSize);
|
setPaginationParams(pageNumber, pageSize);
|
||||||
},
|
},
|
||||||
[setPaginationParams],
|
[setPaginationParams],
|
||||||
@ -88,7 +89,7 @@ export const useGetPaginationWithRouter = () => {
|
|||||||
[setPaginationParams, pageSize],
|
[setPaginationParams, pageSize],
|
||||||
);
|
);
|
||||||
|
|
||||||
const pagination: PaginationProps = useMemo(() => {
|
const pagination: Pagination = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
total: 0,
|
total: 0,
|
||||||
@ -97,7 +98,7 @@ export const useGetPaginationWithRouter = () => {
|
|||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
pageSizeOptions: [1, 2, 10, 20, 50, 100],
|
pageSizeOptions: [1, 2, 10, 20, 50, 100],
|
||||||
onChange: onPageChange,
|
onChange: onPageChange,
|
||||||
showTotal: (total) => `${t('total')} ${total}`,
|
showTotal: (total: number) => `${t('total')} ${total}`,
|
||||||
};
|
};
|
||||||
}, [t, onPageChange, page, pageSize]);
|
}, [t, onPageChange, page, pageSize]);
|
||||||
|
|
||||||
@ -109,7 +110,7 @@ export const useGetPaginationWithRouter = () => {
|
|||||||
|
|
||||||
export const useHandleSearchChange = () => {
|
export const useHandleSearchChange = () => {
|
||||||
const [searchString, setSearchString] = useState('');
|
const [searchString, setSearchString] = useState('');
|
||||||
const { setPagination } = useGetPaginationWithRouter();
|
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||||
const handleInputChange = useCallback(
|
const handleInputChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
@ -119,21 +120,21 @@ export const useHandleSearchChange = () => {
|
|||||||
[setPagination],
|
[setPagination],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { handleInputChange, searchString };
|
return { handleInputChange, searchString, pagination, setPagination };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetPagination = () => {
|
export const useGetPagination = () => {
|
||||||
const [pagination, setPagination] = useState({ page: 1, pageSize: 10 });
|
const [pagination, setPagination] = useState({ page: 1, pageSize: 10 });
|
||||||
const { t } = useTranslate('common');
|
const { t } = useTranslate('common');
|
||||||
|
|
||||||
const onPageChange: PaginationProps['onChange'] = useCallback(
|
const onPageChange: Pagination['onChange'] = useCallback(
|
||||||
(pageNumber: number, pageSize: number) => {
|
(pageNumber: number, pageSize: number) => {
|
||||||
setPagination({ page: pageNumber, pageSize });
|
setPagination({ page: pageNumber, pageSize });
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentPagination: PaginationProps = useMemo(() => {
|
const currentPagination: Pagination = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
total: 0,
|
total: 0,
|
||||||
@ -142,7 +143,7 @@ export const useGetPagination = () => {
|
|||||||
pageSize: pagination.pageSize,
|
pageSize: pagination.pageSize,
|
||||||
pageSizeOptions: [1, 2, 10, 20, 50, 100],
|
pageSizeOptions: [1, 2, 10, 20, 50, 100],
|
||||||
onChange: onPageChange,
|
onChange: onPageChange,
|
||||||
showTotal: (total) => `${t('total')} ${total}`,
|
showTotal: (total: number) => `${t('total')} ${total}`,
|
||||||
};
|
};
|
||||||
}, [t, onPageChange, pagination]);
|
}, [t, onPageChange, pagination]);
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,17 @@ export const useNavigatePage = () => {
|
|||||||
[navigate],
|
[navigate],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const navigateToMemoryList = useCallback(
|
||||||
|
({ isCreate = false }: { isCreate?: boolean }) => {
|
||||||
|
if (isCreate) {
|
||||||
|
navigate(Routes.Memories + '?isCreate=true');
|
||||||
|
} else {
|
||||||
|
navigate(Routes.Memories);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[navigate],
|
||||||
|
);
|
||||||
|
|
||||||
const navigateToDataset = useCallback(
|
const navigateToDataset = useCallback(
|
||||||
(id: string) => () => {
|
(id: string) => () => {
|
||||||
// navigate(`${Routes.DatasetBase}${Routes.DataSetOverview}/${id}`);
|
// navigate(`${Routes.DatasetBase}${Routes.DataSetOverview}/${id}`);
|
||||||
@ -105,6 +116,12 @@ export const useNavigatePage = () => {
|
|||||||
},
|
},
|
||||||
[navigate],
|
[navigate],
|
||||||
);
|
);
|
||||||
|
const navigateToMemory = useCallback(
|
||||||
|
(id: string) => () => {
|
||||||
|
navigate(`${Routes.Memory}${Routes.MemoryMessage}/${id}`);
|
||||||
|
},
|
||||||
|
[navigate],
|
||||||
|
);
|
||||||
|
|
||||||
const navigateToChunkParsedResult = useCallback(
|
const navigateToChunkParsedResult = useCallback(
|
||||||
(id: string, knowledgeId?: string) => () => {
|
(id: string, knowledgeId?: string) => () => {
|
||||||
@ -196,5 +213,7 @@ export const useNavigatePage = () => {
|
|||||||
navigateToDataflowResult,
|
navigateToDataflowResult,
|
||||||
navigateToDataFile,
|
navigateToDataFile,
|
||||||
navigateToDataSourceDetail,
|
navigateToDataSourceDetail,
|
||||||
|
navigateToMemory,
|
||||||
|
navigateToMemoryList,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ export interface Pagination {
|
|||||||
current: number;
|
current: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
total: number;
|
total: number;
|
||||||
|
onChange?: (page: number, pageSize: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseState {
|
export interface BaseState {
|
||||||
|
|||||||
@ -99,6 +99,29 @@ export default {
|
|||||||
search: 'Search',
|
search: 'Search',
|
||||||
welcome: 'Welcome to',
|
welcome: 'Welcome to',
|
||||||
dataset: 'Dataset',
|
dataset: 'Dataset',
|
||||||
|
Memories: 'Memory',
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
memory: 'Memory',
|
||||||
|
createMemory: 'Create Memory',
|
||||||
|
name: 'Name',
|
||||||
|
memoryNamePlaceholder: 'memory name',
|
||||||
|
memoryType: 'Memory type',
|
||||||
|
embeddingModel: 'Embedding model',
|
||||||
|
selectModel: 'Select model',
|
||||||
|
llm: 'LLM',
|
||||||
|
},
|
||||||
|
memoryDetail: {
|
||||||
|
messages: {
|
||||||
|
sessionId: 'Session ID',
|
||||||
|
agent: 'Agent',
|
||||||
|
type: 'Type',
|
||||||
|
validDate: 'Valid date',
|
||||||
|
forgetAt: 'Forget at',
|
||||||
|
source: 'Source',
|
||||||
|
enable: 'Enable',
|
||||||
|
action: 'Action',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
knowledgeList: {
|
knowledgeList: {
|
||||||
welcome: 'Welcome back',
|
welcome: 'Welcome back',
|
||||||
@ -2044,14 +2067,21 @@ Important structured information may include: names, dates, locations, events, k
|
|||||||
delFilesContent: 'Selected {{count}} files',
|
delFilesContent: 'Selected {{count}} files',
|
||||||
delChat: 'Delete chat',
|
delChat: 'Delete chat',
|
||||||
delMember: 'Delete member',
|
delMember: 'Delete member',
|
||||||
|
delMemory: 'Delete memory',
|
||||||
},
|
},
|
||||||
|
|
||||||
empty: {
|
empty: {
|
||||||
noMCP: 'No MCP servers available',
|
noMCP: 'No MCP servers available',
|
||||||
agentTitle: 'No agent app created yet',
|
agentTitle: 'No agent app created yet',
|
||||||
|
notFoundAgent: 'Agent app not found',
|
||||||
datasetTitle: 'No dataset created yet',
|
datasetTitle: 'No dataset created yet',
|
||||||
|
notFoundDataset: 'Dataset not found',
|
||||||
chatTitle: 'No chat app created yet',
|
chatTitle: 'No chat app created yet',
|
||||||
|
notFoundChat: 'Chat app not found',
|
||||||
searchTitle: 'No search app created yet',
|
searchTitle: 'No search app created yet',
|
||||||
|
notFoundSearch: 'Search app not found',
|
||||||
|
memoryTitle: 'No memory created yet',
|
||||||
|
notFoundMemory: 'Memory not found',
|
||||||
addNow: 'Add Now',
|
addNow: 'Add Now',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1900,9 +1900,15 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
|
|||||||
empty: {
|
empty: {
|
||||||
noMCP: '暂无 MCP 服务器可用',
|
noMCP: '暂无 MCP 服务器可用',
|
||||||
agentTitle: '尚未创建智能体',
|
agentTitle: '尚未创建智能体',
|
||||||
|
notFoundAgent: '未查询到智能体',
|
||||||
datasetTitle: '尚未创建数据集',
|
datasetTitle: '尚未创建数据集',
|
||||||
|
notFoundDataset: '未查询到数据集',
|
||||||
chatTitle: '尚未创建聊天应用',
|
chatTitle: '尚未创建聊天应用',
|
||||||
|
notFoundChat: '未查询到聊天应用',
|
||||||
searchTitle: '尚未创建搜索应用',
|
searchTitle: '尚未创建搜索应用',
|
||||||
|
notFoundSearch: '未查询到搜索应用',
|
||||||
|
memoryTitle: '尚未创建记忆',
|
||||||
|
notFoundMemory: '未查询到记忆',
|
||||||
addNow: '立即添加',
|
addNow: '立即添加',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -81,19 +81,20 @@ export default function Agents() {
|
|||||||
}, [isCreate, showCreatingModal, searchUrl, setSearchUrl]);
|
}, [isCreate, showCreatingModal, searchUrl, setSearchUrl]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(!data?.length || data?.length <= 0) && (
|
{(!data?.length || data?.length <= 0) && !searchString && (
|
||||||
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
<EmptyAppCard
|
<EmptyAppCard
|
||||||
showIcon
|
showIcon
|
||||||
size="large"
|
size="large"
|
||||||
className="w-[480px] p-14"
|
className="w-[480px] p-14"
|
||||||
|
isSearch={!!searchString}
|
||||||
type={EmptyCardType.Agent}
|
type={EmptyCardType.Agent}
|
||||||
onClick={() => showCreatingModal()}
|
onClick={() => showCreatingModal()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<section className="flex flex-col w-full flex-1">
|
<section className="flex flex-col w-full flex-1">
|
||||||
{!!data?.length && (
|
{(!!data?.length || searchString) && (
|
||||||
<>
|
<>
|
||||||
<div className="px-8 pt-8 ">
|
<div className="px-8 pt-8 ">
|
||||||
<ListFilterBar
|
<ListFilterBar
|
||||||
@ -138,6 +139,18 @@ export default function Agents() {
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
</div>
|
</div>
|
||||||
|
{(!data?.length || data?.length <= 0) && searchString && (
|
||||||
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
|
<EmptyAppCard
|
||||||
|
showIcon
|
||||||
|
size="large"
|
||||||
|
className="w-[480px] p-14"
|
||||||
|
isSearch={!!searchString}
|
||||||
|
type={EmptyCardType.Agent}
|
||||||
|
onClick={() => showCreatingModal()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
||||||
{data.map((x) => {
|
{data.map((x) => {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { Switch } from '@/components/ui/switch';
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { FieldValues, useFormContext } from 'react-hook-form';
|
||||||
import {
|
import {
|
||||||
useHandleKbEmbedding,
|
useHandleKbEmbedding,
|
||||||
useHasParsedDocument,
|
useHasParsedDocument,
|
||||||
@ -65,17 +65,59 @@ export function ChunkMethodItem(props: IProps) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export function EmbeddingModelItem({ line = 1, isEdit }: IProps) {
|
|
||||||
|
export const EmbeddingSelect = ({
|
||||||
|
isEdit,
|
||||||
|
field,
|
||||||
|
name,
|
||||||
|
}: {
|
||||||
|
isEdit: boolean;
|
||||||
|
field: FieldValues;
|
||||||
|
name?: string;
|
||||||
|
}) => {
|
||||||
const { t } = useTranslate('knowledgeConfiguration');
|
const { t } = useTranslate('knowledgeConfiguration');
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const embeddingModelOptions = useSelectEmbeddingModelOptions();
|
const embeddingModelOptions = useSelectEmbeddingModelOptions();
|
||||||
const { handleChange } = useHandleKbEmbedding();
|
const { handleChange } = useHandleKbEmbedding();
|
||||||
const disabled = useHasParsedDocument(isEdit);
|
const disabled = useHasParsedDocument(isEdit);
|
||||||
const oldValue = useMemo(() => {
|
const oldValue = useMemo(() => {
|
||||||
const embdStr = form.getValues('embd_id');
|
const embdStr = form.getValues(name || 'embd_id');
|
||||||
return embdStr || '';
|
return embdStr || '';
|
||||||
}, [form]);
|
}, [form]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
return (
|
||||||
|
<Spin
|
||||||
|
spinning={loading}
|
||||||
|
className={cn(' rounded-lg after:bg-bg-base', {
|
||||||
|
'opacity-20': loading,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<SelectWithSearch
|
||||||
|
onChange={async (value) => {
|
||||||
|
field.onChange(value);
|
||||||
|
if (isEdit && disabled) {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await handleChange({
|
||||||
|
embed_id: value,
|
||||||
|
callback: field.onChange,
|
||||||
|
});
|
||||||
|
if (res.code !== 0) {
|
||||||
|
field.onChange(oldValue);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={field.value}
|
||||||
|
options={embeddingModelOptions}
|
||||||
|
placeholder={t('embeddingModelPlaceholder')}
|
||||||
|
/>
|
||||||
|
</Spin>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function EmbeddingModelItem({ line = 1, isEdit }: IProps) {
|
||||||
|
const { t } = useTranslate('knowledgeConfiguration');
|
||||||
|
const form = useFormContext();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
@ -102,33 +144,10 @@ export function EmbeddingModelItem({ line = 1, isEdit }: IProps) {
|
|||||||
className={cn('text-muted-foreground', { 'w-3/4': line === 1 })}
|
className={cn('text-muted-foreground', { 'w-3/4': line === 1 })}
|
||||||
>
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Spin
|
<EmbeddingSelect
|
||||||
spinning={loading}
|
isEdit={!!isEdit}
|
||||||
className={cn(' rounded-lg after:bg-bg-base', {
|
field={field}
|
||||||
'opacity-20': loading,
|
></EmbeddingSelect>
|
||||||
})}
|
|
||||||
>
|
|
||||||
<SelectWithSearch
|
|
||||||
onChange={async (value) => {
|
|
||||||
field.onChange(value);
|
|
||||||
if (isEdit && disabled) {
|
|
||||||
setLoading(true);
|
|
||||||
const res = await handleChange({
|
|
||||||
embed_id: value,
|
|
||||||
callback: field.onChange,
|
|
||||||
});
|
|
||||||
if (res.code !== 0) {
|
|
||||||
field.onChange(oldValue);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
value={field.value}
|
|
||||||
options={embeddingModelOptions}
|
|
||||||
placeholder={t('embeddingModelPlaceholder')}
|
|
||||||
triggerClassName="!bg-bg-base"
|
|
||||||
/>
|
|
||||||
</Spin>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -70,18 +70,19 @@ export default function Datasets() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className="py-4 flex-1 flex flex-col">
|
<section className="py-4 flex-1 flex flex-col">
|
||||||
{(!kbs?.length || kbs?.length <= 0) && (
|
{(!kbs?.length || kbs?.length <= 0) && !searchString && (
|
||||||
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
<EmptyAppCard
|
<EmptyAppCard
|
||||||
showIcon
|
showIcon
|
||||||
size="large"
|
size="large"
|
||||||
className="w-[480px] p-14"
|
className="w-[480px] p-14"
|
||||||
|
isSearch={!!searchString}
|
||||||
type={EmptyCardType.Dataset}
|
type={EmptyCardType.Dataset}
|
||||||
onClick={() => showModal()}
|
onClick={() => showModal()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!!kbs?.length && (
|
{(!!kbs?.length || searchString) && (
|
||||||
<>
|
<>
|
||||||
<ListFilterBar
|
<ListFilterBar
|
||||||
title={t('header.dataset')}
|
title={t('header.dataset')}
|
||||||
@ -98,6 +99,18 @@ export default function Datasets() {
|
|||||||
{t('knowledgeList.createKnowledgeBase')}
|
{t('knowledgeList.createKnowledgeBase')}
|
||||||
</Button>
|
</Button>
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
|
{(!kbs?.length || kbs?.length <= 0) && searchString && (
|
||||||
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
|
<EmptyAppCard
|
||||||
|
showIcon
|
||||||
|
size="large"
|
||||||
|
className="w-[480px] p-14"
|
||||||
|
isSearch={!!searchString}
|
||||||
|
type={EmptyCardType.Dataset}
|
||||||
|
onClick={() => showModal()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
||||||
{kbs.map((dataset) => {
|
{kbs.map((dataset) => {
|
||||||
|
|||||||
75
web/src/pages/memories/add-or-edit-modal.tsx
Normal file
75
web/src/pages/memories/add-or-edit-modal.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { DynamicForm, DynamicFormRef } from '@/components/dynamic-form';
|
||||||
|
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 { createMemoryFields } from './constants';
|
||||||
|
import { IMemory } from './interface';
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit?: (data: any) => void;
|
||||||
|
initialMemory: IMemory;
|
||||||
|
loading?: boolean;
|
||||||
|
};
|
||||||
|
export const AddOrEditModal = (props: IProps) => {
|
||||||
|
const { open, onClose, onSubmit, initialMemory } = props;
|
||||||
|
// const [fields, setFields] = useState<FormFieldConfig[]>(createMemoryFields);
|
||||||
|
// const formRef = useRef<DynamicFormRef>(null);
|
||||||
|
const [formInstance, setFormInstance] = useState<DynamicFormRef | null>(null);
|
||||||
|
|
||||||
|
const formCallbackRef = useCallback((node: DynamicFormRef | null) => {
|
||||||
|
if (node) {
|
||||||
|
// formRef.current = node;
|
||||||
|
setFormInstance(node);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
const { modelOptions } = useModelOptions();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialMemory && initialMemory.id) {
|
||||||
|
formInstance?.onFieldUpdate('memory_type', { hidden: true });
|
||||||
|
formInstance?.onFieldUpdate('embedding', { hidden: true });
|
||||||
|
formInstance?.onFieldUpdate('llm', { hidden: true });
|
||||||
|
} else {
|
||||||
|
formInstance?.onFieldUpdate('llm', { options: modelOptions as any });
|
||||||
|
}
|
||||||
|
}, [modelOptions, formInstance, initialMemory]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onOpenChange={onClose}
|
||||||
|
className="!w-[480px]"
|
||||||
|
title={
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div>
|
||||||
|
<HomeIcon name="memory" width={'24'} />
|
||||||
|
</div>
|
||||||
|
{t('memory.createMemory')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
showfooter={false}
|
||||||
|
confirmLoading={props.loading}
|
||||||
|
>
|
||||||
|
<DynamicForm.Root
|
||||||
|
ref={formCallbackRef}
|
||||||
|
fields={createMemoryFields}
|
||||||
|
onSubmit={() => {}}
|
||||||
|
defaultValues={initialMemory}
|
||||||
|
>
|
||||||
|
<div className="flex justify-end gap-2 pb-5">
|
||||||
|
<DynamicForm.CancelButton handleCancel={onClose} />
|
||||||
|
<DynamicForm.SavingButton
|
||||||
|
submitLoading={false}
|
||||||
|
submitFunc={(data) => {
|
||||||
|
onSubmit?.(data);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DynamicForm.Root>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
41
web/src/pages/memories/constants/index.tsx
Normal file
41
web/src/pages/memories/constants/index.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { FormFieldConfig, FormFieldType } from '@/components/dynamic-form';
|
||||||
|
import { EmbeddingSelect } from '@/pages/dataset/dataset-setting/configuration/common-item';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
|
||||||
|
export const createMemoryFields = [
|
||||||
|
{
|
||||||
|
name: 'memory_name',
|
||||||
|
label: t('memory.name'),
|
||||||
|
placeholder: t('memory.memoryNamePlaceholder'),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'memory_type',
|
||||||
|
label: t('memory.memoryType'),
|
||||||
|
type: FormFieldType.MultiSelect,
|
||||||
|
placeholder: t('memory.descriptionPlaceholder'),
|
||||||
|
options: [
|
||||||
|
{ label: 'Raw', value: 'raw' },
|
||||||
|
{ label: 'Semantic', value: 'semantic' },
|
||||||
|
{ label: 'Episodic', value: 'episodic' },
|
||||||
|
{ label: 'Procedural', value: 'procedural' },
|
||||||
|
],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'embedding',
|
||||||
|
label: t('memory.embeddingModel'),
|
||||||
|
placeholder: t('memory.selectModel'),
|
||||||
|
required: true,
|
||||||
|
// hideLabel: true,
|
||||||
|
// type: 'custom',
|
||||||
|
render: (field) => <EmbeddingSelect field={field} isEdit={false} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'llm',
|
||||||
|
label: t('memory.llm'),
|
||||||
|
placeholder: t('memory.selectModel'),
|
||||||
|
required: true,
|
||||||
|
type: FormFieldType.Select,
|
||||||
|
},
|
||||||
|
] as FormFieldConfig[];
|
||||||
288
web/src/pages/memories/hooks.ts
Normal file
288
web/src/pages/memories/hooks.ts
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
// src/pages/next-memoryes/hooks.ts
|
||||||
|
|
||||||
|
import message from '@/components/ui/message';
|
||||||
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
|
import { useHandleSearchChange } from '@/hooks/logic-hooks';
|
||||||
|
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 { useCallback, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useParams, useSearchParams } from 'umi';
|
||||||
|
import {
|
||||||
|
CreateMemoryResponse,
|
||||||
|
DeleteMemoryProps,
|
||||||
|
DeleteMemoryResponse,
|
||||||
|
ICreateMemoryProps,
|
||||||
|
IMemory,
|
||||||
|
IMemoryAppDetailProps,
|
||||||
|
MemoryDetailResponse,
|
||||||
|
MemoryListResponse,
|
||||||
|
} from './interface';
|
||||||
|
|
||||||
|
export const useCreateMemory = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isError,
|
||||||
|
mutateAsync: createMemoryMutation,
|
||||||
|
} = useMutation<CreateMemoryResponse, Error, ICreateMemoryProps>({
|
||||||
|
mutationKey: ['createMemory'],
|
||||||
|
mutationFn: async (props) => {
|
||||||
|
const { data: response } = await memoryService.createMemory(props);
|
||||||
|
if (response.code !== 0) {
|
||||||
|
throw new Error(response.message || 'Failed to create memory');
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
message.success(t('message.created'));
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
message.error(t('message.error', { error: error.message }));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const createMemory = useCallback(
|
||||||
|
(props: ICreateMemoryProps) => {
|
||||||
|
return createMemoryMutation(props);
|
||||||
|
},
|
||||||
|
[createMemoryMutation],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { data, isError, createMemory };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFetchMemoryList = () => {
|
||||||
|
const { handleInputChange, searchString, pagination, setPagination } =
|
||||||
|
useHandleSearchChange();
|
||||||
|
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||||
|
const { data, isLoading, isError, refetch } = useQuery<
|
||||||
|
MemoryListResponse,
|
||||||
|
Error
|
||||||
|
>({
|
||||||
|
queryKey: [
|
||||||
|
'memoryList',
|
||||||
|
{
|
||||||
|
debouncedSearchString,
|
||||||
|
...pagination,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data: response } = await memoryService.getMemoryList(
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
keywords: debouncedSearchString,
|
||||||
|
page_size: pagination.pageSize,
|
||||||
|
page: pagination.current,
|
||||||
|
},
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
if (response.code !== 0) {
|
||||||
|
throw new Error(response.message || 'Failed to fetch memory list');
|
||||||
|
}
|
||||||
|
console.log(response);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// const setMemoryListParams = (newParams: MemoryListParams) => {
|
||||||
|
// setMemoryParams((prevParams) => ({
|
||||||
|
// ...prevParams,
|
||||||
|
// ...newParams,
|
||||||
|
// }));
|
||||||
|
// };
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
pagination,
|
||||||
|
searchString,
|
||||||
|
handleInputChange,
|
||||||
|
setPagination,
|
||||||
|
refetch,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFetchMemoryDetail = (tenantId?: string) => {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const [memoryParams] = useSearchParams();
|
||||||
|
const shared_id = memoryParams.get('shared_id');
|
||||||
|
const memoryId = id || shared_id;
|
||||||
|
let param: { id: string | null; tenant_id?: string } = {
|
||||||
|
id: memoryId,
|
||||||
|
};
|
||||||
|
if (shared_id) {
|
||||||
|
param = {
|
||||||
|
id: memoryId,
|
||||||
|
tenant_id: tenantId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const fetchMemoryDetailFunc = shared_id
|
||||||
|
? memoryService.getMemoryDetailShare
|
||||||
|
: memoryService.getMemoryDetail;
|
||||||
|
|
||||||
|
const { data, isLoading, isError } = useQuery<MemoryDetailResponse, Error>({
|
||||||
|
queryKey: ['memoryDetail', memoryId],
|
||||||
|
enabled: !shared_id || !!tenantId,
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data: response } = await fetchMemoryDetailFunc(param);
|
||||||
|
if (response.code !== 0) {
|
||||||
|
throw new Error(response.message || 'Failed to fetch memory detail');
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data: data?.data, isLoading, isError };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteMemory = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isError,
|
||||||
|
mutateAsync: deleteMemoryMutation,
|
||||||
|
} = useMutation<DeleteMemoryResponse, Error, DeleteMemoryProps>({
|
||||||
|
mutationKey: ['deleteMemory'],
|
||||||
|
mutationFn: async (props) => {
|
||||||
|
const { data: response } = await memoryService.deleteMemory(props);
|
||||||
|
if (response.code !== 0) {
|
||||||
|
throw new Error(response.message || 'Failed to delete memory');
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['memoryList'] });
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
message.success(t('message.deleted'));
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
message.error(t('message.error', { error: error.message }));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteMemory = useCallback(
|
||||||
|
(props: DeleteMemoryProps) => {
|
||||||
|
return deleteMemoryMutation(props);
|
||||||
|
},
|
||||||
|
[deleteMemoryMutation],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { data, isError, deleteMemory };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateMemory = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isError,
|
||||||
|
mutateAsync: updateMemoryMutation,
|
||||||
|
} = useMutation<any, Error, IMemoryAppDetailProps>({
|
||||||
|
mutationKey: ['updateMemory'],
|
||||||
|
mutationFn: async (formData) => {
|
||||||
|
const { data: response } = await updateMemoryById(formData.id, formData);
|
||||||
|
if (response.code !== 0) {
|
||||||
|
throw new Error(response.message || 'Failed to update memory');
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
message.success(t('message.updated'));
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ['memoryDetail', variables.id],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
message.error(t('message.error', { error: error.message }));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateMemory = useCallback(
|
||||||
|
(formData: IMemoryAppDetailProps) => {
|
||||||
|
return updateMemoryMutation(formData);
|
||||||
|
},
|
||||||
|
[updateMemoryMutation],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { data, isError, updateMemory };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRenameMemory = () => {
|
||||||
|
const [memory, setMemory] = useState<IMemory>({} as IMemory);
|
||||||
|
const { navigateToMemory } = useNavigatePage();
|
||||||
|
const {
|
||||||
|
visible: openCreateModal,
|
||||||
|
hideModal: hideChatRenameModal,
|
||||||
|
showModal: showChatRenameModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
const { updateMemory } = useUpdateMemory();
|
||||||
|
const { createMemory } = useCreateMemory();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleShowChatRenameModal = useCallback(
|
||||||
|
(record?: IMemory) => {
|
||||||
|
if (record) {
|
||||||
|
setMemory(record);
|
||||||
|
}
|
||||||
|
showChatRenameModal();
|
||||||
|
},
|
||||||
|
[showChatRenameModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleHideModal = useCallback(() => {
|
||||||
|
hideChatRenameModal();
|
||||||
|
setMemory({} as IMemory);
|
||||||
|
}, [hideChatRenameModal]);
|
||||||
|
|
||||||
|
const onMemoryRenameOk = useCallback(
|
||||||
|
async (data: ICreateMemoryProps, callBack?: () => void) => {
|
||||||
|
let res;
|
||||||
|
setLoading(true);
|
||||||
|
if (memory?.id) {
|
||||||
|
try {
|
||||||
|
// const reponse = await memoryService.getMemoryDetail({
|
||||||
|
// id: memory?.id,
|
||||||
|
// });
|
||||||
|
// const detail = reponse.data?.data;
|
||||||
|
// console.log('detail-->', detail);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
// const { id, created_by, update_time, ...memoryDataTemp } = detail;
|
||||||
|
res = await updateMemory({
|
||||||
|
// ...memoryDataTemp,
|
||||||
|
name: data.memory_name,
|
||||||
|
id: memory?.id,
|
||||||
|
} as unknown as IMemoryAppDetailProps);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('error', e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = await createMemory(data);
|
||||||
|
}
|
||||||
|
if (res && !memory?.id) {
|
||||||
|
navigateToMemory(res?.id)();
|
||||||
|
}
|
||||||
|
callBack?.();
|
||||||
|
setLoading(false);
|
||||||
|
handleHideModal();
|
||||||
|
},
|
||||||
|
[memory, createMemory, handleHideModal, navigateToMemory, updateMemory],
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
memoryRenameLoading: loading,
|
||||||
|
initialMemory: memory,
|
||||||
|
onMemoryRenameOk,
|
||||||
|
openCreateModal,
|
||||||
|
hideMemoryModal: handleHideModal,
|
||||||
|
showMemoryRenameModal: handleShowChatRenameModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
163
web/src/pages/memories/index.tsx
Normal file
163
web/src/pages/memories/index.tsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { CardContainer } from '@/components/card-container';
|
||||||
|
import { EmptyCardType } from '@/components/empty/constant';
|
||||||
|
import { EmptyAppCard } from '@/components/empty/empty';
|
||||||
|
import ListFilterBar from '@/components/list-filter-bar';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
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 { useSearchParams } from 'umi';
|
||||||
|
import { AddOrEditModal } from './add-or-edit-modal';
|
||||||
|
import { useFetchMemoryList, useRenameMemory } from './hooks';
|
||||||
|
import { ICreateMemoryProps } from './interface';
|
||||||
|
import { MemoryCard } from './memory-card';
|
||||||
|
|
||||||
|
export default function MemoryList() {
|
||||||
|
// const { data } = useFetchFlowList();
|
||||||
|
const { t } = useTranslate('memory');
|
||||||
|
// const [isEdit, setIsEdit] = useState(false);
|
||||||
|
const {
|
||||||
|
data: list,
|
||||||
|
pagination,
|
||||||
|
searchString,
|
||||||
|
handleInputChange,
|
||||||
|
setPagination,
|
||||||
|
refetch: refetchList,
|
||||||
|
} = useFetchMemoryList();
|
||||||
|
|
||||||
|
const {
|
||||||
|
openCreateModal,
|
||||||
|
showMemoryRenameModal,
|
||||||
|
hideMemoryModal,
|
||||||
|
searchRenameLoading,
|
||||||
|
onMemoryRenameOk,
|
||||||
|
initialMemory,
|
||||||
|
} = useRenameMemory();
|
||||||
|
|
||||||
|
const onMemoryConfirm = (data: ICreateMemoryProps) => {
|
||||||
|
onMemoryRenameOk(data, () => {
|
||||||
|
refetchList();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const openCreateModalFun = useCallback(() => {
|
||||||
|
// setIsEdit(false);
|
||||||
|
showMemoryRenameModal();
|
||||||
|
}, [showMemoryRenameModal]);
|
||||||
|
const handlePageChange = useCallback(
|
||||||
|
(page: number, pageSize?: number) => {
|
||||||
|
setPagination({ page, pageSize });
|
||||||
|
},
|
||||||
|
[setPagination],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [searchUrl, setMemoryUrl] = useSearchParams();
|
||||||
|
const isCreate = searchUrl.get('isCreate') === 'true';
|
||||||
|
useEffect(() => {
|
||||||
|
if (isCreate) {
|
||||||
|
openCreateModalFun();
|
||||||
|
searchUrl.delete('isCreate');
|
||||||
|
setMemoryUrl(searchUrl);
|
||||||
|
}
|
||||||
|
}, [isCreate, openCreateModalFun, searchUrl, setMemoryUrl]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="w-full h-full flex flex-col">
|
||||||
|
{(!list?.data?.memory_list?.length ||
|
||||||
|
list?.data?.memory_list?.length <= 0) &&
|
||||||
|
!searchString && (
|
||||||
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
|
<EmptyAppCard
|
||||||
|
showIcon
|
||||||
|
size="large"
|
||||||
|
className="w-[480px] p-14"
|
||||||
|
isSearch={!!searchString}
|
||||||
|
type={EmptyCardType.Memory}
|
||||||
|
onClick={() => openCreateModalFun()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(!!list?.data?.memory_list?.length || searchString) && (
|
||||||
|
<>
|
||||||
|
<div className="px-8 pt-8">
|
||||||
|
<ListFilterBar
|
||||||
|
icon="memory"
|
||||||
|
title={t('memory')}
|
||||||
|
showFilter={false}
|
||||||
|
onSearchChange={handleInputChange}
|
||||||
|
searchString={searchString}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant={'default'}
|
||||||
|
onClick={() => {
|
||||||
|
openCreateModalFun();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
{t('createMemory')}
|
||||||
|
</Button>
|
||||||
|
</ListFilterBar>
|
||||||
|
</div>
|
||||||
|
{(!list?.data?.memory_list?.length ||
|
||||||
|
list?.data?.memory_list?.length <= 0) &&
|
||||||
|
searchString && (
|
||||||
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
|
<EmptyAppCard
|
||||||
|
showIcon
|
||||||
|
size="large"
|
||||||
|
className="w-[480px] p-14"
|
||||||
|
isSearch={!!searchString}
|
||||||
|
type={EmptyCardType.Memory}
|
||||||
|
onClick={() => openCreateModalFun()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex-1">
|
||||||
|
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
||||||
|
{list?.data.memory_list.map((x) => {
|
||||||
|
return (
|
||||||
|
<MemoryCard
|
||||||
|
key={x.id}
|
||||||
|
data={x}
|
||||||
|
showMemoryRenameModal={() => {
|
||||||
|
showMemoryRenameModal(x);
|
||||||
|
}}
|
||||||
|
></MemoryCard>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CardContainer>
|
||||||
|
</div>
|
||||||
|
{list?.data.total && list?.data.total > 0 && (
|
||||||
|
<div className="px-8 mb-4">
|
||||||
|
<RAGFlowPagination
|
||||||
|
{...pick(pagination, 'current', 'pageSize')}
|
||||||
|
// total={pagination.total}
|
||||||
|
total={list?.data.total}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{/* {openCreateModal && (
|
||||||
|
<RenameDialog
|
||||||
|
hideModal={hideMemoryRenameModal}
|
||||||
|
onOk={onMemoryRenameConfirm}
|
||||||
|
initialName={initialMemoryName}
|
||||||
|
loading={searchRenameLoading}
|
||||||
|
title={<HomeIcon name="memory" width={'24'} />}
|
||||||
|
></RenameDialog>
|
||||||
|
)} */}
|
||||||
|
{openCreateModal && (
|
||||||
|
<AddOrEditModal
|
||||||
|
initialMemory={initialMemory}
|
||||||
|
open={openCreateModal}
|
||||||
|
loading={searchRenameLoading}
|
||||||
|
onClose={hideMemoryModal}
|
||||||
|
onSubmit={onMemoryConfirm}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
121
web/src/pages/memories/interface.ts
Normal file
121
web/src/pages/memories/interface.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
export interface ICreateMemoryProps {
|
||||||
|
memory_name: string;
|
||||||
|
memory_type: Array<string>;
|
||||||
|
embedding: string;
|
||||||
|
llm: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateMemoryResponse {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MemoryListParams {
|
||||||
|
keywords?: string;
|
||||||
|
parser_id?: string;
|
||||||
|
page?: number;
|
||||||
|
page_size?: number;
|
||||||
|
orderby?: string;
|
||||||
|
desc?: boolean;
|
||||||
|
owner_ids?: string;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
name: 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;
|
||||||
|
forgetting_policy: ForgettingPolicy;
|
||||||
|
temperature: string;
|
||||||
|
system_prompt: string;
|
||||||
|
user_prompt: string;
|
||||||
|
}
|
||||||
|
export interface MemoryListResponse {
|
||||||
|
code: number;
|
||||||
|
data: {
|
||||||
|
memory_list: Array<IMemory>;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteMemoryProps {
|
||||||
|
memory_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteMemoryResponse {
|
||||||
|
code: number;
|
||||||
|
data: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IllmSettingProps {
|
||||||
|
llm_id: string;
|
||||||
|
parameter: string;
|
||||||
|
temperature?: number;
|
||||||
|
top_p?: number;
|
||||||
|
frequency_penalty?: number;
|
||||||
|
presence_penalty?: number;
|
||||||
|
}
|
||||||
|
interface IllmSettingEnableProps {
|
||||||
|
temperatureEnabled?: boolean;
|
||||||
|
topPEnabled?: boolean;
|
||||||
|
presencePenaltyEnabled?: boolean;
|
||||||
|
frequencyPenaltyEnabled?: boolean;
|
||||||
|
}
|
||||||
|
export interface IMemoryAppDetailProps {
|
||||||
|
avatar: any;
|
||||||
|
created_by: string;
|
||||||
|
description: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
memory_config: {
|
||||||
|
cross_languages: string[];
|
||||||
|
doc_ids: string[];
|
||||||
|
chat_id: string;
|
||||||
|
highlight: boolean;
|
||||||
|
kb_ids: string[];
|
||||||
|
keyword: boolean;
|
||||||
|
query_mindmap: boolean;
|
||||||
|
related_memory: boolean;
|
||||||
|
rerank_id: string;
|
||||||
|
use_rerank?: boolean;
|
||||||
|
similarity_threshold: number;
|
||||||
|
summary: boolean;
|
||||||
|
llm_setting: IllmSettingProps & IllmSettingEnableProps;
|
||||||
|
top_k: number;
|
||||||
|
use_kg: boolean;
|
||||||
|
vector_similarity_weight: number;
|
||||||
|
web_memory: boolean;
|
||||||
|
chat_settingcross_languages: string[];
|
||||||
|
meta_data_filter?: {
|
||||||
|
method: string;
|
||||||
|
manual: { key: string; op: string; value: string }[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
tenant_id: string;
|
||||||
|
update_time: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MemoryDetailResponse {
|
||||||
|
code: number;
|
||||||
|
data: IMemoryAppDetailProps;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export type IUpdateMemoryProps = Omit<IMemoryAppDetailProps, 'id'> & {
|
||||||
|
// id: string;
|
||||||
|
// };
|
||||||
32
web/src/pages/memories/memory-card.tsx
Normal file
32
web/src/pages/memories/memory-card.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { HomeCard } from '@/components/home-card';
|
||||||
|
import { MoreButton } from '@/components/more-button';
|
||||||
|
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
|
import { IMemory } from './interface';
|
||||||
|
import { MemoryDropdown } from './memory-dropdown';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
data: IMemory;
|
||||||
|
showMemoryRenameModal: (data: IMemory) => void;
|
||||||
|
}
|
||||||
|
export function MemoryCard({ data, showMemoryRenameModal }: IProps) {
|
||||||
|
const { navigateToMemory } = useNavigatePage();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HomeCard
|
||||||
|
data={{
|
||||||
|
name: data?.name,
|
||||||
|
avatar: data?.avatar,
|
||||||
|
description: data?.description,
|
||||||
|
}}
|
||||||
|
moreDropdown={
|
||||||
|
<MemoryDropdown
|
||||||
|
dataset={data}
|
||||||
|
showMemoryRenameModal={showMemoryRenameModal}
|
||||||
|
>
|
||||||
|
<MoreButton></MoreButton>
|
||||||
|
</MemoryDropdown>
|
||||||
|
}
|
||||||
|
onClick={navigateToMemory(data?.id)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
74
web/src/pages/memories/memory-dropdown.tsx
Normal file
74
web/src/pages/memories/memory-dropdown.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import {
|
||||||
|
ConfirmDeleteDialog,
|
||||||
|
ConfirmDeleteDialogNode,
|
||||||
|
} from '@/components/confirm-delete-dialog';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import { PenLine, Trash2 } from 'lucide-react';
|
||||||
|
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IMemoryAppProps, useDeleteMemory } from './hooks';
|
||||||
|
|
||||||
|
export function MemoryDropdown({
|
||||||
|
children,
|
||||||
|
dataset,
|
||||||
|
showMemoryRenameModal,
|
||||||
|
}: PropsWithChildren & {
|
||||||
|
dataset: IMemoryAppProps;
|
||||||
|
showMemoryRenameModal: (dataset: IMemoryAppProps) => void;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { deleteMemory } = useDeleteMemory();
|
||||||
|
const handleShowChatRenameModal: MouseEventHandler<HTMLDivElement> =
|
||||||
|
useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
showMemoryRenameModal(dataset);
|
||||||
|
},
|
||||||
|
[dataset, showMemoryRenameModal],
|
||||||
|
);
|
||||||
|
const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => {
|
||||||
|
deleteMemory({ search_id: dataset.id });
|
||||||
|
}, [dataset.id, deleteMemory]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem onClick={handleShowChatRenameModal}>
|
||||||
|
{t('common.rename')} <PenLine />
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<ConfirmDeleteDialog
|
||||||
|
onOk={handleDelete}
|
||||||
|
title={t('deleteModal.delMemory')}
|
||||||
|
content={{
|
||||||
|
node: (
|
||||||
|
<ConfirmDeleteDialogNode
|
||||||
|
avatar={{ avatar: dataset.avatar, name: dataset.name }}
|
||||||
|
name={dataset.name}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="text-state-error"
|
||||||
|
onSelect={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('common.delete')} <Trash2 />
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</ConfirmDeleteDialog>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
web/src/pages/memory/constant.tsx
Normal file
3
web/src/pages/memory/constant.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export enum MemoryApiAction {
|
||||||
|
FetchMemoryDetail = 'fetchMemoryDetail',
|
||||||
|
}
|
||||||
59
web/src/pages/memory/hooks/use-memory-messages.ts
Normal file
59
web/src/pages/memory/hooks/use-memory-messages.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
59
web/src/pages/memory/hooks/use-memory-setting.ts
Normal file
59
web/src/pages/memory/hooks/use-memory-setting.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { useHandleSearchChange } from '@/hooks/logic-hooks';
|
||||||
|
import { IMemory } from '@/pages/memories/interface';
|
||||||
|
import { getMemoryDetailById } 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 || {};
|
||||||
|
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<IMemory>({
|
||||||
|
queryKey: [...queryKey, searchString, pagination],
|
||||||
|
initialData: {} as IMemory,
|
||||||
|
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,
|
||||||
|
// });
|
||||||
|
return data?.data ?? {};
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
handleInputChange,
|
||||||
|
searchString,
|
||||||
|
pagination,
|
||||||
|
setPagination,
|
||||||
|
};
|
||||||
|
};
|
||||||
17
web/src/pages/memory/index.tsx
Normal file
17
web/src/pages/memory/index.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Spotlight from '@/components/spotlight';
|
||||||
|
import { Outlet } from 'umi';
|
||||||
|
import { SideBar } from './sidebar';
|
||||||
|
|
||||||
|
export default function DatasetWrapper() {
|
||||||
|
return (
|
||||||
|
<section className="flex h-full flex-col w-full">
|
||||||
|
<div className="flex flex-1 min-h-0">
|
||||||
|
<SideBar></SideBar>
|
||||||
|
<div className=" relative flex-1 overflow-auto border-[0.5px] border-border-button p-5 rounded-md mr-5 mb-5">
|
||||||
|
<Spotlight />
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
web/src/pages/memory/memory-message/index.tsx
Normal file
51
web/src/pages/memory/memory-message/index.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import ListFilterBar from '@/components/list-filter-bar';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { useFetchMemoryMessageList } from '../hooks/use-memory-messages';
|
||||||
|
import { MemoryTable } from './message-table';
|
||||||
|
|
||||||
|
export default function MemoryMessage() {
|
||||||
|
const {
|
||||||
|
searchString,
|
||||||
|
// documents,
|
||||||
|
data,
|
||||||
|
pagination,
|
||||||
|
handleInputChange,
|
||||||
|
setPagination,
|
||||||
|
// filterValue,
|
||||||
|
// handleFilterSubmit,
|
||||||
|
loading,
|
||||||
|
} = useFetchMemoryMessageList();
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<ListFilterBar
|
||||||
|
title="Dataset"
|
||||||
|
onSearchChange={handleInputChange}
|
||||||
|
searchString={searchString}
|
||||||
|
// value={filterValue}
|
||||||
|
// onChange={handleFilterSubmit}
|
||||||
|
// onOpenChange={onOpenChange}
|
||||||
|
// filters={filters}
|
||||||
|
leftPanel={
|
||||||
|
<div className="items-start">
|
||||||
|
<div className="pb-1">{t('knowledgeDetails.subbarFiles')}</div>
|
||||||
|
<div className="text-text-secondary text-sm">
|
||||||
|
{t('knowledgeDetails.datasetDescription')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
></ListFilterBar>
|
||||||
|
<MemoryTable
|
||||||
|
messages={data?.messages?.message_list ?? []}
|
||||||
|
pagination={pagination}
|
||||||
|
setPagination={setPagination}
|
||||||
|
total={data?.messages?.total ?? 0}
|
||||||
|
// rowSelection={rowSelection}
|
||||||
|
// setRowSelection={setRowSelection}
|
||||||
|
// loading={loading}
|
||||||
|
></MemoryTable>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="h-4 w-4 rounded-full bg-text ">message</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
web/src/pages/memory/memory-message/interface.ts
Normal file
19
web/src/pages/memory/memory-message/interface.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export interface IMessageInfo {
|
||||||
|
message_id: number;
|
||||||
|
message_type: 'semantic' | 'raw' | 'procedural';
|
||||||
|
source_id: string | '-';
|
||||||
|
id: string;
|
||||||
|
user_id: string;
|
||||||
|
agent_id: string;
|
||||||
|
agent_name: string;
|
||||||
|
session_id: string;
|
||||||
|
valid_at: string;
|
||||||
|
invalid_at: string;
|
||||||
|
forget_at: string;
|
||||||
|
status: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessageTableProps {
|
||||||
|
messages: { message_list: Array<IMessageInfo>; total: number };
|
||||||
|
storage_type: string;
|
||||||
|
}
|
||||||
225
web/src/pages/memory/memory-message/message-table.tsx
Normal file
225
web/src/pages/memory/memory-message/message-table.tsx
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
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 { IMessageInfo } from './interface';
|
||||||
|
|
||||||
|
export type MemoryTableProps = {
|
||||||
|
messages: Array<IMessageInfo>;
|
||||||
|
total: number;
|
||||||
|
pagination: Pagination;
|
||||||
|
setPagination: (params: { page: number; pageSize: number }) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function MemoryTable({
|
||||||
|
messages,
|
||||||
|
total,
|
||||||
|
pagination,
|
||||||
|
setPagination,
|
||||||
|
}: MemoryTableProps) {
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({});
|
||||||
|
|
||||||
|
// Define columns for the memory table
|
||||||
|
const columns: ColumnDef<IMessageInfo>[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: 'session_id',
|
||||||
|
header: () => <span>{t('memoryDetail.messages.sessionId')}</span>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-sm font-medium ">
|
||||||
|
{row.getValue('session_id')}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'agent_name',
|
||||||
|
header: () => <span>{t('memoryDetail.messages.agent')}</span>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-sm font-medium ">
|
||||||
|
{row.getValue('agent_name')}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'message_type',
|
||||||
|
header: () => <span>{t('memoryDetail.messages.type')}</span>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-sm font-medium capitalize">
|
||||||
|
{row.getValue('message_type')}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'valid_at',
|
||||||
|
header: () => <span>{t('memoryDetail.messages.validDate')}</span>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-sm ">{row.getValue('valid_at')}</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'forget_at',
|
||||||
|
header: () => <span>{t('memoryDetail.messages.forgetAt')}</span>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-sm ">{row.getValue('forget_at')}</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'source_id',
|
||||||
|
header: () => <span>{t('memoryDetail.messages.source')}</span>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-sm ">{row.getValue('source_id')}</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'status',
|
||||||
|
header: () => <span>{t('memoryDetail.messages.enable')}</span>,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const isEnabled = row.getValue('status') as boolean;
|
||||||
|
return (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Switch defaultChecked={isEnabled} onChange={() => {}} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'action',
|
||||||
|
header: () => <span>{t('memoryDetail.messages.action')}</span>,
|
||||||
|
meta: {
|
||||||
|
cellClassName: 'w-12',
|
||||||
|
},
|
||||||
|
cell: () => (
|
||||||
|
<div className=" hidden group-hover:flex">
|
||||||
|
<Button variant={'ghost'} className="bg-transparent">
|
||||||
|
<TextSelect />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={'delete'}
|
||||||
|
className="bg-transparent"
|
||||||
|
aria-label="Edit"
|
||||||
|
>
|
||||||
|
<Eraser />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentPagination = useMemo(() => {
|
||||||
|
return {
|
||||||
|
pageIndex: (pagination.current || 1) - 1,
|
||||||
|
pageSize: pagination.pageSize || 10,
|
||||||
|
};
|
||||||
|
}, [pagination]);
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: messages,
|
||||||
|
columns,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
manualPagination: true,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnFilters,
|
||||||
|
columnVisibility,
|
||||||
|
pagination: currentPagination,
|
||||||
|
},
|
||||||
|
rowCount: total,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
<Table rootClassName="max-h-[calc(100vh-222px)]">
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext(),
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody className="relative">
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && 'selected'}
|
||||||
|
className="group"
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
|
<Empty type={EmptyType.Data} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-end py-4 absolute bottom-3 right-3">
|
||||||
|
<RAGFlowPagination
|
||||||
|
{...pick(pagination, 'current', 'pageSize')}
|
||||||
|
total={total}
|
||||||
|
onChange={(page, pageSize) => {
|
||||||
|
setPagination({ page, pageSize });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
web/src/pages/memory/memory-setting/index.tsx
Normal file
13
web/src/pages/memory/memory-setting/index.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export default function MemoryMessage() {
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="h-4 w-4 rounded-full bg-text ">setting</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
web/src/pages/memory/sidebar/hooks.tsx
Normal file
17
web/src/pages/memory/sidebar/hooks.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Routes } from '@/routes';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useNavigate, useParams } from 'umi';
|
||||||
|
|
||||||
|
export const useHandleMenuClick = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const handleMenuClick = useCallback(
|
||||||
|
(key: Routes) => () => {
|
||||||
|
navigate(`${Routes.Memory}${key}/${id}`);
|
||||||
|
},
|
||||||
|
[id, navigate],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { handleMenuClick };
|
||||||
|
};
|
||||||
88
web/src/pages/memory/sidebar/index.tsx
Normal file
88
web/src/pages/memory/sidebar/index.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
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 { 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) {
|
||||||
|
const pathName = useSecondPathName();
|
||||||
|
const { handleMenuClick } = useHandleMenuClick();
|
||||||
|
// refreshCount: be for avatar img sync update on top left
|
||||||
|
const { data } = useFetchMemoryBaseConfiguration({ refreshCount });
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const items = useMemo(() => {
|
||||||
|
const list = [
|
||||||
|
{
|
||||||
|
icon: <Logs className="size-4" />,
|
||||||
|
label: t(`knowledgeDetails.overview`),
|
||||||
|
key: Routes.MemoryMessage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Banknote className="size-4" />,
|
||||||
|
label: t(`knowledgeDetails.configuration`),
|
||||||
|
key: Routes.MemorySetting,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return list;
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className="relative p-5 space-y-8">
|
||||||
|
<div className="flex gap-2.5 max-w-[200px] items-center">
|
||||||
|
<RAGFlowAvatar
|
||||||
|
avatar={data.avatar}
|
||||||
|
name={data.name}
|
||||||
|
className="size-16"
|
||||||
|
></RAGFlowAvatar>
|
||||||
|
<div className=" text-text-secondary text-xs space-y-1 overflow-hidden">
|
||||||
|
<h3 className="text-lg font-semibold line-clamp-1 text-text-primary text-ellipsis overflow-hidden">
|
||||||
|
{data.name}
|
||||||
|
</h3>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-[200px] flex flex-col gap-5">
|
||||||
|
{items.map((item, itemIdx) => {
|
||||||
|
const active = '/' + pathName === item.key;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={itemIdx}
|
||||||
|
variant={active ? 'secondary' : 'ghost'}
|
||||||
|
className={cn(
|
||||||
|
'w-full justify-start gap-2.5 px-3 relative h-10 text-text-secondary',
|
||||||
|
{
|
||||||
|
'bg-bg-card': active,
|
||||||
|
'text-text-primary': active,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
onClick={handleMenuClick(item.key)}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -50,18 +50,19 @@ export default function ChatList() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex flex-col w-full flex-1">
|
<section className="flex flex-col w-full flex-1">
|
||||||
{data.dialogs?.length <= 0 && (
|
{data.dialogs?.length <= 0 && !searchString && (
|
||||||
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
<EmptyAppCard
|
<EmptyAppCard
|
||||||
showIcon
|
showIcon
|
||||||
size="large"
|
size="large"
|
||||||
className="w-[480px] p-14"
|
className="w-[480px] p-14"
|
||||||
|
isSearch={!!searchString}
|
||||||
type={EmptyCardType.Chat}
|
type={EmptyCardType.Chat}
|
||||||
onClick={() => handleShowCreateModal()}
|
onClick={() => handleShowCreateModal()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{data.dialogs?.length > 0 && (
|
{(data.dialogs?.length > 0 || searchString) && (
|
||||||
<>
|
<>
|
||||||
<div className="px-8 pt-8">
|
<div className="px-8 pt-8">
|
||||||
<ListFilterBar
|
<ListFilterBar
|
||||||
@ -76,6 +77,18 @@ export default function ChatList() {
|
|||||||
</Button>
|
</Button>
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
</div>
|
</div>
|
||||||
|
{data.dialogs?.length <= 0 && searchString && (
|
||||||
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
|
<EmptyAppCard
|
||||||
|
showIcon
|
||||||
|
size="large"
|
||||||
|
className="w-[480px] p-14"
|
||||||
|
isSearch={!!searchString}
|
||||||
|
type={EmptyCardType.Chat}
|
||||||
|
onClick={() => handleShowCreateModal()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
||||||
{data.dialogs.map((x) => {
|
{data.dialogs.map((x) => {
|
||||||
|
|||||||
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
import message from '@/components/ui/message';
|
import message from '@/components/ui/message';
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
|
import { useHandleSearchChange } from '@/hooks/logic-hooks';
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import searchService from '@/services/search-service';
|
import searchService from '@/services/search-service';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useDebounce } from 'ahooks';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams, useSearchParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
@ -84,21 +86,34 @@ interface SearchListResponse {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useFetchSearchList = (params?: SearchListParams) => {
|
export const useFetchSearchList = () => {
|
||||||
const [searchParams, setSearchParams] = useState<SearchListParams>({
|
const { handleInputChange, searchString, pagination, setPagination } =
|
||||||
page: 1,
|
useHandleSearchChange();
|
||||||
page_size: 50,
|
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||||
...params,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data, isLoading, isError, refetch } = useQuery<
|
const { data, isLoading, isError, refetch } = useQuery<
|
||||||
SearchListResponse,
|
SearchListResponse,
|
||||||
Error
|
Error
|
||||||
>({
|
>({
|
||||||
queryKey: ['searchList', searchParams],
|
queryKey: [
|
||||||
|
'searchList',
|
||||||
|
{
|
||||||
|
debouncedSearchString,
|
||||||
|
...pagination,
|
||||||
|
},
|
||||||
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data: response } =
|
const { data: response } = await searchService.getSearchList(
|
||||||
await searchService.getSearchList(searchParams);
|
{
|
||||||
|
params: {
|
||||||
|
keywords: debouncedSearchString,
|
||||||
|
page_size: pagination.pageSize,
|
||||||
|
page: pagination.current,
|
||||||
|
},
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
if (response.code !== 0) {
|
if (response.code !== 0) {
|
||||||
throw new Error(response.message || 'Failed to fetch search list');
|
throw new Error(response.message || 'Failed to fetch search list');
|
||||||
}
|
}
|
||||||
@ -106,19 +121,14 @@ export const useFetchSearchList = (params?: SearchListParams) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const setSearchListParams = (newParams: SearchListParams) => {
|
|
||||||
setSearchParams((prevParams) => ({
|
|
||||||
...prevParams,
|
|
||||||
...newParams,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
searchParams,
|
pagination,
|
||||||
setSearchListParams,
|
searchString,
|
||||||
|
handleInputChange,
|
||||||
|
setPagination,
|
||||||
refetch,
|
refetch,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { RenameDialog } from '@/components/rename-dialog';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
|
import { pick } from 'lodash';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useSearchParams } from 'umi';
|
import { useSearchParams } from 'umi';
|
||||||
@ -19,10 +20,13 @@ export default function SearchList() {
|
|||||||
// const [isEdit, setIsEdit] = useState(false);
|
// const [isEdit, setIsEdit] = useState(false);
|
||||||
const {
|
const {
|
||||||
data: list,
|
data: list,
|
||||||
searchParams,
|
pagination,
|
||||||
setSearchListParams,
|
searchString,
|
||||||
|
handleInputChange,
|
||||||
|
setPagination,
|
||||||
refetch: refetchList,
|
refetch: refetchList,
|
||||||
} = useFetchSearchList();
|
} = useFetchSearchList();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
openCreateModal,
|
openCreateModal,
|
||||||
showSearchRenameModal,
|
showSearchRenameModal,
|
||||||
@ -32,9 +36,9 @@ export default function SearchList() {
|
|||||||
initialSearchName,
|
initialSearchName,
|
||||||
} = useRenameSearch();
|
} = useRenameSearch();
|
||||||
|
|
||||||
const handleSearchChange = (value: string) => {
|
// const handleSearchChange = (value: string) => {
|
||||||
console.log(value);
|
// console.log(value);
|
||||||
};
|
// };
|
||||||
const onSearchRenameConfirm = (name: string) => {
|
const onSearchRenameConfirm = (name: string) => {
|
||||||
onSearchRenameOk(name, () => {
|
onSearchRenameOk(name, () => {
|
||||||
refetchList();
|
refetchList();
|
||||||
@ -44,10 +48,12 @@ export default function SearchList() {
|
|||||||
// setIsEdit(false);
|
// setIsEdit(false);
|
||||||
showSearchRenameModal();
|
showSearchRenameModal();
|
||||||
}, [showSearchRenameModal]);
|
}, [showSearchRenameModal]);
|
||||||
const handlePageChange = (page: number, pageSize: number) => {
|
const handlePageChange = useCallback(
|
||||||
// setIsEdit(false);
|
(page: number, pageSize?: number) => {
|
||||||
setSearchListParams({ ...searchParams, page, page_size: pageSize });
|
setPagination({ page, pageSize });
|
||||||
};
|
},
|
||||||
|
[setPagination],
|
||||||
|
);
|
||||||
|
|
||||||
const [searchUrl, setSearchUrl] = useSearchParams();
|
const [searchUrl, setSearchUrl] = useSearchParams();
|
||||||
const isCreate = searchUrl.get('isCreate') === 'true';
|
const isCreate = searchUrl.get('isCreate') === 'true';
|
||||||
@ -62,25 +68,28 @@ export default function SearchList() {
|
|||||||
return (
|
return (
|
||||||
<section className="w-full h-full flex flex-col">
|
<section className="w-full h-full flex flex-col">
|
||||||
{(!list?.data?.search_apps?.length ||
|
{(!list?.data?.search_apps?.length ||
|
||||||
list?.data?.search_apps?.length <= 0) && (
|
list?.data?.search_apps?.length <= 0) &&
|
||||||
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
!searchString && (
|
||||||
<EmptyAppCard
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
showIcon
|
<EmptyAppCard
|
||||||
size="large"
|
showIcon
|
||||||
className="w-[480px] p-14"
|
size="large"
|
||||||
type={EmptyCardType.Search}
|
className="w-[480px] p-14"
|
||||||
onClick={() => openCreateModalFun()}
|
type={EmptyCardType.Search}
|
||||||
/>
|
isSearch={!!searchString}
|
||||||
</div>
|
onClick={() => openCreateModalFun()}
|
||||||
)}
|
/>
|
||||||
{!!list?.data?.search_apps?.length && (
|
</div>
|
||||||
|
)}
|
||||||
|
{(!!list?.data?.search_apps?.length || searchString) && (
|
||||||
<>
|
<>
|
||||||
<div className="px-8 pt-8">
|
<div className="px-8 pt-8">
|
||||||
<ListFilterBar
|
<ListFilterBar
|
||||||
icon="searches"
|
icon="searches"
|
||||||
title={t('searchApps')}
|
title={t('searchApps')}
|
||||||
showFilter={false}
|
showFilter={false}
|
||||||
onSearchChange={(e) => handleSearchChange(e.target.value)}
|
searchString={searchString}
|
||||||
|
onSearchChange={handleInputChange}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant={'default'}
|
variant={'default'}
|
||||||
@ -93,6 +102,20 @@ export default function SearchList() {
|
|||||||
</Button>
|
</Button>
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
</div>
|
</div>
|
||||||
|
{(!list?.data?.search_apps?.length ||
|
||||||
|
list?.data?.search_apps?.length <= 0) &&
|
||||||
|
searchString && (
|
||||||
|
<div className="flex w-full items-center justify-center h-[calc(100vh-164px)]">
|
||||||
|
<EmptyAppCard
|
||||||
|
showIcon
|
||||||
|
size="large"
|
||||||
|
className="w-[480px] p-14"
|
||||||
|
type={EmptyCardType.Search}
|
||||||
|
isSearch={!!searchString}
|
||||||
|
onClick={() => openCreateModalFun()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
||||||
{list?.data.search_apps.map((x) => {
|
{list?.data.search_apps.map((x) => {
|
||||||
@ -111,8 +134,8 @@ export default function SearchList() {
|
|||||||
{list?.data.total && list?.data.total > 0 && (
|
{list?.data.total && list?.data.total > 0 && (
|
||||||
<div className="px-8 mb-4">
|
<div className="px-8 mb-4">
|
||||||
<RAGFlowPagination
|
<RAGFlowPagination
|
||||||
current={searchParams.page}
|
{...pick(pagination, 'current', 'pageSize')}
|
||||||
pageSize={searchParams.page_size}
|
// total={pagination.total}
|
||||||
total={list?.data.total}
|
total={list?.data.total}
|
||||||
onChange={handlePageChange}
|
onChange={handlePageChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { ControllerRenderProps, useFormContext } from 'react-hook-form';
|
import { ControllerRenderProps, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
/* ---------------- Token Field ---------------- */
|
/* ---------------- Token Field ---------------- */
|
||||||
|
|
||||||
@ -48,15 +49,15 @@ type ConfluenceIndexingMode = 'everything' | 'space' | 'page';
|
|||||||
export type ConfluenceIndexingModeFieldProps = ControllerRenderProps;
|
export type ConfluenceIndexingModeFieldProps = ControllerRenderProps;
|
||||||
|
|
||||||
export const ConfluenceIndexingModeField = (
|
export const ConfluenceIndexingModeField = (
|
||||||
fieldProps: ConfluenceIndexingModeFieldProps,
|
fieldProps: ControllerRenderProps,
|
||||||
) => {
|
) => {
|
||||||
const { value, onChange, disabled } = fieldProps;
|
const { value, onChange, disabled } = fieldProps;
|
||||||
|
const [mode, setMode] = useState<ConfluenceIndexingMode>(
|
||||||
|
value || 'everything',
|
||||||
|
);
|
||||||
const { watch, setValue } = useFormContext();
|
const { watch, setValue } = useFormContext();
|
||||||
|
|
||||||
const mode = useMemo<ConfluenceIndexingMode>(
|
useEffect(() => setMode(value), [value]);
|
||||||
() => (value as ConfluenceIndexingMode) || 'everything',
|
|
||||||
[value],
|
|
||||||
);
|
|
||||||
|
|
||||||
const spaceValue = watch('config.space');
|
const spaceValue = watch('config.space');
|
||||||
const pageIdValue = watch('config.page_id');
|
const pageIdValue = watch('config.page_id');
|
||||||
@ -66,27 +67,40 @@ export const ConfluenceIndexingModeField = (
|
|||||||
if (!value) onChange('everything');
|
if (!value) onChange('everything');
|
||||||
}, [value, onChange]);
|
}, [value, onChange]);
|
||||||
|
|
||||||
const handleModeChange = (nextMode?: string) => {
|
const handleModeChange = useCallback(
|
||||||
const normalized = (nextMode || 'everything') as ConfluenceIndexingMode;
|
(nextMode?: string) => {
|
||||||
onChange(normalized);
|
let normalized: ConfluenceIndexingMode = 'everything';
|
||||||
|
if (nextMode) {
|
||||||
|
normalized = nextMode as ConfluenceIndexingMode;
|
||||||
|
setMode(normalized);
|
||||||
|
onChange(normalized);
|
||||||
|
} else {
|
||||||
|
setMode(mode);
|
||||||
|
normalized = mode;
|
||||||
|
onChange(mode);
|
||||||
|
// onChange(mode);
|
||||||
|
}
|
||||||
|
if (normalized === 'everything') {
|
||||||
|
setValue('config.space', '');
|
||||||
|
setValue('config.page_id', '');
|
||||||
|
setValue('config.index_recursively', false);
|
||||||
|
} else if (normalized === 'space') {
|
||||||
|
setValue('config.page_id', '');
|
||||||
|
setValue('config.index_recursively', false);
|
||||||
|
} else if (normalized === 'page') {
|
||||||
|
setValue('config.space', '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[mode, onChange, setValue],
|
||||||
|
);
|
||||||
|
|
||||||
if (normalized === 'everything') {
|
const debouncedHandleChange = useMemo(
|
||||||
setValue('config.space', '', { shouldDirty: true, shouldTouch: true });
|
() =>
|
||||||
setValue('config.page_id', '', { shouldDirty: true, shouldTouch: true });
|
debounce(() => {
|
||||||
setValue('config.index_recursively', false, {
|
handleModeChange();
|
||||||
shouldDirty: true,
|
}, 300),
|
||||||
shouldTouch: true,
|
[handleModeChange],
|
||||||
});
|
);
|
||||||
} else if (normalized === 'space') {
|
|
||||||
setValue('config.page_id', '', { shouldDirty: true, shouldTouch: true });
|
|
||||||
setValue('config.index_recursively', false, {
|
|
||||||
shouldDirty: true,
|
|
||||||
shouldTouch: true,
|
|
||||||
});
|
|
||||||
} else if (normalized === 'page') {
|
|
||||||
setValue('config.space', '', { shouldDirty: true, shouldTouch: true });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full rounded-lg border border-border-button bg-bg-card p-4 space-y-4">
|
<div className="w-full rounded-lg border border-border-button bg-bg-card p-4 space-y-4">
|
||||||
@ -127,12 +141,11 @@ export const ConfluenceIndexingModeField = (
|
|||||||
<Input
|
<Input
|
||||||
className="w-full"
|
className="w-full"
|
||||||
value={spaceValue ?? ''}
|
value={spaceValue ?? ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
setValue('config.space', e.target.value, {
|
const value = e.target.value;
|
||||||
shouldDirty: true,
|
setValue('config.space', value);
|
||||||
shouldTouch: true,
|
debouncedHandleChange();
|
||||||
})
|
}}
|
||||||
}
|
|
||||||
placeholder="e.g. KB"
|
placeholder="e.g. KB"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
@ -148,12 +161,10 @@ export const ConfluenceIndexingModeField = (
|
|||||||
<Input
|
<Input
|
||||||
className="w-full"
|
className="w-full"
|
||||||
value={pageIdValue ?? ''}
|
value={pageIdValue ?? ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
setValue('config.page_id', e.target.value, {
|
setValue('config.page_id', e.target.value);
|
||||||
shouldDirty: true,
|
debouncedHandleChange();
|
||||||
shouldTouch: true,
|
}}
|
||||||
})
|
|
||||||
}
|
|
||||||
placeholder="e.g. 123456"
|
placeholder="e.g. 123456"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
@ -164,12 +175,10 @@ export const ConfluenceIndexingModeField = (
|
|||||||
<div className="flex items-center gap-2 pt-2">
|
<div className="flex items-center gap-2 pt-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={Boolean(indexRecursively)}
|
checked={Boolean(indexRecursively)}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) => {
|
||||||
setValue('config.index_recursively', Boolean(checked), {
|
setValue('config.index_recursively', Boolean(checked));
|
||||||
shouldDirty: true,
|
debouncedHandleChange();
|
||||||
shouldTouch: true,
|
}}
|
||||||
})
|
|
||||||
}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-text-secondary">
|
<span className="text-sm text-text-secondary">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { FormFieldType } from '@/components/dynamic-form';
|
import { FormFieldType } from '@/components/dynamic-form';
|
||||||
import SvgIcon from '@/components/svg-icon';
|
import SvgIcon from '@/components/svg-icon';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
import { ControllerRenderProps } from 'react-hook-form';
|
||||||
import { ConfluenceIndexingModeField } from './component/confluence-token-field';
|
import { ConfluenceIndexingModeField } from './component/confluence-token-field';
|
||||||
import GmailTokenField from './component/gmail-token-field';
|
import GmailTokenField from './component/gmail-token-field';
|
||||||
import GoogleDriveTokenField from './component/google-drive-token-field';
|
import GoogleDriveTokenField from './component/google-drive-token-field';
|
||||||
@ -237,7 +238,9 @@ export const DataSourceFormFields = {
|
|||||||
required: false,
|
required: false,
|
||||||
horizontal: true,
|
horizontal: true,
|
||||||
labelClassName: 'self-start pt-4',
|
labelClassName: 'self-start pt-4',
|
||||||
render: (fieldProps) => <ConfluenceIndexingModeField {...fieldProps} />,
|
render: (fieldProps: ControllerRenderProps) => (
|
||||||
|
<ConfluenceIndexingModeField {...fieldProps} />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Space Key',
|
label: 'Space Key',
|
||||||
@ -598,6 +601,7 @@ export const DataSourceFormDefaultValues = {
|
|||||||
confluence_username: '',
|
confluence_username: '',
|
||||||
confluence_access_token: '',
|
confluence_access_token: '',
|
||||||
},
|
},
|
||||||
|
index_mode: 'everything',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSourceKey.GOOGLE_DRIVE]: {
|
[DataSourceKey.GOOGLE_DRIVE]: {
|
||||||
|
|||||||
@ -136,7 +136,7 @@ const SourceDetailPage = () => {
|
|||||||
...customFields,
|
...customFields,
|
||||||
] as FormFieldConfig[];
|
] as FormFieldConfig[];
|
||||||
|
|
||||||
const neweFields = fields.map((field) => {
|
const newFields = fields.map((field) => {
|
||||||
return {
|
return {
|
||||||
...field,
|
...field,
|
||||||
horizontal: true,
|
horizontal: true,
|
||||||
@ -145,7 +145,7 @@ const SourceDetailPage = () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setFields(neweFields);
|
setFields(newFields);
|
||||||
|
|
||||||
const defultValueTemp = {
|
const defultValueTemp = {
|
||||||
...(DataSourceFormDefaultValues[
|
...(DataSourceFormDefaultValues[
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
import { LlmIcon } from '@/components/svg-icon';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { APIMapUrl } from '@/constants/llm';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { ArrowUpRight, Plus } from 'lucide-react';
|
||||||
|
|
||||||
|
export const LLMHeader = ({ name }: { name: string }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center space-x-3 mb-3">
|
||||||
|
<LlmIcon name={name} imgClass="h-8 w-8 text-text-primary" />
|
||||||
|
<div className="flex flex-1 gap-1 items-center">
|
||||||
|
<div className="font-normal text-base truncate">{name}</div>
|
||||||
|
{!!APIMapUrl[name as keyof typeof APIMapUrl] && (
|
||||||
|
<Button
|
||||||
|
variant={'ghost'}
|
||||||
|
className=" bg-transparent w-4 h-5"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.open(APIMapUrl[name as keyof typeof APIMapUrl]);
|
||||||
|
}}
|
||||||
|
// target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<ArrowUpRight size={16} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button className=" px-2 items-center gap-0 text-xs h-6 rounded-md transition-colors hidden group-hover:flex">
|
||||||
|
<Plus size={12} />
|
||||||
|
{t('addTheModel')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -2,9 +2,10 @@
|
|||||||
import { LlmIcon } from '@/components/svg-icon';
|
import { LlmIcon } from '@/components/svg-icon';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { SearchInput } from '@/components/ui/input';
|
import { SearchInput } from '@/components/ui/input';
|
||||||
|
import { APIMapUrl } from '@/constants/llm';
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
import { useSelectLlmList } from '@/hooks/use-llm-request';
|
import { useSelectLlmList } from '@/hooks/use-llm-request';
|
||||||
import { Plus } from 'lucide-react';
|
import { ArrowUpRight, Plus } from 'lucide-react';
|
||||||
import { FC, useMemo, useState } from 'react';
|
import { FC, useMemo, useState } from 'react';
|
||||||
|
|
||||||
type TagType =
|
type TagType =
|
||||||
@ -128,10 +129,26 @@ export const AvailableModels: FC<{
|
|||||||
>
|
>
|
||||||
<div className="flex items-center space-x-3 mb-3">
|
<div className="flex items-center space-x-3 mb-3">
|
||||||
<LlmIcon name={model.name} imgClass="h-8 w-8 text-text-primary" />
|
<LlmIcon name={model.name} imgClass="h-8 w-8 text-text-primary" />
|
||||||
<div className="flex-1">
|
<div className="flex flex-1 gap-1 items-center">
|
||||||
<div className="font-normal text-base truncate">
|
<div className="font-normal text-base truncate">
|
||||||
{model.name}
|
{model.name}
|
||||||
</div>
|
</div>
|
||||||
|
{!!APIMapUrl[model.name as keyof typeof APIMapUrl] && (
|
||||||
|
<Button
|
||||||
|
variant={'ghost'}
|
||||||
|
className=" bg-transparent w-4 h-5"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.open(
|
||||||
|
APIMapUrl[model.name as keyof typeof APIMapUrl],
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
// target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<ArrowUpRight size={16} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button className=" px-2 items-center gap-0 text-xs h-6 rounded-md transition-colors hidden group-hover:flex">
|
<Button className=" px-2 items-center gap-0 text-xs h-6 rounded-md transition-colors hidden group-hover:flex">
|
||||||
<Plus size={12} />
|
<Plus size={12} />
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { useTranslate } from '@/hooks/common-hooks';
|
|||||||
import { KeyboardEventHandler, useCallback, useEffect } from 'react';
|
import { KeyboardEventHandler, useCallback, useEffect } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { ApiKeyPostBody } from '../../../interface';
|
import { ApiKeyPostBody } from '../../../interface';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
|
|
||||||
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@ -70,7 +71,7 @@ const ApiKeyModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('configureModelTitle')}
|
title={<LLMHeader name={llmFactory} />}
|
||||||
open={visible}
|
open={visible}
|
||||||
onOpenChange={(open) => !open && hideModal()}
|
onOpenChange={(open) => !open && hideModal()}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||||
import { Form, Input, InputNumber, Modal, Select, Switch } from 'antd';
|
import { Form, Input, InputNumber, Modal, Select, Switch } from 'antd';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
|
|
||||||
type FieldType = IAddLlmRequestBody & {
|
type FieldType = IAddLlmRequestBody & {
|
||||||
api_version: string;
|
api_version: string;
|
||||||
@ -57,7 +58,7 @@ const AzureOpenAIModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('addLlmTitle', { name: llmFactory })}
|
title={<LLMHeader name={llmFactory} />}
|
||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||||
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
|
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
import { BedrockRegionList } from '../../constant';
|
import { BedrockRegionList } from '../../constant';
|
||||||
|
|
||||||
type FieldType = IAddLlmRequestBody & {
|
type FieldType = IAddLlmRequestBody & {
|
||||||
@ -42,7 +43,7 @@ const BedrockModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('addLlmTitle', { name: llmFactory })}
|
title={<LLMHeader name={llmFactory} />}
|
||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||||
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
|
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
|
|
||||||
type FieldType = IAddLlmRequestBody & {
|
type FieldType = IAddLlmRequestBody & {
|
||||||
fish_audio_ak: string;
|
fish_audio_ak: string;
|
||||||
@ -39,7 +40,7 @@ const FishAudioModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('addLlmTitle', { name: llmFactory })}
|
title={<LLMHeader name={llmFactory} />}
|
||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useTranslate } from '@/hooks/common-hooks';
|
|||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||||
import { Form, Input, InputNumber, Modal, Select } from 'antd';
|
import { Form, Input, InputNumber, Modal, Select } from 'antd';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
|
|
||||||
type FieldType = IAddLlmRequestBody & {
|
type FieldType = IAddLlmRequestBody & {
|
||||||
google_project_id: string;
|
google_project_id: string;
|
||||||
@ -41,7 +42,7 @@ const GoogleModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('addLlmTitle', { name: llmFactory })}
|
title={<LLMHeader name={llmFactory} />}
|
||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||||
import { Form, Input, Modal } from 'antd';
|
import { Form, Input, Modal } from 'antd';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
|
|
||||||
type FieldType = IAddLlmRequestBody & {
|
type FieldType = IAddLlmRequestBody & {
|
||||||
vision: boolean;
|
vision: boolean;
|
||||||
@ -46,7 +47,7 @@ const HunyuanModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('addLlmTitle', { name: llmFactory })}
|
title={<LLMHeader name={llmFactory} />}
|
||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||||
import { Flex, Form, Input, Modal, Select, Space } from 'antd';
|
import { Flex, Form, Input, Modal, Select, Space } from 'antd';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
|
|
||||||
type FieldType = IAddLlmRequestBody & {
|
type FieldType = IAddLlmRequestBody & {
|
||||||
TencentCloud_sid: string;
|
TencentCloud_sid: string;
|
||||||
@ -45,7 +46,7 @@ const TencentCloudModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('addLlmTitle', { name: llmFactory })}
|
title={<LLMHeader name={llmFactory} />}
|
||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
|
|
||||||
type FieldType = IAddLlmRequestBody & {
|
type FieldType = IAddLlmRequestBody & {
|
||||||
vision: boolean;
|
vision: boolean;
|
||||||
@ -147,11 +148,7 @@ const OllamaModal = ({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={<LLMHeader name={llmFactory} />}
|
||||||
editMode
|
|
||||||
? t('editLlmTitle', { name: llmFactory })
|
|
||||||
: t('addLlmTitle', { name: llmFactory })
|
|
||||||
}
|
|
||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||||
import { Form, Input, InputNumber, Modal, Select } from 'antd';
|
import { Form, Input, InputNumber, Modal, Select } from 'antd';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
|
|
||||||
type FieldType = IAddLlmRequestBody & {
|
type FieldType = IAddLlmRequestBody & {
|
||||||
vision: boolean;
|
vision: boolean;
|
||||||
@ -51,7 +52,7 @@ const SparkModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('addLlmTitle', { name: llmFactory })}
|
title={<LLMHeader name={llmFactory} />}
|
||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||||
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
|
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
|
|
||||||
type FieldType = IAddLlmRequestBody & {
|
type FieldType = IAddLlmRequestBody & {
|
||||||
vision: boolean;
|
vision: boolean;
|
||||||
@ -45,7 +46,7 @@ const VolcEngineModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('addLlmTitle', { name: llmFactory })}
|
title={<LLMHeader name={llmFactory} />}
|
||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||||
import { Form, Input, InputNumber, Modal, Select } from 'antd';
|
import { Form, Input, InputNumber, Modal, Select } from 'antd';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
import { LLMHeader } from '../../components/llm-header';
|
||||||
|
|
||||||
type FieldType = IAddLlmRequestBody & {
|
type FieldType = IAddLlmRequestBody & {
|
||||||
vision: boolean;
|
vision: boolean;
|
||||||
@ -49,7 +50,7 @@ const YiyanModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t('addLlmTitle', { name: llmFactory })}
|
title={<LLMHeader name={llmFactory} />}
|
||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
@ -11,6 +11,10 @@ export enum Routes {
|
|||||||
Agent = '/agent',
|
Agent = '/agent',
|
||||||
AgentTemplates = '/agent-templates',
|
AgentTemplates = '/agent-templates',
|
||||||
Agents = '/agents',
|
Agents = '/agents',
|
||||||
|
Memories = '/memories',
|
||||||
|
Memory = '/memory',
|
||||||
|
MemoryMessage = '/memory-message',
|
||||||
|
MemorySetting = '/memory-setting',
|
||||||
AgentList = '/agent-list',
|
AgentList = '/agent-list',
|
||||||
Searches = '/next-searches',
|
Searches = '/next-searches',
|
||||||
Search = '/next-search',
|
Search = '/next-search',
|
||||||
@ -89,6 +93,7 @@ const routes = [
|
|||||||
path: Routes.AgentList,
|
path: Routes.AgentList,
|
||||||
component: `@/pages/${Routes.Agents}`,
|
component: `@/pages/${Routes.Agents}`,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/document/:id',
|
path: '/document/:id',
|
||||||
component: '@/pages/document-viewer',
|
component: '@/pages/document-viewer',
|
||||||
@ -149,6 +154,41 @@ const routes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: Routes.Memories,
|
||||||
|
layout: false,
|
||||||
|
component: '@/layouts/next',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: Routes.Memories,
|
||||||
|
component: `@/pages${Routes.Memories}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.Memory}`,
|
||||||
|
layout: false,
|
||||||
|
component: '@/layouts/next',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: `${Routes.Memory}`,
|
||||||
|
layout: false,
|
||||||
|
component: `@/pages${Routes.Memory}`,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: `${Routes.Memory}/${Routes.MemoryMessage}/:id`,
|
||||||
|
component: `@/pages${Routes.Memory}${Routes.MemoryMessage}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.Memory}/${Routes.MemorySetting}/:id`,
|
||||||
|
component: `@/pages${Routes.Memory}${Routes.MemorySetting}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// component: `@/pages${Routes.DatasetBase}`,
|
||||||
|
// component: `@/pages${Routes.Memory}`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `${Routes.Search}/:id`,
|
path: `${Routes.Search}/:id`,
|
||||||
layout: false,
|
layout: false,
|
||||||
|
|||||||
43
web/src/services/memory-service.ts
Normal file
43
web/src/services/memory-service.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import api from '@/utils/api';
|
||||||
|
import { registerNextServer } from '@/utils/register-server';
|
||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
const {
|
||||||
|
createMemory,
|
||||||
|
getMemoryList,
|
||||||
|
deleteMemory,
|
||||||
|
getMemoryDetail,
|
||||||
|
updateMemorySetting,
|
||||||
|
// getMemoryDetailShare,
|
||||||
|
} = api;
|
||||||
|
const methods = {
|
||||||
|
createMemory: {
|
||||||
|
url: createMemory,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
getMemoryList: {
|
||||||
|
url: getMemoryList,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
deleteMemory: { url: deleteMemory, method: 'post' },
|
||||||
|
// getMemoryDetail: {
|
||||||
|
// url: getMemoryDetail,
|
||||||
|
// method: 'get',
|
||||||
|
// },
|
||||||
|
// updateMemorySetting: {
|
||||||
|
// url: updateMemorySetting,
|
||||||
|
// method: 'post',
|
||||||
|
// },
|
||||||
|
// getMemoryDetailShare: {
|
||||||
|
// url: getMemoryDetailShare,
|
||||||
|
// method: 'get',
|
||||||
|
// },
|
||||||
|
} as const;
|
||||||
|
const memoryService = registerNextServer<keyof typeof methods>(methods);
|
||||||
|
export const updateMemoryById = (id: string, data: any) => {
|
||||||
|
return request.post(updateMemorySetting(id), { data });
|
||||||
|
};
|
||||||
|
export const getMemoryDetailById = (id: string, data: any) => {
|
||||||
|
return request.post(getMemoryDetail(id), { data });
|
||||||
|
};
|
||||||
|
export default memoryService;
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import registerServer from '@/utils/register-server';
|
import { registerNextServer } from '@/utils/register-server';
|
||||||
import request from '@/utils/request';
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
createSearch,
|
createSearch,
|
||||||
@ -49,6 +48,6 @@ const methods = {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
const searchService = registerServer<keyof typeof methods>(methods, request);
|
const searchService = registerNextServer<keyof typeof methods>(methods);
|
||||||
|
|
||||||
export default searchService;
|
export default searchService;
|
||||||
|
|||||||
@ -226,6 +226,13 @@ export default {
|
|||||||
getRelatedQuestionsShare: `${ExternalApi}${api_host}/searchbots/related_questions`,
|
getRelatedQuestionsShare: `${ExternalApi}${api_host}/searchbots/related_questions`,
|
||||||
retrievalTestShare: `${ExternalApi}${api_host}/searchbots/retrieval_test`,
|
retrievalTestShare: `${ExternalApi}${api_host}/searchbots/retrieval_test`,
|
||||||
|
|
||||||
|
// memory
|
||||||
|
createMemory: `${api_host}/memory/create`,
|
||||||
|
getMemoryList: `${api_host}/memory/list`,
|
||||||
|
deleteMemory: (id: string) => `${api_host}/memory/rm/${id}`,
|
||||||
|
getMemoryDetail: (id: string) => `${api_host}/memory/detail/${id}`,
|
||||||
|
updateMemorySetting: (id: string) => `${api_host}/memory/update/${id}`,
|
||||||
|
|
||||||
// data pipeline
|
// data pipeline
|
||||||
fetchDataflow: (id: string) => `${api_host}/dataflow/get/${id}`,
|
fetchDataflow: (id: string) => `${api_host}/dataflow/get/${id}`,
|
||||||
setDataflow: `${api_host}/dataflow/set`,
|
setDataflow: `${api_host}/dataflow/set`,
|
||||||
|
|||||||
Reference in New Issue
Block a user