Fix: Issues with metadata parameter addition failures and single-file chunk saving failures. (#12818)

### What problem does this PR solve?

Fix: Issues with metadata parameter addition failures and single-file
chunk saving failures.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2026-01-26 18:00:40 +08:00
committed by GitHub
parent 13076bb87b
commit 1d93519cb2
11 changed files with 170 additions and 93 deletions

View File

@ -144,15 +144,13 @@ export function ChunkMethodDialog({
pages: z pages: z
.array(z.object({ from: z.coerce.number(), to: z.coerce.number() })) .array(z.object({ from: z.coerce.number(), to: z.coerce.number() }))
.optional(), .optional(),
metadata: z metadata: z.any().optional(),
built_in_metadata: z
.array( .array(
z z.object({
.object({ key: z.string().optional(),
key: z.string().optional(), type: z.string().optional(),
description: z.string().optional(), }),
enum: z.array(z.string().optional()).optional(),
})
.optional(),
) )
.optional(), .optional(),
enable_metadata: z.boolean().optional(), enable_metadata: z.boolean().optional(),

View File

@ -184,6 +184,7 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
}, },
knowledgeDetails: { knowledgeDetails: {
metadata: { metadata: {
selectFiles: 'Selected {{count}} files',
type: 'Type', type: 'Type',
fieldNameInvalid: 'Field name can only contain letters or underscores.', fieldNameInvalid: 'Field name can only contain letters or underscores.',
builtIn: 'Built-in', builtIn: 'Built-in',
@ -205,10 +206,10 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
fieldExists: 'Field already exists.', fieldExists: 'Field already exists.',
fieldSetting: 'Field settings', fieldSetting: 'Field settings',
changesAffectNewParses: 'Changes affect new parses only.', changesAffectNewParses: 'Changes affect new parses only.',
editMetadataForDataset: 'View and edit metadata for ', // editMetadataForDataset: 'View and edit metadata for ',
restrictDefinedValues: 'Restrict to defined values', restrictDefinedValues: 'Restrict to defined values',
metadataGenerationSettings: 'Metadata generation settings', metadataGenerationSettings: 'Metadata generation settings',
manageMetadataForDataset: 'Manage metadata for this dataset', // manageMetadataForDataset: 'Manage metadata for this dataset',
manageMetadata: 'Manage metadata', manageMetadata: 'Manage metadata',
metadata: 'Metadata', metadata: 'Metadata',
values: 'Values', values: 'Values',

View File

@ -175,6 +175,7 @@ export default {
}, },
knowledgeDetails: { knowledgeDetails: {
metadata: { metadata: {
selectFiles: '已选择 {{count}} 个文件',
type: '类型', type: '类型',
fieldNameInvalid: '字段名称只能包含字母或下划线。', fieldNameInvalid: '字段名称只能包含字母或下划线。',
builtIn: '内置', builtIn: '内置',
@ -192,10 +193,10 @@ export default {
fieldExists: '字段名已存在。', fieldExists: '字段名已存在。',
fieldSetting: '字段设置', fieldSetting: '字段设置',
changesAffectNewParses: '更改仅影响新的解析。', changesAffectNewParses: '更改仅影响新的解析。',
editMetadataForDataset: '查看和编辑元数据 ', // editMetadataForDataset: '查看和编辑元数据 ',
restrictDefinedValues: '限制为已定义的值', restrictDefinedValues: '限制为已定义的值',
metadataGenerationSettings: '元数据生成设置', metadataGenerationSettings: '元数据生成设置',
manageMetadataForDataset: '管理此数据集的元数据', // manageMetadataForDataset: '管理此数据集的元数据',
manageMetadata: '管理元数据', manageMetadata: '管理元数据',
metadata: '元数据', metadata: '元数据',
values: '值', values: '值',

View File

@ -11,11 +11,7 @@ import { RowSelectionState } from '@tanstack/react-table';
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 { useParams } from 'react-router'; import { useParams } from 'react-router';
import { import { DEFAULT_VALUE_TYPE, MetadataType } from '../constant';
DEFAULT_VALUE_TYPE,
MetadataType,
metadataValueTypeEnum,
} from '../constant';
import { import {
IBuiltInMetadataItem, IBuiltInMetadataItem,
IMetaDataReturnJSONSettings, IMetaDataReturnJSONSettings,
@ -25,6 +21,7 @@ import {
MetadataOperations, MetadataOperations,
MetadataValueType, MetadataValueType,
ShowManageMetadataModalProps, ShowManageMetadataModalProps,
UpdateOperation,
} from '../interface'; } from '../interface';
export const util = { export const util = {
@ -136,6 +133,7 @@ export const useMetadataOperations = () => {
deletes: [], deletes: [],
updates: [], updates: [],
}); });
// const operationsRef = useRef(operations);
const addDeleteRow = useCallback((key: string) => { const addDeleteRow = useCallback((key: string) => {
setOperations((prev) => ({ setOperations((prev) => ({
@ -165,41 +163,60 @@ export const useMetadataOperations = () => {
newValue: string | string[], newValue: string | string[],
type?: MetadataValueType, type?: MetadataValueType,
) => { ) => {
let newValuesRes: string | string[]; // let newValuesRes: string | string[];
if (type !== metadataValueTypeEnum['list']) { // if (type !== metadataValueTypeEnum['list']) {
if (Array.isArray(newValue) && newValue.length > 0) { // if (Array.isArray(newValue) && newValue.length > 0) {
newValuesRes = newValue[0]; // newValuesRes = newValue[0];
} else { // } else {
newValuesRes = newValue; // newValuesRes = newValue;
} // }
} else { // } else {
newValuesRes = newValue; // newValuesRes = newValue;
} // }
setOperations((prev) => { setOperations((prev) => {
let updatedUpdates = [...prev.updates];
const existsIndex = prev.updates.findIndex( const existsIndex = prev.updates.findIndex(
(update) => update.key === key && update.match === originalValue, (update) =>
update.key === key &&
update.match === originalValue &&
update.match !== '',
); );
if (existsIndex > -1) { if (existsIndex > -1) {
const updatedUpdates = [...prev.updates];
updatedUpdates[existsIndex] = { updatedUpdates[existsIndex] = {
key, key,
match: originalValue, match: originalValue,
value: newValuesRes, value: newValue,
valueType: type || DEFAULT_VALUE_TYPE, valueType: type || DEFAULT_VALUE_TYPE,
}; };
return {
...prev, // operationsRef.current = updatedOperations;
updates: updatedUpdates, } else {
}; updatedUpdates.push({
key,
match: originalValue,
value: newValue,
valueType: type,
});
} }
return { updatedUpdates = updatedUpdates.reduce((pre, cur) => {
if (
!pre.some(
(item) =>
item.key === cur.key &&
item.match === cur.match &&
item.value === cur.value,
)
) {
pre.push(cur);
}
return pre;
}, [] as UpdateOperation[]);
const updatedOperations = {
...prev, ...prev,
updates: [ updates: updatedUpdates,
...prev.updates,
{ key, match: originalValue, value: newValuesRes, valueType: type },
],
}; };
return updatedOperations;
}); });
}, },
[], [],
@ -213,6 +230,7 @@ export const useMetadataOperations = () => {
}, []); }, []);
return { return {
// operationsRef,
operations, operations,
addDeleteBatch, addDeleteBatch,
addDeleteRow, addDeleteRow,
@ -272,6 +290,7 @@ export const useManageMetaDataModal = (
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData); const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { const {
// operationsRef,
operations, operations,
addDeleteRow, addDeleteRow,
addDeleteBatch, addDeleteBatch,

View File

@ -72,6 +72,7 @@ export type IManageModalProps = {
isVerticalShowValue?: boolean; isVerticalShowValue?: boolean;
builtInMetadata?: IBuiltInMetadataItem[]; builtInMetadata?: IBuiltInMetadataItem[];
success?: (data: any) => void; success?: (data: any) => void;
secondTitle?: ReactNode;
}; };
export interface IManageValuesProps { export interface IManageValuesProps {
@ -98,12 +99,12 @@ export interface IManageValuesProps {
addDeleteValue: (key: string, value: string) => void; addDeleteValue: (key: string, value: string) => void;
} }
interface DeleteOperation { export interface DeleteOperation {
key: string; key: string;
value?: string; value?: string;
} }
interface UpdateOperation { export interface UpdateOperation {
key: string; key: string;
match: string; match: string;
value: string | string[]; value: string | string[];

View File

@ -4,12 +4,7 @@ import { Input } from '@/components/ui/input';
import { DateInput } from '@/components/ui/input-date'; import { DateInput } from '@/components/ui/input-date';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { ColumnDef, Row, Table } from '@tanstack/react-table'; import { ColumnDef, Row, Table } from '@tanstack/react-table';
import { import { ListChevronsDownUp, Settings, Trash2 } from 'lucide-react';
ListChevronsDownUp,
ListChevronsUpDown,
Settings,
Trash2,
} from 'lucide-react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@ -51,12 +46,16 @@ export const useMetadataColumns = ({
onOk: () => {}, onOk: () => {},
onCancel: () => {}, onCancel: () => {},
}); });
const [expanded, setExpanded] = useState(true); // const [expanded, setExpanded] = useState(true);
const [editingValue, setEditingValue] = useState<{ const [editingValue, setEditingValue] = useState<{
field: string; field: string;
value: string; value: string;
newValue: string; newValue: string;
} | null>(null); } | null>(null);
const [rowExpandedStates, setRowExpandedStates] = useState<
Record<string, boolean>
>({});
const isSettingsMode = const isSettingsMode =
metadataType === MetadataType.Setting || metadataType === MetadataType.Setting ||
metadataType === MetadataType.SingleFileSetting || metadataType === MetadataType.SingleFileSetting ||
@ -142,21 +141,7 @@ export const useMetadataColumns = ({
</div> </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', accessorKey: 'description',
header: () => <span>{t('knowledgeDetails.metadata.description')}</span>, header: () => <span>{t('knowledgeDetails.metadata.description')}</span>,
@ -182,7 +167,7 @@ export const useMetadataColumns = ({
header: () => ( header: () => (
<div className="flex items-center"> <div className="flex items-center">
<span>{t('knowledgeDetails.metadata.values')}</span> <span>{t('knowledgeDetails.metadata.values')}</span>
<div {/* <div
className="ml-2 p-1 cursor-pointer" className="ml-2 p-1 cursor-pointer"
onClick={() => { onClick={() => {
setExpanded(!expanded); setExpanded(!expanded);
@ -194,16 +179,25 @@ export const useMetadataColumns = ({
<ListChevronsUpDown size={14} /> <ListChevronsUpDown size={14} />
)} )}
{expanded} {expanded}
</div> </div> */}
</div> </div>
), ),
cell: ({ row }) => { cell: ({ row }) => {
const values = row.getValue('values') as Array<string>; const values = row.getValue('values') as Array<string>;
const displayedValues = expanded ? values : values.slice(0, 2); const isRowExpanded = rowExpandedStates[row.original.field] ?? false;
const toggleRowExpanded = () => {
setRowExpandedStates((prev) => ({
...prev,
[row.original.field]: !isRowExpanded,
}));
};
const displayedValues = isRowExpanded ? values : values.slice(0, 2);
const hasMore = Array.isArray(values) && values.length > 2; const hasMore = Array.isArray(values) && values.length > 2;
return ( return (
<div className="flex flex-col gap-1"> <div className="flex gap-1">
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{displayedValues?.map((value: string) => { {displayedValues?.map((value: string) => {
const isEditing = const isEditing =
@ -218,7 +212,6 @@ export const useMetadataColumns = ({
<DateInput <DateInput
value={new Date(editingValue.newValue)} value={new Date(editingValue.newValue)}
onChange={(value) => { onChange={(value) => {
console.log('value', value);
const newValue = { const newValue = {
...editingValue, ...editingValue,
newValue: formatDate( newValue: formatDate(
@ -228,13 +221,7 @@ export const useMetadataColumns = ({
}; };
setEditingValue(newValue); setEditingValue(newValue);
saveEditedValue(newValue); saveEditedValue(newValue);
// onValueChange(index, formatDate(value), true);
}} }}
// openChange={(open) => {
// console.log('open', open);
// if (!open) {
// }
// }}
showTimeSelect={true} showTimeSelect={true}
/> />
)} )}
@ -249,7 +236,7 @@ export const useMetadataColumns = ({
newValue: e.target.value, newValue: e.target.value,
}) })
} }
onBlur={saveEditedValue} onBlur={() => saveEditedValue()}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
saveEditedValue(); saveEditedValue();
@ -316,10 +303,37 @@ export const useMetadataColumns = ({
</Button> </Button>
); );
})} })}
{hasMore && !expanded && (
<div className="text-text-secondary self-end">...</div>
)}
</div> </div>
{hasMore && !isRowExpanded && (
<Button
variant={'ghost'}
className="border border-border-button h-auto px-2 py-1"
onClick={(e) => {
e.stopPropagation();
toggleRowExpanded();
}}
>
<div className="text-text-secondary">
+{values.length - 2}
</div>
</Button>
)}
{hasMore && isRowExpanded && (
// <div className="self-end mt-1">
<Button
variant={'ghost'}
className="bg-transparent px-2 py-1"
onClick={(e) => {
e.stopPropagation();
toggleRowExpanded();
}}
>
<div className="text-text-secondary">
<ListChevronsDownUp size={14} />
</div>
</Button>
// </div>
)}
</div> </div>
); );
}, },
@ -390,10 +404,10 @@ export const useMetadataColumns = ({
isDeleteSingleValue, isDeleteSingleValue,
handleEditValueRow, handleEditValueRow,
metadataType, metadataType,
expanded, // expanded,
editingValue, editingValue,
saveEditedValue, saveEditedValue,
showTypeColumn, rowExpandedStates,
]); ]);
return { return {

View File

@ -69,6 +69,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
builtInMetadata, builtInMetadata,
success, success,
documentIds, documentIds,
secondTitle,
} = props; } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const [valueData, setValueData] = useState<IMetaDataTableData>({ const [valueData, setValueData] = useState<IMetaDataTableData>({
@ -316,7 +317,9 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
<> <>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div>{t('knowledgeDetails.metadata.metadata')}</div> <div className="w-1/2">
{secondTitle || t('knowledgeDetails.metadata.metadata')}
</div>
<div> <div>
{metadataType === MetadataType.Manage && ( {metadataType === MetadataType.Manage && (
<Button <Button
@ -564,7 +567,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
isAddValue={isAddValue || isAddValueMode} isAddValue={isAddValue || isAddValueMode}
isShowDescription={isShowDescription} isShowDescription={isShowDescription}
isShowValueSwitch={isShowValueSwitch} isShowValueSwitch={isShowValueSwitch}
isShowType={isSettingsMode} isShowType={true}
isVerticalShowValue={isVerticalShowValue} isVerticalShowValue={isVerticalShowValue}
isAddValueMode={isAddValueMode} isAddValueMode={isAddValueMode}
// handleDeleteSingleValue={handleDeleteSingleValue} // handleDeleteSingleValue={handleDeleteSingleValue}

View File

@ -40,11 +40,13 @@ import {
util, util,
} from '../../components/metedata/hooks/use-manage-modal'; } from '../../components/metedata/hooks/use-manage-modal';
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { import {
IBuiltInMetadataItem, IBuiltInMetadataItem,
IMetaDataReturnJSONSettings, IMetaDataReturnJSONSettings,
} from '../../components/metedata/interface'; } from '../../components/metedata/interface';
import { ManageMetadataModal } from '../../components/metedata/manage-modal'; import { ManageMetadataModal } from '../../components/metedata/manage-modal';
import { useKnowledgeBaseContext } from '../../contexts/knowledge-base-context';
import { import {
useHandleKbEmbedding, useHandleKbEmbedding,
useHasParsedDocument, useHasParsedDocument,
@ -378,6 +380,7 @@ export function AutoMetadata({
const location = useLocation(); const location = useLocation();
const form = useFormContext(); const form = useFormContext();
const datasetContext = useContext(DataSetContext); const datasetContext = useContext(DataSetContext);
const { knowledgeBase } = useKnowledgeBaseContext();
const { const {
manageMetadataVisible, manageMetadataVisible,
showManageMetadataModal, showManageMetadataModal,
@ -396,16 +399,31 @@ export function AutoMetadata({
type: type, type: type,
record: otherData, record: otherData,
builtInMetadata, builtInMetadata,
secondTitle: knowledgeBase ? (
<div className="w-full flex items-center gap-1 text-sm text-text-secondary">
<RAGFlowAvatar
avatar={knowledgeBase.avatar}
name={knowledgeBase.name}
className="size-8"
></RAGFlowAvatar>
<div className=" text-text-primary text-base space-y-1 overflow-hidden">
{knowledgeBase.name}
</div>
</div>
) : (
<></>
),
}); });
}, [form, otherData, showManageMetadataModal, type]); }, [form, otherData, showManageMetadataModal, knowledgeBase, type]);
useEffect(() => { useEffect(() => {
const locationState = location.state as const locationState = location.state as
| { openMetadata?: boolean } | { openMetadata?: boolean }
| undefined; | undefined;
if (locationState?.openMetadata && !datasetContext?.loading) { if (locationState?.openMetadata && !datasetContext?.loading) {
setTimeout(() => { const timer = setTimeout(() => {
handleClickOpenMetadata(); handleClickOpenMetadata();
clearTimeout(timer);
}, 0); }, 0);
locationState.openMetadata = false; locationState.openMetadata = false;
history.replace({ ...location }, locationState); history.replace({ ...location }, locationState);
@ -475,6 +493,7 @@ export function AutoMetadata({
isShowValueSwitch={true} isShowValueSwitch={true}
isVerticalShowValue={false} isVerticalShowValue={false}
builtInMetadata={metadataConfig.builtInMetadata} builtInMetadata={metadataConfig.builtInMetadata}
secondTitle={metadataConfig.secondTitle}
success={(data?: { success={(data?: {
metadata?: IMetaDataReturnJSONSettings; metadata?: IMetaDataReturnJSONSettings;
builtInMetadata?: IBuiltInMetadataItem[]; builtInMetadata?: IBuiltInMetadataItem[];

View File

@ -104,14 +104,21 @@ export default function Dataset() {
isEditField: false, isEditField: false,
isDeleteSingleValue: true, isDeleteSingleValue: true,
isAddValue: true, isAddValue: true,
secondTitle: (
<>
{t('knowledgeDetails.metadata.selectFiles', {
count: documents.length,
})}
</>
),
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('knowledgeDetails.metadata.manageMetadataForDataset')} {t('knowledgeDetails.metadata.manageMetadataForDataset')}
</div> </div> */}
</div> </div>
), ),
documentIds: documents.map((doc) => doc.id), documentIds: documents.map((doc) => doc.id),
@ -240,9 +247,9 @@ export default function Dataset() {
<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('knowledgeDetails.metadata.manageMetadataForDataset')} {t('knowledgeDetails.metadata.manageMetadataForDataset')}
</div> </div> */}
</div> </div>
) )
} }
@ -255,6 +262,7 @@ export default function Dataset() {
isVerticalShowValue={metadataConfig.isVerticalShowValue} isVerticalShowValue={metadataConfig.isVerticalShowValue}
isEditField={metadataConfig.isEditField} isEditField={metadataConfig.isEditField}
isDeleteSingleValue={metadataConfig.isDeleteSingleValue} isDeleteSingleValue={metadataConfig.isDeleteSingleValue}
secondTitle={metadataConfig.secondTitle}
type={metadataConfig.type} type={metadataConfig.type}
documentIds={metadataConfig.documentIds} documentIds={metadataConfig.documentIds}
otherData={metadataConfig.record} otherData={metadataConfig.record}

View File

@ -9,7 +9,14 @@ 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, PenIcon, Play, Trash2 } from 'lucide-react'; import {
Ban,
CircleCheck,
CircleX,
Cylinder,
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';
@ -143,7 +150,7 @@ export function useBulkOperateDataset({
{ {
id: 'batch-metadata', id: 'batch-metadata',
label: t('knowledgeDetails.metadata.metadata'), label: t('knowledgeDetails.metadata.metadata'),
icon: <PenIcon />, icon: <Cylinder />,
}, },
]; ];

View File

@ -191,10 +191,16 @@ export function useDatasetTableColumns({
<div className="text-base font-normal"> <div className="text-base font-normal">
{t('metadata.editMetadata')} {t('metadata.editMetadata')}
</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.original.name} {row.original.name}
</div> </div> */}
</div>
),
secondTitle: (
<div className="w-full flex gap-1 text-sm text-text-secondary">
<FileIcon name={row.original.name}></FileIcon>
<div className="truncate">{row.original.name}</div>
</div> </div>
), ),
isDeleteSingleValue: true, isDeleteSingleValue: true,