mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-24 23:46:52 +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:
@ -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>
|
||||
)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user