Fix: Fixed an issue where parser configurations could be added infinitely #9869 (#10464)

### What problem does this PR solve?

Fix: Fixed an issue where parser configurations could be added
infinitely #9869

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
balibabu
2025-10-10 16:30:13 +08:00
committed by GitHub
parent fc46d6bb87
commit f35c5ed119
12 changed files with 83 additions and 56 deletions

View File

@ -39,9 +39,9 @@ export function HomeCard({
/> />
</div> </div>
<div className="flex flex-col justify-between gap-1 flex-1 h-full w-[calc(100%-50px)]"> <div className="flex flex-col justify-between gap-1 flex-1 h-full w-[calc(100%-50px)]">
<section className="flex justify-between w-full"> <section className="flex justify-between">
<section className="flex gap-1 items-center w-full"> <section className="flex flex-1 min-w-0 gap-1 items-center">
<div className="text-base font-bold w-80% text-ellipsis overflow-hidden leading-snug"> <div className="text-base font-bold leading-snug truncate">
{data.name} {data.name}
</div> </div>
{icon} {icon}

View File

@ -106,7 +106,7 @@ export function Header() {
}, [navigate]); }, [navigate]);
return ( return (
<section className="p-5 pr-14 flex justify-between items-center "> <section className="py-5 px-10 flex justify-between items-center ">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<img <img
src={'/logo.svg'} src={'/logo.svg'}

View File

@ -1729,13 +1729,15 @@ This delimiter is used to split the input text into several text pieces echo of
regularExpressions: 'Regular Expressions', regularExpressions: 'Regular Expressions',
overlappedPercent: 'Overlapped percent', overlappedPercent: 'Overlapped percent',
searchMethod: 'Search method', searchMethod: 'Search method',
searchMethodTip: `Defines how the content can be searched — by full-text, embedding, or both.
The Tokenizer will store the content in the corresponding data structures for the selected methods.`,
begin: 'File', begin: 'File',
parserMethod: 'Parsing method', parserMethod: 'Parsing method',
systemPrompt: 'System Prompt', systemPrompt: 'System Prompt',
systemPromptPlaceholder: systemPromptPlaceholder:
'Enter system prompt for image analysis, if empty the system default value will be used', 'Enter system prompt for image analysis, if empty the system default value will be used',
exportJson: 'Export JSON', exportJson: 'Export JSON',
viewResult: 'View Result', viewResult: 'View result',
running: 'Running', running: 'Running',
summary: 'Augmented Context', summary: 'Augmented Context',
keywords: 'Keywords', keywords: 'Keywords',

View File

@ -1634,6 +1634,8 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
regularExpressions: '正则表达式', regularExpressions: '正则表达式',
overlappedPercent: '重叠百分比', overlappedPercent: '重叠百分比',
searchMethod: '搜索方法', searchMethod: '搜索方法',
searchMethodTip: `决定该数据集启用的搜索方式,可选择全文、向量,或两者兼有。
Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
filenameEmbdWeight: '文件名嵌入权重', filenameEmbdWeight: '文件名嵌入权重',
begin: '文件', begin: '文件',
parserMethod: '解析方法', parserMethod: '解析方法',

View File

@ -9,18 +9,29 @@ import {
SelectWithSearchFlagOptionType, SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search'; } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form'; import { RAGFlowFormItem } from '@/components/ragflow-form';
import { upperFirst } from 'lodash'; import { upperCase, upperFirst } from 'lodash';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FileType, OutputFormatMap, PdfOutputFormat } from '../../constant'; import {
FileType,
OutputFormatMap,
SpreadsheetOutputFormat,
} from '../../constant';
import { CommonProps } from './interface'; import { CommonProps } from './interface';
import { buildFieldNameWithPrefix } from './utils'; import { buildFieldNameWithPrefix } from './utils';
const UppercaseFields = [
SpreadsheetOutputFormat.Html,
SpreadsheetOutputFormat.Json,
];
function buildOutputOptionsFormatMap() { function buildOutputOptionsFormatMap() {
return Object.entries(OutputFormatMap).reduce< return Object.entries(OutputFormatMap).reduce<
Record<string, SelectWithSearchFlagOptionType[]> Record<string, SelectWithSearchFlagOptionType[]>
>((pre, [key, value]) => { >((pre, [key, value]) => {
pre[key] = Object.values(value).map((v) => ({ pre[key] = Object.values(value).map((v) => ({
label: v === PdfOutputFormat.Json ? 'JSON' : upperFirst(v), label: UppercaseFields.some((x) => x === v)
? upperCase(v)
: upperFirst(v),
value: v, value: v,
})); }));
return pre; return pre;

View File

@ -1,4 +1,7 @@
import { SelectWithSearch } from '@/components/originui/select-with-search'; import {
SelectWithSearch,
SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form'; import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button'; import { BlockButton, Button } from '@/components/ui/button';
import { Form } from '@/components/ui/form'; import { Form } from '@/components/ui/form';
@ -49,6 +52,7 @@ type ParserItemProps = {
index: number; index: number;
fieldLength: number; fieldLength: number;
remove: UseFieldArrayRemove; remove: UseFieldArrayRemove;
fileFormatOptions: SelectWithSearchFlagOptionType[];
}; };
export const FormSchema = z.object({ export const FormSchema = z.object({
@ -67,7 +71,13 @@ export const FormSchema = z.object({
export type ParserFormSchemaType = z.infer<typeof FormSchema>; export type ParserFormSchemaType = z.infer<typeof FormSchema>;
function ParserItem({ name, index, fieldLength, remove }: ParserItemProps) { function ParserItem({
name,
index,
fieldLength,
remove,
fileFormatOptions,
}: ParserItemProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const form = useFormContext<ParserFormSchemaType>(); const form = useFormContext<ParserFormSchemaType>();
const ref = useRef(null); const ref = useRef(null);
@ -79,23 +89,15 @@ function ParserItem({ name, index, fieldLength, remove }: ParserItemProps) {
const values = form.getValues(); const values = form.getValues();
const parserList = values.setups.slice(); // Adding, deleting, or modifying the parser array will not change the reference. const parserList = values.setups.slice(); // Adding, deleting, or modifying the parser array will not change the reference.
const FileFormatOptions = buildOptions(
FileType,
t,
'dataflow.fileFormatOptions',
).filter(
(x) => x.value !== FileType.Video, // Temporarily hide the video option
);
const filteredFileFormatOptions = useMemo(() => { const filteredFileFormatOptions = useMemo(() => {
const otherFileFormatList = parserList const otherFileFormatList = parserList
.filter((_, idx) => idx !== index) .filter((_, idx) => idx !== index)
.map((x) => x.fileFormat); .map((x) => x.fileFormat);
return FileFormatOptions.filter((x) => { return fileFormatOptions.filter((x) => {
return !otherFileFormatList.includes(x.value); return !otherFileFormatList.includes(x.value);
}); });
}, [FileFormatOptions, index, parserList]); }, [fileFormatOptions, index, parserList]);
const Widget = const Widget =
typeof fileFormat === 'string' && fileFormat in FileFormatWidgetMap typeof fileFormat === 'string' && fileFormat in FileFormatWidgetMap
@ -158,6 +160,14 @@ const ParserForm = ({ node }: INextOperatorForm) => {
const { t } = useTranslation(); const { t } = useTranslation();
const defaultValues = useFormValues(initialParserValues, node); const defaultValues = useFormValues(initialParserValues, node);
const FileFormatOptions = buildOptions(
FileType,
t,
'dataflow.fileFormatOptions',
).filter(
(x) => x.value !== FileType.Video, // Temporarily hide the video option
);
const form = useForm<z.infer<typeof FormSchema>>({ const form = useForm<z.infer<typeof FormSchema>>({
defaultValues, defaultValues,
resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema),
@ -194,12 +204,15 @@ const ParserForm = ({ node }: INextOperatorForm) => {
index={index} index={index}
fieldLength={fields.length} fieldLength={fields.length}
remove={remove} remove={remove}
fileFormatOptions={FileFormatOptions}
></ParserItem> ></ParserItem>
); );
})} })}
<BlockButton onClick={add} type="button" className="mt-2.5"> {fields.length < FileFormatOptions.length && (
{t('dataflow.addParser')} <BlockButton onClick={add} type="button" className="mt-2.5">
</BlockButton> {t('dataflow.addParser')}
</BlockButton>
)}
</form> </form>
<div className="p-5"> <div className="p-5">
<Output list={outputList}></Output> <Output list={outputList}></Output>

View File

@ -58,6 +58,7 @@ const TokenizerForm = ({ node }: INextOperatorForm) => {
<RAGFlowFormItem <RAGFlowFormItem
name="search_method" name="search_method"
label={t('dataflow.searchMethod')} label={t('dataflow.searchMethod')}
tooltip={t('dataflow.searchMethodTip')}
> >
{(field) => ( {(field) => (
<MultiSelect <MultiSelect

View File

@ -162,9 +162,7 @@ export default function DataFlow() {
onClick={handleRunAgent} onClick={handleRunAgent}
loading={running} loading={running}
> >
<CirclePlay <CirclePlay className={isParsing ? 'animate-spin' : ''} />
className={isParsing || isLogEmpty ? 'animate-spin' : ''}
/>
{isParsing || running ? t('dataflow.running') : t('flow.run')} {isParsing || running ? t('dataflow.running') : t('flow.run')}
</ButtonLoading> </ButtonLoading>

View File

@ -78,7 +78,7 @@ export function DataflowTimeline({ traceList }: DataflowTimelineProps) {
<div className="flex-1 flex items-center gap-5"> <div className="flex-1 flex items-center gap-5">
<Progress value={progress} className="h-1 flex-1" /> <Progress value={progress} className="h-1 flex-1" />
<span className="text-accent-primary text-xs"> <span className="text-accent-primary text-xs">
{progress}% {progress.toFixed(2)}%
</span> </span>
</div> </div>
</section> </section>

View File

@ -55,10 +55,10 @@ export function LogSheet({
return ( return (
<Sheet open onOpenChange={hideModal} modal={false}> <Sheet open onOpenChange={hideModal} modal={false}>
<SheetContent <SheetContent
className={cn('top-20')} className={cn('top-20 h-auto flex flex-col p-0 gap-0')}
onInteractOutside={(e) => e.preventDefault()} onInteractOutside={(e) => e.preventDefault()}
> >
<SheetHeader> <SheetHeader className="p-5">
<SheetTitle className="flex items-center gap-2.5"> <SheetTitle className="flex items-center gap-2.5">
<Logs className="size-4" /> {t('flow.log')} <Logs className="size-4" /> {t('flow.log')}
{isCompleted && ( {isCompleted && (
@ -82,30 +82,32 @@ export function LogSheet({
)} )}
</SheetTitle> </SheetTitle>
</SheetHeader> </SheetHeader>
<section className="max-h-[82vh] overflow-auto mt-6"> <section className="flex-1 overflow-auto px-5 pt-5">
{isLogEmpty ? ( {isLogEmpty ? (
<SkeletonCard className="mt-2" /> <SkeletonCard className="mt-2" />
) : ( ) : (
<DataflowTimeline traceList={logs}></DataflowTimeline> <DataflowTimeline traceList={logs}></DataflowTimeline>
)} )}
</section> </section>
{isParsing ? ( <div className="px-5 pb-5">
<Button {isParsing ? (
className="w-full mt-8 bg-state-error/10 text-state-error hover:bg-state-error hover:text-bg-base" <Button
onClick={handleCancel} className="w-full mt-8 bg-state-error/10 text-state-error hover:bg-state-error hover:text-bg-base"
> onClick={handleCancel}
<CirclePause /> {t('dataflow.cancel')} >
</Button> <CirclePause /> {t('dataflow.cancel')}
) : ( </Button>
<Button ) : (
onClick={handleDownloadJson} <Button
disabled={isEndOutputEmpty(logs)} onClick={handleDownloadJson}
className="w-full mt-8 bg-accent-primary-5 text-text-secondary hover:bg-accent-primary-5 hover:text-accent-primary hover:border-accent-primary hover:border" disabled={isEndOutputEmpty(logs)}
> className="w-full mt-8 bg-accent-primary-5 text-text-secondary hover:bg-accent-primary-5 hover:text-accent-primary hover:border-accent-primary hover:border"
<SquareArrowOutUpRight /> >
{t('dataflow.exportJson')} <SquareArrowOutUpRight />
</Button> {t('dataflow.exportJson')}
)} </Button>
)}
</div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
); );

View File

@ -42,7 +42,7 @@ export function Banner() {
export function NextBanner() { export function NextBanner() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<section className="text-5xl pt-10 pb-14 font-bold"> <section className="text-5xl pt-10 pb-14 font-bold px-10">
<span className="text-text-primary">{t('header.welcome')}</span> <span className="text-text-primary">{t('header.welcome')}</span>
<span className="pl-3 text-transparent bg-clip-text bg-gradient-to-l from-[#40EBE3] to-[#4A51FF]"> <span className="pl-3 text-transparent bg-clip-text bg-gradient-to-l from-[#40EBE3] to-[#4A51FF]">
RAGFlow RAGFlow

View File

@ -4,15 +4,13 @@ import { Datasets } from './datasets';
const Home = () => { const Home = () => {
return ( return (
<div className="mx-8"> <section>
<section> <NextBanner></NextBanner>
<NextBanner></NextBanner> <section className="h-[calc(100dvh-260px)] overflow-auto px-10">
<section className="h-[calc(100dvh-260px)] overflow-auto scrollbar-thin"> <Datasets></Datasets>
<Datasets></Datasets> <Applications></Applications>
<Applications></Applications>
</section>
</section> </section>
</div> </section>
); );
}; };