mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-11-10 21:32:43 +08:00
Fix: when save dataset parser_config, warn user only new files take effects (#21416)
This commit is contained in:
1 parent
77b249eb7a
commit
14133cc5f7
4 files changed
+125
-2
No files matched your search
@@ -772,6 +772,8 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
parsingMode: 'Parsing mode',
|
||||
manualSetup: 'Custom ingestion pipeline',
|
||||
builtIn: 'Built-in parsing template',
|
||||
parserSettingsChangeWarning:
|
||||
'Updated parser settings apply only to newly uploaded files. To apply these settings to existing files, update their parser settings and re-parse them.',
|
||||
noConfigChunkerHint:
|
||||
'The built-in {{name}} parser handles chunking automatically, so no additional configuration is required.',
|
||||
titleDescription:
|
||||
|
||||
@@ -708,6 +708,8 @@ export default {
|
||||
parsingMode: '解析方式',
|
||||
manualSetup: '自定义Ingestion pipeline',
|
||||
builtIn: '内置解析模板',
|
||||
parserSettingsChangeWarning:
|
||||
'更新后的解析设置仅对新上传的文件生效。如需对现有文件生效,请修改这些文件的解析设置并重新解析。',
|
||||
noConfigChunkerHint:
|
||||
'内置的 {{name}} 解析器会自动处理分块,因此无需额外配置。',
|
||||
titleDescription: '在这里更新知识库配置,特别是大语言模型和提示词。',
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import Divider from '@/components/ui/divider';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import {
|
||||
Form,
|
||||
FormField,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
usePipelineOperatorNodes,
|
||||
useResetParserConfigOnPipelineChange,
|
||||
} from '@/hooks/use-pipeline-operator';
|
||||
import { buildParserConfigFromNodes } from '@/utils/pipeline-operator';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
@@ -42,6 +44,7 @@ import {
|
||||
usePipelineDataList,
|
||||
useSaveDatasetSetting,
|
||||
} from './hooks';
|
||||
import { normalizeParserConfigForComparison } from './parser-config';
|
||||
import { useRevealSubmitErrors } from './use-reveal-submit-errors';
|
||||
|
||||
export default function DatasetSetting() {
|
||||
@@ -146,9 +149,39 @@ export default function DatasetSetting() {
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (data: z.infer<typeof formSchema>) => {
|
||||
await handleSave(data);
|
||||
const savedValues = form.formState.defaultValues;
|
||||
const operatorDefaults = buildParserConfigFromNodes(operatorNodes);
|
||||
const [savedParserConfig, parserConfig] = [
|
||||
savedValues?.parser_config,
|
||||
data.parser_config,
|
||||
].map((config) =>
|
||||
normalizeParserConfigForComparison(config, operatorDefaults, t),
|
||||
);
|
||||
const parserSettingsChanged =
|
||||
data.parse_type !== savedValues?.parse_type ||
|
||||
(data.parser_id || '') !== (savedValues?.parser_id || '') ||
|
||||
(data.pipeline_id || '') !== (savedValues?.pipeline_id || '') ||
|
||||
!isEqual(parserConfig, savedParserConfig);
|
||||
|
||||
if (knowledgeDetails.chunk_count > 0 && parserSettingsChanged) {
|
||||
await new Promise<void>((resolve) => {
|
||||
Modal.warning({
|
||||
title: t('common.warn'),
|
||||
content: t('knowledgeConfiguration.parserSettingsChangeWarning'),
|
||||
okText: t('common.ok'),
|
||||
closable: false,
|
||||
showCancel: false,
|
||||
onOk: resolve,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const result = await handleSave(data);
|
||||
if (result.code === 0) {
|
||||
form.reset(data);
|
||||
}
|
||||
},
|
||||
[handleSave],
|
||||
[form, handleSave, knowledgeDetails.chunk_count, operatorNodes, t],
|
||||
);
|
||||
|
||||
const { errors } = useFormState({
|
||||
@@ -294,6 +327,7 @@ export default function DatasetSetting() {
|
||||
loading={
|
||||
datasetSettingLoading ||
|
||||
saveLoading ||
|
||||
form.formState.isSubmitting ||
|
||||
(operatorNodesLoading && operatorNodes.length === 0)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Languages } from '@/components/cross-language-form-field';
|
||||
import { ParseDocumentType } from '@/components/layout-recognize-form-field';
|
||||
import { Operator } from '@/constants/agent';
|
||||
import { FileType } from '@/constants/file';
|
||||
import { normalizeParserFormValues } from '@/pages/agent/form/parser-form/utils';
|
||||
import { transformApiResponseToForm } from '@/pages/agent/form/title-chunker-form/hook';
|
||||
import {
|
||||
getOperatorType,
|
||||
normalizeOperatorForm,
|
||||
transformExtractorConfigToForm,
|
||||
transformFormConfigToApi,
|
||||
} from '@/utils/pipeline-operator';
|
||||
import type { TFunction } from 'i18next';
|
||||
|
||||
export function normalizeParserConfigForComparison(
|
||||
config: Record<string, any> | undefined,
|
||||
operatorDefaults: Record<string, any>,
|
||||
t: TFunction,
|
||||
) {
|
||||
const entries = Object.entries({ ...operatorDefaults, ...config })
|
||||
.filter(([operatorId]) => operatorId.includes(':'))
|
||||
.map(([operatorId, values]) => {
|
||||
const operatorType = getOperatorType(operatorId);
|
||||
let normalized = normalizeOperatorForm(operatorId, values);
|
||||
|
||||
// Mirror initialization performed inside the operator forms, including
|
||||
// defaults that their mounted controls write back without user edits.
|
||||
if (operatorType === Operator.Parser) {
|
||||
normalized = normalizeParserFormValues(normalized);
|
||||
normalized.setups = normalized.setups.map(
|
||||
(setup: Record<string, any>) => {
|
||||
const next = { ...setup };
|
||||
if (
|
||||
setup.fileFormat === FileType.PDF &&
|
||||
setup.parse_method &&
|
||||
![
|
||||
ParseDocumentType.DeepDOC,
|
||||
ParseDocumentType.PlainText,
|
||||
ParseDocumentType.TCADPParser,
|
||||
].includes(setup.parse_method) &&
|
||||
!setup.lang
|
||||
) {
|
||||
next.lang = Languages[0];
|
||||
}
|
||||
if (
|
||||
[
|
||||
FileType.PDF,
|
||||
FileType.Spreadsheet,
|
||||
FileType.PowerPoint,
|
||||
].includes(setup.fileFormat) &&
|
||||
setup.parse_method === ParseDocumentType.TCADPParser
|
||||
) {
|
||||
next.table_result_type ||= '1';
|
||||
next.markdown_image_response_type ||= '1';
|
||||
}
|
||||
return next;
|
||||
},
|
||||
);
|
||||
} else if (operatorType === Operator.Extractor) {
|
||||
normalized = transformExtractorConfigToForm(normalized);
|
||||
for (const feature of ['keywords', 'questions', 'summary']) {
|
||||
normalized[feature] = {
|
||||
...normalized[feature],
|
||||
system_prompt:
|
||||
normalized[feature]?.system_prompt ||
|
||||
t(`flow.prompts.system.${feature}`),
|
||||
};
|
||||
}
|
||||
} else if (
|
||||
operatorType === Operator.TitleChunker ||
|
||||
operatorType === Operator.ManualChunker
|
||||
) {
|
||||
normalized = transformApiResponseToForm(normalized);
|
||||
if (operatorType === Operator.ManualChunker) {
|
||||
normalized.method = 'group';
|
||||
}
|
||||
}
|
||||
|
||||
return [operatorId, transformFormConfigToApi(operatorType, normalized)];
|
||||
});
|
||||
|
||||
// Registered optional controls can add undefined properties. Compare the
|
||||
// serialized settings sent to the API, where those properties are omitted.
|
||||
return JSON.parse(JSON.stringify(Object.fromEntries(entries)));
|
||||
}
|
||||
Reference in new issue
Block a user