Fix: Update the parsing editor to support dynamic field names and optimize UI styles #9869 (#10535)

### What problem does this PR solve?

Fix: Update the parsing editor to support dynamic field names and
optimize UI styles #9869
-Modify the default background color of UI components such as Input and
Select to 'bg bg base'`
-Remove TagItems component reference from naive configuration page

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-10-14 13:31:48 +08:00
committed by GitHub
parent 781d49cd0e
commit 66c69d10fe
13 changed files with 207 additions and 87 deletions

View File

@ -1,3 +1,4 @@
import { cn } from '@/lib/utils';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -36,6 +37,7 @@ export const DelimiterInput = forwardRef<HTMLInputElement, InputProps & IProps>(
maxLength={maxLength} maxLength={maxLength}
defaultValue={defaultValue} defaultValue={defaultValue}
ref={ref} ref={ref}
className={cn('bg-bg-base', props.className)}
{...props} {...props}
></Input> ></Input>
); );

View File

@ -31,7 +31,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input <input
type={type} type={type}
className={cn( className={cn(
'flex h-8 w-full rounded-md border border-input bg-bg-card px-2 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', 'flex h-8 w-full rounded-md border border-input bg-bg-base px-2 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className, className,
)} )}
ref={ref} ref={ref}
@ -65,7 +65,11 @@ const ExpandedInput = ({
{prefix} {prefix}
</span> </span>
<Input <Input
className={cn({ 'pr-8': !!suffix, 'pl-8': !!prefix }, className)} className={cn(
{ 'pr-8': !!suffix, 'pl-8': !!prefix },
'bg-bg-base',
className,
)}
{...props} {...props}
></Input> ></Input>
<span <span

View File

@ -291,7 +291,7 @@ export const RAGFlowSelect = forwardRef<
onReset={handleReset} onReset={handleReset}
allowClear={allowClear} allowClear={allowClear}
ref={ref} ref={ref}
className={triggerClassName} className={cn(triggerClassName, 'bg-bg-base')}
> >
<SelectValue placeholder={placeholder}>{label}</SelectValue> <SelectValue placeholder={placeholder}>{label}</SelectValue>
</SelectTrigger> </SelectTrigger>

View File

@ -24,6 +24,12 @@ export const useNavigatePage = () => {
}, },
[navigate], [navigate],
); );
const navigateToDatasetOverview = useCallback(
(id: string) => () => {
navigate(`${Routes.DatasetBase}${Routes.DataSetOverview}/${id}`);
},
[navigate],
);
const navigateToDataFile = useCallback( const navigateToDataFile = useCallback(
(id: string) => () => { (id: string) => () => {
@ -160,6 +166,7 @@ export const useNavigatePage = () => {
return { return {
navigateToDatasetList, navigateToDatasetList,
navigateToDataset, navigateToDataset,
navigateToDatasetOverview,
navigateToHome, navigateToHome,
navigateToProfile, navigateToProfile,
navigateToChatList, navigateToChatList,

View File

@ -1672,6 +1672,7 @@ This delimiter is used to split the input text into several text pieces echo of
page: '{{page}} /Page', page: '{{page}} /Page',
}, },
dataflowParser: { dataflowParser: {
result: 'Result',
parseSummary: 'Parse Summary', parseSummary: 'Parse Summary',
parseSummaryTip: 'Parserdeepdoc', parseSummaryTip: 'Parserdeepdoc',
rerunFromCurrentStep: 'Rerun From Current Step', rerunFromCurrentStep: 'Rerun From Current Step',

View File

@ -1588,6 +1588,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
page: '{{page}}条/页', page: '{{page}}条/页',
}, },
dataflowParser: { dataflowParser: {
result: '结果',
parseSummary: '解析摘要', parseSummary: '解析摘要',
parseSummaryTip: '解析器: deepdoc', parseSummaryTip: '解析器: deepdoc',
rerunFromCurrentStep: '从当前步骤重新运行', rerunFromCurrentStep: '从当前步骤重新运行',

View File

@ -1,6 +1,6 @@
import { CheckedState } from '@radix-ui/react-checkbox'; import { CheckedState } from '@radix-ui/react-checkbox';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
import { IChunk } from '../../interface'; import { ComponentParams, IChunk } from '../../interface';
import { parserKeyMap } from './json-parser'; import { parserKeyMap } from './json-parser';
export interface FormatPreserveEditorProps { export interface FormatPreserveEditorProps {
@ -28,6 +28,7 @@ export type IJsonContainerProps = {
value: { value: {
[key: string]: string; [key: string]: string;
}[]; }[];
params: ComponentParams;
}; };
isChunck?: boolean; isChunck?: boolean;
handleCheck: (e: CheckedState, index: number) => void; handleCheck: (e: CheckedState, index: number) => void;
@ -52,6 +53,7 @@ export type IObjContainerProps = {
key: string; key: string;
type: string; type: string;
value: string; value: string;
params: ComponentParams;
}; };
isChunck?: boolean; isChunck?: boolean;
handleCheck: (e: CheckedState, index: number) => void; handleCheck: (e: CheckedState, index: number) => void;

View File

@ -1,6 +1,7 @@
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { useCallback, useEffect } from 'react'; import { isArray } from 'lodash';
import { useCallback, useEffect, useMemo } from 'react';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
import styles from '../../index.less'; import styles from '../../index.less';
import { useParserInit } from './hook'; import { useParserInit } from './hook';
@ -33,7 +34,13 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
editDivRef, editDivRef,
} = useParserInit({ initialValue }); } = useParserInit({ initialValue });
const parserKey = parserKeyMap[content.key as keyof typeof parserKeyMap]; const parserKey = useMemo(() => {
const key =
content.key === 'chunks' && content.params.field_name
? content.params.field_name
: parserKeyMap[content.key as keyof typeof parserKeyMap];
return key;
}, [content]);
const handleEdit = useCallback( const handleEdit = useCallback(
(e?: any, index?: number) => { (e?: any, index?: number) => {
@ -73,67 +80,68 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
return ( return (
<> <>
{content.value?.map((item, index) => { {isArray(content.value) &&
if ( content.value?.map((item, index) => {
item[parserKeyMap[content.key as keyof typeof parserKeyMap]] === '' if (
) { item[parserKeyMap[content.key as keyof typeof parserKeyMap]] === ''
return null; ) {
} return null;
return ( }
<section return (
key={index} <section
className={cn( key={index}
isChunck className={cn(
? 'bg-bg-card my-2 p-2 rounded-lg flex gap-1 items-start' isChunck
: '', ? 'bg-bg-card my-2 p-2 rounded-lg flex gap-1 items-start'
activeEditIndex === index && isChunck ? 'bg-bg-title' : '', : '',
)} activeEditIndex === index && isChunck ? 'bg-bg-title' : '',
> )}
{isChunck && !isReadonly && ( >
<Checkbox {isChunck && !isReadonly && (
onCheckedChange={(e) => { <Checkbox
handleCheck(e, index); onCheckedChange={(e) => {
}} handleCheck(e, index);
checked={selectedChunkIds?.some( }}
(id) => id.toString() === index.toString(), checked={selectedChunkIds?.some(
)} (id) => id.toString() === index.toString(),
></Checkbox> )}
)} ></Checkbox>
{activeEditIndex === index && ( )}
<div {activeEditIndex === index && (
ref={editDivRef} <div
contentEditable={!isReadonly} ref={editDivRef}
onBlur={handleSave} contentEditable={!isReadonly}
className={cn( onBlur={handleSave}
'w-full bg-transparent text-text-secondary border-none focus-visible:border-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none p-0', className={cn(
'w-full bg-transparent text-text-secondary border-none focus-visible:border-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none p-0',
className, className,
)} )}
></div> ></div>
)} )}
{activeEditIndex !== index && ( {activeEditIndex !== index && (
<div <div
className={cn( className={cn(
'text-text-secondary overflow-auto scrollbar-auto w-full', 'text-text-secondary overflow-auto scrollbar-auto w-full',
{ {
[styles.contentEllipsis]: [styles.contentEllipsis]:
textMode === ChunkTextMode.Ellipse, textMode === ChunkTextMode.Ellipse,
}, },
)} )}
key={index} key={index}
onClick={(e) => { onClick={(e) => {
clickChunk(item); clickChunk(item);
if (!isReadonly) { if (!isReadonly) {
handleEdit(e, index); handleEdit(e, index);
} }
}} }}
> >
{item[parserKeyMap[content.key]]} {item[parserKey]}
</div> </div>
)} )}
</section> </section>
); );
})} })}
</> </>
); );
}; };

View File

@ -218,18 +218,11 @@ export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
const nodes: Array<ITimelineNodeObj & { id: number | string }> = []; const nodes: Array<ITimelineNodeObj & { id: number | string }> = [];
console.log('time-->', data); console.log('time-->', data);
const times = data?.dsl?.components; const times = data?.dsl?.components;
const graphNodes = data?.dsl?.graph?.nodes;
if (times) { if (times) {
const getNode = ( const getNode = (key: string, index: number, type: TimelineNodeType) => {
key: string,
index: number,
type:
| TimelineNodeType.begin
| TimelineNodeType.parser
| TimelineNodeType.tokenizer
| TimelineNodeType.characterSplitter
| TimelineNodeType.titleSplitter,
) => {
const node = times[key].obj; const node = times[key].obj;
const graphNode = graphNodes?.find((item) => item.id === key);
const name = camelCase( const name = camelCase(
node.component_name, node.component_name,
) as keyof typeof TimelineNodeObj; ) as keyof typeof TimelineNodeObj;
@ -247,6 +240,7 @@ export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
} }
const timeNode = { const timeNode = {
...TimelineNodeObj[name], ...TimelineNodeObj[name],
title: graphNode?.data?.name,
id: index, id: index,
className: 'w-32', className: 'w-32',
completed: false, completed: false,
@ -255,6 +249,13 @@ export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
), ),
type: tempType, type: tempType,
detail: { value: times[key], key: key }, detail: { value: times[key], key: key },
} as ITimelineNodeObj & {
id: number | string;
className: string;
completed: boolean;
date: string;
type: TimelineNodeType;
detail: { value: IDslComponent; key: string };
}; };
console.log('timeNodetype-->', type); console.log('timeNodetype-->', type);
nodes.push(timeNode); nodes.push(timeNode);
@ -329,3 +330,30 @@ export function useFetchPipelineResult({
return { pipelineResult }; return { pipelineResult };
} }
export const useSummaryInfo = (
data: IPipelineFileLogDetail,
currentTimeNode: TimelineNode,
) => {
const summaryInfo = useMemo(() => {
if (currentTimeNode.type === TimelineNodeType.parser) {
const setups =
currentTimeNode.detail?.value?.obj?.params?.setups?.[
data.document_suffix
];
if (setups) {
const { output_format, parse_method } = setups;
const res = [];
if (parse_method) {
res.push(`${t('dataflow.parserMethod')}: ${parse_method}`);
}
if (output_format) {
res.push(`${t('dataflow.outputFormat')}: ${output_format}`);
}
return res.join(' ');
}
}
return '';
}, [data, currentTimeNode]);
return { summaryInfo };
};

View File

@ -9,6 +9,7 @@ import {
useGetPipelineResultSearchParams, useGetPipelineResultSearchParams,
useHandleChunkCardClick, useHandleChunkCardClick,
useRerunDataflow, useRerunDataflow,
useSummaryInfo,
useTimelineDataFlow, useTimelineDataFlow,
} from './hooks'; } from './hooks';
@ -61,7 +62,7 @@ const Chunk = () => {
); );
const { const {
navigateToDataset, navigateToDatasetOverview,
navigateToDatasetList, navigateToDatasetList,
navigateToAgents, navigateToAgents,
navigateToDataflow, navigateToDataflow,
@ -150,7 +151,7 @@ const Chunk = () => {
({} as TimelineNode) ({} as TimelineNode)
); );
}, [activeStepId, timelineNodes]); }, [activeStepId, timelineNodes]);
const { summaryInfo } = useSummaryInfo(dataset, currentTimeNode);
return ( return (
<> <>
<PageHeader> <PageHeader>
@ -175,7 +176,7 @@ const Chunk = () => {
<BreadcrumbLink <BreadcrumbLink
onClick={() => { onClick={() => {
if (knowledgeId) { if (knowledgeId) {
navigateToDataset(knowledgeId)(); navigateToDatasetOverview(knowledgeId)();
} }
if (agentId) { if (agentId) {
navigateToDataflow(agentId)(); navigateToDataflow(agentId)();
@ -220,7 +221,7 @@ const Chunk = () => {
></DocumentPreview> ></DocumentPreview>
</section> </section>
</div> </div>
<div className="h-dvh border-r -mt-3"></div> <div className="h-[calc(100vh-100px)] border-r -mt-3"></div>
<div className="w-3/5 h-full"> <div className="w-3/5 h-full">
{/* {currentTimeNode?.type === TimelineNodeType.splitter && ( {/* {currentTimeNode?.type === TimelineNodeType.splitter && (
<ChunkerContainer <ChunkerContainer
@ -246,6 +247,7 @@ const Chunk = () => {
key: string; key: string;
} }
} }
summaryInfo={summaryInfo}
clickChunk={handleChunkCardClick} clickChunk={handleChunkCardClick}
reRunFunc={handleReRunFunc} reRunFunc={handleReRunFunc}
/> />

View File

@ -1,6 +1,6 @@
import { PipelineResultSearchParams } from './constant'; import { PipelineResultSearchParams } from './constant';
interface ComponentParams { export interface ComponentParams {
debug_inputs: Record<string, any>; debug_inputs: Record<string, any>;
delay_after_error: number; delay_after_error: number;
description: string; description: string;
@ -8,6 +8,7 @@ interface ComponentParams {
exception_goto: any; exception_goto: any;
exception_method: any; exception_method: any;
inputs: Record<string, any>; inputs: Record<string, any>;
field_name: string;
max_retries: number; max_retries: number;
message_history_window_size: number; message_history_window_size: number;
outputs: { outputs: {
@ -30,6 +31,66 @@ export interface IDslComponent {
obj: ComponentObject; obj: ComponentObject;
upstream: Array<string>; upstream: Array<string>;
} }
interface NodeData {
label: string;
name: string;
form?: {
outputs?: Record<
string,
{
type: string;
value: string | Array<Record<string, any>> | number;
}
>;
setups?: Array<Record<string, any>>;
chunk_token_size?: number;
delimiters?: Array<{
value: string;
}>;
overlapped_percent?: number;
};
}
interface EdgeData {
isHovered: boolean;
}
interface Position {
x: number;
y: number;
}
interface Measured {
height: number;
width: number;
}
interface Node {
data: NodeData;
dragging: boolean;
id: string;
measured: Measured;
position: Position;
selected: boolean;
sourcePosition: string;
targetPosition: string;
type: string;
}
interface Edge {
data: EdgeData;
id: string;
source: string;
sourceHandle: string;
target: string;
targetHandle: string;
}
interface GraphData {
edges: Edge[];
nodes: Node[];
}
export interface IPipelineFileLogDetail { export interface IPipelineFileLogDetail {
avatar: string; avatar: string;
create_date: string; create_date: string;
@ -42,6 +103,7 @@ export interface IPipelineFileLogDetail {
components: { components: {
[key: string]: IDslComponent; [key: string]: IDslComponent;
}; };
graph: GraphData;
task_id: string; task_id: string;
path: Array<string>; path: Array<string>;
}; };

View File

@ -19,6 +19,7 @@ interface IProps {
data: { value: IDslComponent; key: string }; data: { value: IDslComponent; key: string };
reRunLoading: boolean; reRunLoading: boolean;
clickChunk: (chunk: IChunk) => void; clickChunk: (chunk: IChunk) => void;
summaryInfo: string;
reRunFunc: (data: { value: IDslComponent; key: string }) => void; reRunFunc: (data: { value: IDslComponent; key: string }) => void;
} }
const ParserContainer = (props: IProps) => { const ParserContainer = (props: IProps) => {
@ -31,6 +32,7 @@ const ParserContainer = (props: IProps) => {
reRunLoading, reRunLoading,
clickChunk, clickChunk,
isReadonly, isReadonly,
summaryInfo,
} = props; } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]); const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
@ -46,6 +48,7 @@ const ParserContainer = (props: IProps) => {
key, key,
type, type,
value, value,
params: data?.value?.obj?.params,
}; };
}, [data]); }, [data]);
@ -130,7 +133,7 @@ const ParserContainer = (props: IProps) => {
const newText = [...initialText.value, { text: text || ' ' }]; const newText = [...initialText.value, { text: text || ' ' }];
setInitialText({ setInitialText({
...initialText, ...initialText,
value: newText, value: newText as any,
}); });
}, },
[initialText], [initialText],
@ -156,15 +159,16 @@ const ParserContainer = (props: IProps) => {
{t('dataflowParser.parseSummary')} {t('dataflowParser.parseSummary')}
</h2> </h2>
<div className="text-[12px] text-text-secondary italic "> <div className="text-[12px] text-text-secondary italic ">
{t('dataflowParser.parseSummaryTip')} {/* {t('dataflowParser.parseSummaryTip')} */}
{summaryInfo}
</div> </div>
</div> </div>
)} )}
{isChunck && ( {isChunck && (
<div> <div>
<h2 className="text-[16px]">{t('chunk.chunkResult')}</h2> <h2 className="text-[16px]">{t('dataflowParser.result')}</h2>
<div className="text-[12px] text-text-secondary italic"> <div className="text-[12px] text-text-secondary italic">
{t('chunk.chunkResultTip')} {/* {t('chunk.chunkResultTip')} */}
</div> </div>
</div> </div>
)} )}

View File

@ -6,7 +6,6 @@ import { DelimiterFormField } from '@/components/delimiter-form-field';
import { ExcelToHtmlFormField } from '@/components/excel-to-html-form-field'; import { ExcelToHtmlFormField } from '@/components/excel-to-html-form-field';
import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field'; import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field';
import { MaxTokenNumberFormField } from '@/components/max-token-number-from-field'; import { MaxTokenNumberFormField } from '@/components/max-token-number-from-field';
import { TagItems } from '../components/tag-item';
import { import {
ConfigurationFormContainer, ConfigurationFormContainer,
MainContainer, MainContainer,
@ -26,7 +25,7 @@ export function NaiveConfiguration() {
<AutoKeywordsFormField></AutoKeywordsFormField> <AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField> <AutoQuestionsFormField></AutoQuestionsFormField>
<ExcelToHtmlFormField></ExcelToHtmlFormField> <ExcelToHtmlFormField></ExcelToHtmlFormField>
<TagItems></TagItems> {/* <TagItems></TagItems> */}
</ConfigurationFormContainer> </ConfigurationFormContainer>
</MainContainer> </MainContainer>
); );