Fix: Optimized knowledge base file parsing and display #9869 (#10292)

### What problem does this PR solve?

Fix: Optimized knowledge base file parsing and display #9869

- Optimized the ChunkMethodDialog component logic and adjusted
FormSchema validation rules
- Updated the document information interface definition, adding
pipeline_id, pipeline_name, and suffix fields
- Refactored the ChunkResultBar component, removing filter-related logic
and simplifying the input box and chunk creation functionality
- Improved FormatPreserveEditor to support text mode switching
(full/omitted) display control
- Updated timeline node titles to more accurate semantic descriptions
(e.g., character splitters)
- Optimized the data flow result page structure and style, dynamically
adjusting height and content display
- Fixed the table sorting function on the dataset overview page and
enhanced the display of task type icons and status mapping.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-09-25 19:53:49 +08:00
committed by GitHub
parent abe7132630
commit 14273b4595
27 changed files with 674 additions and 386 deletions

View File

@ -1,55 +1,34 @@
import { Input } from '@/components/originui/input';
import { Button } from '@/components/ui/button';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Radio } from '@/components/ui/radio';
import { useTranslate } from '@/hooks/common-hooks';
import { cn } from '@/lib/utils';
import { SearchOutlined } from '@ant-design/icons';
import { ListFilter, Plus } from 'lucide-react';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import { ChunkTextMode } from '../../constant';
interface ChunkResultBarProps {
changeChunkTextMode: React.Dispatch<React.SetStateAction<string | number>>;
available: number | undefined;
selectAllChunk: (value: boolean) => void;
handleSetAvailable: (value: number | undefined) => void;
createChunk: () => void;
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
searchString: string;
createChunk: (text: string) => void;
}
export default ({
changeChunkTextMode,
available,
selectAllChunk,
handleSetAvailable,
createChunk,
handleInputChange,
searchString,
}: ChunkResultBarProps) => {
export default ({ changeChunkTextMode, createChunk }: ChunkResultBarProps) => {
const { t } = useTranslate('chunk');
const [textSelectValue, setTextSelectValue] = useState<string | number>(
ChunkTextMode.Full,
);
const handleFilterChange = (e: string | number) => {
const value = e === -1 ? undefined : (e as number);
selectAllChunk(false);
handleSetAvailable(value);
};
const filterContent = (
<div className="w-[200px]">
<Radio.Group onChange={handleFilterChange} value={available}>
<div className="flex flex-col gap-2 p-4">
<Radio value={-1}>{t('all')}</Radio>
<Radio value={1}>{t('enabled')}</Radio>
<Radio value={0}>{t('disabled')}</Radio>
</div>
</Radio.Group>
</div>
);
// const handleFilterChange = (e: string | number) => {
// const value = e === -1 ? undefined : (e as number);
// selectAllChunk(false);
// handleSetAvailable(value);
// };
// const filterContent = (
// <div className="w-[200px]">
// <Radio.Group onChange={handleFilterChange} value={available}>
// <div className="flex flex-col gap-2 p-4">
// <Radio value={-1}>{t('all')}</Radio>
// <Radio value={1}>{t('enabled')}</Radio>
// <Radio value={0}>{t('disabled')}</Radio>
// </div>
// </Radio.Group>
// </div>
// );
const textSelectOptions = [
{ label: t(ChunkTextMode.Full), value: ChunkTextMode.Full },
{ label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse },
@ -78,7 +57,7 @@ export default ({
</div>
))}
</div>
<Input
{/* <Input
className="bg-bg-card text-muted-foreground"
style={{ width: 200 }}
placeholder={t('search')}
@ -95,9 +74,9 @@ export default ({
<PopoverContent className="p-0 w-[200px]">
{filterContent}
</PopoverContent>
</Popover>
</Popover> */}
<Button
onClick={() => createChunk()}
onClick={() => createChunk('')}
variant={'secondary'}
className="bg-bg-card text-muted-foreground hover:bg-card"
>

View File

@ -2,8 +2,9 @@ import { Checkbox } from '@/components/ui/checkbox';
import { Textarea } from '@/components/ui/textarea';
import { cn } from '@/lib/utils';
import { CheckedState } from '@radix-ui/react-checkbox';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { ChunkTextMode } from '../../constant';
import styles from '../../index.less';
interface FormatPreserveEditorProps {
initialValue: {
key: string;
@ -17,6 +18,7 @@ interface FormatPreserveEditorProps {
isChunck?: boolean;
handleCheckboxClick?: (id: string | number, checked: boolean) => void;
selectedChunkIds?: string[];
textMode?: ChunkTextMode;
}
const FormatPreserveEditor = ({
initialValue,
@ -25,6 +27,7 @@ const FormatPreserveEditor = ({
isChunck,
handleCheckboxClick,
selectedChunkIds,
textMode,
}: FormatPreserveEditorProps) => {
const [content, setContent] = useState(initialValue);
// const [isEditing, setIsEditing] = useState(false);
@ -32,6 +35,10 @@ const FormatPreserveEditor = ({
undefined,
);
console.log('initialValue', initialValue);
useEffect(() => {
setContent(initialValue);
}, [initialValue]);
const handleEdit = (e?: any, index?: number) => {
console.log(e, index, content);
if (content.key === 'json') {
@ -143,7 +150,12 @@ const FormatPreserveEditor = ({
)}
{activeEditIndex !== index && (
<div
className="text-text-secondary overflow-auto scrollbar-auto whitespace-pre-wrap"
className={cn(
'text-text-secondary overflow-auto scrollbar-auto whitespace-pre-wrap w-full',
{
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,
},
)}
key={index}
onClick={(e) => {
handleEdit(e, index);

View File

@ -37,11 +37,11 @@ export const TimelineNodeObj = {
icon: <Heading size={13} />,
},
[TimelineNodeType.characterSplitter]: {
title: 'Title Splitter',
title: 'Character Splitter',
icon: <Heading size={13} />,
},
[TimelineNodeType.splitter]: {
title: 'Character Splitter',
title: 'Splitter',
icon: <Blocks size={13} />,
},
[TimelineNodeType.tokenizer]: {
@ -50,40 +50,6 @@ export const TimelineNodeObj = {
clickable: false,
},
};
// export const TimelineNodeArr = [
// {
// id: 1,
// title: 'File',
// icon: <PlayIcon size={13} />,
// clickable: false,
// type: TimelineNodeType.begin,
// },
// {
// id: 2,
// title: 'Context Generator',
// icon: <PlayIcon size={13} />,
// type: TimelineNodeType.contextGenerator,
// },
// {
// id: 3,
// title: 'Title Splitter',
// icon: <PlayIcon size={13} />,
// type: TimelineNodeType.titleSplitter,
// },
// {
// id: 4,
// title: 'Character Splitter',
// icon: <PlayIcon size={13} />,
// type: TimelineNodeType.characterSplitter,
// },
// {
// id: 5,
// title: 'Tokenizer',
// icon: <CheckLine size={13} />,
// clickable: false,
// type: TimelineNodeType.tokenizer,
// },
// ]
export interface TimelineDataFlowProps {
activeId: number | string;
activeFunc: (id: number | string, step: TimelineNode) => void;

View File

@ -249,7 +249,6 @@ export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
}
const timeNode = {
...TimelineNodeObj[name],
clickable: true,
id: index,
className: 'w-32',
completed: false,

View File

@ -82,15 +82,6 @@
}
}
.card {
:global {
.ant-card-body {
padding: 10px;
margin: 0;
}
margin-bottom: 10px;
}
cursor: pointer;
.contentEllipsis {
.multipleLineEllipsis(3);
}

View File

@ -42,7 +42,7 @@ const Chunk = () => {
data: { documentInfo },
} = useFetchNextChunkList();
const { selectedChunkId } = useHandleChunkCardClick();
const [activeStepId, setActiveStepId] = useState<number | string>(0);
const [activeStepId, setActiveStepId] = useState<number | string>(2);
const { data: dataset } = useFetchPipelineFileLogDetail();
const { t } = useTranslation();

View File

@ -1,14 +1,16 @@
import { TimelineNode } from '@/components/originui/timeline';
import Spotlight from '@/components/spotlight';
import { Spin } from '@/components/ui/spin';
import { cn } from '@/lib/utils';
import classNames from 'classnames';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import ChunkResultBar from './components/chunk-result-bar';
import CheckboxSets from './components/chunk-result-bar/checkbox-sets';
import FormatPreserEditor from './components/parse-editer';
import RerunButton from './components/rerun-button';
import { TimelineNodeType } from './constant';
import { useFetchParserList } from './hooks';
import { useChangeChunkTextMode, useFetchParserList } from './hooks';
import { IDslComponent } from './interface';
interface IProps {
isChange: boolean;
@ -23,6 +25,7 @@ const ParserContainer = (props: IProps) => {
const { t } = useTranslation();
const { loading } = useFetchParserList();
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
const { changeChunkTextMode, textMode } = useChangeChunkTextMode();
const initialValue = useMemo(() => {
const outputs = data?.value?.obj?.params?.outputs;
const key = outputs?.output_format?.value;
@ -108,6 +111,19 @@ const ParserContainer = (props: IProps) => {
step?.type === TimelineNodeType.characterSplitter ||
step?.type === TimelineNodeType.titleSplitter ||
step?.type === TimelineNodeType.splitter;
const handleCreateChunk = useCallback(
(text: string) => {
console.log('handleCreateChunk', text);
const newText = [...initialText.value, { text: text || ' ' }];
setInitialText({
...initialText,
value: newText,
});
console.log('newText', newText, initialText);
},
[initialText],
);
return (
<>
{isChange && (
@ -122,28 +138,50 @@ const ParserContainer = (props: IProps) => {
<div className={classNames('flex flex-col w-full')}>
<Spin spinning={loading} className="" size="large">
<div className="h-[50px] flex flex-col justify-end pb-[5px]">
<div>
<h2 className="text-[16px]">
{t('dataflowParser.parseSummary')}
</h2>
<div className="text-[12px] text-text-secondary italic ">
{t('dataflowParser.parseSummaryTip')}
{!isChunck && (
<div>
<h2 className="text-[16px]">
{t('dataflowParser.parseSummary')}
</h2>
<div className="text-[12px] text-text-secondary italic ">
{t('dataflowParser.parseSummaryTip')}
</div>
</div>
</div>
)}
{isChunck && (
<div>
<h2 className="text-[16px]">{t('chunk.chunkResult')}</h2>
<div className="text-[12px] text-text-secondary italic">
{t('chunk.chunkResultTip')}
</div>
</div>
)}
</div>
{isChunck && (
<div className="pt-[5px] pb-[5px]">
<div className="pt-[5px] pb-[5px] flex justify-between items-center">
<CheckboxSets
selectAllChunk={selectAllChunk}
removeChunk={handleRemoveChunk}
checked={selectedChunkIds.length === initialText.value.length}
selectedChunkIds={selectedChunkIds}
/>
<ChunkResultBar
changeChunkTextMode={changeChunkTextMode}
createChunk={handleCreateChunk}
/>
</div>
)}
<div className=" border rounded-lg p-[20px] box-border h-[calc(100vh-180px)] w-[calc(100%-20px)] overflow-auto scrollbar-none">
<div
className={cn(
' border rounded-lg p-[20px] box-border w-[calc(100%-20px)] overflow-auto scrollbar-none',
{
'h-[calc(100vh-240px)]': isChunck,
'h-[calc(100vh-180px)]': !isChunck,
},
)}
>
<FormatPreserEditor
initialValue={initialText}
onSave={handleSave}
@ -151,6 +189,7 @@ const ParserContainer = (props: IProps) => {
initialText.key !== 'json' ? '!h-[calc(100vh-220px)]' : ''
}
isChunck={isChunck}
textMode={textMode}
isDelete={
step?.type === TimelineNodeType.characterSplitter ||
step?.type === TimelineNodeType.titleSplitter ||