mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-01 16:15:07 +08:00
Feat: Improve metadata logic (#12730)
### What problem does this PR solve? Feat: Improve metadata logic ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -834,6 +834,7 @@ const DynamicForm = {
|
|||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
ref,
|
ref,
|
||||||
() => ({
|
() => ({
|
||||||
|
form: form,
|
||||||
submit: () => {
|
submit: () => {
|
||||||
form.handleSubmit((values) => {
|
form.handleSubmit((values) => {
|
||||||
const filteredValues = filterActiveValues(values);
|
const filteredValues = filterActiveValues(values);
|
||||||
@ -938,7 +939,6 @@ const DynamicForm = {
|
|||||||
) as <T extends FieldValues>(
|
) as <T extends FieldValues>(
|
||||||
props: DynamicFormProps<T> & { ref?: React.Ref<DynamicFormRef> },
|
props: DynamicFormProps<T> & { ref?: React.Ref<DynamicFormRef> },
|
||||||
) => React.ReactElement,
|
) => React.ReactElement,
|
||||||
|
|
||||||
SavingButton: ({
|
SavingButton: ({
|
||||||
submitLoading,
|
submitLoading,
|
||||||
buttonText,
|
buttonText,
|
||||||
@ -1015,4 +1015,6 @@ const DynamicForm = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DynamicForm.Root.displayName = 'DynamicFormRoot';
|
||||||
|
|
||||||
export { DynamicForm };
|
export { DynamicForm };
|
||||||
|
|||||||
108
web/src/components/ui/input-date.tsx
Normal file
108
web/src/components/ui/input-date.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { Calendar } from '@/components/originui/calendar';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Locale } from 'date-fns';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { Calendar as CalendarIcon } from 'lucide-react';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
interface DateInputProps extends Omit<
|
||||||
|
React.InputHTMLAttributes<HTMLInputElement>,
|
||||||
|
'value' | 'onChange'
|
||||||
|
> {
|
||||||
|
value?: Date;
|
||||||
|
onChange?: (date: Date | undefined) => void;
|
||||||
|
showTimeSelect?: boolean;
|
||||||
|
dateFormat?: string;
|
||||||
|
timeFormat?: string;
|
||||||
|
showTimeSelectOnly?: boolean;
|
||||||
|
showTimeInput?: boolean;
|
||||||
|
timeInputLabel?: string;
|
||||||
|
locale?: Locale; // Support for internationalization
|
||||||
|
}
|
||||||
|
|
||||||
|
const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
dateFormat = 'DD/MM/YYYY',
|
||||||
|
timeFormat = 'HH:mm:ss',
|
||||||
|
showTimeSelect = false,
|
||||||
|
showTimeSelectOnly = false,
|
||||||
|
showTimeInput = false,
|
||||||
|
timeInputLabel = '',
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
|
const handleDateSelect = (date: Date | undefined) => {
|
||||||
|
onChange?.(date);
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine display format based on the type of date picker
|
||||||
|
let displayFormat = dateFormat;
|
||||||
|
if (showTimeSelect) {
|
||||||
|
displayFormat = `${dateFormat} ${timeFormat}`;
|
||||||
|
} else if (showTimeSelectOnly) {
|
||||||
|
displayFormat = timeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the date according to the specified format
|
||||||
|
const formattedValue = React.useMemo(() => {
|
||||||
|
return value && !isNaN(value.getTime())
|
||||||
|
? dayjs(value).format(displayFormat)
|
||||||
|
: '';
|
||||||
|
}, [value, displayFormat]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
ref={ref}
|
||||||
|
value={formattedValue}
|
||||||
|
readOnly
|
||||||
|
className={cn(
|
||||||
|
'bg-bg-card hover:text-text-primary border-border-button w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px] cursor-pointer',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<CalendarIcon
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-muted-foreground/80 group-hover:text-foreground shrink-0 transition-colors"
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-2" align="start">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={value}
|
||||||
|
onSelect={handleDateSelect}
|
||||||
|
initialFocus
|
||||||
|
{...(showTimeSelect && {
|
||||||
|
showTimeInput,
|
||||||
|
timeInputLabel,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
DateInput.displayName = 'DateInput';
|
||||||
|
|
||||||
|
export { DateInput };
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -17,10 +18,12 @@ export interface InputSelectOption {
|
|||||||
export interface InputSelectProps {
|
export interface InputSelectProps {
|
||||||
/** Options for the select component */
|
/** Options for the select component */
|
||||||
options?: InputSelectOption[];
|
options?: InputSelectOption[];
|
||||||
/** Selected values - string for single select, array for multi select */
|
/** Selected values - type depends on the input type */
|
||||||
value?: string | string[];
|
value?: string | string[] | number | number[] | Date | Date[];
|
||||||
/** Callback when value changes */
|
/** Callback when value changes */
|
||||||
onChange?: (value: string | string[]) => void;
|
onChange?: (
|
||||||
|
value: string | string[] | number | number[] | Date | Date[],
|
||||||
|
) => void;
|
||||||
/** Placeholder text */
|
/** Placeholder text */
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
/** Additional class names */
|
/** Additional class names */
|
||||||
@ -29,6 +32,8 @@ export interface InputSelectProps {
|
|||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
/** Whether to allow multiple selections */
|
/** Whether to allow multiple selections */
|
||||||
multi?: boolean;
|
multi?: boolean;
|
||||||
|
/** Type of input: text, number, date, or datetime */
|
||||||
|
type?: 'text' | 'number' | 'date' | 'datetime';
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
||||||
@ -41,6 +46,7 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
multi = false,
|
multi = false,
|
||||||
|
type = 'text',
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
@ -50,36 +56,108 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Normalize value to array for consistent handling
|
// Normalize value to array for consistent handling based on type
|
||||||
const normalizedValue = Array.isArray(value) ? value : value ? [value] : [];
|
const normalizedValue = React.useMemo(() => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
} else if (value !== undefined && value !== null) {
|
||||||
|
if (type === 'number') {
|
||||||
|
return typeof value === 'number' ? [value] : [Number(value)];
|
||||||
|
} else if (type === 'date' || type === 'datetime') {
|
||||||
|
return value instanceof Date ? [value] : [new Date(value as any)];
|
||||||
|
} else {
|
||||||
|
return typeof value === 'string' ? [value] : [String(value)];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}, [value, type]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a tag from the selected values
|
* Removes a tag from the selected values
|
||||||
* @param tagValue - The value of the tag to remove
|
* @param tagValue - The value of the tag to remove
|
||||||
*/
|
*/
|
||||||
const handleRemoveTag = (tagValue: string) => {
|
const handleRemoveTag = (tagValue: any) => {
|
||||||
const newValue = normalizedValue.filter((v) => v !== tagValue);
|
let newValue: any[];
|
||||||
|
|
||||||
|
if (type === 'number') {
|
||||||
|
newValue = (normalizedValue as number[]).filter((v) => v !== tagValue);
|
||||||
|
} else if (type === 'date' || type === 'datetime') {
|
||||||
|
newValue = (normalizedValue as Date[]).filter(
|
||||||
|
(v) => v.getTime() !== tagValue.getTime(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newValue = (normalizedValue as string[]).filter((v) => v !== tagValue);
|
||||||
|
}
|
||||||
|
|
||||||
// Return single value if not multi-select, otherwise return array
|
// Return single value if not multi-select, otherwise return array
|
||||||
onChange?.(multi ? newValue : newValue[0] || '');
|
let result: string | number | Date | string[] | number[] | Date[];
|
||||||
|
if (multi) {
|
||||||
|
result = newValue;
|
||||||
|
} else {
|
||||||
|
if (type === 'number') {
|
||||||
|
result = newValue[0] || 0;
|
||||||
|
} else if (type === 'date' || type === 'datetime') {
|
||||||
|
result = newValue[0] || new Date();
|
||||||
|
} else {
|
||||||
|
result = newValue[0] || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange?.(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a tag to the selected values
|
* Adds a tag to the selected values
|
||||||
* @param optionValue - The value of the tag to add
|
* @param optionValue - The value of the tag to add
|
||||||
*/
|
*/
|
||||||
const handleAddTag = (optionValue: string) => {
|
const handleAddTag = (optionValue: any) => {
|
||||||
let newValue: string[];
|
let newValue: any[];
|
||||||
|
|
||||||
if (multi) {
|
if (multi) {
|
||||||
// For multi-select, add to array if not already included
|
// For multi-select, add to array if not already included
|
||||||
if (!normalizedValue.includes(optionValue)) {
|
if (type === 'number') {
|
||||||
newValue = [...normalizedValue, optionValue];
|
const numValue =
|
||||||
onChange?.(newValue);
|
typeof optionValue === 'number' ? optionValue : Number(optionValue);
|
||||||
|
if (
|
||||||
|
!(normalizedValue as number[]).includes(numValue) &&
|
||||||
|
!isNaN(numValue)
|
||||||
|
) {
|
||||||
|
newValue = [...(normalizedValue as number[]), numValue];
|
||||||
|
onChange?.(newValue as number[]);
|
||||||
|
}
|
||||||
|
} else if (type === 'date' || type === 'datetime') {
|
||||||
|
const dateValue =
|
||||||
|
optionValue instanceof Date ? optionValue : new Date(optionValue);
|
||||||
|
if (
|
||||||
|
!(normalizedValue as Date[]).some(
|
||||||
|
(d) => d.getTime() === dateValue.getTime(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
newValue = [...(normalizedValue as Date[]), dateValue];
|
||||||
|
onChange?.(newValue as Date[]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!(normalizedValue as string[]).includes(optionValue)) {
|
||||||
|
newValue = [...(normalizedValue as string[]), optionValue];
|
||||||
|
onChange?.(newValue as string[]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For single-select, replace the value
|
// For single-select, replace the value
|
||||||
newValue = [optionValue];
|
if (type === 'number') {
|
||||||
onChange?.(optionValue);
|
const numValue =
|
||||||
|
typeof optionValue === 'number' ? optionValue : Number(optionValue);
|
||||||
|
if (!isNaN(numValue)) {
|
||||||
|
onChange?.(numValue);
|
||||||
|
}
|
||||||
|
} else if (type === 'date' || type === 'datetime') {
|
||||||
|
const dateValue =
|
||||||
|
optionValue instanceof Date ? optionValue : new Date(optionValue);
|
||||||
|
onChange?.(dateValue);
|
||||||
|
} else {
|
||||||
|
onChange?.(optionValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setInputValue('');
|
setInputValue('');
|
||||||
@ -89,16 +167,7 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newValue = e.target.value;
|
const newValue = e.target.value;
|
||||||
setInputValue(newValue);
|
setInputValue(newValue);
|
||||||
setOpen(newValue.length > 0); // Open popover when there's input
|
setOpen(!!newValue); // Open popover when there's input
|
||||||
|
|
||||||
// If input matches an option exactly, add it
|
|
||||||
const matchedOption = options.find(
|
|
||||||
(opt) => opt.label.toLowerCase() === newValue.toLowerCase(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchedOption && !normalizedValue.includes(matchedOption.value)) {
|
|
||||||
handleAddTag(matchedOption.value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
@ -111,9 +180,37 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
const newValue = [...normalizedValue];
|
const newValue = [...normalizedValue];
|
||||||
newValue.pop();
|
newValue.pop();
|
||||||
// Return single value if not multi-select, otherwise return array
|
// Return single value if not multi-select, otherwise return array
|
||||||
onChange?.(multi ? newValue : newValue[0] || '');
|
let result: string | number | Date | string[] | number[] | Date[];
|
||||||
|
if (multi) {
|
||||||
|
result = newValue;
|
||||||
|
} else {
|
||||||
|
if (type === 'number') {
|
||||||
|
result = newValue[0] || 0;
|
||||||
|
} else if (type === 'date' || type === 'datetime') {
|
||||||
|
result = newValue[0] || new Date();
|
||||||
|
} else {
|
||||||
|
result = newValue[0] || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange?.(result);
|
||||||
} else if (e.key === 'Enter' && inputValue.trim() !== '') {
|
} else if (e.key === 'Enter' && inputValue.trim() !== '') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
let valueToAdd: any;
|
||||||
|
|
||||||
|
if (type === 'number') {
|
||||||
|
const numValue = Number(inputValue);
|
||||||
|
if (isNaN(numValue)) return; // Don't add invalid numbers
|
||||||
|
valueToAdd = numValue;
|
||||||
|
} else if (type === 'date' || type === 'datetime') {
|
||||||
|
const dateValue = new Date(inputValue);
|
||||||
|
if (isNaN(dateValue.getTime())) return; // Don't add invalid dates
|
||||||
|
valueToAdd = dateValue;
|
||||||
|
} else {
|
||||||
|
valueToAdd = inputValue;
|
||||||
|
}
|
||||||
|
|
||||||
// Add input value as a new tag if it doesn't exist in options
|
// Add input value as a new tag if it doesn't exist in options
|
||||||
const matchedOption = options.find(
|
const matchedOption = options.find(
|
||||||
(opt) => opt.label.toLowerCase() === inputValue.toLowerCase(),
|
(opt) => opt.label.toLowerCase() === inputValue.toLowerCase(),
|
||||||
@ -124,10 +221,16 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
} else {
|
} else {
|
||||||
// If not in options, create a new tag with the input value
|
// If not in options, create a new tag with the input value
|
||||||
if (
|
if (
|
||||||
!normalizedValue.includes(inputValue) &&
|
!normalizedValue.some((v) =>
|
||||||
|
type === 'number'
|
||||||
|
? Number(v) === Number(valueToAdd)
|
||||||
|
: type === 'date' || type === 'datetime'
|
||||||
|
? new Date(v as any).getTime() === valueToAdd.getTime()
|
||||||
|
: String(v) === valueToAdd,
|
||||||
|
) &&
|
||||||
inputValue.trim() !== ''
|
inputValue.trim() !== ''
|
||||||
) {
|
) {
|
||||||
handleAddTag(inputValue);
|
handleAddTag(valueToAdd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
@ -160,26 +263,68 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
|
|
||||||
// Filter options to exclude already selected ones (only for multi-select)
|
// Filter options to exclude already selected ones (only for multi-select)
|
||||||
const availableOptions = multi
|
const availableOptions = multi
|
||||||
? options.filter((option) => !normalizedValue.includes(option.value))
|
? options.filter(
|
||||||
|
(option) =>
|
||||||
|
!normalizedValue.some((v) =>
|
||||||
|
type === 'number'
|
||||||
|
? Number(v) === Number(option.value)
|
||||||
|
: type === 'date' || type === 'datetime'
|
||||||
|
? new Date(v as any).getTime() ===
|
||||||
|
new Date(option.value).getTime()
|
||||||
|
: String(v) === option.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
: options;
|
: options;
|
||||||
|
|
||||||
const filteredOptions = availableOptions.filter(
|
const filteredOptions = availableOptions.filter(
|
||||||
(option) =>
|
(option) =>
|
||||||
!inputValue ||
|
!inputValue ||
|
||||||
option.label.toLowerCase().includes(inputValue.toLowerCase()),
|
option.label
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(inputValue.toString().toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
// If there are no matching options but there is an input value, create a new option with the input value
|
// If there are no matching options but there is an input value, create a new option with the input value
|
||||||
const hasMatchingOptions = filteredOptions.length > 0;
|
const showInputAsOption = React.useMemo(() => {
|
||||||
const showInputAsOption =
|
if (!inputValue) return false;
|
||||||
inputValue &&
|
|
||||||
!hasMatchingOptions &&
|
const hasLabelMatch = options.some(
|
||||||
!normalizedValue.includes(inputValue);
|
(option) =>
|
||||||
|
option.label.toLowerCase() === inputValue.toString().toLowerCase(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let isAlreadySelected = false;
|
||||||
|
if (type === 'number') {
|
||||||
|
const numValue = Number(inputValue);
|
||||||
|
isAlreadySelected =
|
||||||
|
!isNaN(numValue) && (normalizedValue as number[]).includes(numValue);
|
||||||
|
} else if (type === 'date' || type === 'datetime') {
|
||||||
|
const dateValue = new Date(inputValue);
|
||||||
|
isAlreadySelected =
|
||||||
|
!isNaN(dateValue.getTime()) &&
|
||||||
|
(normalizedValue as Date[]).some(
|
||||||
|
(d) => d.getTime() === dateValue.getTime(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
isAlreadySelected = (normalizedValue as string[]).includes(inputValue);
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
'showInputAsOption',
|
||||||
|
hasLabelMatch,
|
||||||
|
isAlreadySelected,
|
||||||
|
inputValue.toString().trim(),
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
!hasLabelMatch &&
|
||||||
|
!isAlreadySelected &&
|
||||||
|
inputValue.toString().trim() !== ''
|
||||||
|
);
|
||||||
|
}, [inputValue, options, normalizedValue, type]);
|
||||||
|
|
||||||
const triggerElement = (
|
const triggerElement = (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-wrap items-center gap-1 w-full rounded-md border-0.5 border-border-button bg-bg-input px-3 py-2 min-h-[40px] cursor-text',
|
'flex flex-wrap items-center gap-1 w-full rounded-md border-0.5 border-border-button bg-bg-input px-3 py-1 min-h-8 cursor-text',
|
||||||
'outline-none transition-colors',
|
'outline-none transition-colors',
|
||||||
'focus-within:outline-none focus-within:ring-1 focus-within:ring-accent-primary',
|
'focus-within:outline-none focus-within:ring-1 focus-within:ring-accent-primary',
|
||||||
className,
|
className,
|
||||||
@ -189,14 +334,22 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
>
|
>
|
||||||
{/* Render selected tags - only show tags if multi is true or if single select has a value */}
|
{/* Render selected tags - only show tags if multi is true or if single select has a value */}
|
||||||
{multi &&
|
{multi &&
|
||||||
normalizedValue.map((tagValue) => {
|
normalizedValue.map((tagValue, index) => {
|
||||||
const option = options.find((opt) => opt.value === tagValue) || {
|
const option = options.find((opt) =>
|
||||||
value: tagValue,
|
type === 'number'
|
||||||
label: tagValue,
|
? Number(opt.value) === Number(tagValue)
|
||||||
|
: type === 'date' || type === 'datetime'
|
||||||
|
? new Date(opt.value).getTime() ===
|
||||||
|
new Date(tagValue).getTime()
|
||||||
|
: String(opt.value) === String(tagValue),
|
||||||
|
) || {
|
||||||
|
value: String(tagValue),
|
||||||
|
label: String(tagValue),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={tagValue}
|
key={`${tagValue}-${index}`}
|
||||||
className="flex items-center bg-bg-card text-text-primary rounded px-2 py-1 text-xs mr-1 mb-1 border border-border-card"
|
className="flex items-center bg-bg-card text-text-primary rounded px-2 py-1 text-xs mr-1 mb-1 border border-border-card"
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
@ -215,11 +368,22 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{/* For single select, show the selected value as text instead of a tag */}
|
{/* For single select, show the selected value as text instead of a tag */}
|
||||||
{!multi && normalizedValue[0] && (
|
{!multi && !isEmpty(normalizedValue[0]) && (
|
||||||
<div className="flex items-center mr-2 max-w-full">
|
<div className={cn('flex items-center max-w-full')}>
|
||||||
<div className="flex-1 truncate">
|
<div className="flex-1 truncate">
|
||||||
{options.find((opt) => opt.value === normalizedValue[0])?.label ||
|
{options.find((opt) =>
|
||||||
normalizedValue[0]}
|
type === 'number'
|
||||||
|
? Number(opt.value) === Number(normalizedValue[0])
|
||||||
|
: type === 'date' || type === 'datetime'
|
||||||
|
? new Date(opt.value).getTime() ===
|
||||||
|
new Date(normalizedValue[0]).getTime()
|
||||||
|
: String(opt.value) === String(normalizedValue[0]),
|
||||||
|
)?.label ||
|
||||||
|
(type === 'number'
|
||||||
|
? String(normalizedValue[0])
|
||||||
|
: type === 'date' || type === 'datetime'
|
||||||
|
? new Date(normalizedValue[0] as any).toLocaleString()
|
||||||
|
: String(normalizedValue[0]))}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -235,19 +399,37 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Input field for adding new tags - hide if single select and value is already selected, or in multi select when not focused */}
|
{/* Input field for adding new tags - hide if single select and value is already selected, or in multi select when not focused */}
|
||||||
{(multi ? isFocused : multi || !normalizedValue[0]) && (
|
{(multi ? isFocused : multi || isEmpty(normalizedValue[0])) && (
|
||||||
<Input
|
<Input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type={
|
||||||
value={inputValue}
|
type === 'date'
|
||||||
|
? 'date'
|
||||||
|
: type === 'datetime'
|
||||||
|
? 'datetime-local'
|
||||||
|
: type === 'number'
|
||||||
|
? 'number'
|
||||||
|
: 'text'
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
type === 'number' && inputValue
|
||||||
|
? String(inputValue)
|
||||||
|
: type === 'date' || type === 'datetime'
|
||||||
|
? inputValue
|
||||||
|
: inputValue
|
||||||
|
}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder={
|
placeholder={
|
||||||
(multi ? normalizedValue.length === 0 : !normalizedValue[0])
|
(
|
||||||
|
multi
|
||||||
|
? normalizedValue.length === 0
|
||||||
|
: isEmpty(normalizedValue[0])
|
||||||
|
)
|
||||||
? placeholder
|
? placeholder
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
className="flex-grow min-w-[50px] border-none px-1 py-0 bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 h-auto !w-fit"
|
className="flex-grow min-w-[50px] border-none px-1 py-0 bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 h-auto "
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onFocus={handleInputFocus}
|
onFocus={handleInputFocus}
|
||||||
onBlur={handleInputBlur}
|
onBlur={handleInputBlur}
|
||||||
@ -272,7 +454,19 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
<div
|
<div
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className="px-4 py-2 hover:bg-border-button cursor-pointer text-text-secondary w-full truncate"
|
className="px-4 py-2 hover:bg-border-button cursor-pointer text-text-secondary w-full truncate"
|
||||||
onClick={() => handleAddTag(option.value)}
|
onClick={() => {
|
||||||
|
let optionValue: any;
|
||||||
|
if (type === 'number') {
|
||||||
|
optionValue = Number(option.value);
|
||||||
|
if (isNaN(optionValue)) return; // Skip invalid numbers
|
||||||
|
} else if (type === 'date' || type === 'datetime') {
|
||||||
|
optionValue = new Date(option.value);
|
||||||
|
if (isNaN(optionValue.getTime())) return; // Skip invalid dates
|
||||||
|
} else {
|
||||||
|
optionValue = option.value;
|
||||||
|
}
|
||||||
|
handleAddTag(optionValue);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
</div>
|
</div>
|
||||||
@ -281,9 +475,17 @@ const InputSelect = React.forwardRef<HTMLInputElement, InputSelectProps>(
|
|||||||
<div
|
<div
|
||||||
key={inputValue}
|
key={inputValue}
|
||||||
className="px-4 py-2 hover:bg-border-button cursor-pointer text-text-secondary w-full truncate"
|
className="px-4 py-2 hover:bg-border-button cursor-pointer text-text-secondary w-full truncate"
|
||||||
onClick={() => handleAddTag(inputValue)}
|
onClick={() =>
|
||||||
|
handleAddTag(
|
||||||
|
type === 'number'
|
||||||
|
? Number(inputValue)
|
||||||
|
: type === 'date' || type === 'datetime'
|
||||||
|
? new Date(inputValue)
|
||||||
|
: inputValue,
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t('common.add')} "{inputValue}"
|
{t('common.add')} "{inputValue}"
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{filteredOptions.length === 0 && !showInputAsOption && (
|
{filteredOptions.length === 0 && !showInputAsOption && (
|
||||||
|
|||||||
@ -274,7 +274,6 @@ export const useSendMessageWithSse = (
|
|||||||
const val = JSON.parse(value?.data || '');
|
const val = JSON.parse(value?.data || '');
|
||||||
const d = val?.data;
|
const d = val?.data;
|
||||||
if (typeof d !== 'boolean') {
|
if (typeof d !== 'boolean') {
|
||||||
|
|
||||||
setAnswer((prev) => {
|
setAnswer((prev) => {
|
||||||
const prevAnswer = prev.answer || '';
|
const prevAnswer = prev.answer || '';
|
||||||
const currentAnswer = d.answer || '';
|
const currentAnswer = d.answer || '';
|
||||||
|
|||||||
@ -184,6 +184,10 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
|||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
metadata: {
|
metadata: {
|
||||||
|
type: 'Type',
|
||||||
|
fieldNameInvalid: 'Field name can only contain letters or underscores.',
|
||||||
|
builtIn: 'Built-in',
|
||||||
|
generation: 'Generation',
|
||||||
toMetadataSetting: 'Generation settings',
|
toMetadataSetting: 'Generation settings',
|
||||||
toMetadataSettingTip: 'Set auto-metadata in Configuration.',
|
toMetadataSettingTip: 'Set auto-metadata in Configuration.',
|
||||||
descriptionTip:
|
descriptionTip:
|
||||||
@ -289,7 +293,7 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
|||||||
localFiles: 'Local files',
|
localFiles: 'Local files',
|
||||||
emptyFiles: 'Create empty file',
|
emptyFiles: 'Create empty file',
|
||||||
webCrawl: 'Web crawl',
|
webCrawl: 'Web crawl',
|
||||||
chunkNumber: 'Chunk number',
|
chunkNumber: 'Chunks',
|
||||||
uploadDate: 'Upload date',
|
uploadDate: 'Upload date',
|
||||||
chunkMethod: 'Chunking method',
|
chunkMethod: 'Chunking method',
|
||||||
enabled: 'Enable',
|
enabled: 'Enable',
|
||||||
|
|||||||
@ -175,6 +175,10 @@ export default {
|
|||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
metadata: {
|
metadata: {
|
||||||
|
type: '类型',
|
||||||
|
fieldNameInvalid: '字段名称只能包含字母或下划线。',
|
||||||
|
builtIn: '内置',
|
||||||
|
generation: '生成',
|
||||||
toMetadataSettingTip: '在配置中设置自动元数据',
|
toMetadataSettingTip: '在配置中设置自动元数据',
|
||||||
toMetadataSetting: '生成设置',
|
toMetadataSetting: '生成设置',
|
||||||
descriptionTip:
|
descriptionTip:
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import message from '@/components/ui/message';
|
import message from '@/components/ui/message';
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
|
import { useSelectedIds } from '@/hooks/logic-hooks/use-row-selection';
|
||||||
import {
|
import {
|
||||||
DocumentApiAction,
|
DocumentApiAction,
|
||||||
useSetDocumentMeta,
|
useSetDocumentMeta,
|
||||||
@ -9,13 +10,13 @@ import kbService, {
|
|||||||
updateMetaData,
|
updateMetaData,
|
||||||
} from '@/services/knowledge-service';
|
} from '@/services/knowledge-service';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { RowSelectionState } from '@tanstack/react-table';
|
||||||
import { TFunction } from 'i18next';
|
import { TFunction } from 'i18next';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import {
|
import {
|
||||||
IBuiltInMetadataItem,
|
IBuiltInMetadataItem,
|
||||||
IMetaDataJsonSchemaProperty,
|
|
||||||
IMetaDataReturnJSONSettings,
|
IMetaDataReturnJSONSettings,
|
||||||
IMetaDataReturnJSONType,
|
IMetaDataReturnJSONType,
|
||||||
IMetaDataReturnType,
|
IMetaDataReturnType,
|
||||||
@ -76,14 +77,16 @@ export const MetadataDeleteMap = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_VALUE_TYPE: MetadataValueType = 'string';
|
const DEFAULT_VALUE_TYPE: MetadataValueType = 'string';
|
||||||
const VALUE_TYPES_WITH_ENUM = new Set<MetadataValueType>(['enum']);
|
// const VALUE_TYPES_WITH_ENUM = new Set<MetadataValueType>(['enum']);
|
||||||
const VALUE_TYPE_LABELS: Record<MetadataValueType, string> = {
|
const VALUE_TYPE_LABELS: Record<MetadataValueType, string> = {
|
||||||
string: 'String',
|
string: 'String',
|
||||||
bool: 'Bool',
|
|
||||||
enum: 'Enum',
|
|
||||||
time: 'Time',
|
time: 'Time',
|
||||||
int: 'Int',
|
number: 'Number',
|
||||||
float: 'Float',
|
// bool: 'Bool',
|
||||||
|
// enum: 'Enum',
|
||||||
|
// 'list<string>': 'List<String>',
|
||||||
|
// int: 'Int',
|
||||||
|
// float: 'Float',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const metadataValueTypeOptions = Object.entries(VALUE_TYPE_LABELS).map(
|
export const metadataValueTypeOptions = Object.entries(VALUE_TYPE_LABELS).map(
|
||||||
@ -93,82 +96,101 @@ export const metadataValueTypeOptions = Object.entries(VALUE_TYPE_LABELS).map(
|
|||||||
export const getMetadataValueTypeLabel = (value?: MetadataValueType) =>
|
export const getMetadataValueTypeLabel = (value?: MetadataValueType) =>
|
||||||
VALUE_TYPE_LABELS[value || DEFAULT_VALUE_TYPE] || VALUE_TYPE_LABELS.string;
|
VALUE_TYPE_LABELS[value || DEFAULT_VALUE_TYPE] || VALUE_TYPE_LABELS.string;
|
||||||
|
|
||||||
export const isMetadataValueTypeWithEnum = (value?: MetadataValueType) =>
|
// export const isMetadataValueTypeWithEnum = (value?: MetadataValueType) =>
|
||||||
VALUE_TYPES_WITH_ENUM.has(value || DEFAULT_VALUE_TYPE);
|
// VALUE_TYPES_WITH_ENUM.has(value || DEFAULT_VALUE_TYPE);
|
||||||
|
|
||||||
const schemaToValueType = (
|
// const schemaToValueType = (
|
||||||
property?: IMetaDataJsonSchemaProperty,
|
// property?: IMetaDataJsonSchemaProperty,
|
||||||
): MetadataValueType => {
|
// ): MetadataValueType => {
|
||||||
if (!property) return DEFAULT_VALUE_TYPE;
|
// if (!property) return DEFAULT_VALUE_TYPE;
|
||||||
if (
|
// if (
|
||||||
property.type === 'array' &&
|
// property.type === 'array' &&
|
||||||
property.items?.type === 'string' &&
|
// property.items?.type === 'string' &&
|
||||||
(property.items.enum?.length || 0) > 0
|
// (property.items.enum?.length || 0) > 0
|
||||||
) {
|
// ) {
|
||||||
return 'enum';
|
// return 'enum';
|
||||||
}
|
// }
|
||||||
if (property.type === 'boolean') return 'bool';
|
// if (property.type === 'boolean') return 'bool';
|
||||||
if (property.type === 'integer') return 'int';
|
// if (property.type === 'integer') return 'int';
|
||||||
if (property.type === 'number') return 'float';
|
// if (property.type === 'number') return 'float';
|
||||||
if (property.type === 'string' && property.format) {
|
// if (property.type === 'string' && property.format) {
|
||||||
return 'time';
|
// return 'time';
|
||||||
}
|
// }
|
||||||
if (property.type === 'string' && property.enum?.length) {
|
// if (property.type === 'string' && property.enum?.length) {
|
||||||
return 'enum';
|
// return 'enum';
|
||||||
}
|
// }
|
||||||
return DEFAULT_VALUE_TYPE;
|
// return DEFAULT_VALUE_TYPE;
|
||||||
};
|
// };
|
||||||
|
|
||||||
const valueTypeToSchema = (
|
// const valueTypeToSchema = (
|
||||||
valueType: MetadataValueType,
|
// valueType: MetadataValueType,
|
||||||
description: string,
|
// description: string,
|
||||||
values: string[],
|
// values: string[],
|
||||||
): IMetaDataJsonSchemaProperty => {
|
// ): IMetaDataJsonSchemaProperty => {
|
||||||
const schema: IMetaDataJsonSchemaProperty = {
|
// const schema: IMetaDataJsonSchemaProperty = {
|
||||||
description: description || '',
|
// description: description || '',
|
||||||
};
|
// };
|
||||||
|
|
||||||
switch (valueType) {
|
// switch (valueType) {
|
||||||
case 'bool':
|
// case 'bool':
|
||||||
schema.type = 'boolean';
|
// schema.type = 'boolean';
|
||||||
return schema;
|
// return schema;
|
||||||
case 'int':
|
// case 'int':
|
||||||
schema.type = 'integer';
|
// schema.type = 'integer';
|
||||||
return schema;
|
// return schema;
|
||||||
case 'float':
|
// case 'float':
|
||||||
schema.type = 'number';
|
// schema.type = 'number';
|
||||||
return schema;
|
// return schema;
|
||||||
case 'time':
|
// case 'time':
|
||||||
schema.type = 'string';
|
// schema.type = 'string';
|
||||||
schema.format = 'date-time';
|
// schema.format = 'date-time';
|
||||||
return schema;
|
// return schema;
|
||||||
case 'enum':
|
// case 'enum':
|
||||||
schema.type = 'string';
|
// schema.type = 'string';
|
||||||
if (values?.length) {
|
// if (values?.length) {
|
||||||
schema.enum = values;
|
// schema.enum = values;
|
||||||
}
|
// }
|
||||||
return schema;
|
// return schema;
|
||||||
case 'string':
|
// case 'string':
|
||||||
default:
|
// default:
|
||||||
schema.type = 'string';
|
// schema.type = 'string';
|
||||||
if (values?.length) {
|
// if (values?.length) {
|
||||||
schema.enum = values;
|
// schema.enum = values;
|
||||||
}
|
// }
|
||||||
return schema;
|
// return schema;
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
export const util = {
|
export const util = {
|
||||||
changeToMetaDataTableData(data: IMetaDataReturnType): IMetaDataTableData[] {
|
changeToMetaDataTableData(data: IMetaDataReturnType): IMetaDataTableData[] {
|
||||||
return Object.entries(data).map(([key, value]) => {
|
const res = Object.entries(data).map(
|
||||||
const values = value.map(([v]) => v.toString());
|
([key, value]: [
|
||||||
console.log('values', values);
|
string,
|
||||||
return {
|
(
|
||||||
field: key,
|
| { type: string; values: Array<Array<string | number>> }
|
||||||
description: '',
|
| Array<Array<string | number>>
|
||||||
values: values,
|
),
|
||||||
} as IMetaDataTableData;
|
]) => {
|
||||||
});
|
if (Array.isArray(value)) {
|
||||||
|
const values = value.map(([v]) => v.toString());
|
||||||
|
return {
|
||||||
|
field: key,
|
||||||
|
valueType: DEFAULT_VALUE_TYPE,
|
||||||
|
description: '',
|
||||||
|
values: values,
|
||||||
|
} as IMetaDataTableData;
|
||||||
|
}
|
||||||
|
const { type, values } = value;
|
||||||
|
const valueArr = values.map(([v]) => v.toString());
|
||||||
|
return {
|
||||||
|
field: key,
|
||||||
|
valueType: type,
|
||||||
|
description: '',
|
||||||
|
values: valueArr,
|
||||||
|
} as IMetaDataTableData;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
JSONToMetaDataTableData(
|
JSONToMetaDataTableData(
|
||||||
@ -204,31 +226,13 @@ export const util = {
|
|||||||
tableDataToMetaDataSettingJSON(
|
tableDataToMetaDataSettingJSON(
|
||||||
data: IMetaDataTableData[],
|
data: IMetaDataTableData[],
|
||||||
): IMetaDataReturnJSONSettings {
|
): IMetaDataReturnJSONSettings {
|
||||||
const properties = data.reduce<Record<string, IMetaDataJsonSchemaProperty>>(
|
return data.map((item) => {
|
||||||
(acc, item) => {
|
return {
|
||||||
if (!item.field) {
|
key: item.field,
|
||||||
return acc;
|
description: item.description,
|
||||||
}
|
enum: item.values,
|
||||||
const valueType = item.valueType || DEFAULT_VALUE_TYPE;
|
};
|
||||||
const values =
|
});
|
||||||
isMetadataValueTypeWithEnum(valueType) && item.restrictDefinedValues
|
|
||||||
? item.values
|
|
||||||
: [];
|
|
||||||
acc[item.field] = valueTypeToSchema(
|
|
||||||
valueType,
|
|
||||||
item.description,
|
|
||||||
values,
|
|
||||||
);
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'object',
|
|
||||||
properties,
|
|
||||||
additionalProperties: false,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
metaDataSettingJSONToMetaDataTableData(
|
metaDataSettingJSONToMetaDataTableData(
|
||||||
@ -248,7 +252,7 @@ export const util = {
|
|||||||
}
|
}
|
||||||
const properties = data.properties || {};
|
const properties = data.properties || {};
|
||||||
return Object.entries(properties).map(([key, property]) => {
|
return Object.entries(properties).map(([key, property]) => {
|
||||||
const valueType = schemaToValueType(property);
|
const valueType = 'string';
|
||||||
const values = property.enum || property.items?.enum || [];
|
const values = property.enum || property.items?.enum || [];
|
||||||
return {
|
return {
|
||||||
field: key,
|
field: key,
|
||||||
@ -274,6 +278,13 @@ export const useMetadataOperations = () => {
|
|||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const addDeleteBatch = useCallback((keys: string[]) => {
|
||||||
|
setOperations((prev) => ({
|
||||||
|
...prev,
|
||||||
|
deletes: [...prev.deletes, ...keys.map((key) => ({ key }))],
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const addDeleteValue = useCallback((key: string, value: string) => {
|
const addDeleteValue = useCallback((key: string, value: string) => {
|
||||||
setOperations((prev) => ({
|
setOperations((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -281,15 +292,6 @@ export const useMetadataOperations = () => {
|
|||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// const addUpdateValue = useCallback(
|
|
||||||
// (key: string, value: string | string[]) => {
|
|
||||||
// setOperations((prev) => ({
|
|
||||||
// ...prev,
|
|
||||||
// updates: [...prev.updates, { key, value }],
|
|
||||||
// }));
|
|
||||||
// },
|
|
||||||
// [],
|
|
||||||
// );
|
|
||||||
const addUpdateValue = useCallback(
|
const addUpdateValue = useCallback(
|
||||||
(key: string, originalValue: string, newValue: string) => {
|
(key: string, originalValue: string, newValue: string) => {
|
||||||
setOperations((prev) => {
|
setOperations((prev) => {
|
||||||
@ -330,6 +332,7 @@ export const useMetadataOperations = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
operations,
|
operations,
|
||||||
|
addDeleteBatch,
|
||||||
addDeleteRow,
|
addDeleteRow,
|
||||||
addDeleteValue,
|
addDeleteValue,
|
||||||
addUpdateValue,
|
addUpdateValue,
|
||||||
@ -339,48 +342,29 @@ export const useMetadataOperations = () => {
|
|||||||
|
|
||||||
export const useFetchMetaDataManageData = (
|
export const useFetchMetaDataManageData = (
|
||||||
type: MetadataType = MetadataType.Manage,
|
type: MetadataType = MetadataType.Manage,
|
||||||
|
documentIds?: string[],
|
||||||
) => {
|
) => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
// const [data, setData] = useState<IMetaDataTableData[]>([]);
|
|
||||||
// const [loading, setLoading] = useState(false);
|
|
||||||
// const fetchData = useCallback(async (): Promise<IMetaDataTableData[]> => {
|
|
||||||
// setLoading(true);
|
|
||||||
// const { data } = await getMetaDataService({
|
|
||||||
// kb_id: id as string,
|
|
||||||
// });
|
|
||||||
// setLoading(false);
|
|
||||||
// if (data?.data?.summary) {
|
|
||||||
// return util.changeToMetaDataTableData(data.data.summary);
|
|
||||||
// }
|
|
||||||
// return [];
|
|
||||||
// }, [id]);
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (type === MetadataType.Manage) {
|
|
||||||
// fetchData()
|
|
||||||
// .then((res) => {
|
|
||||||
// setData(res);
|
|
||||||
// })
|
|
||||||
// .catch((res) => {
|
|
||||||
// console.error(res);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }, [type, fetchData]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isFetching: loading,
|
isFetching: loading,
|
||||||
refetch,
|
refetch,
|
||||||
} = useQuery<IMetaDataTableData[]>({
|
} = useQuery<IMetaDataTableData[]>({
|
||||||
queryKey: ['fetchMetaData', id],
|
queryKey: ['fetchMetaData', id, documentIds],
|
||||||
enabled: !!id && type === MetadataType.Manage,
|
enabled:
|
||||||
|
!!id &&
|
||||||
|
(type === MetadataType.Manage || type === MetadataType.UpdateSingle),
|
||||||
initialData: [],
|
initialData: [],
|
||||||
gcTime: 1000,
|
gcTime: 1000,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await getMetaDataService({
|
const { data } = await getMetaDataService({
|
||||||
kb_id: id as string,
|
kb_id: id as string,
|
||||||
|
doc_ids: documentIds,
|
||||||
});
|
});
|
||||||
if (data?.data?.summary) {
|
if (data?.data?.summary) {
|
||||||
return util.changeToMetaDataTableData(data.data.summary);
|
const res = util.changeToMetaDataTableData(data.data.summary);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
@ -392,20 +376,23 @@ export const useFetchMetaDataManageData = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchTypeList = [MetadataType.Manage, MetadataType.UpdateSingle];
|
||||||
export const useManageMetaDataModal = (
|
export const useManageMetaDataModal = (
|
||||||
metaData: IMetaDataTableData[] = [],
|
metaData: IMetaDataTableData[] = [],
|
||||||
type: MetadataType = MetadataType.Manage,
|
type: MetadataType = MetadataType.Manage,
|
||||||
otherData?: Record<string, any>,
|
otherData?: Record<string, any>,
|
||||||
|
documentIds?: string[],
|
||||||
) => {
|
) => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data, loading } = useFetchMetaDataManageData(type);
|
const { data, loading } = useFetchMetaDataManageData(type, documentIds);
|
||||||
|
|
||||||
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
|
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const {
|
const {
|
||||||
operations,
|
operations,
|
||||||
addDeleteRow,
|
addDeleteRow,
|
||||||
|
addDeleteBatch,
|
||||||
addDeleteValue,
|
addDeleteValue,
|
||||||
addUpdateValue,
|
addUpdateValue,
|
||||||
resetOperations,
|
resetOperations,
|
||||||
@ -414,7 +401,7 @@ export const useManageMetaDataModal = (
|
|||||||
const { setDocumentMeta } = useSetDocumentMeta();
|
const { setDocumentMeta } = useSetDocumentMeta();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (type === MetadataType.Manage) {
|
if (fetchTypeList.includes(type)) {
|
||||||
if (data) {
|
if (data) {
|
||||||
setTableData(data);
|
setTableData(data);
|
||||||
} else {
|
} else {
|
||||||
@ -424,7 +411,7 @@ export const useManageMetaDataModal = (
|
|||||||
}, [data, type]);
|
}, [data, type]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (type !== MetadataType.Manage) {
|
if (!fetchTypeList.includes(type)) {
|
||||||
if (metaData) {
|
if (metaData) {
|
||||||
setTableData(metaData);
|
setTableData(metaData);
|
||||||
} else {
|
} else {
|
||||||
@ -447,7 +434,6 @@ export const useManageMetaDataModal = (
|
|||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
// console.log('newTableData', newTableData, prevTableData);
|
|
||||||
return newTableData;
|
return newTableData;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -461,18 +447,31 @@ export const useManageMetaDataModal = (
|
|||||||
const newTableData = prevTableData.filter(
|
const newTableData = prevTableData.filter(
|
||||||
(item) => item.field !== field,
|
(item) => item.field !== field,
|
||||||
);
|
);
|
||||||
// console.log('newTableData', newTableData, prevTableData);
|
|
||||||
return newTableData;
|
return newTableData;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[addDeleteRow],
|
[addDeleteRow],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDeleteBatchRow = useCallback(
|
||||||
|
(fields: string[]) => {
|
||||||
|
addDeleteBatch(fields);
|
||||||
|
setTableData((prevTableData) => {
|
||||||
|
const newTableData = prevTableData.filter(
|
||||||
|
(item) => !fields.includes(item.field),
|
||||||
|
);
|
||||||
|
return newTableData;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[addDeleteBatch],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSaveManage = useCallback(
|
const handleSaveManage = useCallback(
|
||||||
async (callback: () => void) => {
|
async (callback: () => void) => {
|
||||||
const { data: res } = await updateMetaData({
|
const { data: res } = await updateMetaData({
|
||||||
kb_id: id as string,
|
kb_id: id as string,
|
||||||
data: operations,
|
data: operations,
|
||||||
|
doc_ids: documentIds,
|
||||||
});
|
});
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
@ -483,7 +482,7 @@ export const useManageMetaDataModal = (
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[operations, id, t, queryClient, resetOperations],
|
[operations, id, t, queryClient, resetOperations, documentIds],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSaveUpdateSingle = useCallback(
|
const handleSaveUpdateSingle = useCallback(
|
||||||
@ -506,13 +505,22 @@ export const useManageMetaDataModal = (
|
|||||||
const handleSaveSettings = useCallback(
|
const handleSaveSettings = useCallback(
|
||||||
async (callback: () => void, builtInMetadata?: IBuiltInMetadataItem[]) => {
|
async (callback: () => void, builtInMetadata?: IBuiltInMetadataItem[]) => {
|
||||||
const data = util.tableDataToMetaDataSettingJSON(tableData);
|
const data = util.tableDataToMetaDataSettingJSON(tableData);
|
||||||
callback?.();
|
const { data: res } = await kbService.kbUpdateMetaData({
|
||||||
|
kb_id: id,
|
||||||
|
metadata: data,
|
||||||
|
builtInMetadata: builtInMetadata || [],
|
||||||
|
});
|
||||||
|
if (res.code === 0) {
|
||||||
|
message.success(t('message.operated'));
|
||||||
|
callback?.();
|
||||||
|
}
|
||||||
|
// callback?.();
|
||||||
return {
|
return {
|
||||||
metadata: data,
|
metadata: data,
|
||||||
builtInMetadata: builtInMetadata || [],
|
builtInMetadata: builtInMetadata || [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[tableData],
|
[tableData, t, id],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSaveSingleFileSettings = useCallback(
|
const handleSaveSingleFileSettings = useCallback(
|
||||||
@ -540,17 +548,19 @@ export const useManageMetaDataModal = (
|
|||||||
builtInMetadata,
|
builtInMetadata,
|
||||||
}: {
|
}: {
|
||||||
callback: () => void;
|
callback: () => void;
|
||||||
builtInMetadata?: string[];
|
builtInMetadata?: IBuiltInMetadataItem[];
|
||||||
}) => {
|
}) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MetadataType.UpdateSingle:
|
case MetadataType.UpdateSingle:
|
||||||
handleSaveUpdateSingle(callback);
|
// handleSaveUpdateSingle(callback);
|
||||||
|
handleSaveManage(callback);
|
||||||
break;
|
break;
|
||||||
case MetadataType.Manage:
|
case MetadataType.Manage:
|
||||||
handleSaveManage(callback);
|
handleSaveManage(callback);
|
||||||
break;
|
break;
|
||||||
case MetadataType.Setting:
|
case MetadataType.Setting:
|
||||||
return handleSaveSettings(callback, builtInMetadata);
|
return handleSaveSettings(callback, builtInMetadata);
|
||||||
|
|
||||||
case MetadataType.SingleFileSetting:
|
case MetadataType.SingleFileSetting:
|
||||||
return handleSaveSingleFileSettings(callback);
|
return handleSaveSingleFileSettings(callback);
|
||||||
default:
|
default:
|
||||||
@ -561,7 +571,7 @@ export const useManageMetaDataModal = (
|
|||||||
[
|
[
|
||||||
handleSaveManage,
|
handleSaveManage,
|
||||||
type,
|
type,
|
||||||
handleSaveUpdateSingle,
|
// handleSaveUpdateSingle,
|
||||||
handleSaveSettings,
|
handleSaveSettings,
|
||||||
handleSaveSingleFileSettings,
|
handleSaveSingleFileSettings,
|
||||||
],
|
],
|
||||||
@ -572,6 +582,7 @@ export const useManageMetaDataModal = (
|
|||||||
setTableData,
|
setTableData,
|
||||||
handleDeleteSingleValue,
|
handleDeleteSingleValue,
|
||||||
handleDeleteSingleRow,
|
handleDeleteSingleRow,
|
||||||
|
handleDeleteBatchRow,
|
||||||
loading,
|
loading,
|
||||||
handleSave,
|
handleSave,
|
||||||
addUpdateValue,
|
addUpdateValue,
|
||||||
@ -593,17 +604,8 @@ export const useManageMetadata = () => {
|
|||||||
(config?: ShowManageMetadataModalProps) => {
|
(config?: ShowManageMetadataModalProps) => {
|
||||||
const { metadata } = config || {};
|
const { metadata } = config || {};
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
// const dataTemp = Object.entries(metadata).map(([key, value]) => {
|
|
||||||
// return {
|
|
||||||
// field: key,
|
|
||||||
// description: '',
|
|
||||||
// values: Array.isArray(value) ? value : [value],
|
|
||||||
// } as IMetaDataTableData;
|
|
||||||
// });
|
|
||||||
setTableData(metadata);
|
setTableData(metadata);
|
||||||
console.log('metadata-2', metadata);
|
|
||||||
}
|
}
|
||||||
console.log('metadata-3', metadata);
|
|
||||||
if (config) {
|
if (config) {
|
||||||
setConfig(config);
|
setConfig(config);
|
||||||
}
|
}
|
||||||
@ -619,3 +621,35 @@ export const useManageMetadata = () => {
|
|||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useOperateData = ({
|
||||||
|
rowSelection,
|
||||||
|
list,
|
||||||
|
handleDeleteBatchRow,
|
||||||
|
}: {
|
||||||
|
rowSelection: RowSelectionState;
|
||||||
|
list: IMetaDataTableData[];
|
||||||
|
handleDeleteBatchRow: (keys: string[]) => void;
|
||||||
|
}) => {
|
||||||
|
const mapList = useMemo(() => {
|
||||||
|
return list.map((x) => {
|
||||||
|
return { ...x, id: x.field };
|
||||||
|
});
|
||||||
|
}, [list]);
|
||||||
|
const { selectedIds: selectedRowKeys } = useSelectedIds(
|
||||||
|
rowSelection,
|
||||||
|
mapList,
|
||||||
|
);
|
||||||
|
// const handleDelete = useCallback(() => {
|
||||||
|
// console.log('rowSelection', rowSelection);
|
||||||
|
// }, [rowSelection]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
const deletedKeys = selectedRowKeys.filter((x) =>
|
||||||
|
mapList.some((y) => y.id === x),
|
||||||
|
);
|
||||||
|
handleDeleteBatchRow(deletedKeys);
|
||||||
|
return;
|
||||||
|
}, [selectedRowKeys, mapList, handleDeleteBatchRow]);
|
||||||
|
return { handleDelete };
|
||||||
|
};
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import { MetadataDeleteMap, MetadataType } from '../hooks/use-manage-modal';
|
||||||
isMetadataValueTypeWithEnum,
|
|
||||||
MetadataDeleteMap,
|
|
||||||
MetadataType,
|
|
||||||
} from '../hooks/use-manage-modal';
|
|
||||||
import { IManageValuesProps, IMetaDataTableData } from '../interface';
|
import { IManageValuesProps, IMetaDataTableData } from '../interface';
|
||||||
|
|
||||||
export const useManageValues = (props: IManageValuesProps) => {
|
export const useManageValues = (props: IManageValuesProps) => {
|
||||||
@ -48,7 +44,7 @@ export const useManageValues = (props: IManageValuesProps) => {
|
|||||||
|
|
||||||
// Use functional update to avoid closure issues
|
// Use functional update to avoid closure issues
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(field: string, value: any) => {
|
async (field: string, value: any) => {
|
||||||
if (field === 'field' && existsKeys.includes(value)) {
|
if (field === 'field' && existsKeys.includes(value)) {
|
||||||
setValueError((prev) => {
|
setValueError((prev) => {
|
||||||
return {
|
return {
|
||||||
@ -71,17 +67,16 @@ export const useManageValues = (props: IManageValuesProps) => {
|
|||||||
if (field === 'valueType') {
|
if (field === 'valueType') {
|
||||||
const nextValueType = (value ||
|
const nextValueType = (value ||
|
||||||
'string') as IMetaDataTableData['valueType'];
|
'string') as IMetaDataTableData['valueType'];
|
||||||
const supportsEnum = isMetadataValueTypeWithEnum(nextValueType);
|
|
||||||
if (!supportsEnum) {
|
// const supportsEnum = isMetadataValueTypeWithEnum(nextValueType);
|
||||||
setTempValues([]);
|
// if (!supportsEnum) {
|
||||||
}
|
// setTempValues([]);
|
||||||
|
// }
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
valueType: nextValueType,
|
valueType: nextValueType,
|
||||||
values: supportsEnum ? prev.values : [],
|
values: prev.values || [],
|
||||||
restrictDefinedValues: supportsEnum
|
restrictDefinedValues: prev.restrictDefinedValues,
|
||||||
? prev.restrictDefinedValues || nextValueType === 'enum'
|
|
||||||
: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -89,6 +84,7 @@ export const useManageValues = (props: IManageValuesProps) => {
|
|||||||
[field]: value,
|
[field]: value,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
[existsKeys, type, t],
|
[existsKeys, type, t],
|
||||||
);
|
);
|
||||||
@ -113,23 +109,46 @@ export const useManageValues = (props: IManageValuesProps) => {
|
|||||||
if (type === MetadataType.Setting && valueError.field) {
|
if (type === MetadataType.Setting && valueError.field) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const supportsEnum = isMetadataValueTypeWithEnum(metaData.valueType);
|
// const supportsEnum = isMetadataValueTypeWithEnum(metaData.valueType);
|
||||||
if (!supportsEnum) {
|
// if (!supportsEnum) {
|
||||||
onSave({
|
// onSave({
|
||||||
...metaData,
|
// ...metaData,
|
||||||
values: [],
|
// values: [],
|
||||||
restrictDefinedValues: false,
|
// restrictDefinedValues: false,
|
||||||
});
|
// });
|
||||||
handleHideModal();
|
// handleHideModal();
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
onSave(metaData);
|
onSave(metaData);
|
||||||
handleHideModal();
|
handleHideModal();
|
||||||
}, [metaData, onSave, handleHideModal, type, valueError]);
|
}, [metaData, onSave, handleHideModal, type, valueError]);
|
||||||
|
|
||||||
|
// Handle blur event, synchronize to main state
|
||||||
|
const handleValueBlur = useCallback(
|
||||||
|
(values?: string[]) => {
|
||||||
|
const newValues = values || tempValues;
|
||||||
|
if (data.values.length > 0) {
|
||||||
|
newValues.forEach((newValue, index) => {
|
||||||
|
if (index < data.values.length) {
|
||||||
|
const originalValue = data.values[index];
|
||||||
|
if (originalValue !== newValue) {
|
||||||
|
addUpdateValue(metaData.field, originalValue, newValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (newValue) {
|
||||||
|
addUpdateValue(metaData.field, '', newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleChange('values', [...new Set([...newValues])]);
|
||||||
|
},
|
||||||
|
[handleChange, tempValues, metaData, data, addUpdateValue],
|
||||||
|
);
|
||||||
|
|
||||||
// Handle value changes, only update temporary state
|
// Handle value changes, only update temporary state
|
||||||
const handleValueChange = useCallback(
|
const handleValueChange = useCallback(
|
||||||
(index: number, value: string) => {
|
(index: number, value: string, isUpdate: boolean = false) => {
|
||||||
setTempValues((prev) => {
|
setTempValues((prev) => {
|
||||||
if (prev.includes(value)) {
|
if (prev.includes(value)) {
|
||||||
setValueError((prev) => {
|
setValueError((prev) => {
|
||||||
@ -149,32 +168,15 @@ export const useManageValues = (props: IManageValuesProps) => {
|
|||||||
}
|
}
|
||||||
const newValues = [...prev];
|
const newValues = [...prev];
|
||||||
newValues[index] = value;
|
newValues[index] = value;
|
||||||
|
if (isUpdate) {
|
||||||
|
handleValueBlur(newValues);
|
||||||
|
}
|
||||||
return newValues;
|
return newValues;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[t, type],
|
[t, type, handleValueBlur],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle blur event, synchronize to main state
|
|
||||||
const handleValueBlur = useCallback(() => {
|
|
||||||
if (data.values.length > 0) {
|
|
||||||
tempValues.forEach((newValue, index) => {
|
|
||||||
if (index < data.values.length) {
|
|
||||||
const originalValue = data.values[index];
|
|
||||||
if (originalValue !== newValue) {
|
|
||||||
addUpdateValue(metaData.field, originalValue, newValue);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (newValue) {
|
|
||||||
addUpdateValue(metaData.field, '', newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
handleChange('values', [...new Set([...tempValues])]);
|
|
||||||
}, [handleChange, tempValues, metaData, data, addUpdateValue]);
|
|
||||||
|
|
||||||
// Handle delete operation
|
// Handle delete operation
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
@ -198,6 +200,14 @@ export const useManageValues = (props: IManageValuesProps) => {
|
|||||||
[addDeleteValue, metaData],
|
[addDeleteValue, metaData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleClearValues = useCallback(() => {
|
||||||
|
setTempValues([]);
|
||||||
|
setMetaData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
values: [],
|
||||||
|
}));
|
||||||
|
}, [setTempValues, setMetaData]);
|
||||||
|
|
||||||
const showDeleteModal = (item: string, callback: () => void) => {
|
const showDeleteModal = (item: string, callback: () => void) => {
|
||||||
setDeleteDialogContent({
|
setDeleteDialogContent({
|
||||||
visible: true,
|
visible: true,
|
||||||
@ -227,6 +237,7 @@ export const useManageValues = (props: IManageValuesProps) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
metaData,
|
metaData,
|
||||||
|
handleClearValues,
|
||||||
tempValues,
|
tempValues,
|
||||||
valueError,
|
valueError,
|
||||||
deleteDialogContent,
|
deleteDialogContent,
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { MetadataType } from './hook';
|
import { MetadataType } from './hooks/use-manage-modal';
|
||||||
export type IMetaDataReturnType = Record<string, Array<Array<string | number>>>;
|
export type IMetaDataReturnType = Record<
|
||||||
|
string,
|
||||||
|
| { type: string; values: Array<Array<string | number>> }
|
||||||
|
| Array<Array<string | number>>
|
||||||
|
>;
|
||||||
export type IMetaDataReturnJSONType = Record<
|
export type IMetaDataReturnJSONType = Record<
|
||||||
string,
|
string,
|
||||||
Array<string | number> | string
|
Array<string | number> | string
|
||||||
@ -32,11 +36,11 @@ export type IMetaDataReturnJSONSettings =
|
|||||||
|
|
||||||
export type MetadataValueType =
|
export type MetadataValueType =
|
||||||
| 'string'
|
| 'string'
|
||||||
| 'bool'
|
// | 'list<string>'
|
||||||
| 'enum'
|
// | 'bool'
|
||||||
|
// | 'enum'
|
||||||
| 'time'
|
| 'time'
|
||||||
| 'int'
|
| 'number';
|
||||||
| 'float';
|
|
||||||
|
|
||||||
export type IMetaDataTableData = {
|
export type IMetaDataTableData = {
|
||||||
field: string;
|
field: string;
|
||||||
@ -52,6 +56,7 @@ export type IBuiltInMetadataItem = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type IManageModalProps = {
|
export type IManageModalProps = {
|
||||||
|
documentIds?: string[];
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
isShowDescription?: boolean;
|
isShowDescription?: boolean;
|
||||||
isDeleteSingleValue?: boolean;
|
isDeleteSingleValue?: boolean;
|
||||||
@ -118,4 +123,5 @@ export type ShowManageMetadataModalProps = Partial<IManageModalProps> & {
|
|||||||
options?: ShowManageMetadataModalOptions;
|
options?: ShowManageMetadataModalOptions;
|
||||||
title?: ReactNode | string;
|
title?: ReactNode | string;
|
||||||
isDeleteSingleValue?: boolean;
|
isDeleteSingleValue?: boolean;
|
||||||
|
documentIds?: string[];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,372 @@
|
|||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import {
|
||||||
|
ListChevronsDownUp,
|
||||||
|
ListChevronsUpDown,
|
||||||
|
Settings,
|
||||||
|
Trash2,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
getMetadataValueTypeLabel,
|
||||||
|
MetadataDeleteMap,
|
||||||
|
MetadataType,
|
||||||
|
} from './hooks/use-manage-modal';
|
||||||
|
import { IMetaDataTableData } from './interface';
|
||||||
|
|
||||||
|
interface IUseMetadataColumns {
|
||||||
|
isDeleteSingleValue: boolean;
|
||||||
|
metadataType: MetadataType;
|
||||||
|
handleDeleteSingleValue: (field: string, value: string) => void;
|
||||||
|
handleDeleteSingleRow: (field: string) => void;
|
||||||
|
handleEditValueRow: (data: IMetaDataTableData, index: number) => void;
|
||||||
|
showTypeColumn: boolean;
|
||||||
|
isShowDescription: boolean;
|
||||||
|
setTableData: React.Dispatch<React.SetStateAction<IMetaDataTableData[]>>;
|
||||||
|
setShouldSave: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMetadataColumns = ({
|
||||||
|
isDeleteSingleValue,
|
||||||
|
metadataType,
|
||||||
|
handleDeleteSingleValue,
|
||||||
|
handleDeleteSingleRow,
|
||||||
|
handleEditValueRow,
|
||||||
|
isShowDescription,
|
||||||
|
setTableData,
|
||||||
|
setShouldSave,
|
||||||
|
}: IUseMetadataColumns) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
||||||
|
visible: false,
|
||||||
|
title: '',
|
||||||
|
name: '',
|
||||||
|
warnText: '',
|
||||||
|
onOk: () => {},
|
||||||
|
onCancel: () => {},
|
||||||
|
});
|
||||||
|
const [expanded, setExpanded] = useState(true);
|
||||||
|
const [editingValue, setEditingValue] = useState<{
|
||||||
|
field: string;
|
||||||
|
value: string;
|
||||||
|
newValue: string;
|
||||||
|
} | null>(null);
|
||||||
|
const isSettingsMode =
|
||||||
|
metadataType === MetadataType.Setting ||
|
||||||
|
metadataType === MetadataType.SingleFileSetting ||
|
||||||
|
metadataType === MetadataType.UpdateSingle;
|
||||||
|
|
||||||
|
const showTypeColumn = isSettingsMode;
|
||||||
|
const handleEditValue = (field: string, value: string) => {
|
||||||
|
setEditingValue({ field, value, newValue: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveEditedValue = useCallback(() => {
|
||||||
|
if (editingValue) {
|
||||||
|
setTableData((prev) => {
|
||||||
|
return prev.map((row) => {
|
||||||
|
if (row.field === editingValue.field) {
|
||||||
|
const updatedValues = row.values.map((v) =>
|
||||||
|
v === editingValue.value ? editingValue.newValue : v,
|
||||||
|
);
|
||||||
|
return { ...row, values: updatedValues };
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setEditingValue(null);
|
||||||
|
setShouldSave(true);
|
||||||
|
}
|
||||||
|
}, [editingValue, setTableData]);
|
||||||
|
|
||||||
|
const cancelEditValue = () => {
|
||||||
|
setEditingValue(null);
|
||||||
|
};
|
||||||
|
const hideDeleteModal = () => {
|
||||||
|
setDeleteDialogContent({
|
||||||
|
visible: false,
|
||||||
|
title: '',
|
||||||
|
name: '',
|
||||||
|
warnText: '',
|
||||||
|
onOk: () => {},
|
||||||
|
onCancel: () => {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const columns = useMemo(() => {
|
||||||
|
const cols: ColumnDef<IMetaDataTableData>[] = [
|
||||||
|
...(MetadataType.Manage === metadataType ||
|
||||||
|
MetadataType.UpdateSingle === metadataType
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
table.getIsAllPageRowsSelected() ||
|
||||||
|
(table.getIsSomePageRowsSelected() && 'indeterminate')
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
table.toggleAllPageRowsSelected(!!value)
|
||||||
|
}
|
||||||
|
aria-label="Select all"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
|
aria-label="Select row"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
accessorKey: 'field',
|
||||||
|
header: () => <span>{t('knowledgeDetails.metadata.field')}</span>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-sm text-accent-primary">
|
||||||
|
{row.getValue('field')}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// ...(showTypeColumn
|
||||||
|
// ? ([
|
||||||
|
// {
|
||||||
|
// accessorKey: 'valueType',
|
||||||
|
// header: () => <span>Type</span>,
|
||||||
|
// cell: ({ row }) => (
|
||||||
|
// <div className="text-sm">
|
||||||
|
// {getMetadataValueTypeLabel(
|
||||||
|
// row.original.valueType as IMetaDataTableData['valueType'],
|
||||||
|
// )}
|
||||||
|
// </div>
|
||||||
|
// ),
|
||||||
|
// },
|
||||||
|
// ] as ColumnDef<IMetaDataTableData>[])
|
||||||
|
// : []),
|
||||||
|
{
|
||||||
|
accessorKey: 'description',
|
||||||
|
header: () => <span>{t('knowledgeDetails.metadata.description')}</span>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-sm truncate max-w-32">
|
||||||
|
{row.getValue('description')}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'valueType',
|
||||||
|
header: () => <span>{t('knowledgeDetails.metadata.type')}</span>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-sm">
|
||||||
|
{getMetadataValueTypeLabel(
|
||||||
|
row.original.valueType as IMetaDataTableData['valueType'],
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'values',
|
||||||
|
header: () => (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span>{t('knowledgeDetails.metadata.values')}</span>
|
||||||
|
<div
|
||||||
|
className="ml-2 p-1 cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
setExpanded(!expanded);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{expanded ? (
|
||||||
|
<ListChevronsDownUp size={14} />
|
||||||
|
) : (
|
||||||
|
<ListChevronsUpDown size={14} />
|
||||||
|
)}
|
||||||
|
{expanded}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const values = row.getValue('values') as Array<string>;
|
||||||
|
// const supportsEnum = isMetadataValueTypeWithEnum(
|
||||||
|
// row.original.valueType,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (!supportsEnum || !Array.isArray(values) || values.length === 0) {
|
||||||
|
// return <div></div>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
const displayedValues = expanded ? values : values.slice(0, 2);
|
||||||
|
const hasMore = Array.isArray(values) && values.length > 2;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{displayedValues?.map((value: string) => {
|
||||||
|
const isEditing =
|
||||||
|
editingValue &&
|
||||||
|
editingValue.field === row.getValue('field') &&
|
||||||
|
editingValue.value === value;
|
||||||
|
|
||||||
|
return isEditing ? (
|
||||||
|
<div key={value}>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={editingValue.newValue}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEditingValue({
|
||||||
|
...editingValue,
|
||||||
|
newValue: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onBlur={saveEditedValue}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
saveEditedValue();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
cancelEditValue();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
// className="text-sm min-w-20 max-w-32 outline-none bg-transparent px-1 py-0.5"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
key={value}
|
||||||
|
variant={'ghost'}
|
||||||
|
className="border border-border-button"
|
||||||
|
onClick={() =>
|
||||||
|
handleEditValue(row.getValue('field'), value)
|
||||||
|
}
|
||||||
|
aria-label="Edit"
|
||||||
|
>
|
||||||
|
<div className="flex gap-1 items-center">
|
||||||
|
<div className="text-sm truncate max-w-24">{value}</div>
|
||||||
|
{isDeleteSingleValue && (
|
||||||
|
<Button
|
||||||
|
variant={'delete'}
|
||||||
|
className="p-0 bg-transparent"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
// showDeleteDialogContent(value, row);
|
||||||
|
setDeleteDialogContent({
|
||||||
|
visible: true,
|
||||||
|
title:
|
||||||
|
t('common.delete') +
|
||||||
|
' ' +
|
||||||
|
t('knowledgeDetails.metadata.value'),
|
||||||
|
name: value,
|
||||||
|
warnText:
|
||||||
|
MetadataDeleteMap(t)[
|
||||||
|
metadataType as MetadataType
|
||||||
|
].warnValueText,
|
||||||
|
onOk: () => {
|
||||||
|
hideDeleteModal();
|
||||||
|
handleDeleteSingleValue(
|
||||||
|
row.getValue('field'),
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
hideDeleteModal();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{hasMore && !expanded && (
|
||||||
|
<div className="text-text-secondary self-end">...</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'action',
|
||||||
|
header: () => <span>{t('knowledgeDetails.metadata.action')}</span>,
|
||||||
|
meta: {
|
||||||
|
cellClassName: 'w-12',
|
||||||
|
},
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className=" flex opacity-0 group-hover:opacity-100 gap-2">
|
||||||
|
<Button
|
||||||
|
variant={'ghost'}
|
||||||
|
className="bg-transparent px-1 py-0"
|
||||||
|
onClick={() => {
|
||||||
|
handleEditValueRow(row.original, row.index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Settings />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={'delete'}
|
||||||
|
className="p-0 bg-transparent"
|
||||||
|
onClick={() => {
|
||||||
|
setDeleteDialogContent({
|
||||||
|
visible: true,
|
||||||
|
title:
|
||||||
|
// t('common.delete') +
|
||||||
|
// ' ' +
|
||||||
|
// t('knowledgeDetails.metadata.metadata')
|
||||||
|
MetadataDeleteMap(t)[metadataType as MetadataType].title,
|
||||||
|
name: row.getValue('field'),
|
||||||
|
warnText:
|
||||||
|
MetadataDeleteMap(t)[metadataType as MetadataType]
|
||||||
|
.warnFieldText,
|
||||||
|
onOk: () => {
|
||||||
|
hideDeleteModal();
|
||||||
|
handleDeleteSingleRow(row.getValue('field'));
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
hideDeleteModal();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isShowDescription) {
|
||||||
|
return cols.filter((col) => {
|
||||||
|
if ('accessorKey' in col && col.accessorKey === 'description') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return cols;
|
||||||
|
}, [
|
||||||
|
handleDeleteSingleRow,
|
||||||
|
t,
|
||||||
|
handleDeleteSingleValue,
|
||||||
|
isShowDescription,
|
||||||
|
isDeleteSingleValue,
|
||||||
|
handleEditValueRow,
|
||||||
|
metadataType,
|
||||||
|
expanded,
|
||||||
|
editingValue,
|
||||||
|
saveEditedValue,
|
||||||
|
showTypeColumn,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
columns,
|
||||||
|
deleteDialogContent,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { BulkOperateBar } from '@/components/bulk-operate-bar';
|
||||||
import {
|
import {
|
||||||
ConfirmDeleteDialog,
|
ConfirmDeleteDialog,
|
||||||
ConfirmDeleteDialogNode,
|
ConfirmDeleteDialogNode,
|
||||||
@ -5,7 +6,6 @@ import {
|
|||||||
import { EmptyType } from '@/components/empty/constant';
|
import { EmptyType } from '@/components/empty/constant';
|
||||||
import Empty from '@/components/empty/empty';
|
import Empty from '@/components/empty/empty';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Modal } from '@/components/ui/modal/modal';
|
import { Modal } from '@/components/ui/modal/modal';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import {
|
import {
|
||||||
@ -18,9 +18,9 @@ import {
|
|||||||
} from '@/components/ui/table';
|
} from '@/components/ui/table';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
|
import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection';
|
||||||
import { Routes } from '@/routes';
|
import { Routes } from '@/routes';
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
|
||||||
flexRender,
|
flexRender,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
getFilteredRowModel,
|
getFilteredRowModel,
|
||||||
@ -28,28 +28,22 @@ import {
|
|||||||
getSortedRowModel,
|
getSortedRowModel,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import {
|
import { Plus, Trash2 } from 'lucide-react';
|
||||||
ListChevronsDownUp,
|
|
||||||
ListChevronsUpDown,
|
|
||||||
Plus,
|
|
||||||
Settings,
|
|
||||||
Trash2,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useHandleMenuClick } from '../../sidebar/hooks';
|
import { useHandleMenuClick } from '../../sidebar/hooks';
|
||||||
import {
|
import {
|
||||||
MetadataDeleteMap,
|
|
||||||
MetadataType,
|
|
||||||
getMetadataValueTypeLabel,
|
getMetadataValueTypeLabel,
|
||||||
isMetadataValueTypeWithEnum,
|
MetadataType,
|
||||||
useManageMetaDataModal,
|
useManageMetaDataModal,
|
||||||
|
useOperateData,
|
||||||
} from './hooks/use-manage-modal';
|
} from './hooks/use-manage-modal';
|
||||||
import {
|
import {
|
||||||
IBuiltInMetadataItem,
|
IBuiltInMetadataItem,
|
||||||
IManageModalProps,
|
IManageModalProps,
|
||||||
IMetaDataTableData,
|
IMetaDataTableData,
|
||||||
} from './interface';
|
} from './interface';
|
||||||
|
import { useMetadataColumns } from './manage-modal-column';
|
||||||
import { ManageValuesModal } from './manage-values-modal';
|
import { ManageValuesModal } from './manage-values-modal';
|
||||||
|
|
||||||
type MetadataSettingsTab = 'generation' | 'built-in';
|
type MetadataSettingsTab = 'generation' | 'built-in';
|
||||||
@ -71,6 +65,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
isVerticalShowValue = true,
|
isVerticalShowValue = true,
|
||||||
builtInMetadata,
|
builtInMetadata,
|
||||||
success,
|
success,
|
||||||
|
documentIds,
|
||||||
} = props;
|
} = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [valueData, setValueData] = useState<IMetaDataTableData>({
|
const [valueData, setValueData] = useState<IMetaDataTableData>({
|
||||||
@ -80,25 +75,11 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
valueType: 'string',
|
valueType: 'string',
|
||||||
});
|
});
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(true);
|
|
||||||
const [activeTab, setActiveTab] = useState<MetadataSettingsTab>('generation');
|
const [activeTab, setActiveTab] = useState<MetadataSettingsTab>('generation');
|
||||||
const [currentValueIndex, setCurrentValueIndex] = useState<number>(0);
|
const [currentValueIndex, setCurrentValueIndex] = useState<number>(0);
|
||||||
const [builtInSelection, setBuiltInSelection] = useState<
|
const [builtInSelection, setBuiltInSelection] = useState<
|
||||||
IBuiltInMetadataItem[]
|
IBuiltInMetadataItem[]
|
||||||
>([]);
|
>([]);
|
||||||
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
|
||||||
visible: false,
|
|
||||||
title: '',
|
|
||||||
name: '',
|
|
||||||
warnText: '',
|
|
||||||
onOk: () => {},
|
|
||||||
onCancel: () => {},
|
|
||||||
});
|
|
||||||
const [editingValue, setEditingValue] = useState<{
|
|
||||||
field: string;
|
|
||||||
value: string;
|
|
||||||
newValue: string;
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
tableData,
|
tableData,
|
||||||
@ -108,7 +89,13 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
handleSave,
|
handleSave,
|
||||||
addUpdateValue,
|
addUpdateValue,
|
||||||
addDeleteValue,
|
addDeleteValue,
|
||||||
} = useManageMetaDataModal(originalTableData, metadataType, otherData);
|
handleDeleteBatchRow,
|
||||||
|
} = useManageMetaDataModal(
|
||||||
|
originalTableData,
|
||||||
|
metadataType,
|
||||||
|
otherData,
|
||||||
|
documentIds,
|
||||||
|
);
|
||||||
const { handleMenuClick } = useHandleMenuClick();
|
const { handleMenuClick } = useHandleMenuClick();
|
||||||
const [shouldSave, setShouldSave] = useState(false);
|
const [shouldSave, setShouldSave] = useState(false);
|
||||||
const {
|
const {
|
||||||
@ -116,20 +103,12 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
showModal: showManageValuesModal,
|
showModal: showManageValuesModal,
|
||||||
hideModal: hideManageValuesModal,
|
hideModal: hideManageValuesModal,
|
||||||
} = useSetModalState();
|
} = useSetModalState();
|
||||||
const hideDeleteModal = () => {
|
|
||||||
setDeleteDialogContent({
|
|
||||||
visible: false,
|
|
||||||
title: '',
|
|
||||||
name: '',
|
|
||||||
warnText: '',
|
|
||||||
onOk: () => {},
|
|
||||||
onCancel: () => {},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const isSettingsMode =
|
const isSettingsMode =
|
||||||
metadataType === MetadataType.Setting ||
|
metadataType === MetadataType.Setting ||
|
||||||
metadataType === MetadataType.SingleFileSetting;
|
metadataType === MetadataType.SingleFileSetting ||
|
||||||
|
metadataType === MetadataType.UpdateSingle;
|
||||||
|
|
||||||
const showTypeColumn = isSettingsMode;
|
const showTypeColumn = isSettingsMode;
|
||||||
const builtInRows = useMemo(
|
const builtInRows = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@ -183,31 +162,6 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
[builtInSelection],
|
[builtInSelection],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleEditValue = (field: string, value: string) => {
|
|
||||||
setEditingValue({ field, value, newValue: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveEditedValue = useCallback(() => {
|
|
||||||
if (editingValue) {
|
|
||||||
setTableData((prev) => {
|
|
||||||
return prev.map((row) => {
|
|
||||||
if (row.field === editingValue.field) {
|
|
||||||
const updatedValues = row.values.map((v) =>
|
|
||||||
v === editingValue.value ? editingValue.newValue : v,
|
|
||||||
);
|
|
||||||
return { ...row, values: updatedValues };
|
|
||||||
}
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
setEditingValue(null);
|
|
||||||
setShouldSave(true);
|
|
||||||
}
|
|
||||||
}, [editingValue, setTableData]);
|
|
||||||
|
|
||||||
const cancelEditValue = () => {
|
|
||||||
setEditingValue(null);
|
|
||||||
};
|
|
||||||
const handAddValueRow = () => {
|
const handAddValueRow = () => {
|
||||||
setValueData({
|
setValueData({
|
||||||
field: '',
|
field: '',
|
||||||
@ -226,229 +180,19 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
},
|
},
|
||||||
[showManageValuesModal],
|
[showManageValuesModal],
|
||||||
);
|
);
|
||||||
|
const { rowSelection, rowSelectionIsEmpty, setRowSelection, selectedCount } =
|
||||||
const columns: ColumnDef<IMetaDataTableData>[] = useMemo(() => {
|
useRowSelection();
|
||||||
const cols: ColumnDef<IMetaDataTableData>[] = [
|
const { columns, deleteDialogContent } = useMetadataColumns({
|
||||||
{
|
isDeleteSingleValue: !!isDeleteSingleValue,
|
||||||
accessorKey: 'field',
|
|
||||||
header: () => <span>{t('knowledgeDetails.metadata.field')}</span>,
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="text-sm text-accent-primary">
|
|
||||||
{row.getValue('field')}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
...(showTypeColumn
|
|
||||||
? ([
|
|
||||||
{
|
|
||||||
accessorKey: 'valueType',
|
|
||||||
header: () => <span>Type</span>,
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="text-sm">
|
|
||||||
{getMetadataValueTypeLabel(
|
|
||||||
row.original.valueType as IMetaDataTableData['valueType'],
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
] as ColumnDef<IMetaDataTableData>[])
|
|
||||||
: []),
|
|
||||||
{
|
|
||||||
accessorKey: 'description',
|
|
||||||
header: () => <span>{t('knowledgeDetails.metadata.description')}</span>,
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="text-sm truncate max-w-32">
|
|
||||||
{row.getValue('description')}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'values',
|
|
||||||
header: () => (
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span>{t('knowledgeDetails.metadata.values')}</span>
|
|
||||||
<div
|
|
||||||
className="ml-2 p-1 cursor-pointer"
|
|
||||||
onClick={() => {
|
|
||||||
setExpanded(!expanded);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{expanded ? (
|
|
||||||
<ListChevronsDownUp size={14} />
|
|
||||||
) : (
|
|
||||||
<ListChevronsUpDown size={14} />
|
|
||||||
)}
|
|
||||||
{expanded}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const values = row.getValue('values') as Array<string>;
|
|
||||||
const supportsEnum = isMetadataValueTypeWithEnum(
|
|
||||||
row.original.valueType,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!supportsEnum || !Array.isArray(values) || values.length === 0) {
|
|
||||||
return <div></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayedValues = expanded ? values : values.slice(0, 2);
|
|
||||||
const hasMore = Array.isArray(values) && values.length > 2;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<div className="flex flex-wrap gap-1">
|
|
||||||
{displayedValues?.map((value: string) => {
|
|
||||||
const isEditing =
|
|
||||||
editingValue &&
|
|
||||||
editingValue.field === row.getValue('field') &&
|
|
||||||
editingValue.value === value;
|
|
||||||
|
|
||||||
return isEditing ? (
|
|
||||||
<div key={value}>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={editingValue.newValue}
|
|
||||||
onChange={(e) =>
|
|
||||||
setEditingValue({
|
|
||||||
...editingValue,
|
|
||||||
newValue: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onBlur={saveEditedValue}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
saveEditedValue();
|
|
||||||
} else if (e.key === 'Escape') {
|
|
||||||
cancelEditValue();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
autoFocus
|
|
||||||
// className="text-sm min-w-20 max-w-32 outline-none bg-transparent px-1 py-0.5"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
key={value}
|
|
||||||
variant={'ghost'}
|
|
||||||
className="border border-border-button"
|
|
||||||
onClick={() =>
|
|
||||||
handleEditValue(row.getValue('field'), value)
|
|
||||||
}
|
|
||||||
aria-label="Edit"
|
|
||||||
>
|
|
||||||
<div className="flex gap-1 items-center">
|
|
||||||
<div className="text-sm truncate max-w-24">{value}</div>
|
|
||||||
{isDeleteSingleValue && (
|
|
||||||
<Button
|
|
||||||
variant={'delete'}
|
|
||||||
className="p-0 bg-transparent"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setDeleteDialogContent({
|
|
||||||
visible: true,
|
|
||||||
title:
|
|
||||||
t('common.delete') +
|
|
||||||
' ' +
|
|
||||||
t('knowledgeDetails.metadata.value'),
|
|
||||||
name: value,
|
|
||||||
warnText:
|
|
||||||
MetadataDeleteMap(t)[
|
|
||||||
metadataType as MetadataType
|
|
||||||
].warnValueText,
|
|
||||||
onOk: () => {
|
|
||||||
hideDeleteModal();
|
|
||||||
handleDeleteSingleValue(
|
|
||||||
row.getValue('field'),
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
hideDeleteModal();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Trash2 />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{hasMore && !expanded && (
|
|
||||||
<div className="text-text-secondary self-end">...</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'action',
|
|
||||||
header: () => <span>{t('knowledgeDetails.metadata.action')}</span>,
|
|
||||||
meta: {
|
|
||||||
cellClassName: 'w-12',
|
|
||||||
},
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className=" flex opacity-0 group-hover:opacity-100 gap-2">
|
|
||||||
<Button
|
|
||||||
variant={'ghost'}
|
|
||||||
className="bg-transparent px-1 py-0"
|
|
||||||
onClick={() => {
|
|
||||||
handleEditValueRow(row.original, row.index);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Settings />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={'delete'}
|
|
||||||
className="p-0 bg-transparent"
|
|
||||||
onClick={() => {
|
|
||||||
setDeleteDialogContent({
|
|
||||||
visible: true,
|
|
||||||
title:
|
|
||||||
// t('common.delete') +
|
|
||||||
// ' ' +
|
|
||||||
// t('knowledgeDetails.metadata.metadata')
|
|
||||||
MetadataDeleteMap(t)[metadataType as MetadataType].title,
|
|
||||||
name: row.getValue('field'),
|
|
||||||
warnText:
|
|
||||||
MetadataDeleteMap(t)[metadataType as MetadataType]
|
|
||||||
.warnFieldText,
|
|
||||||
onOk: () => {
|
|
||||||
hideDeleteModal();
|
|
||||||
handleDeleteSingleRow(row.getValue('field'));
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
hideDeleteModal();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Trash2 />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
if (!isShowDescription) {
|
|
||||||
return cols.filter((col) => col.accessorKey !== 'description');
|
|
||||||
}
|
|
||||||
return cols;
|
|
||||||
}, [
|
|
||||||
handleDeleteSingleRow,
|
|
||||||
t,
|
|
||||||
handleDeleteSingleValue,
|
|
||||||
isShowDescription,
|
|
||||||
isDeleteSingleValue,
|
|
||||||
handleEditValueRow,
|
|
||||||
metadataType,
|
metadataType,
|
||||||
expanded,
|
setTableData,
|
||||||
editingValue,
|
handleDeleteSingleValue,
|
||||||
saveEditedValue,
|
handleDeleteSingleRow,
|
||||||
|
handleEditValueRow,
|
||||||
|
isShowDescription,
|
||||||
showTypeColumn,
|
showTypeColumn,
|
||||||
]);
|
setShouldSave,
|
||||||
|
});
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: tableData as IMetaDataTableData[],
|
data: tableData as IMetaDataTableData[],
|
||||||
@ -457,7 +201,11 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
manualPagination: true,
|
manualPagination: true,
|
||||||
|
state: {
|
||||||
|
rowSelection,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSaveValues = (data: IMetaDataTableData) => {
|
const handleSaveValues = (data: IMetaDataTableData) => {
|
||||||
@ -506,7 +254,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
handleSave({ callback: () => {}, builtInMetadata: builtInSelection });
|
handleSave({ callback: () => {}, builtInMetadata: builtInSelection });
|
||||||
setShouldSave(false);
|
setShouldSave(false);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
console.log('shouldSave');
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [tableData, shouldSave, handleSave, builtInSelection]);
|
}, [tableData, shouldSave, handleSave, builtInSelection]);
|
||||||
@ -515,6 +263,25 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
return tableData.map((item) => item.field);
|
return tableData.map((item) => item.field);
|
||||||
}, [tableData]);
|
}, [tableData]);
|
||||||
|
|
||||||
|
const { handleDelete } = useOperateData({
|
||||||
|
rowSelection,
|
||||||
|
list: tableData,
|
||||||
|
handleDeleteBatchRow,
|
||||||
|
});
|
||||||
|
|
||||||
|
const operateList = [
|
||||||
|
{
|
||||||
|
id: 'delete',
|
||||||
|
label: t('common.delete'),
|
||||||
|
icon: <Trash2 />,
|
||||||
|
onClick: async () => {
|
||||||
|
await handleDelete();
|
||||||
|
// if (code === 0) {
|
||||||
|
// setRowSelection({});
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
@ -528,7 +295,6 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
callback: hideModal,
|
callback: hideModal,
|
||||||
builtInMetadata: builtInSelection,
|
builtInMetadata: builtInSelection,
|
||||||
});
|
});
|
||||||
console.log('data', res);
|
|
||||||
success?.(res);
|
success?.(res);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -559,15 +325,25 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{metadataType === MetadataType.Setting ? (
|
|
||||||
|
{rowSelectionIsEmpty || (
|
||||||
|
<BulkOperateBar
|
||||||
|
list={operateList}
|
||||||
|
count={selectedCount}
|
||||||
|
></BulkOperateBar>
|
||||||
|
)}
|
||||||
|
{metadataType === MetadataType.Setting ||
|
||||||
|
metadataType === MetadataType.SingleFileSetting ? (
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onValueChange={(v) => setActiveTab(v as MetadataSettingsTab)}
|
onValueChange={(v) => setActiveTab(v as MetadataSettingsTab)}
|
||||||
>
|
>
|
||||||
<TabsList className="w-fit">
|
<TabsList className="w-fit">
|
||||||
<TabsTrigger value="generation">Generation</TabsTrigger>
|
<TabsTrigger value="generation">
|
||||||
|
{t('knowledgeDetails.metadata.generation')}
|
||||||
|
</TabsTrigger>
|
||||||
<TabsTrigger value="built-in">
|
<TabsTrigger value="built-in">
|
||||||
{t('knowledgeConfiguration.builtIn')}
|
{t('knowledgeDetails.metadata.builtIn')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="generation">
|
<TabsContent value="generation">
|
||||||
|
|||||||
@ -2,49 +2,87 @@ import {
|
|||||||
ConfirmDeleteDialog,
|
ConfirmDeleteDialog,
|
||||||
ConfirmDeleteDialogNode,
|
ConfirmDeleteDialogNode,
|
||||||
} from '@/components/confirm-delete-dialog';
|
} from '@/components/confirm-delete-dialog';
|
||||||
|
import { DynamicForm, FormFieldType } from '@/components/dynamic-form';
|
||||||
import EditTag from '@/components/edit-tag';
|
import EditTag from '@/components/edit-tag';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { FormLabel } from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { DateInput } from '@/components/ui/input-date';
|
||||||
import { Modal } from '@/components/ui/modal/modal';
|
import { Modal } from '@/components/ui/modal/modal';
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
import { formatPureDate } from '@/utils/date';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import dayjs from 'dayjs';
|
||||||
import { Plus, Trash2 } from 'lucide-react';
|
import { Plus, Trash2 } from 'lucide-react';
|
||||||
import { memo } from 'react';
|
import { memo, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import { metadataValueTypeOptions } from './hooks/use-manage-modal';
|
||||||
isMetadataValueTypeWithEnum,
|
|
||||||
metadataValueTypeOptions,
|
|
||||||
} from './hooks/use-manage-modal';
|
|
||||||
import { useManageValues } from './hooks/use-manage-values-modal';
|
import { useManageValues } from './hooks/use-manage-values-modal';
|
||||||
import { IManageValuesProps } from './interface';
|
import { IManageValuesProps, MetadataValueType } from './interface';
|
||||||
|
|
||||||
// Create a separate input component, wrapped with memo to avoid unnecessary re-renders
|
// Create a separate input component, wrapped with memo to avoid unnecessary re-renders
|
||||||
const ValueInputItem = memo(
|
const ValueInputItem = memo(
|
||||||
({
|
({
|
||||||
item,
|
item,
|
||||||
index,
|
index,
|
||||||
|
type,
|
||||||
onValueChange,
|
onValueChange,
|
||||||
onDelete,
|
onDelete,
|
||||||
onBlur,
|
onBlur,
|
||||||
}: {
|
}: {
|
||||||
item: string;
|
item: string;
|
||||||
index: number;
|
index: number;
|
||||||
onValueChange: (index: number, value: string) => void;
|
type: MetadataValueType;
|
||||||
|
onValueChange: (index: number, value: string, isUpdate?: boolean) => void;
|
||||||
onDelete: (index: number) => void;
|
onDelete: (index: number) => void;
|
||||||
onBlur: (index: number) => void;
|
onBlur: (index: number) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const value = useMemo(() => {
|
||||||
|
if (type === 'time') {
|
||||||
|
if (item) {
|
||||||
|
try {
|
||||||
|
// Using dayjs to parse date strings in various formats including DD/MM/YYYY
|
||||||
|
const parsedDate = dayjs(item, [
|
||||||
|
'YYYY-MM-DD',
|
||||||
|
'DD/MM/YYYY',
|
||||||
|
'MM/DD/YYYY',
|
||||||
|
'DD-MM-YYYY',
|
||||||
|
'MM-DD-YYYY',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!parsedDate.isValid()) {
|
||||||
|
console.error('Invalid date format:', item);
|
||||||
|
return undefined; // Return current date as fallback
|
||||||
|
}
|
||||||
|
return parsedDate.toDate();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing date:', item, error);
|
||||||
|
return undefined; // Return current date as fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}, [item, type]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`value-item-${index}`}
|
key={`value-item-${index}`}
|
||||||
className="flex items-center gap-2.5 w-full"
|
className="flex items-center gap-2.5 w-full"
|
||||||
>
|
>
|
||||||
<div className="flex-1 w-full">
|
<div className="flex-1 w-full">
|
||||||
<Input
|
{type === 'time' && (
|
||||||
value={item}
|
<DateInput
|
||||||
onChange={(e) => onValueChange(index, e.target.value)}
|
value={value as Date}
|
||||||
onBlur={() => onBlur(index)}
|
onChange={(value) => {
|
||||||
/>
|
onValueChange(index, formatPureDate(value), true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type !== 'time' && (
|
||||||
|
<Input
|
||||||
|
value={value as string}
|
||||||
|
type={type === 'number' ? 'number' : 'text'}
|
||||||
|
onChange={(e) => onValueChange(index, e.target.value)}
|
||||||
|
onBlur={() => onBlur(index)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@ -59,12 +97,15 @@ const ValueInputItem = memo(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ValueInputItem.displayName = 'ValueInputItem';
|
||||||
|
|
||||||
export const ManageValuesModal = (props: IManageValuesProps) => {
|
export const ManageValuesModal = (props: IManageValuesProps) => {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
isEditField,
|
isEditField,
|
||||||
visible,
|
visible,
|
||||||
isAddValue,
|
isAddValue,
|
||||||
|
isShowValueSwitch,
|
||||||
isShowDescription,
|
isShowDescription,
|
||||||
isVerticalShowValue,
|
isVerticalShowValue,
|
||||||
isShowType,
|
isShowType,
|
||||||
@ -74,6 +115,7 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
tempValues,
|
tempValues,
|
||||||
valueError,
|
valueError,
|
||||||
deleteDialogContent,
|
deleteDialogContent,
|
||||||
|
handleClearValues,
|
||||||
handleChange,
|
handleChange,
|
||||||
handleValueChange,
|
handleValueChange,
|
||||||
handleValueBlur,
|
handleValueBlur,
|
||||||
@ -84,7 +126,79 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
handleHideModal,
|
handleHideModal,
|
||||||
} = useManageValues(props);
|
} = useManageValues(props);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const canShowValues = isMetadataValueTypeWithEnum(metaData.valueType);
|
|
||||||
|
const formRef = useRef<any>();
|
||||||
|
|
||||||
|
const [valueType, setValueType] = useState<MetadataValueType>(
|
||||||
|
metaData.valueType || 'string',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Define form fields based on component properties
|
||||||
|
const formFields = [
|
||||||
|
...(isEditField
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: 'field',
|
||||||
|
label: t('knowledgeDetails.metadata.fieldName'),
|
||||||
|
type: FormFieldType.Text,
|
||||||
|
required: true,
|
||||||
|
validation: {
|
||||||
|
pattern: /^[a-zA-Z_]*$/,
|
||||||
|
message: t('knowledgeDetails.metadata.fieldNameInvalid'),
|
||||||
|
},
|
||||||
|
defaultValue: metaData.field,
|
||||||
|
onChange: (value: string) => handleChange('field', value),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(isShowType
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: 'valueType',
|
||||||
|
label: 'Type',
|
||||||
|
type: FormFieldType.Select,
|
||||||
|
options: metadataValueTypeOptions,
|
||||||
|
defaultValue: metaData.valueType || 'string',
|
||||||
|
onChange: (value: string) => {
|
||||||
|
setValueType(value as MetadataValueType);
|
||||||
|
handleChange('valueType', value);
|
||||||
|
handleClearValues();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(isShowDescription
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
label: t('knowledgeDetails.metadata.description'),
|
||||||
|
type: FormFieldType.Textarea,
|
||||||
|
tooltip: t('knowledgeDetails.metadata.descriptionTip'),
|
||||||
|
defaultValue: metaData.description,
|
||||||
|
className: 'mt-2',
|
||||||
|
onChange: (value: string) => handleChange('description', value),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(isShowValueSwitch
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: 'restrictDefinedValues',
|
||||||
|
label: t('knowledgeDetails.metadata.restrictDefinedValues'),
|
||||||
|
tooltip: t('knowledgeDetails.metadata.restrictDefinedValuesTip'),
|
||||||
|
type: FormFieldType.Switch,
|
||||||
|
defaultValue: metaData.restrictDefinedValues || false,
|
||||||
|
onChange: (value: boolean) =>
|
||||||
|
handleChange('restrictDefinedValues', value),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
const handleSubmit = () => {
|
||||||
|
handleSave();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -93,7 +207,7 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
onCancel={handleHideModal}
|
onCancel={handleHideModal}
|
||||||
className="!w-[460px]"
|
className="!w-[460px]"
|
||||||
okText={t('common.confirm')}
|
okText={t('common.confirm')}
|
||||||
onOk={handleSave}
|
onOk={() => formRef.current?.submit(handleSubmit)}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
footer={null}
|
footer={null}
|
||||||
>
|
>
|
||||||
@ -103,52 +217,18 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
{metaData.field}
|
{metaData.field}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isEditField && (
|
|
||||||
<div className="flex flex-col gap-2">
|
{formFields.length > 0 && (
|
||||||
<div>{t('knowledgeDetails.metadata.fieldName')}</div>
|
<DynamicForm.Root
|
||||||
<div>
|
ref={formRef}
|
||||||
<Input
|
fields={formFields}
|
||||||
value={metaData.field}
|
onSubmit={handleSubmit}
|
||||||
onChange={(e) => {
|
className="space-y-4"
|
||||||
const value = e.target?.value || '';
|
/>
|
||||||
if (/^[a-zA-Z_]*$/.test(value)) {
|
|
||||||
handleChange('field', value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="text-state-error text-sm">{valueError.field}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{isShowType && (
|
|
||||||
<div className="flex flex-col gap-2">
|
{((metaData.restrictDefinedValues && isShowValueSwitch) ||
|
||||||
<div>Type</div>
|
!isShowValueSwitch) && (
|
||||||
<RAGFlowSelect
|
|
||||||
value={metaData.valueType || 'string'}
|
|
||||||
options={metadataValueTypeOptions}
|
|
||||||
onChange={(value) => handleChange('valueType', value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isShowDescription && (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<FormLabel
|
|
||||||
className="text-text-primary text-base"
|
|
||||||
tooltip={t('knowledgeDetails.metadata.descriptionTip')}
|
|
||||||
>
|
|
||||||
{t('knowledgeDetails.metadata.description')}
|
|
||||||
</FormLabel>
|
|
||||||
<div>
|
|
||||||
<Textarea
|
|
||||||
value={metaData.description}
|
|
||||||
onChange={(e) => {
|
|
||||||
handleChange('description', e.target?.value || '');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{canShowValues && (
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div>{t('knowledgeDetails.metadata.values')}</div>
|
<div>{t('knowledgeDetails.metadata.values')}</div>
|
||||||
@ -172,6 +252,7 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
key={`value-item-${index}`}
|
key={`value-item-${index}`}
|
||||||
item={item}
|
item={item}
|
||||||
index={index}
|
index={index}
|
||||||
|
type={valueType || 'string'}
|
||||||
onValueChange={handleValueChange}
|
onValueChange={handleValueChange}
|
||||||
onDelete={(idx: number) => {
|
onDelete={(idx: number) => {
|
||||||
showDeleteModal(item, () => {
|
showDeleteModal(item, () => {
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
import { BulkOperateBar } from '@/components/bulk-operate-bar';
|
import {
|
||||||
|
BulkOperateBar,
|
||||||
|
BulkOperateItemType,
|
||||||
|
} from '@/components/bulk-operate-bar';
|
||||||
import { FileUploadDialog } from '@/components/file-upload-dialog';
|
import { FileUploadDialog } from '@/components/file-upload-dialog';
|
||||||
import ListFilterBar from '@/components/list-filter-bar';
|
import ListFilterBar from '@/components/list-filter-bar';
|
||||||
import { RenameDialog } from '@/components/rename-dialog';
|
import { RenameDialog } from '@/components/rename-dialog';
|
||||||
@ -13,7 +16,7 @@ import {
|
|||||||
import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection';
|
import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection';
|
||||||
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||||
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
||||||
import { Pen, Upload } from 'lucide-react';
|
import { Upload } from 'lucide-react';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -95,6 +98,38 @@ export default function Dataset() {
|
|||||||
rowSelection,
|
rowSelection,
|
||||||
setRowSelection,
|
setRowSelection,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleAddMetadataWithDocuments = () => {
|
||||||
|
showManageMetadataModal({
|
||||||
|
type: MetadataType.Manage,
|
||||||
|
isCanAdd: false,
|
||||||
|
isEditField: false,
|
||||||
|
isDeleteSingleValue: true,
|
||||||
|
isAddValue: true,
|
||||||
|
title: (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="text-base font-normal">
|
||||||
|
{t('knowledgeDetails.metadata.manageMetadata')}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-text-secondary">
|
||||||
|
{t('knowledgeDetails.metadata.manageMetadataForDataset')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
documentIds: documents.map((doc) => doc.id),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedList = list.map((item) => {
|
||||||
|
if (item.id === 'batch-metadata') {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
onClick: handleAddMetadataWithDocuments,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="absolute top-4 right-5">
|
<div className="absolute top-4 right-5">
|
||||||
@ -118,37 +153,37 @@ export default function Dataset() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
preChildren={
|
// preChildren={
|
||||||
<Button
|
// <Button
|
||||||
variant={'ghost'}
|
// variant={'ghost'}
|
||||||
className="border border-border-button"
|
// className="border border-border-button"
|
||||||
onClick={() =>
|
// onClick={() =>
|
||||||
showManageMetadataModal({
|
// showManageMetadataModal({
|
||||||
type: MetadataType.Manage,
|
// type: MetadataType.Manage,
|
||||||
isCanAdd: false,
|
// isCanAdd: false,
|
||||||
isEditField: true,
|
// isEditField: false,
|
||||||
isDeleteSingleValue: true,
|
// isDeleteSingleValue: true,
|
||||||
title: (
|
// title: (
|
||||||
<div className="flex flex-col gap-2">
|
// <div className="flex flex-col gap-2">
|
||||||
<div className="text-base font-normal">
|
// <div className="text-base font-normal">
|
||||||
{t('knowledgeDetails.metadata.manageMetadata')}
|
// {t('knowledgeDetails.metadata.manageMetadata')}
|
||||||
</div>
|
// </div>
|
||||||
<div className="text-sm text-text-secondary">
|
// <div className="text-sm text-text-secondary">
|
||||||
{t(
|
// {t(
|
||||||
'knowledgeDetails.metadata.manageMetadataForDataset',
|
// 'knowledgeDetails.metadata.manageMetadataForDataset',
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
),
|
// ),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
>
|
// >
|
||||||
<div className="flex gap-1 items-center">
|
// <div className="flex gap-1 items-center">
|
||||||
<Pen size={14} />
|
// <Pen size={14} />
|
||||||
{t('knowledgeDetails.metadata.metadata')}
|
// {t('knowledgeDetails.metadata.metadata')}
|
||||||
</div>
|
// </div>
|
||||||
</Button>
|
// </Button>
|
||||||
}
|
// }
|
||||||
>
|
>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
@ -169,7 +204,10 @@ export default function Dataset() {
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
{rowSelectionIsEmpty || (
|
{rowSelectionIsEmpty || (
|
||||||
<BulkOperateBar list={list} count={selectedCount}></BulkOperateBar>
|
<BulkOperateBar
|
||||||
|
list={updatedList as BulkOperateItemType[]}
|
||||||
|
count={selectedCount}
|
||||||
|
></BulkOperateBar>
|
||||||
)}
|
)}
|
||||||
<DatasetTable
|
<DatasetTable
|
||||||
documents={documents}
|
documents={documents}
|
||||||
@ -218,6 +256,7 @@ export default function Dataset() {
|
|||||||
isEditField={metadataConfig.isEditField}
|
isEditField={metadataConfig.isEditField}
|
||||||
isDeleteSingleValue={metadataConfig.isDeleteSingleValue}
|
isDeleteSingleValue={metadataConfig.isDeleteSingleValue}
|
||||||
type={metadataConfig.type}
|
type={metadataConfig.type}
|
||||||
|
documentIds={metadataConfig.documentIds}
|
||||||
otherData={metadataConfig.record}
|
otherData={metadataConfig.record}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
useSetDocumentStatus,
|
useSetDocumentStatus,
|
||||||
} from '@/hooks/use-document-request';
|
} from '@/hooks/use-document-request';
|
||||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||||
import { Ban, CircleCheck, CircleX, Play, Trash2 } from 'lucide-react';
|
import { Ban, CircleCheck, CircleX, PenIcon, Play, Trash2 } from 'lucide-react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@ -140,6 +140,11 @@ export function useBulkOperateDataset({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'batch-metadata',
|
||||||
|
label: t('knowledgeDetails.metadata.metadata'),
|
||||||
|
icon: <PenIcon />,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return { chunkNum, list, visible, hideModal, showModal, handleRunClick };
|
return { chunkNum, list, visible, hideModal, showModal, handleRunClick };
|
||||||
|
|||||||
@ -16,10 +16,7 @@ import { formatDate } from '@/utils/date';
|
|||||||
import { ColumnDef } from '@tanstack/table-core';
|
import { ColumnDef } from '@tanstack/table-core';
|
||||||
import { ArrowUpDown, MonitorUp } from 'lucide-react';
|
import { ArrowUpDown, MonitorUp } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import { MetadataType } from '../components/metedata/hooks/use-manage-modal';
|
||||||
MetadataType,
|
|
||||||
util,
|
|
||||||
} from '../components/metedata/hooks/use-manage-modal';
|
|
||||||
import { ShowManageMetadataModalProps } from '../components/metedata/interface';
|
import { ShowManageMetadataModalProps } from '../components/metedata/interface';
|
||||||
import { DatasetActionCell } from './dataset-action-cell';
|
import { DatasetActionCell } from './dataset-action-cell';
|
||||||
import { ParsingStatusCell } from './parsing-status-cell';
|
import { ParsingStatusCell } from './parsing-status-cell';
|
||||||
@ -181,12 +178,12 @@ export function useDatasetTableColumns({
|
|||||||
className="capitalize cursor-pointer"
|
className="capitalize cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
showManageMetadataModal({
|
showManageMetadataModal({
|
||||||
metadata: util.JSONToMetaDataTableData(
|
// metadata: util.JSONToMetaDataTableData(
|
||||||
row.original.meta_fields || {},
|
// row.original.meta_fields || {},
|
||||||
),
|
// ),
|
||||||
isCanAdd: true,
|
isCanAdd: true,
|
||||||
type: MetadataType.UpdateSingle,
|
type: MetadataType.UpdateSingle,
|
||||||
record: row,
|
record: row.original,
|
||||||
title: (
|
title: (
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<div className="text-base font-normal">
|
<div className="text-base font-normal">
|
||||||
@ -199,6 +196,7 @@ export function useDatasetTableColumns({
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
isDeleteSingleValue: true,
|
isDeleteSingleValue: true,
|
||||||
|
documentIds: [row.original.id],
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -264,10 +264,22 @@ export const listDocument = (
|
|||||||
export const documentFilter = (kb_id: string) =>
|
export const documentFilter = (kb_id: string) =>
|
||||||
request.post(api.get_dataset_filter, { kb_id });
|
request.post(api.get_dataset_filter, { kb_id });
|
||||||
|
|
||||||
export const getMetaDataService = ({ kb_id }: { kb_id: string }) =>
|
export const getMetaDataService = ({
|
||||||
request.post(api.getMetaData, { data: { kb_id } });
|
kb_id,
|
||||||
export const updateMetaData = ({ kb_id, data }: { kb_id: string; data: any }) =>
|
doc_ids,
|
||||||
request.post(api.updateMetaData, { data: { kb_id, ...data } });
|
}: {
|
||||||
|
kb_id: string;
|
||||||
|
doc_ids?: string[];
|
||||||
|
}) => request.post(api.getMetaData, { data: { kb_id, doc_ids } });
|
||||||
|
export const updateMetaData = ({
|
||||||
|
kb_id,
|
||||||
|
doc_ids,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
kb_id: string;
|
||||||
|
doc_ids?: string[];
|
||||||
|
data: any;
|
||||||
|
}) => request.post(api.updateMetaData, { data: { kb_id, doc_ids, ...data } });
|
||||||
|
|
||||||
export const listDataPipelineLogDocument = (
|
export const listDataPipelineLogDocument = (
|
||||||
params?: IFetchKnowledgeListRequestParams,
|
params?: IFetchKnowledgeListRequestParams,
|
||||||
|
|||||||
Reference in New Issue
Block a user