mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-07 19:15:05 +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"
|
||||
/>
|
||||
<FormLabel
|
||||
<div
|
||||
onClick={() =>
|
||||
handleCheckChange({
|
||||
checked: !field.value?.includes(item.id.toString()),
|
||||
@ -88,9 +88,10 @@ const FilterItem = memo(
|
||||
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}
|
||||
</FormLabel>
|
||||
</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
@ -101,7 +102,7 @@ const FilterItem = memo(
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
FilterItem.displayName = 'FilterItem';
|
||||
export const FilterField = memo(
|
||||
({
|
||||
item,
|
||||
|
||||
@ -15,11 +15,17 @@ import { useForm } from 'react-hook-form';
|
||||
import { z, ZodArray, ZodString } from 'zod';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
|
||||
import { Form, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
|
||||
import { t } from 'i18next';
|
||||
import { FilterField } from './filter-field';
|
||||
import { FilterChange, FilterCollection, FilterValue } from './interface';
|
||||
import {
|
||||
FilterChange,
|
||||
FilterCollection,
|
||||
FilterType,
|
||||
FilterValue,
|
||||
} from './interface';
|
||||
|
||||
export type CheckboxFormMultipleProps = {
|
||||
filters?: FilterCollection[];
|
||||
@ -30,6 +36,41 @@ export type CheckboxFormMultipleProps = {
|
||||
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({
|
||||
filters = [],
|
||||
value,
|
||||
@ -37,21 +78,22 @@ function CheckboxFormMultiple({
|
||||
setOpen,
|
||||
filterGroup,
|
||||
}: CheckboxFormMultipleProps) {
|
||||
const [resolvedFilters, setResolvedFilters] =
|
||||
useState<FilterCollection[]>(filters);
|
||||
// const [resolvedFilters, setResolvedFilters] =
|
||||
// useState<FilterCollection[]>(filters);
|
||||
const [searchTerms, setSearchTerms] = useState<Record<string, string>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (filters && filters.length > 0) {
|
||||
setResolvedFilters(filters);
|
||||
}
|
||||
}, [filters]);
|
||||
// useEffect(() => {
|
||||
// if (filters && filters.length > 0) {
|
||||
// setResolvedFilters(filters);
|
||||
// }
|
||||
// }, [filters]);
|
||||
|
||||
const fieldsDict = useMemo(() => {
|
||||
if (resolvedFilters.length === 0) {
|
||||
if (filters.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return resolvedFilters.reduce<Record<string, any>>((pre, cur) => {
|
||||
return filters.reduce<Record<string, any>>((pre, cur) => {
|
||||
const hasNested = cur.list?.some(
|
||||
(item) => item.list && item.list.length > 0,
|
||||
);
|
||||
@ -63,14 +105,14 @@ function CheckboxFormMultiple({
|
||||
}
|
||||
return pre;
|
||||
}, {});
|
||||
}, [resolvedFilters]);
|
||||
}, [filters]);
|
||||
|
||||
const FormSchema = useMemo(() => {
|
||||
if (resolvedFilters.length === 0) {
|
||||
if (filters.length === 0) {
|
||||
return z.object({});
|
||||
}
|
||||
return z.object(
|
||||
resolvedFilters.reduce<
|
||||
filters.reduce<
|
||||
Record<
|
||||
string,
|
||||
ZodArray<ZodString, 'many'> | z.ZodObject<any> | z.ZodOptional<any>
|
||||
@ -90,13 +132,10 @@ function CheckboxFormMultiple({
|
||||
return pre;
|
||||
}, {}),
|
||||
);
|
||||
}, [resolvedFilters]);
|
||||
// const FormSchema = useMemo(() => {
|
||||
// return z.object({});
|
||||
// }, []);
|
||||
}, [filters]);
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: resolvedFilters.length > 0 ? zodResolver(FormSchema) : undefined,
|
||||
resolver: filters.length > 0 ? zodResolver(FormSchema) : undefined,
|
||||
defaultValues: fieldsDict,
|
||||
});
|
||||
|
||||
@ -112,10 +151,10 @@ function CheckboxFormMultiple({
|
||||
}, [fieldsDict, onChange, setOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resolvedFilters.length > 0) {
|
||||
if (filters.length > 0) {
|
||||
form.reset(value || fieldsDict);
|
||||
}
|
||||
}, [form, value, resolvedFilters, fieldsDict]);
|
||||
}, [form, value, filters, fieldsDict]);
|
||||
|
||||
const filterList = useMemo(() => {
|
||||
const filterSet = filterGroup
|
||||
@ -131,6 +170,26 @@ function CheckboxFormMultiple({
|
||||
return filters.filter((x) => !filterList.includes(x.field));
|
||||
}, [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 (
|
||||
<Form {...form}>
|
||||
<form
|
||||
@ -142,9 +201,11 @@ function CheckboxFormMultiple({
|
||||
{filterGroup &&
|
||||
Object.keys(filterGroup).map((key) => {
|
||||
const filterKeys = filterGroup[key];
|
||||
const thisFilters = filters.filter((x) =>
|
||||
const originalFilters = filters.filter((x) =>
|
||||
filterKeys.includes(x.field),
|
||||
);
|
||||
const thisFilters = getFilteredFilters(originalFilters);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
@ -153,15 +214,29 @@ function CheckboxFormMultiple({
|
||||
<div className="text-text-primary text-sm">{key}</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
{thisFilters.map((x) => (
|
||||
<FilterField
|
||||
key={x.field}
|
||||
item={{ ...x, id: x.field }}
|
||||
parent={{
|
||||
...x,
|
||||
id: x.field,
|
||||
field: ``,
|
||||
}}
|
||||
/>
|
||||
<div key={x.field}>
|
||||
{x.canSearch && (
|
||||
<div className="mb-2">
|
||||
<Input
|
||||
placeholder={t('common.search') + '...'}
|
||||
value={searchTerms[x.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>
|
||||
@ -169,15 +244,29 @@ function CheckboxFormMultiple({
|
||||
})}
|
||||
{notInfilterGroup &&
|
||||
notInfilterGroup.map((x) => {
|
||||
const filteredItem = getFilteredFilters([x])[0];
|
||||
|
||||
return (
|
||||
<FormItem className="space-y-4" key={x.field}>
|
||||
<div>
|
||||
<FormLabel className="text-text-primary text-sm">
|
||||
{x.label}
|
||||
</FormLabel>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<FormLabel className="text-text-primary text-sm">
|
||||
{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>
|
||||
{x.list?.length &&
|
||||
x.list.map((item) => {
|
||||
{!!filteredItem.list?.length &&
|
||||
filteredItem.list.map((item) => {
|
||||
return (
|
||||
<FilterField
|
||||
key={item.id}
|
||||
|
||||
@ -44,6 +44,7 @@ export const FilterButton = React.forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
FilterButton.displayName = 'FilterButton';
|
||||
export default function ListFilterBar({
|
||||
title,
|
||||
children,
|
||||
|
||||
@ -5,17 +5,16 @@ export type FilterType = {
|
||||
list?: FilterType[];
|
||||
value?: string | string[];
|
||||
count?: number;
|
||||
canSearch?: boolean;
|
||||
};
|
||||
|
||||
export type FilterCollection = {
|
||||
field: string;
|
||||
label: string;
|
||||
list: FilterType[];
|
||||
canSearch?: boolean;
|
||||
};
|
||||
|
||||
export type FilterValue = Record<
|
||||
string,
|
||||
Array<string> | Record<string, Array<string>>
|
||||
>;
|
||||
|
||||
export type FilterChange = (value: FilterValue) => void;
|
||||
|
||||
Reference in New Issue
Block a user