mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-01 16:15:07 +08:00
Feat: Enhanced metadata functionality (#12560)
### What problem does this PR solve? Feat: Enhanced metadata functionality - Metadata filtering supports searching. - Values can be directly modified. ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -80,7 +80,7 @@ const FilterItem = memo(
|
|||||||
}
|
}
|
||||||
// className="hidden group-hover:block"
|
// className="hidden group-hover:block"
|
||||||
/>
|
/>
|
||||||
<FormLabel
|
<div
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleCheckChange({
|
handleCheckChange({
|
||||||
checked: !field.value?.includes(item.id.toString()),
|
checked: !field.value?.includes(item.id.toString()),
|
||||||
@ -88,9 +88,10 @@ const FilterItem = memo(
|
|||||||
item,
|
item,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
className="truncate w-[200px] text-sm font-normal leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-text-secondary"
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</FormLabel>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -101,7 +102,7 @@ const FilterItem = memo(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
FilterItem.displayName = 'FilterItem';
|
||||||
export const FilterField = memo(
|
export const FilterField = memo(
|
||||||
({
|
({
|
||||||
item,
|
item,
|
||||||
|
|||||||
@ -15,11 +15,17 @@ import { useForm } from 'react-hook-form';
|
|||||||
import { z, ZodArray, ZodString } from 'zod';
|
import { z, ZodArray, ZodString } from 'zod';
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
|
||||||
import { Form, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
|
import { Form, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { FilterField } from './filter-field';
|
import { FilterField } from './filter-field';
|
||||||
import { FilterChange, FilterCollection, FilterValue } from './interface';
|
import {
|
||||||
|
FilterChange,
|
||||||
|
FilterCollection,
|
||||||
|
FilterType,
|
||||||
|
FilterValue,
|
||||||
|
} from './interface';
|
||||||
|
|
||||||
export type CheckboxFormMultipleProps = {
|
export type CheckboxFormMultipleProps = {
|
||||||
filters?: FilterCollection[];
|
filters?: FilterCollection[];
|
||||||
@ -30,6 +36,41 @@ export type CheckboxFormMultipleProps = {
|
|||||||
filterGroup?: Record<string, string[]>;
|
filterGroup?: Record<string, string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterNestedList = (
|
||||||
|
list: FilterType[],
|
||||||
|
searchTerm: string,
|
||||||
|
): FilterType[] => {
|
||||||
|
if (!searchTerm) return list;
|
||||||
|
|
||||||
|
const term = searchTerm.toLowerCase();
|
||||||
|
|
||||||
|
return list
|
||||||
|
.filter((item) => {
|
||||||
|
if (
|
||||||
|
item.label.toString().toLowerCase().includes(term) ||
|
||||||
|
item.id.toLowerCase().includes(term)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.list && item.list.length > 0) {
|
||||||
|
const filteredSubList = filterNestedList(item.list, searchTerm);
|
||||||
|
return filteredSubList.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.map((item) => {
|
||||||
|
if (item.list && item.list.length > 0) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
list: filterNestedList(item.list, searchTerm),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function CheckboxFormMultiple({
|
function CheckboxFormMultiple({
|
||||||
filters = [],
|
filters = [],
|
||||||
value,
|
value,
|
||||||
@ -37,21 +78,22 @@ function CheckboxFormMultiple({
|
|||||||
setOpen,
|
setOpen,
|
||||||
filterGroup,
|
filterGroup,
|
||||||
}: CheckboxFormMultipleProps) {
|
}: CheckboxFormMultipleProps) {
|
||||||
const [resolvedFilters, setResolvedFilters] =
|
// const [resolvedFilters, setResolvedFilters] =
|
||||||
useState<FilterCollection[]>(filters);
|
// useState<FilterCollection[]>(filters);
|
||||||
|
const [searchTerms, setSearchTerms] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (filters && filters.length > 0) {
|
// if (filters && filters.length > 0) {
|
||||||
setResolvedFilters(filters);
|
// setResolvedFilters(filters);
|
||||||
}
|
// }
|
||||||
}, [filters]);
|
// }, [filters]);
|
||||||
|
|
||||||
const fieldsDict = useMemo(() => {
|
const fieldsDict = useMemo(() => {
|
||||||
if (resolvedFilters.length === 0) {
|
if (filters.length === 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvedFilters.reduce<Record<string, any>>((pre, cur) => {
|
return filters.reduce<Record<string, any>>((pre, cur) => {
|
||||||
const hasNested = cur.list?.some(
|
const hasNested = cur.list?.some(
|
||||||
(item) => item.list && item.list.length > 0,
|
(item) => item.list && item.list.length > 0,
|
||||||
);
|
);
|
||||||
@ -63,14 +105,14 @@ function CheckboxFormMultiple({
|
|||||||
}
|
}
|
||||||
return pre;
|
return pre;
|
||||||
}, {});
|
}, {});
|
||||||
}, [resolvedFilters]);
|
}, [filters]);
|
||||||
|
|
||||||
const FormSchema = useMemo(() => {
|
const FormSchema = useMemo(() => {
|
||||||
if (resolvedFilters.length === 0) {
|
if (filters.length === 0) {
|
||||||
return z.object({});
|
return z.object({});
|
||||||
}
|
}
|
||||||
return z.object(
|
return z.object(
|
||||||
resolvedFilters.reduce<
|
filters.reduce<
|
||||||
Record<
|
Record<
|
||||||
string,
|
string,
|
||||||
ZodArray<ZodString, 'many'> | z.ZodObject<any> | z.ZodOptional<any>
|
ZodArray<ZodString, 'many'> | z.ZodObject<any> | z.ZodOptional<any>
|
||||||
@ -90,13 +132,10 @@ function CheckboxFormMultiple({
|
|||||||
return pre;
|
return pre;
|
||||||
}, {}),
|
}, {}),
|
||||||
);
|
);
|
||||||
}, [resolvedFilters]);
|
}, [filters]);
|
||||||
// const FormSchema = useMemo(() => {
|
|
||||||
// return z.object({});
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
resolver: resolvedFilters.length > 0 ? zodResolver(FormSchema) : undefined,
|
resolver: filters.length > 0 ? zodResolver(FormSchema) : undefined,
|
||||||
defaultValues: fieldsDict,
|
defaultValues: fieldsDict,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -112,10 +151,10 @@ function CheckboxFormMultiple({
|
|||||||
}, [fieldsDict, onChange, setOpen]);
|
}, [fieldsDict, onChange, setOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (resolvedFilters.length > 0) {
|
if (filters.length > 0) {
|
||||||
form.reset(value || fieldsDict);
|
form.reset(value || fieldsDict);
|
||||||
}
|
}
|
||||||
}, [form, value, resolvedFilters, fieldsDict]);
|
}, [form, value, filters, fieldsDict]);
|
||||||
|
|
||||||
const filterList = useMemo(() => {
|
const filterList = useMemo(() => {
|
||||||
const filterSet = filterGroup
|
const filterSet = filterGroup
|
||||||
@ -131,6 +170,26 @@ function CheckboxFormMultiple({
|
|||||||
return filters.filter((x) => !filterList.includes(x.field));
|
return filters.filter((x) => !filterList.includes(x.field));
|
||||||
}, [filterList, filters]);
|
}, [filterList, filters]);
|
||||||
|
|
||||||
|
const handleSearchChange = (field: string, value: string) => {
|
||||||
|
setSearchTerms((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFilteredFilters = (originalFilters: FilterCollection[]) => {
|
||||||
|
return originalFilters.map((filter) => {
|
||||||
|
if (filter.canSearch && searchTerms[filter.field]) {
|
||||||
|
const filteredList = filterNestedList(
|
||||||
|
filter.list,
|
||||||
|
searchTerms[filter.field],
|
||||||
|
);
|
||||||
|
return { ...filter, list: filteredList };
|
||||||
|
}
|
||||||
|
return filter;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
@ -142,9 +201,11 @@ function CheckboxFormMultiple({
|
|||||||
{filterGroup &&
|
{filterGroup &&
|
||||||
Object.keys(filterGroup).map((key) => {
|
Object.keys(filterGroup).map((key) => {
|
||||||
const filterKeys = filterGroup[key];
|
const filterKeys = filterGroup[key];
|
||||||
const thisFilters = filters.filter((x) =>
|
const originalFilters = filters.filter((x) =>
|
||||||
filterKeys.includes(x.field),
|
filterKeys.includes(x.field),
|
||||||
);
|
);
|
||||||
|
const thisFilters = getFilteredFilters(originalFilters);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={key}
|
key={key}
|
||||||
@ -153,15 +214,29 @@ function CheckboxFormMultiple({
|
|||||||
<div className="text-text-primary text-sm">{key}</div>
|
<div className="text-text-primary text-sm">{key}</div>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
{thisFilters.map((x) => (
|
{thisFilters.map((x) => (
|
||||||
<FilterField
|
<div key={x.field}>
|
||||||
key={x.field}
|
{x.canSearch && (
|
||||||
item={{ ...x, id: x.field }}
|
<div className="mb-2">
|
||||||
parent={{
|
<Input
|
||||||
...x,
|
placeholder={t('common.search') + '...'}
|
||||||
id: x.field,
|
value={searchTerms[x.field] || ''}
|
||||||
field: ``,
|
onChange={(e) =>
|
||||||
}}
|
handleSearchChange(x.field, e.target.value)
|
||||||
/>
|
}
|
||||||
|
className="h-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<FilterField
|
||||||
|
key={x.field}
|
||||||
|
item={{ ...x, id: x.field }}
|
||||||
|
parent={{
|
||||||
|
...x,
|
||||||
|
id: x.field,
|
||||||
|
field: ``,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -169,15 +244,29 @@ function CheckboxFormMultiple({
|
|||||||
})}
|
})}
|
||||||
{notInfilterGroup &&
|
{notInfilterGroup &&
|
||||||
notInfilterGroup.map((x) => {
|
notInfilterGroup.map((x) => {
|
||||||
|
const filteredItem = getFilteredFilters([x])[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItem className="space-y-4" key={x.field}>
|
<FormItem className="space-y-4" key={x.field}>
|
||||||
<div>
|
<div>
|
||||||
<FormLabel className="text-text-primary text-sm">
|
<div className="flex justify-between items-center mb-2">
|
||||||
{x.label}
|
<FormLabel className="text-text-primary text-sm">
|
||||||
</FormLabel>
|
{x.label}
|
||||||
|
</FormLabel>
|
||||||
|
{x.canSearch && (
|
||||||
|
<Input
|
||||||
|
placeholder={t('common.search') + '...'}
|
||||||
|
value={searchTerms[x.field] || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleSearchChange(x.field, e.target.value)
|
||||||
|
}
|
||||||
|
className="h-8 w-32 ml-2"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{x.list?.length &&
|
{!!filteredItem.list?.length &&
|
||||||
x.list.map((item) => {
|
filteredItem.list.map((item) => {
|
||||||
return (
|
return (
|
||||||
<FilterField
|
<FilterField
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export const FilterButton = React.forwardRef<
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
FilterButton.displayName = 'FilterButton';
|
||||||
export default function ListFilterBar({
|
export default function ListFilterBar({
|
||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
|
|||||||
@ -5,17 +5,16 @@ export type FilterType = {
|
|||||||
list?: FilterType[];
|
list?: FilterType[];
|
||||||
value?: string | string[];
|
value?: string | string[];
|
||||||
count?: number;
|
count?: number;
|
||||||
|
canSearch?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FilterCollection = {
|
export type FilterCollection = {
|
||||||
field: string;
|
field: string;
|
||||||
label: string;
|
label: string;
|
||||||
list: FilterType[];
|
list: FilterType[];
|
||||||
|
canSearch?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FilterValue = Record<
|
export type FilterValue = Record<
|
||||||
string,
|
string,
|
||||||
Array<string> | Record<string, Array<string>>
|
Array<string> | Record<string, Array<string>>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type FilterChange = (value: FilterValue) => void;
|
export type FilterChange = (value: FilterValue) => void;
|
||||||
|
|||||||
@ -5,6 +5,7 @@ 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 {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@ -25,7 +26,13 @@ import {
|
|||||||
getSortedRowModel,
|
getSortedRowModel,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import { Plus, Settings, Trash2 } from 'lucide-react';
|
import {
|
||||||
|
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';
|
||||||
@ -61,6 +68,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
values: [],
|
values: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [expanded, setExpanded] = useState(true);
|
||||||
const [currentValueIndex, setCurrentValueIndex] = useState<number>(0);
|
const [currentValueIndex, setCurrentValueIndex] = useState<number>(0);
|
||||||
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
||||||
visible: false,
|
visible: false,
|
||||||
@ -70,6 +78,11 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
onOk: () => {},
|
onOk: () => {},
|
||||||
onCancel: () => {},
|
onCancel: () => {},
|
||||||
});
|
});
|
||||||
|
const [editingValue, setEditingValue] = useState<{
|
||||||
|
field: string;
|
||||||
|
value: string;
|
||||||
|
newValue: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
tableData,
|
tableData,
|
||||||
@ -81,6 +94,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
addDeleteValue,
|
addDeleteValue,
|
||||||
} = useManageMetaDataModal(originalTableData, metadataType, otherData);
|
} = useManageMetaDataModal(originalTableData, metadataType, otherData);
|
||||||
const { handleMenuClick } = useHandleMenuClick();
|
const { handleMenuClick } = useHandleMenuClick();
|
||||||
|
const [shouldSave, setShouldSave] = useState(false);
|
||||||
const {
|
const {
|
||||||
visible: manageValuesVisible,
|
visible: manageValuesVisible,
|
||||||
showModal: showManageValuesModal,
|
showModal: showManageValuesModal,
|
||||||
@ -96,6 +110,32 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
onCancel: () => {},
|
onCancel: () => {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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: '',
|
||||||
@ -136,66 +176,119 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'values',
|
accessorKey: 'values',
|
||||||
header: () => <span>{t('knowledgeDetails.metadata.values')}</span>,
|
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 }) => {
|
cell: ({ row }) => {
|
||||||
const values = row.getValue('values') as Array<string>;
|
const values = row.getValue('values') as Array<string>;
|
||||||
|
|
||||||
|
if (!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 (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
{Array.isArray(values) &&
|
<div className="flex flex-wrap gap-1">
|
||||||
values.length > 0 &&
|
{displayedValues?.map((value: string) => {
|
||||||
values
|
const isEditing =
|
||||||
.filter((value: string, index: number) => index < 2)
|
editingValue &&
|
||||||
?.map((value: string) => {
|
editingValue.field === row.getValue('field') &&
|
||||||
return (
|
editingValue.value === value;
|
||||||
<Button
|
|
||||||
key={value}
|
return isEditing ? (
|
||||||
variant={'ghost'}
|
<div key={value}>
|
||||||
className="border border-border-button"
|
<Input
|
||||||
aria-label="Edit"
|
type="text"
|
||||||
>
|
value={editingValue.newValue}
|
||||||
<div className="flex gap-1 items-center">
|
onChange={(e) =>
|
||||||
<div className="text-sm truncate max-w-24">
|
setEditingValue({
|
||||||
{value}
|
...editingValue,
|
||||||
</div>
|
newValue: e.target.value,
|
||||||
{isDeleteSingleValue && (
|
})
|
||||||
<Button
|
}
|
||||||
variant={'delete'}
|
onBlur={saveEditedValue}
|
||||||
className="p-0 bg-transparent"
|
onKeyDown={(e) => {
|
||||||
onClick={() => {
|
if (e.key === 'Enter') {
|
||||||
setDeleteDialogContent({
|
saveEditedValue();
|
||||||
visible: true,
|
} else if (e.key === 'Escape') {
|
||||||
title:
|
cancelEditValue();
|
||||||
t('common.delete') +
|
}
|
||||||
' ' +
|
}}
|
||||||
t('knowledgeDetails.metadata.value'),
|
autoFocus
|
||||||
name: value,
|
// className="text-sm min-w-20 max-w-32 outline-none bg-transparent px-1 py-0.5"
|
||||||
warnText:
|
/>
|
||||||
MetadataDeleteMap(t)[
|
</div>
|
||||||
metadataType as MetadataType
|
) : (
|
||||||
].warnValueText,
|
<Button
|
||||||
onOk: () => {
|
key={value}
|
||||||
hideDeleteModal();
|
variant={'ghost'}
|
||||||
handleDeleteSingleValue(
|
className="border border-border-button"
|
||||||
row.getValue('field'),
|
onClick={() =>
|
||||||
value,
|
handleEditValue(row.getValue('field'), value)
|
||||||
);
|
}
|
||||||
},
|
aria-label="Edit"
|
||||||
onCancel: () => {
|
>
|
||||||
hideDeleteModal();
|
<div className="flex gap-1 items-center">
|
||||||
},
|
<div className="text-sm truncate max-w-24">{value}</div>
|
||||||
});
|
{isDeleteSingleValue && (
|
||||||
}}
|
<Button
|
||||||
>
|
variant={'delete'}
|
||||||
<Trash2 />
|
className="p-0 bg-transparent"
|
||||||
</Button>
|
onClick={(e) => {
|
||||||
)}
|
e.stopPropagation();
|
||||||
</div>
|
setDeleteDialogContent({
|
||||||
</Button>
|
visible: true,
|
||||||
);
|
title:
|
||||||
})}
|
t('common.delete') +
|
||||||
{Array.isArray(values) && values.length > 2 && (
|
' ' +
|
||||||
<div className="text-text-secondary self-end">...</div>
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -260,6 +353,9 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
isDeleteSingleValue,
|
isDeleteSingleValue,
|
||||||
handleEditValueRow,
|
handleEditValueRow,
|
||||||
metadataType,
|
metadataType,
|
||||||
|
expanded,
|
||||||
|
editingValue,
|
||||||
|
saveEditedValue,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
@ -271,7 +367,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
manualPagination: true,
|
manualPagination: true,
|
||||||
});
|
});
|
||||||
const [shouldSave, setShouldSave] = useState(false);
|
|
||||||
const handleSaveValues = (data: IMetaDataTableData) => {
|
const handleSaveValues = (data: IMetaDataTableData) => {
|
||||||
setTableData((prev) => {
|
setTableData((prev) => {
|
||||||
let newData;
|
let newData;
|
||||||
|
|||||||
@ -127,6 +127,7 @@ export default function Dataset() {
|
|||||||
type: MetadataType.Manage,
|
type: MetadataType.Manage,
|
||||||
isCanAdd: false,
|
isCanAdd: false,
|
||||||
isEditField: true,
|
isEditField: 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">
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import { ParsingCard } from './parsing-card';
|
|||||||
import { ReparseDialog } from './reparse-dialog';
|
import { ReparseDialog } from './reparse-dialog';
|
||||||
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
|
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
|
||||||
import { useHandleRunDocumentByIds } from './use-run-document';
|
import { useHandleRunDocumentByIds } from './use-run-document';
|
||||||
import { UseSaveMetaShowType } from './use-save-meta';
|
|
||||||
import { isParserRunning } from './utils';
|
import { isParserRunning } from './utils';
|
||||||
const IconMap = {
|
const IconMap = {
|
||||||
[RunningStatus.UNSTART]: (
|
[RunningStatus.UNSTART]: (
|
||||||
@ -44,13 +43,12 @@ const IconMap = {
|
|||||||
export function ParsingStatusCell({
|
export function ParsingStatusCell({
|
||||||
record,
|
record,
|
||||||
showChangeParserModal,
|
showChangeParserModal,
|
||||||
showSetMetaModal,
|
// showSetMetaModal,
|
||||||
showLog,
|
showLog,
|
||||||
}: {
|
}: {
|
||||||
record: IDocumentInfo;
|
record: IDocumentInfo;
|
||||||
showLog: (record: IDocumentInfo) => void;
|
showLog: (record: IDocumentInfo) => void;
|
||||||
} & UseChangeDocumentParserShowType &
|
} & UseChangeDocumentParserShowType) {
|
||||||
UseSaveMetaShowType) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
run,
|
run,
|
||||||
@ -83,10 +81,6 @@ export function ParsingStatusCell({
|
|||||||
showChangeParserModal(record);
|
showChangeParserModal(record);
|
||||||
}, [record, showChangeParserModal]);
|
}, [record, showChangeParserModal]);
|
||||||
|
|
||||||
const handleShowSetMetaModal = useCallback(() => {
|
|
||||||
showSetMetaModal(record);
|
|
||||||
}, [record, showSetMetaModal]);
|
|
||||||
|
|
||||||
const showParse = useMemo(() => {
|
const showParse = useMemo(() => {
|
||||||
return record.type !== DocumentType.Virtual;
|
return record.type !== DocumentType.Virtual;
|
||||||
}, [record]);
|
}, [record]);
|
||||||
@ -124,9 +118,6 @@ export function ParsingStatusCell({
|
|||||||
<DropdownMenuItem onClick={handleShowChangeParserModal}>
|
<DropdownMenuItem onClick={handleShowChangeParserModal}>
|
||||||
{t('knowledgeDetails.dataPipeline')}
|
{t('knowledgeDetails.dataPipeline')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={handleShowSetMetaModal}>
|
|
||||||
{t('knowledgeDetails.setMetaData')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -172,17 +172,18 @@ export function useDatasetTableColumns({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'run',
|
accessorKey: 'meta_fields',
|
||||||
header: t('Parse'),
|
header: t('metadata.metadata'),
|
||||||
// meta: { cellClassName: 'min-w-[20vw]' },
|
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const length = Object.keys(row.getValue('meta_fields') || {}).length;
|
||||||
return (
|
return (
|
||||||
<ParsingStatusCell
|
<div
|
||||||
record={row.original}
|
className="capitalize cursor-pointer"
|
||||||
showChangeParserModal={showChangeParserModal}
|
onClick={() => {
|
||||||
showSetMetaModal={(row) =>
|
|
||||||
showManageMetadataModal({
|
showManageMetadataModal({
|
||||||
metadata: util.JSONToMetaDataTableData(row.meta_fields || {}),
|
metadata: util.JSONToMetaDataTableData(
|
||||||
|
row.original.meta_fields || {},
|
||||||
|
),
|
||||||
isCanAdd: true,
|
isCanAdd: true,
|
||||||
type: MetadataType.UpdateSingle,
|
type: MetadataType.UpdateSingle,
|
||||||
record: row,
|
record: row,
|
||||||
@ -193,13 +194,28 @@ export function useDatasetTableColumns({
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-text-secondary w-full truncate">
|
<div className="text-sm text-text-secondary w-full truncate">
|
||||||
{t('metadata.editMetadataForDataset')}
|
{t('metadata.editMetadataForDataset')}
|
||||||
{row.name}
|
{row.original.name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
isDeleteSingleValue: true,
|
isDeleteSingleValue: true,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
|
>
|
||||||
|
{length + ' fields'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'run',
|
||||||
|
header: t('Parse'),
|
||||||
|
// meta: { cellClassName: 'min-w-[20vw]' },
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<ParsingStatusCell
|
||||||
|
record={row.original}
|
||||||
|
showChangeParserModal={showChangeParserModal}
|
||||||
showLog={showLog}
|
showLog={showLog}
|
||||||
></ParsingStatusCell>
|
></ParsingStatusCell>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -72,7 +72,12 @@ export function useSelectDatasetFilters() {
|
|||||||
return [
|
return [
|
||||||
{ field: 'type', label: 'File Type', list: fileTypes },
|
{ field: 'type', label: 'File Type', list: fileTypes },
|
||||||
{ field: 'run', label: 'Status', list: fileStatus },
|
{ field: 'run', label: 'Status', list: fileStatus },
|
||||||
{ field: 'metadata', label: 'Metadata field', list: metaDataList },
|
{
|
||||||
|
field: 'metadata',
|
||||||
|
label: 'Metadata field',
|
||||||
|
canSearch: true,
|
||||||
|
list: metaDataList,
|
||||||
|
},
|
||||||
] as FilterCollection[];
|
] as FilterCollection[];
|
||||||
}, [fileStatus, fileTypes, metaDataList]);
|
}, [fileStatus, fileTypes, metaDataList]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user