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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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 { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
@ -36,6 +37,7 @@ export const DelimiterInput = forwardRef<HTMLInputElement, InputProps & IProps>(
maxLength={maxLength}
defaultValue={defaultValue}
ref={ref}
className={cn('bg-bg-base', props.className)}
{...props}
></Input>
);

View File

@ -31,7 +31,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
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,
)}
ref={ref}
@ -65,7 +65,11 @@ const ExpandedInput = ({
{prefix}
</span>
<Input
className={cn({ 'pr-8': !!suffix, 'pl-8': !!prefix }, className)}
className={cn(
{ 'pr-8': !!suffix, 'pl-8': !!prefix },
'bg-bg-base',
className,
)}
{...props}
></Input>
<span

View File

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

View File

@ -24,6 +24,12 @@ export const useNavigatePage = () => {
},
[navigate],
);
const navigateToDatasetOverview = useCallback(
(id: string) => () => {
navigate(`${Routes.DatasetBase}${Routes.DataSetOverview}/${id}`);
},
[navigate],
);
const navigateToDataFile = useCallback(
(id: string) => () => {
@ -160,6 +166,7 @@ export const useNavigatePage = () => {
return {
navigateToDatasetList,
navigateToDataset,
navigateToDatasetOverview,
navigateToHome,
navigateToProfile,
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',
},
dataflowParser: {
result: 'Result',
parseSummary: 'Parse Summary',
parseSummaryTip: 'Parserdeepdoc',
rerunFromCurrentStep: 'Rerun From Current Step',

View File

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

View File

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

View File

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

View File

@ -218,18 +218,11 @@ export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
const nodes: Array<ITimelineNodeObj & { id: number | string }> = [];
console.log('time-->', data);
const times = data?.dsl?.components;
const graphNodes = data?.dsl?.graph?.nodes;
if (times) {
const getNode = (
key: string,
index: number,
type:
| TimelineNodeType.begin
| TimelineNodeType.parser
| TimelineNodeType.tokenizer
| TimelineNodeType.characterSplitter
| TimelineNodeType.titleSplitter,
) => {
const getNode = (key: string, index: number, type: TimelineNodeType) => {
const node = times[key].obj;
const graphNode = graphNodes?.find((item) => item.id === key);
const name = camelCase(
node.component_name,
) as keyof typeof TimelineNodeObj;
@ -247,6 +240,7 @@ export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
}
const timeNode = {
...TimelineNodeObj[name],
title: graphNode?.data?.name,
id: index,
className: 'w-32',
completed: false,
@ -255,6 +249,13 @@ export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
),
type: tempType,
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);
nodes.push(timeNode);
@ -329,3 +330,30 @@ export function useFetchPipelineResult({
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,
useHandleChunkCardClick,
useRerunDataflow,
useSummaryInfo,
useTimelineDataFlow,
} from './hooks';
@ -61,7 +62,7 @@ const Chunk = () => {
);
const {
navigateToDataset,
navigateToDatasetOverview,
navigateToDatasetList,
navigateToAgents,
navigateToDataflow,
@ -150,7 +151,7 @@ const Chunk = () => {
({} as TimelineNode)
);
}, [activeStepId, timelineNodes]);
const { summaryInfo } = useSummaryInfo(dataset, currentTimeNode);
return (
<>
<PageHeader>
@ -175,7 +176,7 @@ const Chunk = () => {
<BreadcrumbLink
onClick={() => {
if (knowledgeId) {
navigateToDataset(knowledgeId)();
navigateToDatasetOverview(knowledgeId)();
}
if (agentId) {
navigateToDataflow(agentId)();
@ -220,7 +221,7 @@ const Chunk = () => {
></DocumentPreview>
</section>
</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">
{/* {currentTimeNode?.type === TimelineNodeType.splitter && (
<ChunkerContainer
@ -246,6 +247,7 @@ const Chunk = () => {
key: string;
}
}
summaryInfo={summaryInfo}
clickChunk={handleChunkCardClick}
reRunFunc={handleReRunFunc}
/>

View File

@ -1,6 +1,6 @@
import { PipelineResultSearchParams } from './constant';
interface ComponentParams {
export interface ComponentParams {
debug_inputs: Record<string, any>;
delay_after_error: number;
description: string;
@ -8,6 +8,7 @@ interface ComponentParams {
exception_goto: any;
exception_method: any;
inputs: Record<string, any>;
field_name: string;
max_retries: number;
message_history_window_size: number;
outputs: {
@ -30,6 +31,66 @@ export interface IDslComponent {
obj: ComponentObject;
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 {
avatar: string;
create_date: string;
@ -42,6 +103,7 @@ export interface IPipelineFileLogDetail {
components: {
[key: string]: IDslComponent;
};
graph: GraphData;
task_id: string;
path: Array<string>;
};

View File

@ -19,6 +19,7 @@ interface IProps {
data: { value: IDslComponent; key: string };
reRunLoading: boolean;
clickChunk: (chunk: IChunk) => void;
summaryInfo: string;
reRunFunc: (data: { value: IDslComponent; key: string }) => void;
}
const ParserContainer = (props: IProps) => {
@ -31,6 +32,7 @@ const ParserContainer = (props: IProps) => {
reRunLoading,
clickChunk,
isReadonly,
summaryInfo,
} = props;
const { t } = useTranslation();
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
@ -46,6 +48,7 @@ const ParserContainer = (props: IProps) => {
key,
type,
value,
params: data?.value?.obj?.params,
};
}, [data]);
@ -130,7 +133,7 @@ const ParserContainer = (props: IProps) => {
const newText = [...initialText.value, { text: text || ' ' }];
setInitialText({
...initialText,
value: newText,
value: newText as any,
});
},
[initialText],
@ -156,15 +159,16 @@ const ParserContainer = (props: IProps) => {
{t('dataflowParser.parseSummary')}
</h2>
<div className="text-[12px] text-text-secondary italic ">
{t('dataflowParser.parseSummaryTip')}
{/* {t('dataflowParser.parseSummaryTip')} */}
{summaryInfo}
</div>
</div>
)}
{isChunck && (
<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">
{t('chunk.chunkResultTip')}
{/* {t('chunk.chunkResultTip')} */}
</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 { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field';
import { MaxTokenNumberFormField } from '@/components/max-token-number-from-field';
import { TagItems } from '../components/tag-item';
import {
ConfigurationFormContainer,
MainContainer,
@ -26,7 +25,7 @@ export function NaiveConfiguration() {
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
<ExcelToHtmlFormField></ExcelToHtmlFormField>
<TagItems></TagItems>
{/* <TagItems></TagItems> */}
</ConfigurationFormContainer>
</MainContainer>
);