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 { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
ControllerRenderProps,
|
||||
DefaultValues,
|
||||
FieldValues,
|
||||
SubmitHandler,
|
||||
@ -26,6 +33,7 @@ import { Textarea } from '@/components/ui/textarea';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { t } from 'i18next';
|
||||
import { Loader } from 'lucide-react';
|
||||
import { MultiSelect, MultiSelectOptionType } from './ui/multi-select';
|
||||
|
||||
// Field type enumeration
|
||||
export enum FormFieldType {
|
||||
@ -35,14 +43,17 @@ export enum FormFieldType {
|
||||
Number = 'number',
|
||||
Textarea = 'textarea',
|
||||
Select = 'select',
|
||||
MultiSelect = 'multi-select',
|
||||
Checkbox = 'checkbox',
|
||||
Tag = 'tag',
|
||||
Custom = 'custom',
|
||||
}
|
||||
|
||||
// Field configuration interface
|
||||
export interface FormFieldConfig {
|
||||
name: string;
|
||||
label: string;
|
||||
hideLabel?: boolean;
|
||||
type: FormFieldType;
|
||||
hidden?: boolean;
|
||||
required?: boolean;
|
||||
@ -57,7 +68,7 @@ export interface FormFieldConfig {
|
||||
max?: number;
|
||||
message?: string;
|
||||
};
|
||||
render?: (fieldProps: any) => React.ReactNode;
|
||||
render?: (fieldProps: ControllerRenderProps) => React.ReactNode;
|
||||
horizontal?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
tooltip?: React.ReactNode;
|
||||
@ -78,10 +89,10 @@ interface DynamicFormProps<T extends FieldValues> {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
defaultValues?: DefaultValues<T>;
|
||||
onFieldUpdate?: (
|
||||
fieldName: string,
|
||||
updatedField: Partial<FormFieldConfig>,
|
||||
) => void;
|
||||
// onFieldUpdate?: (
|
||||
// fieldName: string,
|
||||
// updatedField: Partial<FormFieldConfig>,
|
||||
// ) => void;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
@ -92,6 +103,10 @@ export interface DynamicFormRef {
|
||||
reset: (values?: any) => void;
|
||||
watch: (field: string, callback: (value: any) => void) => () => void;
|
||||
updateFieldType: (fieldName: string, newType: FormFieldType) => void;
|
||||
onFieldUpdate: (
|
||||
fieldName: string,
|
||||
newFieldProperties: Partial<FormFieldConfig>,
|
||||
) => void;
|
||||
}
|
||||
|
||||
// Generate Zod validation schema based on field configurations
|
||||
@ -110,6 +125,14 @@ const generateSchema = (fields: FormFieldConfig[]): ZodSchema<any> => {
|
||||
case FormFieldType.Email:
|
||||
fieldSchema = z.string().email('Please enter a valid email address');
|
||||
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:
|
||||
fieldSchema = z.coerce.number();
|
||||
if (field.validation?.min !== undefined) {
|
||||
@ -275,7 +298,10 @@ const generateDefaultValues = <T extends FieldValues>(
|
||||
defaultValues[field.name] = field.defaultValue;
|
||||
} else if (field.type === FormFieldType.Checkbox) {
|
||||
defaultValues[field.name] = false;
|
||||
} else if (field.type === FormFieldType.Tag) {
|
||||
} else if (
|
||||
field.type === FormFieldType.Tag ||
|
||||
field.type === FormFieldType.MultiSelect
|
||||
) {
|
||||
defaultValues[field.name] = [];
|
||||
} else {
|
||||
defaultValues[field.name] = '';
|
||||
@ -291,17 +317,21 @@ const DynamicForm = {
|
||||
Root: forwardRef(
|
||||
<T extends FieldValues>(
|
||||
{
|
||||
fields,
|
||||
fields: originFields,
|
||||
onSubmit,
|
||||
className = '',
|
||||
children,
|
||||
defaultValues: formDefaultValues = {} as DefaultValues<T>,
|
||||
onFieldUpdate,
|
||||
// onFieldUpdate,
|
||||
labelClassName,
|
||||
}: DynamicFormProps<T>,
|
||||
ref: React.Ref<any>,
|
||||
) => {
|
||||
// Generate validation schema and default values
|
||||
const [fields, setFields] = useState(originFields);
|
||||
useMemo(() => {
|
||||
setFields(originFields);
|
||||
}, [originFields]);
|
||||
const schema = useMemo(() => generateSchema(fields), [fields]);
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
@ -406,43 +436,54 @@ const DynamicForm = {
|
||||
}, [fields, form]);
|
||||
|
||||
// Expose form methods via ref
|
||||
useImperativeHandle(ref, () => ({
|
||||
submit: () => form.handleSubmit(onSubmit)(),
|
||||
getValues: () => form.getValues(),
|
||||
reset: (values?: T) => {
|
||||
if (values) {
|
||||
form.reset(values);
|
||||
} else {
|
||||
form.reset();
|
||||
}
|
||||
},
|
||||
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);
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
submit: () => form.handleSubmit(onSubmit)(),
|
||||
getValues: () => form.getValues(),
|
||||
reset: (values?: T) => {
|
||||
if (values) {
|
||||
form.reset(values);
|
||||
} else {
|
||||
console.warn(
|
||||
'onFieldUpdate prop is not provided. Cannot update field type.',
|
||||
);
|
||||
form.reset();
|
||||
}
|
||||
}, 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(() => {
|
||||
if (formDefaultValues && Object.keys(formDefaultValues).length > 0) {
|
||||
@ -459,6 +500,9 @@ const DynamicForm = {
|
||||
// Render form fields
|
||||
const renderField = (field: FormFieldConfig) => {
|
||||
if (field.render) {
|
||||
if (field.type === FormFieldType.Custom && field.hideLabel) {
|
||||
return <div className="w-full">{field.render({})}</div>;
|
||||
}
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
@ -549,6 +593,43 @@ const DynamicForm = {
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
case FormFieldType.MultiSelect:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
labelClassName={labelClassName || field.labelClassName}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
console.log('multi select value', fieldProps);
|
||||
const finalFieldProps = {
|
||||
...fieldProps,
|
||||
onValueChange: (value: string[]) => {
|
||||
if (fieldProps.onChange) {
|
||||
fieldProps.onChange(value);
|
||||
}
|
||||
field.onChange?.(value);
|
||||
},
|
||||
};
|
||||
return (
|
||||
<MultiSelect
|
||||
variant="inverted"
|
||||
maxCount={100}
|
||||
{...finalFieldProps}
|
||||
// onValueChange={(data) => {
|
||||
// console.log(data);
|
||||
// field.onChange?.(data);
|
||||
// }}
|
||||
options={field.options as MultiSelectOptionType[]}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
case FormFieldType.Checkbox:
|
||||
return (
|
||||
<FormField
|
||||
|
||||
@ -11,23 +11,33 @@ export enum EmptyCardType {
|
||||
Dataset = 'dataset',
|
||||
Chat = 'chat',
|
||||
Search = 'search',
|
||||
Memory = 'memory',
|
||||
}
|
||||
|
||||
export const EmptyCardData = {
|
||||
[EmptyCardType.Agent]: {
|
||||
icon: <HomeIcon name="agents" width={'24'} />,
|
||||
title: t('empty.agentTitle'),
|
||||
notFound: t('empty.notFoundAgent'),
|
||||
},
|
||||
[EmptyCardType.Dataset]: {
|
||||
icon: <HomeIcon name="datasets" width={'24'} />,
|
||||
title: t('empty.datasetTitle'),
|
||||
notFound: t('empty.notFoundDataset'),
|
||||
},
|
||||
[EmptyCardType.Chat]: {
|
||||
icon: <HomeIcon name="chats" width={'24'} />,
|
||||
title: t('empty.chatTitle'),
|
||||
notFound: t('empty.notFoundChat'),
|
||||
},
|
||||
[EmptyCardType.Search]: {
|
||||
icon: <HomeIcon name="searches" width={'24'} />,
|
||||
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;
|
||||
showIcon?: boolean;
|
||||
className?: string;
|
||||
isSearch?: boolean;
|
||||
size?: 'small' | 'large';
|
||||
}) => {
|
||||
const { type, showIcon, className } = props;
|
||||
const { type, showIcon, className, isSearch } = props;
|
||||
let defaultClass = '';
|
||||
let style = {};
|
||||
switch (props.size) {
|
||||
@ -95,19 +96,29 @@ export const EmptyAppCard = (props: {
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<div className=" cursor-pointer " onClick={props.onClick}>
|
||||
<div
|
||||
className=" cursor-pointer "
|
||||
onClick={isSearch ? undefined : props.onClick}
|
||||
>
|
||||
<EmptyCard
|
||||
icon={showIcon ? EmptyCardData[type].icon : undefined}
|
||||
title={EmptyCardData[type].title}
|
||||
title={
|
||||
isSearch ? EmptyCardData[type].notFound : EmptyCardData[type].title
|
||||
}
|
||||
className={className}
|
||||
style={style}
|
||||
// description={EmptyCardData[type].description}
|
||||
>
|
||||
<div
|
||||
className={cn(defaultClass, 'flex items-center justify-start w-full')}
|
||||
>
|
||||
<Plus size={24} />
|
||||
</div>
|
||||
{!isSearch && (
|
||||
<div
|
||||
className={cn(
|
||||
defaultClass,
|
||||
'flex items-center justify-start w-full',
|
||||
)}
|
||||
>
|
||||
<Plus size={24} />
|
||||
</div>
|
||||
)}
|
||||
</EmptyCard>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -9,13 +9,19 @@ export type LLMFormFieldProps = {
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export function LLMFormField({ options, name }: LLMFormFieldProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
export const useModelOptions = () => {
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Chat,
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
return {
|
||||
modelOptions,
|
||||
};
|
||||
};
|
||||
|
||||
export function LLMFormField({ options, name }: LLMFormFieldProps) {
|
||||
const { t } = useTranslation();
|
||||
const { modelOptions } = useModelOptions();
|
||||
|
||||
return (
|
||||
<RAGFlowFormItem name={name || 'llm_id'} label={t('chat.model')}>
|
||||
|
||||
@ -53,14 +53,16 @@ export function RAGFlowFormItem({
|
||||
{label}
|
||||
</FormLabel>
|
||||
)}
|
||||
<FormControl>
|
||||
{typeof children === 'function'
|
||||
? children(field)
|
||||
: isValidElement(children)
|
||||
? cloneElement(children, { ...field })
|
||||
: children}
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<div className="w-full flex flex-col">
|
||||
<FormControl>
|
||||
{typeof children === 'function'
|
||||
? children(field)
|
||||
: isValidElement(children)
|
||||
? cloneElement(children, { ...field })
|
||||
: children}
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -126,3 +126,53 @@ export const IconMap = {
|
||||
[LLMFactory.JiekouAI]: 'jiekouai',
|
||||
[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 { MessageType } from '@/constants/chat';
|
||||
import { LanguageTranslationMap } from '@/constants/common';
|
||||
import { Pagination } from '@/interfaces/common';
|
||||
import { ResponseType } from '@/interfaces/database/base';
|
||||
import {
|
||||
IAnswer,
|
||||
@ -12,7 +13,7 @@ import { IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||
import api from '@/utils/api';
|
||||
import { getAuthorization } from '@/utils/authorization-util';
|
||||
import { buildMessageUuid } from '@/utils/chat';
|
||||
import { PaginationProps, message } from 'antd';
|
||||
import { message } from 'antd';
|
||||
import { FormInstance } from 'antd/lib';
|
||||
import axios from 'axios';
|
||||
import { EventSourceParserStream } from 'eventsource-parser/stream';
|
||||
@ -71,8 +72,8 @@ export const useGetPaginationWithRouter = () => {
|
||||
size: pageSize,
|
||||
} = useSetPaginationParams();
|
||||
|
||||
const onPageChange: PaginationProps['onChange'] = useCallback(
|
||||
(pageNumber: number, pageSize: number) => {
|
||||
const onPageChange: Pagination['onChange'] = useCallback(
|
||||
(pageNumber: number, pageSize?: number) => {
|
||||
setPaginationParams(pageNumber, pageSize);
|
||||
},
|
||||
[setPaginationParams],
|
||||
@ -88,7 +89,7 @@ export const useGetPaginationWithRouter = () => {
|
||||
[setPaginationParams, pageSize],
|
||||
);
|
||||
|
||||
const pagination: PaginationProps = useMemo(() => {
|
||||
const pagination: Pagination = useMemo(() => {
|
||||
return {
|
||||
showQuickJumper: true,
|
||||
total: 0,
|
||||
@ -97,7 +98,7 @@ export const useGetPaginationWithRouter = () => {
|
||||
pageSize: pageSize,
|
||||
pageSizeOptions: [1, 2, 10, 20, 50, 100],
|
||||
onChange: onPageChange,
|
||||
showTotal: (total) => `${t('total')} ${total}`,
|
||||
showTotal: (total: number) => `${t('total')} ${total}`,
|
||||
};
|
||||
}, [t, onPageChange, page, pageSize]);
|
||||
|
||||
@ -109,7 +110,7 @@ export const useGetPaginationWithRouter = () => {
|
||||
|
||||
export const useHandleSearchChange = () => {
|
||||
const [searchString, setSearchString] = useState('');
|
||||
const { setPagination } = useGetPaginationWithRouter();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const value = e.target.value;
|
||||
@ -119,21 +120,21 @@ export const useHandleSearchChange = () => {
|
||||
[setPagination],
|
||||
);
|
||||
|
||||
return { handleInputChange, searchString };
|
||||
return { handleInputChange, searchString, pagination, setPagination };
|
||||
};
|
||||
|
||||
export const useGetPagination = () => {
|
||||
const [pagination, setPagination] = useState({ page: 1, pageSize: 10 });
|
||||
const { t } = useTranslate('common');
|
||||
|
||||
const onPageChange: PaginationProps['onChange'] = useCallback(
|
||||
const onPageChange: Pagination['onChange'] = useCallback(
|
||||
(pageNumber: number, pageSize: number) => {
|
||||
setPagination({ page: pageNumber, pageSize });
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const currentPagination: PaginationProps = useMemo(() => {
|
||||
const currentPagination: Pagination = useMemo(() => {
|
||||
return {
|
||||
showQuickJumper: true,
|
||||
total: 0,
|
||||
@ -142,7 +143,7 @@ export const useGetPagination = () => {
|
||||
pageSize: pagination.pageSize,
|
||||
pageSizeOptions: [1, 2, 10, 20, 50, 100],
|
||||
onChange: onPageChange,
|
||||
showTotal: (total) => `${t('total')} ${total}`,
|
||||
showTotal: (total: number) => `${t('total')} ${total}`,
|
||||
};
|
||||
}, [t, onPageChange, pagination]);
|
||||
|
||||
|
||||
@ -25,6 +25,17 @@ export const useNavigatePage = () => {
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToMemoryList = useCallback(
|
||||
({ isCreate = false }: { isCreate?: boolean }) => {
|
||||
if (isCreate) {
|
||||
navigate(Routes.Memories + '?isCreate=true');
|
||||
} else {
|
||||
navigate(Routes.Memories);
|
||||
}
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToDataset = useCallback(
|
||||
(id: string) => () => {
|
||||
// navigate(`${Routes.DatasetBase}${Routes.DataSetOverview}/${id}`);
|
||||
@ -105,6 +116,12 @@ export const useNavigatePage = () => {
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
const navigateToMemory = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.Memory}${Routes.MemoryMessage}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToChunkParsedResult = useCallback(
|
||||
(id: string, knowledgeId?: string) => () => {
|
||||
@ -196,5 +213,7 @@ export const useNavigatePage = () => {
|
||||
navigateToDataflowResult,
|
||||
navigateToDataFile,
|
||||
navigateToDataSourceDetail,
|
||||
navigateToMemory,
|
||||
navigateToMemoryList,
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ export interface Pagination {
|
||||
current: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
onChange?: (page: number, pageSize: number) => void;
|
||||
}
|
||||
|
||||
export interface BaseState {
|
||||
|
||||
@ -99,6 +99,29 @@ export default {
|
||||
search: 'Search',
|
||||
welcome: 'Welcome to',
|
||||
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: {
|
||||
welcome: 'Welcome back',
|
||||
@ -2044,14 +2067,21 @@ Important structured information may include: names, dates, locations, events, k
|
||||
delFilesContent: 'Selected {{count}} files',
|
||||
delChat: 'Delete chat',
|
||||
delMember: 'Delete member',
|
||||
delMemory: 'Delete memory',
|
||||
},
|
||||
|
||||
empty: {
|
||||
noMCP: 'No MCP servers available',
|
||||
agentTitle: 'No agent app created yet',
|
||||
notFoundAgent: 'Agent app not found',
|
||||
datasetTitle: 'No dataset created yet',
|
||||
notFoundDataset: 'Dataset not found',
|
||||
chatTitle: 'No chat app created yet',
|
||||
notFoundChat: 'Chat app not found',
|
||||
searchTitle: 'No search app created yet',
|
||||
notFoundSearch: 'Search app not found',
|
||||
memoryTitle: 'No memory created yet',
|
||||
notFoundMemory: 'Memory not found',
|
||||
addNow: 'Add Now',
|
||||
},
|
||||
|
||||
|
||||
@ -1900,9 +1900,15 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
|
||||
empty: {
|
||||
noMCP: '暂无 MCP 服务器可用',
|
||||
agentTitle: '尚未创建智能体',
|
||||
notFoundAgent: '未查询到智能体',
|
||||
datasetTitle: '尚未创建数据集',
|
||||
notFoundDataset: '未查询到数据集',
|
||||
chatTitle: '尚未创建聊天应用',
|
||||
notFoundChat: '未查询到聊天应用',
|
||||
searchTitle: '尚未创建搜索应用',
|
||||
notFoundSearch: '未查询到搜索应用',
|
||||
memoryTitle: '尚未创建记忆',
|
||||
notFoundMemory: '未查询到记忆',
|
||||
addNow: '立即添加',
|
||||
},
|
||||
},
|
||||
|
||||
@ -81,19 +81,20 @@ export default function Agents() {
|
||||
}, [isCreate, showCreatingModal, searchUrl, setSearchUrl]);
|
||||
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)]">
|
||||
<EmptyAppCard
|
||||
showIcon
|
||||
size="large"
|
||||
className="w-[480px] p-14"
|
||||
isSearch={!!searchString}
|
||||
type={EmptyCardType.Agent}
|
||||
onClick={() => showCreatingModal()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<section className="flex flex-col w-full flex-1">
|
||||
{!!data?.length && (
|
||||
{(!!data?.length || searchString) && (
|
||||
<>
|
||||
<div className="px-8 pt-8 ">
|
||||
<ListFilterBar
|
||||
@ -138,6 +139,18 @@ export default function Agents() {
|
||||
</DropdownMenu>
|
||||
</ListFilterBar>
|
||||
</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">
|
||||
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
||||
{data.map((x) => {
|
||||
|
||||
@ -12,7 +12,7 @@ import { Switch } from '@/components/ui/switch';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { FieldValues, useFormContext } from 'react-hook-form';
|
||||
import {
|
||||
useHandleKbEmbedding,
|
||||
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 form = useFormContext();
|
||||
const embeddingModelOptions = useSelectEmbeddingModelOptions();
|
||||
const { handleChange } = useHandleKbEmbedding();
|
||||
const disabled = useHasParsedDocument(isEdit);
|
||||
const oldValue = useMemo(() => {
|
||||
const embdStr = form.getValues('embd_id');
|
||||
const embdStr = form.getValues(name || 'embd_id');
|
||||
return embdStr || '';
|
||||
}, [form]);
|
||||
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 (
|
||||
<>
|
||||
<FormField
|
||||
@ -102,33 +144,10 @@ export function EmbeddingModelItem({ line = 1, isEdit }: IProps) {
|
||||
className={cn('text-muted-foreground', { 'w-3/4': line === 1 })}
|
||||
>
|
||||
<FormControl>
|
||||
<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')}
|
||||
triggerClassName="!bg-bg-base"
|
||||
/>
|
||||
</Spin>
|
||||
<EmbeddingSelect
|
||||
isEdit={!!isEdit}
|
||||
field={field}
|
||||
></EmbeddingSelect>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -70,18 +70,19 @@ export default function Datasets() {
|
||||
return (
|
||||
<>
|
||||
<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)]">
|
||||
<EmptyAppCard
|
||||
showIcon
|
||||
size="large"
|
||||
className="w-[480px] p-14"
|
||||
isSearch={!!searchString}
|
||||
type={EmptyCardType.Dataset}
|
||||
onClick={() => showModal()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!!kbs?.length && (
|
||||
{(!!kbs?.length || searchString) && (
|
||||
<>
|
||||
<ListFilterBar
|
||||
title={t('header.dataset')}
|
||||
@ -98,6 +99,18 @@ export default function Datasets() {
|
||||
{t('knowledgeList.createKnowledgeBase')}
|
||||
</Button>
|
||||
</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">
|
||||
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
||||
{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 (
|
||||
<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)]">
|
||||
<EmptyAppCard
|
||||
showIcon
|
||||
size="large"
|
||||
className="w-[480px] p-14"
|
||||
isSearch={!!searchString}
|
||||
type={EmptyCardType.Chat}
|
||||
onClick={() => handleShowCreateModal()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{data.dialogs?.length > 0 && (
|
||||
{(data.dialogs?.length > 0 || searchString) && (
|
||||
<>
|
||||
<div className="px-8 pt-8">
|
||||
<ListFilterBar
|
||||
@ -76,6 +77,18 @@ export default function ChatList() {
|
||||
</Button>
|
||||
</ListFilterBar>
|
||||
</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">
|
||||
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
||||
{data.dialogs.map((x) => {
|
||||
|
||||
@ -2,9 +2,11 @@
|
||||
|
||||
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 searchService from '@/services/search-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';
|
||||
@ -84,21 +86,34 @@ interface SearchListResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const useFetchSearchList = (params?: SearchListParams) => {
|
||||
const [searchParams, setSearchParams] = useState<SearchListParams>({
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
...params,
|
||||
});
|
||||
export const useFetchSearchList = () => {
|
||||
const { handleInputChange, searchString, pagination, setPagination } =
|
||||
useHandleSearchChange();
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
|
||||
const { data, isLoading, isError, refetch } = useQuery<
|
||||
SearchListResponse,
|
||||
Error
|
||||
>({
|
||||
queryKey: ['searchList', searchParams],
|
||||
queryKey: [
|
||||
'searchList',
|
||||
{
|
||||
debouncedSearchString,
|
||||
...pagination,
|
||||
},
|
||||
],
|
||||
queryFn: async () => {
|
||||
const { data: response } =
|
||||
await searchService.getSearchList(searchParams);
|
||||
const { data: response } = await searchService.getSearchList(
|
||||
{
|
||||
params: {
|
||||
keywords: debouncedSearchString,
|
||||
page_size: pagination.pageSize,
|
||||
page: pagination.current,
|
||||
},
|
||||
data: {},
|
||||
},
|
||||
true,
|
||||
);
|
||||
if (response.code !== 0) {
|
||||
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 {
|
||||
data,
|
||||
isLoading,
|
||||
isError,
|
||||
searchParams,
|
||||
setSearchListParams,
|
||||
pagination,
|
||||
searchString,
|
||||
handleInputChange,
|
||||
setPagination,
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@ import { RenameDialog } from '@/components/rename-dialog';
|
||||
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';
|
||||
@ -19,10 +20,13 @@ export default function SearchList() {
|
||||
// const [isEdit, setIsEdit] = useState(false);
|
||||
const {
|
||||
data: list,
|
||||
searchParams,
|
||||
setSearchListParams,
|
||||
pagination,
|
||||
searchString,
|
||||
handleInputChange,
|
||||
setPagination,
|
||||
refetch: refetchList,
|
||||
} = useFetchSearchList();
|
||||
|
||||
const {
|
||||
openCreateModal,
|
||||
showSearchRenameModal,
|
||||
@ -32,9 +36,9 @@ export default function SearchList() {
|
||||
initialSearchName,
|
||||
} = useRenameSearch();
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
console.log(value);
|
||||
};
|
||||
// const handleSearchChange = (value: string) => {
|
||||
// console.log(value);
|
||||
// };
|
||||
const onSearchRenameConfirm = (name: string) => {
|
||||
onSearchRenameOk(name, () => {
|
||||
refetchList();
|
||||
@ -44,10 +48,12 @@ export default function SearchList() {
|
||||
// setIsEdit(false);
|
||||
showSearchRenameModal();
|
||||
}, [showSearchRenameModal]);
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
// setIsEdit(false);
|
||||
setSearchListParams({ ...searchParams, page, page_size: pageSize });
|
||||
};
|
||||
const handlePageChange = useCallback(
|
||||
(page: number, pageSize?: number) => {
|
||||
setPagination({ page, pageSize });
|
||||
},
|
||||
[setPagination],
|
||||
);
|
||||
|
||||
const [searchUrl, setSearchUrl] = useSearchParams();
|
||||
const isCreate = searchUrl.get('isCreate') === 'true';
|
||||
@ -62,25 +68,28 @@ export default function SearchList() {
|
||||
return (
|
||||
<section className="w-full h-full flex flex-col">
|
||||
{(!list?.data?.search_apps?.length ||
|
||||
list?.data?.search_apps?.length <= 0) && (
|
||||
<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}
|
||||
onClick={() => openCreateModalFun()}
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
{(!!list?.data?.search_apps?.length || searchString) && (
|
||||
<>
|
||||
<div className="px-8 pt-8">
|
||||
<ListFilterBar
|
||||
icon="searches"
|
||||
title={t('searchApps')}
|
||||
showFilter={false}
|
||||
onSearchChange={(e) => handleSearchChange(e.target.value)}
|
||||
searchString={searchString}
|
||||
onSearchChange={handleInputChange}
|
||||
>
|
||||
<Button
|
||||
variant={'default'}
|
||||
@ -93,6 +102,20 @@ export default function SearchList() {
|
||||
</Button>
|
||||
</ListFilterBar>
|
||||
</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">
|
||||
<CardContainer className="max-h-[calc(100dvh-280px)] overflow-auto px-8">
|
||||
{list?.data.search_apps.map((x) => {
|
||||
@ -111,8 +134,8 @@ export default function SearchList() {
|
||||
{list?.data.total && list?.data.total > 0 && (
|
||||
<div className="px-8 mb-4">
|
||||
<RAGFlowPagination
|
||||
current={searchParams.page}
|
||||
pageSize={searchParams.page_size}
|
||||
{...pick(pagination, 'current', 'pageSize')}
|
||||
// total={pagination.total}
|
||||
total={list?.data.total}
|
||||
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 { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
/* ---------------- Token Field ---------------- */
|
||||
|
||||
@ -48,15 +49,15 @@ type ConfluenceIndexingMode = 'everything' | 'space' | 'page';
|
||||
export type ConfluenceIndexingModeFieldProps = ControllerRenderProps;
|
||||
|
||||
export const ConfluenceIndexingModeField = (
|
||||
fieldProps: ConfluenceIndexingModeFieldProps,
|
||||
fieldProps: ControllerRenderProps,
|
||||
) => {
|
||||
const { value, onChange, disabled } = fieldProps;
|
||||
const [mode, setMode] = useState<ConfluenceIndexingMode>(
|
||||
value || 'everything',
|
||||
);
|
||||
const { watch, setValue } = useFormContext();
|
||||
|
||||
const mode = useMemo<ConfluenceIndexingMode>(
|
||||
() => (value as ConfluenceIndexingMode) || 'everything',
|
||||
[value],
|
||||
);
|
||||
useEffect(() => setMode(value), [value]);
|
||||
|
||||
const spaceValue = watch('config.space');
|
||||
const pageIdValue = watch('config.page_id');
|
||||
@ -66,27 +67,40 @@ export const ConfluenceIndexingModeField = (
|
||||
if (!value) onChange('everything');
|
||||
}, [value, onChange]);
|
||||
|
||||
const handleModeChange = (nextMode?: string) => {
|
||||
const normalized = (nextMode || 'everything') as ConfluenceIndexingMode;
|
||||
onChange(normalized);
|
||||
const handleModeChange = useCallback(
|
||||
(nextMode?: string) => {
|
||||
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') {
|
||||
setValue('config.space', '', { shouldDirty: true, shouldTouch: true });
|
||||
setValue('config.page_id', '', { shouldDirty: true, shouldTouch: true });
|
||||
setValue('config.index_recursively', false, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
});
|
||||
} 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 });
|
||||
}
|
||||
};
|
||||
const debouncedHandleChange = useMemo(
|
||||
() =>
|
||||
debounce(() => {
|
||||
handleModeChange();
|
||||
}, 300),
|
||||
[handleModeChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<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
|
||||
className="w-full"
|
||||
value={spaceValue ?? ''}
|
||||
onChange={(e) =>
|
||||
setValue('config.space', e.target.value, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
})
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setValue('config.space', value);
|
||||
debouncedHandleChange();
|
||||
}}
|
||||
placeholder="e.g. KB"
|
||||
disabled={disabled}
|
||||
/>
|
||||
@ -148,12 +161,10 @@ export const ConfluenceIndexingModeField = (
|
||||
<Input
|
||||
className="w-full"
|
||||
value={pageIdValue ?? ''}
|
||||
onChange={(e) =>
|
||||
setValue('config.page_id', e.target.value, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
})
|
||||
}
|
||||
onChange={(e) => {
|
||||
setValue('config.page_id', e.target.value);
|
||||
debouncedHandleChange();
|
||||
}}
|
||||
placeholder="e.g. 123456"
|
||||
disabled={disabled}
|
||||
/>
|
||||
@ -164,12 +175,10 @@ export const ConfluenceIndexingModeField = (
|
||||
<div className="flex items-center gap-2 pt-2">
|
||||
<Checkbox
|
||||
checked={Boolean(indexRecursively)}
|
||||
onCheckedChange={(checked) =>
|
||||
setValue('config.index_recursively', Boolean(checked), {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
})
|
||||
}
|
||||
onCheckedChange={(checked) => {
|
||||
setValue('config.index_recursively', Boolean(checked));
|
||||
debouncedHandleChange();
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span className="text-sm text-text-secondary">
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { FormFieldType } from '@/components/dynamic-form';
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { t } from 'i18next';
|
||||
import { ControllerRenderProps } from 'react-hook-form';
|
||||
import { ConfluenceIndexingModeField } from './component/confluence-token-field';
|
||||
import GmailTokenField from './component/gmail-token-field';
|
||||
import GoogleDriveTokenField from './component/google-drive-token-field';
|
||||
@ -237,7 +238,9 @@ export const DataSourceFormFields = {
|
||||
required: false,
|
||||
horizontal: true,
|
||||
labelClassName: 'self-start pt-4',
|
||||
render: (fieldProps) => <ConfluenceIndexingModeField {...fieldProps} />,
|
||||
render: (fieldProps: ControllerRenderProps) => (
|
||||
<ConfluenceIndexingModeField {...fieldProps} />
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Space Key',
|
||||
@ -598,6 +601,7 @@ export const DataSourceFormDefaultValues = {
|
||||
confluence_username: '',
|
||||
confluence_access_token: '',
|
||||
},
|
||||
index_mode: 'everything',
|
||||
},
|
||||
},
|
||||
[DataSourceKey.GOOGLE_DRIVE]: {
|
||||
|
||||
@ -136,7 +136,7 @@ const SourceDetailPage = () => {
|
||||
...customFields,
|
||||
] as FormFieldConfig[];
|
||||
|
||||
const neweFields = fields.map((field) => {
|
||||
const newFields = fields.map((field) => {
|
||||
return {
|
||||
...field,
|
||||
horizontal: true,
|
||||
@ -145,7 +145,7 @@ const SourceDetailPage = () => {
|
||||
},
|
||||
};
|
||||
});
|
||||
setFields(neweFields);
|
||||
setFields(newFields);
|
||||
|
||||
const defultValueTemp = {
|
||||
...(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 { Button } from '@/components/ui/button';
|
||||
import { SearchInput } from '@/components/ui/input';
|
||||
import { APIMapUrl } from '@/constants/llm';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useSelectLlmList } from '@/hooks/use-llm-request';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { ArrowUpRight, Plus } from 'lucide-react';
|
||||
import { FC, useMemo, useState } from 'react';
|
||||
|
||||
type TagType =
|
||||
@ -128,10 +129,26 @@ export const AvailableModels: FC<{
|
||||
>
|
||||
<div className="flex items-center space-x-3 mb-3">
|
||||
<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">
|
||||
{model.name}
|
||||
</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>
|
||||
<Button className=" px-2 items-center gap-0 text-xs h-6 rounded-md transition-colors hidden group-hover:flex">
|
||||
<Plus size={12} />
|
||||
|
||||
@ -14,6 +14,7 @@ import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { KeyboardEventHandler, useCallback, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ApiKeyPostBody } from '../../../interface';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
|
||||
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
||||
loading: boolean;
|
||||
@ -70,7 +71,7 @@ const ApiKeyModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('configureModelTitle')}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOpenChange={(open) => !open && hideModal()}
|
||||
onOk={handleOk}
|
||||
|
||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { Form, Input, InputNumber, Modal, Select, Switch } from 'antd';
|
||||
import omit from 'lodash/omit';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
api_version: string;
|
||||
@ -57,7 +58,7 @@ const AzureOpenAIModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('addLlmTitle', { name: llmFactory })}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
|
||||
import { useMemo } from 'react';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
import { BedrockRegionList } from '../../constant';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
@ -42,7 +43,7 @@ const BedrockModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('addLlmTitle', { name: llmFactory })}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
|
||||
import omit from 'lodash/omit';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
fish_audio_ak: string;
|
||||
@ -39,7 +40,7 @@ const FishAudioModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('addLlmTitle', { name: llmFactory })}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -2,6 +2,7 @@ import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { Form, Input, InputNumber, Modal, Select } from 'antd';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
google_project_id: string;
|
||||
@ -41,7 +42,7 @@ const GoogleModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('addLlmTitle', { name: llmFactory })}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { Form, Input, Modal } from 'antd';
|
||||
import omit from 'lodash/omit';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
vision: boolean;
|
||||
@ -46,7 +47,7 @@ const HunyuanModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('addLlmTitle', { name: llmFactory })}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { Flex, Form, Input, Modal, Select, Space } from 'antd';
|
||||
import omit from 'lodash/omit';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
TencentCloud_sid: string;
|
||||
@ -45,7 +46,7 @@ const TencentCloudModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('addLlmTitle', { name: llmFactory })}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
} from 'antd';
|
||||
import omit from 'lodash/omit';
|
||||
import { useEffect } from 'react';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
vision: boolean;
|
||||
@ -147,11 +148,7 @@ const OllamaModal = ({
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
editMode
|
||||
? t('editLlmTitle', { name: llmFactory })
|
||||
: t('addLlmTitle', { name: llmFactory })
|
||||
}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { Form, Input, InputNumber, Modal, Select } from 'antd';
|
||||
import omit from 'lodash/omit';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
vision: boolean;
|
||||
@ -51,7 +52,7 @@ const SparkModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('addLlmTitle', { name: llmFactory })}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
|
||||
import omit from 'lodash/omit';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
vision: boolean;
|
||||
@ -45,7 +46,7 @@ const VolcEngineModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('addLlmTitle', { name: llmFactory })}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -3,6 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { Form, Input, InputNumber, Modal, Select } from 'antd';
|
||||
import omit from 'lodash/omit';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
vision: boolean;
|
||||
@ -49,7 +50,7 @@ const YiyanModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('addLlmTitle', { name: llmFactory })}
|
||||
title={<LLMHeader name={llmFactory} />}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -11,6 +11,10 @@ export enum Routes {
|
||||
Agent = '/agent',
|
||||
AgentTemplates = '/agent-templates',
|
||||
Agents = '/agents',
|
||||
Memories = '/memories',
|
||||
Memory = '/memory',
|
||||
MemoryMessage = '/memory-message',
|
||||
MemorySetting = '/memory-setting',
|
||||
AgentList = '/agent-list',
|
||||
Searches = '/next-searches',
|
||||
Search = '/next-search',
|
||||
@ -89,6 +93,7 @@ const routes = [
|
||||
path: Routes.AgentList,
|
||||
component: `@/pages/${Routes.Agents}`,
|
||||
},
|
||||
|
||||
{
|
||||
path: '/document/:id',
|
||||
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`,
|
||||
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 registerServer from '@/utils/register-server';
|
||||
import request from '@/utils/request';
|
||||
import { registerNextServer } from '@/utils/register-server';
|
||||
|
||||
const {
|
||||
createSearch,
|
||||
@ -49,6 +48,6 @@ const methods = {
|
||||
method: 'get',
|
||||
},
|
||||
} as const;
|
||||
const searchService = registerServer<keyof typeof methods>(methods, request);
|
||||
const searchService = registerNextServer<keyof typeof methods>(methods);
|
||||
|
||||
export default searchService;
|
||||
|
||||
@ -226,6 +226,13 @@ export default {
|
||||
getRelatedQuestionsShare: `${ExternalApi}${api_host}/searchbots/related_questions`,
|
||||
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
|
||||
fetchDataflow: (id: string) => `${api_host}/dataflow/get/${id}`,
|
||||
setDataflow: `${api_host}/dataflow/set`,
|
||||
|
||||
Reference in New Issue
Block a user