mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
### What problem does this PR solve? Fix: Improve some functional issues with the data source. #10703 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
28
web/src/assets/svg/data-source/confluence.svg
Normal file
28
web/src/assets/svg/data-source/confluence.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="confluence">
|
||||
<mask id="mask0_847_577111" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
|
||||
<rect id="矩形" width="24" height="24" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_847_577111)">
|
||||
</g>
|
||||
<g id="confluence-svgrepo-com 1" clip-path="url(#clip0_847_577111)">
|
||||
<g id="Group">
|
||||
<path id="Vector" d="M3.65113 16.5245C3.46543 16.8274 3.25686 17.1788 3.07973 17.4588C2.92118 17.7267 3.0063 18.0722 3.27115 18.2359L6.98526 20.5215C7.11564 20.602 7.27287 20.6267 7.42168 20.5902C7.57048 20.5537 7.69838 20.4589 7.77665 20.3272C7.92522 20.0787 8.11664 19.7558 8.3252 19.4101C9.79656 16.9817 11.2765 17.2788 13.9449 18.553L17.6276 20.3044C17.7671 20.3707 17.9275 20.3778 18.0723 20.324C18.2171 20.2702 18.3339 20.16 18.3962 20.0187L20.1646 16.0188C20.2896 15.7332 20.1625 15.4001 19.8789 15.2703C19.1018 14.9046 17.5562 14.1761 16.1648 13.5047C11.1594 11.0734 6.90527 11.2305 3.65113 16.5245Z" fill="url(#paint0_linear_847_577111)"/>
|
||||
<path id="Vector_2" d="M20.3049 7.446C20.4906 7.14315 20.6992 6.79174 20.8763 6.51175C21.0348 6.24383 20.9497 5.89828 20.6849 5.73465L16.9708 3.44904C16.8393 3.36051 16.677 3.33054 16.5226 3.3663C16.3683 3.40205 16.2357 3.50031 16.1565 3.6376C16.008 3.88616 15.8165 4.209 15.608 4.5547C14.1366 6.98316 12.6567 6.68603 9.98823 5.4118L6.31698 3.66903C6.1775 3.60264 6.01709 3.59555 5.8723 3.64938C5.72751 3.7032 5.61068 3.81335 5.54844 3.95473L3.77995 7.95454C3.65501 8.24021 3.78214 8.57331 4.06565 8.70308C4.84276 9.06878 6.3884 9.79732 7.77977 10.4687C12.7967 12.8972 17.0508 12.7343 20.3049 7.446Z" fill="url(#paint1_linear_847_577111)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_847_577111" x1="1709.47" y1="982.917" x2="1293.54" y2="28.7335" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.18" stop-color="#0052CC"/>
|
||||
<stop offset="1" stop-color="#2684FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_847_577111" x1="19.6761" y1="-105.181" x2="436.26" y2="849.465" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.18" stop-color="#0052CC"/>
|
||||
<stop offset="1" stop-color="#2684FF"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_847_577111">
|
||||
<rect width="18" height="18" fill="white" transform="translate(3 3)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
625
web/src/components/dynamic-form.tsx
Normal file
625
web/src/components/dynamic-form.tsx
Normal file
@ -0,0 +1,625 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react';
|
||||
import {
|
||||
DefaultValues,
|
||||
FieldValues,
|
||||
SubmitHandler,
|
||||
useForm,
|
||||
useFormContext,
|
||||
} from 'react-hook-form';
|
||||
import { ZodSchema, z } from 'zod';
|
||||
|
||||
import EditTag from '@/components/edit-tag';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { t } from 'i18next';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
// Field type enumeration
|
||||
export enum FormFieldType {
|
||||
Text = 'text',
|
||||
Email = 'email',
|
||||
Password = 'password',
|
||||
Number = 'number',
|
||||
Textarea = 'textarea',
|
||||
Select = 'select',
|
||||
Checkbox = 'checkbox',
|
||||
Tag = 'tag',
|
||||
}
|
||||
|
||||
// Field configuration interface
|
||||
export interface FormFieldConfig {
|
||||
name: string;
|
||||
label: string;
|
||||
type: FormFieldType;
|
||||
hidden?: boolean;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
options?: { label: string; value: string }[];
|
||||
defaultValue?: any;
|
||||
validation?: {
|
||||
pattern?: RegExp;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
message?: string;
|
||||
};
|
||||
render?: (fieldProps: any) => React.ReactNode;
|
||||
horizontal?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
tooltip?: React.ReactNode;
|
||||
}
|
||||
|
||||
// Component props interface
|
||||
interface DynamicFormProps<T extends FieldValues> {
|
||||
fields: FormFieldConfig[];
|
||||
onSubmit: SubmitHandler<T>;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
defaultValues?: DefaultValues<T>;
|
||||
}
|
||||
|
||||
// Form ref interface
|
||||
export interface DynamicFormRef {
|
||||
submit: () => void;
|
||||
getValues: () => any;
|
||||
reset: (values?: any) => void;
|
||||
}
|
||||
|
||||
// Generate Zod validation schema based on field configurations
|
||||
const generateSchema = (fields: FormFieldConfig[]): ZodSchema<any> => {
|
||||
const schema: Record<string, ZodSchema> = {};
|
||||
const nestedSchemas: Record<string, Record<string, ZodSchema>> = {};
|
||||
|
||||
fields.forEach((field) => {
|
||||
let fieldSchema: ZodSchema;
|
||||
|
||||
// Create base validation schema based on field type
|
||||
switch (field.type) {
|
||||
case FormFieldType.Email:
|
||||
fieldSchema = z.string().email('Please enter a valid email address');
|
||||
break;
|
||||
case FormFieldType.Number:
|
||||
fieldSchema = z.coerce.number();
|
||||
if (field.validation?.min !== undefined) {
|
||||
fieldSchema = (fieldSchema as z.ZodNumber).min(
|
||||
field.validation.min,
|
||||
field.validation.message ||
|
||||
`Value cannot be less than ${field.validation.min}`,
|
||||
);
|
||||
}
|
||||
if (field.validation?.max !== undefined) {
|
||||
fieldSchema = (fieldSchema as z.ZodNumber).max(
|
||||
field.validation.max,
|
||||
field.validation.message ||
|
||||
`Value cannot be greater than ${field.validation.max}`,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case FormFieldType.Checkbox:
|
||||
fieldSchema = z.boolean();
|
||||
break;
|
||||
case FormFieldType.Tag:
|
||||
fieldSchema = z.array(z.string());
|
||||
break;
|
||||
default:
|
||||
fieldSchema = z.string();
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle required fields
|
||||
if (field.required) {
|
||||
if (field.type === FormFieldType.Checkbox) {
|
||||
fieldSchema = (fieldSchema as z.ZodBoolean).refine(
|
||||
(val) => val === true,
|
||||
{
|
||||
message: `${field.label} is required`,
|
||||
},
|
||||
);
|
||||
} else if (field.type === FormFieldType.Tag) {
|
||||
fieldSchema = (fieldSchema as z.ZodArray<z.ZodString>).min(1, {
|
||||
message: `${field.label} is required`,
|
||||
});
|
||||
} else {
|
||||
fieldSchema = (fieldSchema as z.ZodString).min(1, {
|
||||
message: `${field.label} is required`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!field.required) {
|
||||
fieldSchema = fieldSchema.optional();
|
||||
}
|
||||
|
||||
// Handle other validation rules
|
||||
if (
|
||||
field.type !== FormFieldType.Number &&
|
||||
field.type !== FormFieldType.Checkbox &&
|
||||
field.type !== FormFieldType.Tag &&
|
||||
field.required
|
||||
) {
|
||||
fieldSchema = fieldSchema as z.ZodString;
|
||||
|
||||
if (field.validation?.minLength !== undefined) {
|
||||
fieldSchema = (fieldSchema as z.ZodString).min(
|
||||
field.validation.minLength,
|
||||
field.validation.message ||
|
||||
`Enter at least ${field.validation.minLength} characters`,
|
||||
);
|
||||
}
|
||||
|
||||
if (field.validation?.maxLength !== undefined) {
|
||||
fieldSchema = (fieldSchema as z.ZodString).max(
|
||||
field.validation.maxLength,
|
||||
field.validation.message ||
|
||||
`Enter up to ${field.validation.maxLength} characters`,
|
||||
);
|
||||
}
|
||||
|
||||
if (field.validation?.pattern) {
|
||||
fieldSchema = (fieldSchema as z.ZodString).regex(
|
||||
field.validation.pattern,
|
||||
field.validation.message || 'Invalid input format',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (field.name.includes('.')) {
|
||||
const keys = field.name.split('.');
|
||||
const firstKey = keys[0];
|
||||
|
||||
if (!nestedSchemas[firstKey]) {
|
||||
nestedSchemas[firstKey] = {};
|
||||
}
|
||||
|
||||
let currentSchema = nestedSchemas[firstKey];
|
||||
for (let i = 1; i < keys.length - 1; i++) {
|
||||
const key = keys[i];
|
||||
if (!currentSchema[key]) {
|
||||
currentSchema[key] = {};
|
||||
}
|
||||
currentSchema = currentSchema[key];
|
||||
}
|
||||
|
||||
const lastKey = keys[keys.length - 1];
|
||||
currentSchema[lastKey] = fieldSchema;
|
||||
} else {
|
||||
schema[field.name] = fieldSchema;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(nestedSchemas).forEach((key) => {
|
||||
const buildNestedSchema = (obj: Record<string, any>): ZodSchema => {
|
||||
const nestedSchema: Record<string, ZodSchema> = {};
|
||||
Object.keys(obj).forEach((subKey) => {
|
||||
if (
|
||||
typeof obj[subKey] === 'object' &&
|
||||
!(obj[subKey] instanceof z.ZodType)
|
||||
) {
|
||||
nestedSchema[subKey] = buildNestedSchema(obj[subKey]);
|
||||
} else {
|
||||
nestedSchema[subKey] = obj[subKey];
|
||||
}
|
||||
});
|
||||
return z.object(nestedSchema);
|
||||
};
|
||||
|
||||
schema[key] = buildNestedSchema(nestedSchemas[key]);
|
||||
});
|
||||
return z.object(schema);
|
||||
};
|
||||
|
||||
// Generate default values based on field configurations
|
||||
const generateDefaultValues = <T extends FieldValues>(
|
||||
fields: FormFieldConfig[],
|
||||
): DefaultValues<T> => {
|
||||
const defaultValues: Record<string, any> = {};
|
||||
|
||||
fields.forEach((field) => {
|
||||
if (field.name.includes('.')) {
|
||||
const keys = field.name.split('.');
|
||||
let current = defaultValues;
|
||||
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const key = keys[i];
|
||||
if (!current[key]) {
|
||||
current[key] = {};
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
|
||||
const lastKey = keys[keys.length - 1];
|
||||
if (field.defaultValue !== undefined) {
|
||||
current[lastKey] = field.defaultValue;
|
||||
} else if (field.type === FormFieldType.Checkbox) {
|
||||
current[lastKey] = false;
|
||||
} else if (field.type === FormFieldType.Tag) {
|
||||
current[lastKey] = [];
|
||||
} else {
|
||||
current[lastKey] = '';
|
||||
}
|
||||
} else {
|
||||
if (field.defaultValue !== undefined) {
|
||||
defaultValues[field.name] = field.defaultValue;
|
||||
} else if (field.type === FormFieldType.Checkbox) {
|
||||
defaultValues[field.name] = false;
|
||||
} else if (field.type === FormFieldType.Tag) {
|
||||
defaultValues[field.name] = [];
|
||||
} else {
|
||||
defaultValues[field.name] = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return defaultValues as DefaultValues<T>;
|
||||
};
|
||||
|
||||
// Dynamic form component
|
||||
const DynamicForm = {
|
||||
Root: forwardRef(
|
||||
<T extends FieldValues>(
|
||||
{
|
||||
fields,
|
||||
onSubmit,
|
||||
className = '',
|
||||
children,
|
||||
defaultValues: formDefaultValues = {} as DefaultValues<T>,
|
||||
}: DynamicFormProps<T>,
|
||||
ref: React.Ref<any>,
|
||||
) => {
|
||||
// Generate validation schema and default values
|
||||
const schema = useMemo(() => generateSchema(fields), [fields]);
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
const value = {
|
||||
...generateDefaultValues(fields),
|
||||
...formDefaultValues,
|
||||
};
|
||||
return value;
|
||||
}, [fields, formDefaultValues]);
|
||||
|
||||
// Initialize form
|
||||
const form = useForm<T>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
// 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,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (formDefaultValues && Object.keys(formDefaultValues).length > 0) {
|
||||
form.reset({
|
||||
...generateDefaultValues(fields),
|
||||
...formDefaultValues,
|
||||
});
|
||||
}
|
||||
}, [form, formDefaultValues, fields]);
|
||||
|
||||
// Submit handler
|
||||
// const handleSubmit = form.handleSubmit(onSubmit);
|
||||
|
||||
// Render form fields
|
||||
const renderField = (field: FormFieldConfig) => {
|
||||
if (field.render) {
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (e: any) => {
|
||||
fieldProps.onChange(e);
|
||||
field.onChange?.(e.target?.value ?? e);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return field.render?.(finalFieldProps);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
switch (field.type) {
|
||||
case FormFieldType.Textarea:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (e: any) => {
|
||||
fieldProps.onChange(e);
|
||||
field.onChange?.(e.target.value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
<Textarea
|
||||
{...finalFieldProps}
|
||||
placeholder={field.placeholder}
|
||||
className="resize-none"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
case FormFieldType.Select:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (value: string) => {
|
||||
console.log('select value', value);
|
||||
if (fieldProps.onChange) {
|
||||
fieldProps.onChange(value);
|
||||
}
|
||||
field.onChange?.(value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
<SelectWithSearch
|
||||
triggerClassName="!shrink"
|
||||
{...finalFieldProps}
|
||||
options={field.options}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
case FormFieldType.Checkbox:
|
||||
return (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={field.name as any}
|
||||
render={({ field: formField }) => (
|
||||
<FormItem
|
||||
className={cn('flex items-center w-full', {
|
||||
'flex-row items-start space-x-3 space-y-0':
|
||||
!field.horizontal,
|
||||
})}
|
||||
>
|
||||
{field.label && !field.horizontal && (
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel
|
||||
className="font-normal"
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{field.label}{' '}
|
||||
{field.required && (
|
||||
<span className="text-destructive">*</span>
|
||||
)}
|
||||
</FormLabel>
|
||||
</div>
|
||||
)}
|
||||
{field.label && field.horizontal && (
|
||||
<div className="space-y-1 leading-none w-1/4">
|
||||
<FormLabel
|
||||
className="font-normal"
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{field.label}{' '}
|
||||
{field.required && (
|
||||
<span className="text-destructive">*</span>
|
||||
)}
|
||||
</FormLabel>
|
||||
</div>
|
||||
)}
|
||||
<FormControl>
|
||||
<div className={cn({ 'w-full': field.horizontal })}>
|
||||
<Checkbox
|
||||
checked={formField.value}
|
||||
onCheckedChange={(checked) => {
|
||||
formField.onChange(checked);
|
||||
field.onChange?.(checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
case FormFieldType.Tag:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (value: string[]) => {
|
||||
fieldProps.onChange(value);
|
||||
field.onChange?.(value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
// <TagInput {...fieldProps} placeholder={field.placeholder} />
|
||||
<div className="w-full">
|
||||
<EditTag {...finalFieldProps}></EditTag>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={field.name}
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
horizontal={field.horizontal}
|
||||
tooltip={field.tooltip}
|
||||
>
|
||||
{(fieldProps) => {
|
||||
const finalFieldProps = field.onChange
|
||||
? {
|
||||
...fieldProps,
|
||||
onChange: (e: any) => {
|
||||
fieldProps.onChange(e);
|
||||
field.onChange?.(e.target.value);
|
||||
},
|
||||
}
|
||||
: fieldProps;
|
||||
return (
|
||||
<Input
|
||||
{...finalFieldProps}
|
||||
type={field.type}
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className={`space-y-6 ${className}`}
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
form.handleSubmit(onSubmit)(e);
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{fields.map((field) => (
|
||||
<div key={field.name} className={cn({ hidden: field.hidden })}>
|
||||
{renderField(field)}
|
||||
</div>
|
||||
))}
|
||||
{children}
|
||||
</>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
},
|
||||
) as <T extends FieldValues>(
|
||||
props: DynamicFormProps<T> & { ref?: React.Ref<DynamicFormRef> },
|
||||
) => React.ReactElement,
|
||||
|
||||
SavingButton: ({
|
||||
submitLoading,
|
||||
buttonText,
|
||||
submitFunc,
|
||||
}: {
|
||||
submitLoading: boolean;
|
||||
buttonText?: string;
|
||||
submitFunc?: (values: FieldValues) => void;
|
||||
}) => {
|
||||
const form = useFormContext();
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={submitLoading}
|
||||
onClick={() => {
|
||||
console.log('form submit');
|
||||
(async () => {
|
||||
console.log('form submit2');
|
||||
try {
|
||||
let beValid = await form.formControl.trigger();
|
||||
console.log('form valid', beValid, form, form.formControl);
|
||||
if (beValid) {
|
||||
form.handleSubmit(async (values) => {
|
||||
console.log('form values', values);
|
||||
submitFunc?.(values);
|
||||
})();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
console.log('form submit3');
|
||||
}
|
||||
})();
|
||||
}}
|
||||
className={cn(
|
||||
'px-2 py-1 bg-primary text-primary-foreground rounded-md hover:bg-primary/90',
|
||||
)}
|
||||
>
|
||||
{submitLoading && (
|
||||
<Loader className="inline-block mr-2 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
{buttonText ?? t('modal.okText')}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
|
||||
CancelButton: ({
|
||||
handleCancel,
|
||||
cancelText,
|
||||
}: {
|
||||
handleCancel: () => void;
|
||||
cancelText?: string;
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleCancel()}
|
||||
className="px-2 py-1 border border-input rounded-md hover:bg-muted"
|
||||
>
|
||||
{cancelText ?? t('modal.cancelText')}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export { DynamicForm };
|
||||
@ -1,7 +1,8 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Search } from 'lucide-react';
|
||||
import { Eye, EyeOff, Search } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
@ -13,6 +14,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
const isControlled = value !== undefined;
|
||||
const { defaultValue, ...restProps } = props;
|
||||
const inputValue = isControlled ? value : defaultValue;
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
if (type === 'number') {
|
||||
const numValue = e.target.value === '' ? '' : Number(e.target.value);
|
||||
@ -28,17 +30,32 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
}
|
||||
};
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
'flex h-8 w-full rounded-md border border-input bg-bg-input px-2 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-text-disabled focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 text-text-primary',
|
||||
className,
|
||||
<div className="relative w-full">
|
||||
<input
|
||||
type={type === 'password' && showPassword ? 'text' : type}
|
||||
className={cn(
|
||||
'flex h-8 w-full rounded-md border border-input bg-bg-input px-2 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-text-disabled focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 text-text-primary',
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
value={inputValue ?? ''}
|
||||
onChange={handleChange}
|
||||
{...restProps}
|
||||
/>
|
||||
{type === 'password' && (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff className="h-4 w-4 text-text-secondary" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4 text-text-secondary" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
ref={ref}
|
||||
value={inputValue ?? ''}
|
||||
onChange={handleChange}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@ -683,6 +683,11 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
||||
tocEnhanceTip: ` During the parsing of the document, table of contents information was generated (see the 'Enable Table of Contents Extraction' option in the General method). This allows the large model to return table of contents items relevant to the user's query, thereby using these items to retrieve related chunks and apply weighting to these chunks during the sorting process. This approach is derived from mimicking the behavioral logic of how humans search for knowledge in books.`,
|
||||
},
|
||||
setting: {
|
||||
addDataSourceModalTital: 'Create your {{name}} connector',
|
||||
deleteSourceModalTitle: 'Delete data source',
|
||||
deleteSourceModalContent: `
|
||||
<p>Are you sure you want to delete this data source link?</p>`,
|
||||
deleteSourceModalConfirmText: 'Comfirm',
|
||||
errorMsg: 'Error message',
|
||||
newDocs: 'New Docs',
|
||||
timeStarted: 'Time started',
|
||||
|
||||
@ -674,6 +674,11 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
tocEnhanceTip: `解析文档时生成了目录信息(见General方法的‘启用目录抽取’),让大模型返回和用户问题相关的目录项,从而利用目录项拿到相关chunk,对这些chunk在排序中进行加权。这种方法来源于模仿人类查询书本中知识的行为逻辑`,
|
||||
},
|
||||
setting: {
|
||||
addDataSourceModalTital: '创建你的 {{name}} 链接',
|
||||
deleteSourceModalTitle: '删除数据源链接',
|
||||
deleteSourceModalContent: `
|
||||
<p>您确定要删除此数据源链接吗?</p>`,
|
||||
deleteSourceModalConfirmText: '确认',
|
||||
errorMsg: '错误信息',
|
||||
newDocs: '新文档',
|
||||
timeStarted: '开始时间',
|
||||
|
||||
@ -49,7 +49,7 @@ export interface IFileLogItem {
|
||||
process_duration: number;
|
||||
progress: number;
|
||||
progress_msg: string;
|
||||
source_from: string;
|
||||
source_type: string;
|
||||
status: string;
|
||||
task_type: string;
|
||||
tenant_id: string;
|
||||
|
||||
@ -11,11 +11,18 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { RunningStatusMap } from '@/constants/knowledge';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { PipelineResultSearchParams } from '@/pages/dataflow-result/constant';
|
||||
import { NavigateToDataflowResultProps } from '@/pages/dataflow-result/interface';
|
||||
import { DataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
||||
import { formatDate, formatSecondsToHumanReadable } from '@/utils/date';
|
||||
import {
|
||||
ColumnDef,
|
||||
@ -77,25 +84,34 @@ export const getFileLogsTableColumns = (
|
||||
{
|
||||
accessorKey: 'fileName',
|
||||
header: t('fileName'),
|
||||
meta: { cellClassName: 'max-w-[20vw]' },
|
||||
cell: ({ row }) => (
|
||||
<div
|
||||
className="flex items-center gap-2 text-text-primary"
|
||||
// onClick={navigateToDataflowResult(
|
||||
// row.original.id,
|
||||
// row.original.kb_id,
|
||||
// )}
|
||||
>
|
||||
<FileIcon name={row.original.document_name}></FileIcon>
|
||||
{row.original.document_name}
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex gap-2 cursor-pointer">
|
||||
<FileIcon name={row.original.document_name}></FileIcon>
|
||||
<span className={cn('truncate')}>
|
||||
{row.original.document_name}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{row.original.document_name}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'source_from',
|
||||
header: t('source'),
|
||||
meta: { cellClassName: 'max-w-[10vw]' },
|
||||
cell: ({ row }) => (
|
||||
<div className="text-text-primary">
|
||||
{row.original.source_from || t('localUpload')}
|
||||
{row.original.source_type
|
||||
? DataSourceInfo[
|
||||
row.original.source_type as keyof typeof DataSourceInfo
|
||||
].icon
|
||||
: t('localUpload')}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
|
||||
@ -66,7 +66,7 @@ export const AddedSourceCard = (props: IAddedSourceCardProps) => {
|
||||
<div
|
||||
key={item.id}
|
||||
className={cn(
|
||||
'flex flex-row items-center justify-between rounded-md bg-bg-input px-2 py-1 cursor-pointer',
|
||||
'flex flex-row items-center justify-between rounded-md bg-bg-card px-2 py-1 cursor-pointer',
|
||||
// { hidden: item.name.indexOf(filterString) <= -1 },
|
||||
)}
|
||||
onClick={() => {
|
||||
|
||||
@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { IConnector } from '@/interfaces/database/knowledge';
|
||||
import { delSourceModal } from '@/pages/user-setting/data-source/component/delete-source-modal';
|
||||
import { DataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
||||
import { IDataSourceBase } from '@/pages/user-setting/data-source/interface';
|
||||
import { Link, Settings, Unlink } from 'lucide-react';
|
||||
@ -26,7 +27,7 @@ interface DataSourceItemProps extends IDataSourceNodeProps {
|
||||
|
||||
const DataSourceItem = (props: DataSourceItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { id, name, icon, openLinkModalFunc, unbindFunc } = props;
|
||||
const { id, name, icon, unbindFunc } = props;
|
||||
|
||||
const { navigateToDataSourceDetail } = useNavigatePage();
|
||||
const toDetail = (id: string) => {
|
||||
@ -71,7 +72,7 @@ const DataSourceItem = (props: DataSourceItemProps) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-1 px-2 rounded-md border ">
|
||||
<div className="flex items-center gap-1">
|
||||
{icon}
|
||||
<div className="w-6 h-6 flex-shrink-0">{icon}</div>
|
||||
<div>{name}</div>
|
||||
</div>
|
||||
<div className="flex gap-1 items-center">
|
||||
@ -94,7 +95,12 @@ const DataSourceItem = (props: DataSourceItemProps) => {
|
||||
variant={'transparent'}
|
||||
className="border-none"
|
||||
onClick={() => {
|
||||
openUnlinkModal();
|
||||
// openUnlinkModal();
|
||||
delSourceModal({
|
||||
data: props,
|
||||
type: 'unlink',
|
||||
onOk: (data) => unbindFunc?.(data as DataSourceItemProps),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Unlink />
|
||||
|
||||
@ -61,6 +61,7 @@ export const useFetchKnowledgeConfigurationOnMount = (
|
||||
'parser_id',
|
||||
'language',
|
||||
'parser_config',
|
||||
'connectors',
|
||||
'pagerank',
|
||||
'avatar',
|
||||
]),
|
||||
|
||||
@ -248,7 +248,7 @@ export default function DatasetSettings() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
{/* <Divider /> */}
|
||||
{parseType === 1 && <ChunkMethodForm />}
|
||||
|
||||
{/* <LinkDataPipeline
|
||||
|
||||
@ -11,6 +11,7 @@ import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useSetDocumentStatus } from '@/hooks/use-document-request';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { ColumnDef } from '@tanstack/table-core';
|
||||
import { ArrowUpDown } from 'lucide-react';
|
||||
@ -120,6 +121,19 @@ export function useDatasetTableColumns({
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'source_from',
|
||||
header: t('source'),
|
||||
cell: ({ row }) => (
|
||||
<div className="text-text-primary">
|
||||
{row.original.source_type
|
||||
? DataSourceInfo[
|
||||
row.original.source_type as keyof typeof DataSourceInfo
|
||||
]?.icon || t('localUpload')
|
||||
: t('localUpload')}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: t('enabled'),
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { DynamicForm, FormFieldConfig } from '@/components/dynamic-form';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DynamicForm, FormFieldConfig } from './component/dynamic-form';
|
||||
import {
|
||||
DataSourceFormBaseFields,
|
||||
DataSourceFormDefaultValues,
|
||||
@ -39,7 +39,12 @@ const AddDataSourceModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('setting.add')}
|
||||
title={
|
||||
<div className="flex flex-col">
|
||||
{sourceData?.icon}
|
||||
{t('setting.addDataSourceModalTital', { name: sourceData?.name })}
|
||||
</div>
|
||||
}
|
||||
open={visible || false}
|
||||
onOpenChange={(open) => !open && hideModal?.()}
|
||||
// onOk={() => handleOk()}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { Settings, Trash2 } from 'lucide-react';
|
||||
import { useDeleteDataSource } from '../hooks';
|
||||
import { IDataSorceInfo, IDataSourceBase } from '../interface';
|
||||
import { delSourceModal } from './delete-source-modal';
|
||||
|
||||
export type IAddedSourceCardProps = IDataSorceInfo & {
|
||||
list: IDataSourceBase[];
|
||||
@ -19,7 +19,7 @@ export const AddedSourceCard = (props: IAddedSourceCardProps) => {
|
||||
<Card className="bg-transparent border border-border-button px-5 pt-[10px] pb-5 rounded-md">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-0 pb-3">
|
||||
{/* <Users className="mr-2 h-5 w-5 text-[#1677ff]" /> */}
|
||||
<CardTitle className="text-base flex gap-1 font-normal">
|
||||
<CardTitle className="text-base items-center flex gap-1 font-normal">
|
||||
{icon}
|
||||
{name}
|
||||
</CardTitle>
|
||||
@ -28,7 +28,7 @@ export const AddedSourceCard = (props: IAddedSourceCardProps) => {
|
||||
{list.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="flex flex-row items-center justify-between rounded-md bg-bg-input px-[10px] py-4"
|
||||
className="flex flex-row items-center justify-between rounded-md bg-bg-card px-[10px] py-4"
|
||||
>
|
||||
<div className="text-sm text-text-secondary ">{item.name}</div>
|
||||
<div className="text-sm text-text-secondary flex gap-2">
|
||||
@ -39,9 +39,20 @@ export const AddedSourceCard = (props: IAddedSourceCardProps) => {
|
||||
toDetail(item.id);
|
||||
}}
|
||||
/>
|
||||
<ConfirmDeleteDialog onOk={() => handleDelete(item)}>
|
||||
<Trash2 className="cursor-pointer" size={14} />
|
||||
</ConfirmDeleteDialog>
|
||||
{/* <ConfirmDeleteDialog onOk={() => handleDelete(item)}> */}
|
||||
<Trash2
|
||||
className="cursor-pointer"
|
||||
size={14}
|
||||
onClick={() =>
|
||||
delSourceModal({
|
||||
data: item,
|
||||
onOk: () => {
|
||||
handleDelete(item);
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
{/* </ConfirmDeleteDialog> */}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Modal, ModalType } from '@/components/ui/modal/modal';
|
||||
import { t } from 'i18next';
|
||||
import { DataSourceInfo } from '../contant';
|
||||
import { IDataSourceBase } from '../interface';
|
||||
|
||||
export type IDelSourceModalProps<T> = Partial<ModalType> & {
|
||||
data?: T;
|
||||
type?: 'delete' | 'unlink';
|
||||
onOk?: (data?: T) => void;
|
||||
};
|
||||
|
||||
export const delSourceModal = <T extends IDataSourceBase>(
|
||||
props: IDelSourceModalProps<T>,
|
||||
) => {
|
||||
const { data, onOk, type = 'delete', ...otherProps } = props;
|
||||
console.log('data', data);
|
||||
const config = {
|
||||
title:
|
||||
type === 'delete'
|
||||
? t('setting.deleteSourceModalTitle')
|
||||
: t('dataflowParser.unlinkSourceModalTitle'),
|
||||
content: (
|
||||
<div className="px-2 py-6">
|
||||
<div className="flex items-center gap-1 p-2 border border-border-button rounded-md mb-3">
|
||||
<div className="w-6 h-6 flex-shrink-0">
|
||||
{data?.source ? DataSourceInfo[data?.source].icon : ''}
|
||||
</div>
|
||||
<div>{data?.name}</div>
|
||||
</div>
|
||||
{type === 'delete' ? (
|
||||
<div
|
||||
className="text-sm text-text-secondary"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('setting.deleteSourceModalContent'),
|
||||
}}
|
||||
></div>
|
||||
) : (
|
||||
<div
|
||||
className="text-sm text-text-secondary"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('dataflowParser.unlinkSourceModalContent'),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
confirmText:
|
||||
type === 'delete'
|
||||
? t('setting.deleteSourceModalConfirmText')
|
||||
: t('dataflowParser.unlinkSourceModalConfirmText'),
|
||||
};
|
||||
Modal.show({
|
||||
visible: true,
|
||||
className: '!w-[560px]',
|
||||
...otherProps,
|
||||
title: config.title,
|
||||
children: config.content,
|
||||
onVisibleChange: () => {
|
||||
Modal.hide();
|
||||
},
|
||||
footer: (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant={'outline'} onClick={() => Modal.hide()}>
|
||||
{t('dataflowParser.changeStepModalCancelText')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
className="!bg-state-error text-text-base"
|
||||
onClick={() => {
|
||||
onOk?.(data);
|
||||
Modal.hide();
|
||||
}}
|
||||
>
|
||||
{config.confirmText}
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
};
|
||||
@ -1,12 +1,12 @@
|
||||
import { FormFieldType } from '@/components/dynamic-form';
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { t } from 'i18next';
|
||||
import { FormFieldType } from './component/dynamic-form';
|
||||
|
||||
export enum DataSourceKey {
|
||||
CONFLUENCE = 'confluence',
|
||||
S3 = 's3',
|
||||
NOTION = 'notion',
|
||||
DISCORD = 'discord',
|
||||
// CONFLUENNCE = 'confluence',
|
||||
// GMAIL = 'gmail',
|
||||
// GOOGLE_DRIVER = 'google_driver',
|
||||
// JIRA = 'jira',
|
||||
@ -19,17 +19,22 @@ export const DataSourceInfo = {
|
||||
[DataSourceKey.S3]: {
|
||||
name: 'S3',
|
||||
description: t(`setting.${DataSourceKey.S3}Description`),
|
||||
icon: <SvgIcon name={'data-source/s3'} width={28} />,
|
||||
icon: <SvgIcon name={'data-source/s3'} width={38} />,
|
||||
},
|
||||
[DataSourceKey.NOTION]: {
|
||||
name: 'Notion',
|
||||
description: t(`setting.${DataSourceKey.NOTION}Description`),
|
||||
icon: <SvgIcon name={'data-source/notion'} width={28} />,
|
||||
icon: <SvgIcon name={'data-source/notion'} width={38} />,
|
||||
},
|
||||
[DataSourceKey.DISCORD]: {
|
||||
name: 'Discord',
|
||||
description: t(`setting.${DataSourceKey.DISCORD}Description`),
|
||||
icon: <SvgIcon name={'data-source/discord'} width={28} />,
|
||||
icon: <SvgIcon name={'data-source/discord'} width={38} />,
|
||||
},
|
||||
[DataSourceKey.CONFLUENCE]: {
|
||||
name: 'Confluence',
|
||||
description: t(`setting.${DataSourceKey.CONFLUENCE}Description`),
|
||||
icon: <SvgIcon name={'data-source/confluence'} width={38} />,
|
||||
},
|
||||
};
|
||||
|
||||
@ -71,7 +76,7 @@ export const DataSourceFormFields = {
|
||||
{
|
||||
label: 'AWS Secret Access Key',
|
||||
name: 'config.credentials.aws_secret_access_key',
|
||||
type: FormFieldType.Text,
|
||||
type: FormFieldType.Password,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
@ -103,7 +108,7 @@ export const DataSourceFormFields = {
|
||||
{
|
||||
label: 'Notion Integration Token',
|
||||
name: 'config.credentials.notion_integration_token',
|
||||
type: FormFieldType.Text,
|
||||
type: FormFieldType.Password,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
@ -117,7 +122,7 @@ export const DataSourceFormFields = {
|
||||
{
|
||||
label: 'Discord Bot Token',
|
||||
name: 'config.credentials.discord_bot_token',
|
||||
type: FormFieldType.Text,
|
||||
type: FormFieldType.Password,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
@ -133,6 +138,38 @@ export const DataSourceFormFields = {
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
|
||||
[DataSourceKey.CONFLUENCE]: [
|
||||
{
|
||||
label: 'Confluence Username',
|
||||
name: 'config.credentials.confluence_username',
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
tooltip: 'A descriptive name for the connector.',
|
||||
},
|
||||
{
|
||||
label: 'Confluence Access Token',
|
||||
name: 'config.credentials.confluence_access_token',
|
||||
type: FormFieldType.Password,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: 'Wiki Base URL',
|
||||
name: 'config.wiki_base',
|
||||
type: FormFieldType.Text,
|
||||
required: false,
|
||||
tooltip:
|
||||
'The base URL of your Confluence instance (e.g., https://your-domain.atlassian.net/wiki)',
|
||||
},
|
||||
{
|
||||
label: 'Is Cloud',
|
||||
name: 'config.is_cloud',
|
||||
type: FormFieldType.Checkbox,
|
||||
required: false,
|
||||
tooltip:
|
||||
'Check if this is a Confluence Cloud instance, uncheck for Confluence Server/Data Center',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const DataSourceFormDefaultValues = {
|
||||
@ -170,4 +207,16 @@ export const DataSourceFormDefaultValues = {
|
||||
},
|
||||
},
|
||||
},
|
||||
[DataSourceKey.CONFLUENCE]: {
|
||||
name: '',
|
||||
source: DataSourceKey.CONFLUENCE,
|
||||
config: {
|
||||
wiki_base: '',
|
||||
is_cloud: true,
|
||||
credentials: {
|
||||
confluence_username: '',
|
||||
confluence_access_token: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import BackButton from '@/components/back-button';
|
||||
import {
|
||||
DynamicForm,
|
||||
DynamicFormRef,
|
||||
FormFieldConfig,
|
||||
FormFieldType,
|
||||
} from '@/components/dynamic-form';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
@ -8,12 +14,6 @@ import { debounce } from 'lodash';
|
||||
import { CirclePause, Repeat } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import {
|
||||
DynamicForm,
|
||||
DynamicFormRef,
|
||||
FormFieldConfig,
|
||||
FormFieldType,
|
||||
} from '../component/dynamic-form';
|
||||
import {
|
||||
DataSourceFormBaseFields,
|
||||
DataSourceFormDefaultValues,
|
||||
@ -112,7 +112,7 @@ const SourceDetailPage = () => {
|
||||
<div className="flex items-center gap-1 w-full relative">
|
||||
<Input {...fieldProps} type={FormFieldType.Number} />
|
||||
<span className="absolute right-0 -translate-x-3 text-text-secondary italic ">
|
||||
minutes
|
||||
seconds
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
|
||||
@ -145,7 +145,7 @@ export const DataSourceLogsTable = () => {
|
||||
const handleToDataSetDetail = useCallback(
|
||||
(id: string) => {
|
||||
console.log('handleToDataSetDetail', id);
|
||||
navigate(`${Routes.DatasetBase}${Routes.DataSetSetting}/${id}`);
|
||||
navigate(`${Routes.DatasetBase}${Routes.DatasetBase}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { CardTitle } from '@/components/ui/card';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Spotlight from '@/components/spotlight';
|
||||
@ -11,21 +11,17 @@ import { DataSourceInfo, DataSourceKey } from './contant';
|
||||
import { useAddDataSource, useListDataSource } from './hooks';
|
||||
import { IDataSorceInfo } from './interface';
|
||||
const dataSourceTemplates = [
|
||||
{
|
||||
id: DataSourceKey.CONFLUENCE,
|
||||
name: DataSourceInfo[DataSourceKey.CONFLUENCE].name,
|
||||
description: DataSourceInfo[DataSourceKey.CONFLUENCE].description,
|
||||
icon: DataSourceInfo[DataSourceKey.CONFLUENCE].icon,
|
||||
},
|
||||
{
|
||||
id: DataSourceKey.S3,
|
||||
name: DataSourceInfo[DataSourceKey.S3].name,
|
||||
description: DataSourceInfo[DataSourceKey.S3].description,
|
||||
icon: DataSourceInfo[DataSourceKey.S3].icon,
|
||||
list: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'S3 Bucket 1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'S3 Bucket 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: DataSourceKey.DISCORD,
|
||||
@ -94,16 +90,16 @@ const DataSource = () => {
|
||||
<div className="w-full flex flex-col gap-4 relative ">
|
||||
<Spotlight />
|
||||
|
||||
<Card className="bg-transparent border-none px-0">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 px-4 pt-4 pb-0">
|
||||
<CardTitle className="text-2xl font-medium">
|
||||
{t('setting.dataSources')}
|
||||
<div className="text-sm text-text-secondary">
|
||||
{t('setting.datasourceDescription')}
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
{/* <Card className="bg-transparent border-none px-0"> */}
|
||||
<section className="flex flex-row items-center justify-between space-y-0 px-4 pt-4 pb-0">
|
||||
<div className="text-2xl font-medium">
|
||||
{t('setting.dataSources')}
|
||||
<div className="text-sm text-text-secondary">
|
||||
{t('setting.datasourceDescription')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/* </Card> */}
|
||||
<Separator className="border-border-button bg-border-button " />
|
||||
<div className=" flex flex-col gap-4 p-4 max-h-[calc(100vh-120px)] overflow-y-auto overflow-x-hidden scrollbar-auto">
|
||||
<div className="flex flex-col gap-3">
|
||||
@ -111,8 +107,8 @@ const DataSource = () => {
|
||||
<AddedSourceCard key={index} {...item} />
|
||||
))}
|
||||
</div>
|
||||
<Card className="bg-transparent border-none mt-8">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-0 pb-4">
|
||||
<section className="bg-transparent border-none mt-8">
|
||||
<header className="flex flex-row items-center justify-between space-y-0 p-0 pb-4">
|
||||
{/* <Users className="mr-2 h-5 w-5 text-[#1677ff]" /> */}
|
||||
<CardTitle className="text-2xl font-semibold">
|
||||
{t('setting.availableSources')}
|
||||
@ -120,16 +116,16 @@ const DataSource = () => {
|
||||
{t('setting.availableSourcesDescription')}
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
</header>
|
||||
<main className="p-0">
|
||||
{/* <TenantTable searchTerm={searchTerm}></TenantTable> */}
|
||||
<div className="grid sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-4 3xl:grid-cols-4 gap-4">
|
||||
{dataSourceTemplates.map((item, index) => (
|
||||
<AbailableSourceCard {...item} key={index} />
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{addingModalVisible && (
|
||||
|
||||
Reference in New Issue
Block a user