mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Fix: Optimized the timeline component and parser editing features #9869 - Introduced the TimelineNodeType type, restructured the timeline node structure, and supported dynamic node generation - Enhanced the FormatPreserveEditor component to support editing and line wrapping of JSON-formatted content - Added a rerun function and loading state to the parser and splitter components - Adjusted the timeline style and interaction logic to enhance the user experience - Improved the modal component and added a destroy method to support more flexible control - Optimized the chunk result display and operation logic, supporting batch deletion and selection ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { TimelineNodeType } from '@/pages/dataflow-result/constant';
|
||||
import { parseColorToRGB } from '@/utils/common-util';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import * as React from 'react';
|
||||
@ -220,6 +221,8 @@ interface TimelineNode
|
||||
completed?: boolean;
|
||||
clickable?: boolean;
|
||||
activeStyle?: TimelineIndicatorNodeProps;
|
||||
detail?: any;
|
||||
type?: TimelineNodeType;
|
||||
}
|
||||
|
||||
interface CustomTimelineProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
@ -252,7 +255,6 @@ const CustomTimeline = ({
|
||||
const [internalActiveStep, setInternalActiveStep] =
|
||||
React.useState(defaultValue);
|
||||
const _lineColor = `rgb(${parseColorToRGB(lineColor)})`;
|
||||
console.log(lineColor, _lineColor);
|
||||
const currentActiveStep = activeStep ?? internalActiveStep;
|
||||
|
||||
const handleStepChange = (step: number, id: string | number) => {
|
||||
@ -284,8 +286,6 @@ const CustomTimeline = ({
|
||||
typeof _nodeSizeTemp === 'number'
|
||||
? `${_nodeSizeTemp}px`
|
||||
: _nodeSizeTemp;
|
||||
console.log('icon-size', nodeSize, node.nodeSize, _nodeSize);
|
||||
// const activeStyle = _activeStyle || {};
|
||||
|
||||
return (
|
||||
<TimelineItem
|
||||
@ -372,11 +372,10 @@ const CustomTimeline = ({
|
||||
)}
|
||||
</TimelineIndicator>
|
||||
|
||||
<TimelineHeader>
|
||||
{node.date && <TimelineDate>{node.date}</TimelineDate>}
|
||||
<TimelineHeader className="transform -translate-x-[40%] text-center">
|
||||
<TimelineTitle
|
||||
className={cn(
|
||||
'text-sm font-medium',
|
||||
'text-sm font-medium -ml-1',
|
||||
isActive && _activeStyle.textColor
|
||||
? `text-${_activeStyle.textColor}`
|
||||
: '',
|
||||
@ -387,6 +386,7 @@ const CustomTimeline = ({
|
||||
>
|
||||
{node.title}
|
||||
</TimelineTitle>
|
||||
{node.date && <TimelineDate>{node.date}</TimelineDate>}
|
||||
</TimelineHeader>
|
||||
{node.content && <TimelineContent>{node.content}</TimelineContent>}
|
||||
</TimelineItem>
|
||||
|
||||
@ -31,6 +31,7 @@ export interface ModalProps {
|
||||
export interface ModalType extends FC<ModalProps> {
|
||||
show: typeof modalIns.show;
|
||||
hide: typeof modalIns.hide;
|
||||
destroy: typeof modalIns.destroy;
|
||||
}
|
||||
|
||||
const Modal: ModalType = ({
|
||||
@ -75,13 +76,13 @@ const Modal: ModalType = ({
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
onOpenChange?.(false);
|
||||
// onCancel?.();
|
||||
}, [onOpenChange]);
|
||||
onCancel?.();
|
||||
}, [onCancel, onOpenChange]);
|
||||
|
||||
const handleOk = useCallback(() => {
|
||||
onOpenChange?.(true);
|
||||
// onOk?.();
|
||||
}, [onOpenChange]);
|
||||
onOk?.();
|
||||
}, [onOk, onOpenChange]);
|
||||
const handleChange = (open: boolean) => {
|
||||
onOpenChange?.(open);
|
||||
console.log('open', open, onOpenChange);
|
||||
@ -208,5 +209,6 @@ Modal.show = modalIns
|
||||
return modalIns.show;
|
||||
};
|
||||
Modal.hide = modalIns.hide;
|
||||
Modal.destroy = modalIns.destroy;
|
||||
|
||||
export { Modal };
|
||||
|
||||
@ -133,10 +133,10 @@ export const useNavigatePage = () => {
|
||||
);
|
||||
|
||||
const navigateToDataflowResult = useCallback(
|
||||
(id: string, knowledgeId?: string) => () => {
|
||||
(id: string, knowledgeId: string, doc_id?: string) => () => {
|
||||
navigate(
|
||||
// `${Routes.ParsedResult}/${id}?${QueryStringMap.KnowledgeId}=${knowledgeId}`,
|
||||
`${Routes.DataflowResult}?id=${id}&type=dataflow`,
|
||||
`${Routes.DataflowResult}?id=${id}&doc_id=${doc_id}&${QueryStringMap.KnowledgeId}=${knowledgeId}&type=dataflow`,
|
||||
);
|
||||
},
|
||||
[navigate],
|
||||
|
||||
@ -154,12 +154,7 @@ const ChunkerContainer = (props: IProps) => {
|
||||
<RerunButton step={step} onRerun={handleReRunFunc} />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={classNames(
|
||||
{ [styles.pagePdfWrapper]: isPdf },
|
||||
'flex flex-col w-full',
|
||||
)}
|
||||
>
|
||||
<div className={classNames('flex flex-col w-full')}>
|
||||
<Spin spinning={loading} className={styles.spin} size="large">
|
||||
<div className="h-[50px] flex flex-row justify-between items-end pb-[5px]">
|
||||
<div>
|
||||
|
||||
@ -1,24 +1,17 @@
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Ban, CircleCheck, Trash2 } from 'lucide-react';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type ICheckboxSetProps = {
|
||||
selectAllChunk: (e: any) => void;
|
||||
removeChunk: (e?: any) => void;
|
||||
switchChunk: (available: number) => void;
|
||||
checked: boolean;
|
||||
selectedChunkIds: string[];
|
||||
};
|
||||
export default (props: ICheckboxSetProps) => {
|
||||
const {
|
||||
selectAllChunk,
|
||||
removeChunk,
|
||||
switchChunk,
|
||||
checked,
|
||||
selectedChunkIds,
|
||||
} = props;
|
||||
const { selectAllChunk, removeChunk, checked, selectedChunkIds } = props;
|
||||
const { t } = useTranslation();
|
||||
const handleSelectAllCheck = useCallback(
|
||||
(e: any) => {
|
||||
@ -32,14 +25,6 @@ export default (props: ICheckboxSetProps) => {
|
||||
removeChunk();
|
||||
}, [removeChunk]);
|
||||
|
||||
const handleEnabledClick = useCallback(() => {
|
||||
switchChunk(1);
|
||||
}, [switchChunk]);
|
||||
|
||||
const handleDisabledClick = useCallback(() => {
|
||||
switchChunk(0);
|
||||
}, [switchChunk]);
|
||||
|
||||
const isSelected = useMemo(() => {
|
||||
return selectedChunkIds?.length > 0;
|
||||
}, [selectedChunkIds]);
|
||||
@ -57,20 +42,6 @@ export default (props: ICheckboxSetProps) => {
|
||||
</div>
|
||||
{isSelected && (
|
||||
<>
|
||||
<div
|
||||
className="flex items-center cursor-pointer text-muted-foreground hover:text-text-primary"
|
||||
onClick={handleEnabledClick}
|
||||
>
|
||||
<CircleCheck size={16} />
|
||||
<span className="block ml-1">{t('chunk.enable')}</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center cursor-pointer text-muted-foreground hover:text-text-primary"
|
||||
onClick={handleDisabledClick}
|
||||
>
|
||||
<Ban size={16} />
|
||||
<span className="block ml-1">{t('chunk.disable')}</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center cursor-pointer text-red-400 hover:text-red-500"
|
||||
onClick={handleDeleteClick}
|
||||
|
||||
@ -1,46 +1,168 @@
|
||||
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';
|
||||
|
||||
interface FormatPreserveEditorProps {
|
||||
initialValue: string;
|
||||
onSave: (value: string) => void;
|
||||
initialValue: {
|
||||
key: string;
|
||||
type: string;
|
||||
value: Array<{ [key: string]: string }>;
|
||||
};
|
||||
onSave: (value: any) => void;
|
||||
className?: string;
|
||||
isSelect?: boolean;
|
||||
isDelete?: boolean;
|
||||
isChunck?: boolean;
|
||||
handleCheckboxClick?: (id: string | number, checked: boolean) => void;
|
||||
selectedChunkIds?: string[];
|
||||
}
|
||||
const FormatPreserveEditor = ({
|
||||
initialValue,
|
||||
onSave,
|
||||
className,
|
||||
isChunck,
|
||||
handleCheckboxClick,
|
||||
selectedChunkIds,
|
||||
}: FormatPreserveEditorProps) => {
|
||||
const [content, setContent] = useState(initialValue);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const handleEdit = () => setIsEditing(true);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(content);
|
||||
setIsEditing(false);
|
||||
// const [isEditing, setIsEditing] = useState(false);
|
||||
const [activeEditIndex, setActiveEditIndex] = useState<number | undefined>(
|
||||
undefined,
|
||||
);
|
||||
console.log('initialValue', initialValue);
|
||||
const handleEdit = (e?: any, index?: number) => {
|
||||
console.log(e, index, content);
|
||||
if (content.key === 'json') {
|
||||
console.log(e, e.target.innerText);
|
||||
setContent((pre) => ({
|
||||
...pre,
|
||||
value: pre.value.map((item, i) => {
|
||||
if (i === index) {
|
||||
return {
|
||||
...item,
|
||||
[Object.keys(item)[0]]: e.target.innerText,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
}));
|
||||
setActiveEditIndex(index);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (e: any) => {
|
||||
if (content.key === 'json') {
|
||||
setContent((pre) => ({
|
||||
...pre,
|
||||
value: pre.value.map((item, i) => {
|
||||
if (i === activeEditIndex) {
|
||||
return {
|
||||
...item,
|
||||
[Object.keys(item)[0]]: e.target.value,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
}));
|
||||
} else {
|
||||
setContent(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const escapeNewlines = (text: string) => {
|
||||
return text.replace(/\n/g, '\\n');
|
||||
};
|
||||
const unescapeNewlines = (text: string) => {
|
||||
return text.replace(/\\n/g, '\n');
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const saveData = {
|
||||
...content,
|
||||
value: content.value?.map((item) => {
|
||||
return { ...item, text: unescapeNewlines(item.text) };
|
||||
}),
|
||||
};
|
||||
onSave(saveData);
|
||||
setActiveEditIndex(undefined);
|
||||
};
|
||||
const handleCheck = (e: CheckedState, id: string | number) => {
|
||||
handleCheckboxClick?.(id, e === 'indeterminate' ? false : e);
|
||||
};
|
||||
return (
|
||||
<div className="editor-container">
|
||||
{isEditing ? (
|
||||
{/* {isEditing && content.key === 'json' ? (
|
||||
<Textarea
|
||||
className={cn(
|
||||
'w-full h-full bg-transparent text-text-secondary',
|
||||
'w-full h-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 min-h-6 p-0',
|
||||
className,
|
||||
)}
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
value={content.value}
|
||||
onChange={handleChange}
|
||||
onBlur={handleSave}
|
||||
autoSize={{ maxRows: 100 }}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<pre className="text-text-secondary" onClick={handleEdit}>
|
||||
{content}
|
||||
<>
|
||||
{content.key === 'json' && */}
|
||||
{content.value.map((item, index) => (
|
||||
<section
|
||||
key={index}
|
||||
className={
|
||||
isChunck
|
||||
? 'bg-bg-card my-2 p-2 rounded-lg flex gap-1 items-start'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{isChunck && (
|
||||
<Checkbox
|
||||
onCheckedChange={(e) => {
|
||||
handleCheck(e, index);
|
||||
}}
|
||||
checked={selectedChunkIds?.some(
|
||||
(id) => id.toString() === index.toString(),
|
||||
)}
|
||||
></Checkbox>
|
||||
)}
|
||||
{activeEditIndex === index && (
|
||||
<Textarea
|
||||
key={'t' + index}
|
||||
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 !h-6 min-h-6 p-0',
|
||||
className,
|
||||
)}
|
||||
value={escapeNewlines(content.value[index].text)}
|
||||
onChange={handleChange}
|
||||
onBlur={handleSave}
|
||||
autoSize={{ maxRows: 100, minRows: 1 }}
|
||||
autoFocus
|
||||
/>
|
||||
)}
|
||||
{activeEditIndex !== index && (
|
||||
<div
|
||||
className="text-text-secondary overflow-auto scrollbar-auto whitespace-pre-wrap"
|
||||
key={index}
|
||||
onClick={(e) => {
|
||||
handleEdit(e, index);
|
||||
}}
|
||||
>
|
||||
{escapeNewlines(item.text)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
))}
|
||||
{/* {content.key !== 'json' && (
|
||||
<pre
|
||||
className="text-text-secondary overflow-auto scrollbar-auto"
|
||||
onClick={handleEdit}
|
||||
>
|
||||
</pre>
|
||||
)}
|
||||
</>
|
||||
)}*/}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,16 +4,15 @@ import { Button } from '@/components/ui/button';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { CircleAlert } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRerunDataflow } from '../../hooks';
|
||||
interface RerunButtonProps {
|
||||
className?: string;
|
||||
step?: TimelineNode;
|
||||
onRerun?: () => void;
|
||||
loading?: boolean;
|
||||
}
|
||||
const RerunButton = (props: RerunButtonProps) => {
|
||||
const { className, step, onRerun } = props;
|
||||
const { className, step, onRerun, loading } = props;
|
||||
const { t } = useTranslation();
|
||||
const { loading } = useRerunDataflow();
|
||||
const clickFunc = () => {
|
||||
console.log('click rerun button');
|
||||
Modal.show({
|
||||
@ -29,15 +28,15 @@ const RerunButton = (props: RerunButtonProps) => {
|
||||
}}
|
||||
></div>
|
||||
),
|
||||
onVisibleChange: () => {
|
||||
Modal.hide();
|
||||
},
|
||||
onOk: () => {
|
||||
okText: t('modal.okText'),
|
||||
cancelText: t('modal.cancelText'),
|
||||
onVisibleChange: (visible: boolean) => {
|
||||
if (!visible) {
|
||||
Modal.destroy();
|
||||
} else {
|
||||
onRerun?.();
|
||||
Modal.hide();
|
||||
},
|
||||
onCancel: () => {
|
||||
Modal.hide();
|
||||
Modal.destroy();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,85 +1,112 @@
|
||||
import { CustomTimeline, TimelineNode } from '@/components/originui/timeline';
|
||||
import {
|
||||
CheckLine,
|
||||
FilePlayIcon,
|
||||
Grid3x2,
|
||||
Blocks,
|
||||
File,
|
||||
FilePlay,
|
||||
FileStack,
|
||||
Heading,
|
||||
ListPlus,
|
||||
PlayIcon,
|
||||
} from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
export enum TimelineNodeType {
|
||||
begin = 'begin',
|
||||
parser = 'parser',
|
||||
chunk = 'chunk',
|
||||
indexer = 'indexer',
|
||||
complete = 'complete',
|
||||
end = 'end',
|
||||
}
|
||||
export const TimelineNodeArr = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Begin',
|
||||
icon: <PlayIcon size={13} />,
|
||||
import { TimelineNodeType } from '../../constant';
|
||||
import { IPipelineFileLogDetail } from '../../interface';
|
||||
|
||||
export type ITimelineNodeObj = {
|
||||
title: string;
|
||||
icon: JSX.Element;
|
||||
clickable?: boolean;
|
||||
type: TimelineNodeType;
|
||||
};
|
||||
|
||||
export const TimelineNodeObj = {
|
||||
[TimelineNodeType.begin]: {
|
||||
title: 'File',
|
||||
icon: <File size={13} />,
|
||||
clickable: false,
|
||||
type: TimelineNodeType.begin,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
[TimelineNodeType.parser]: {
|
||||
title: 'Parser',
|
||||
icon: <FilePlayIcon size={13} />,
|
||||
type: TimelineNodeType.parser,
|
||||
icon: <FilePlay size={13} />,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Chunker',
|
||||
icon: <Grid3x2 size={13} />,
|
||||
type: TimelineNodeType.chunk,
|
||||
[TimelineNodeType.contextGenerator]: {
|
||||
title: 'Context Generator',
|
||||
icon: <FileStack size={13} />,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'Indexer',
|
||||
[TimelineNodeType.titleSplitter]: {
|
||||
title: 'Title Splitter',
|
||||
icon: <Heading size={13} />,
|
||||
},
|
||||
[TimelineNodeType.characterSplitter]: {
|
||||
title: 'Title Splitter',
|
||||
icon: <Heading size={13} />,
|
||||
},
|
||||
[TimelineNodeType.splitter]: {
|
||||
title: 'Character Splitter',
|
||||
icon: <Blocks size={13} />,
|
||||
},
|
||||
[TimelineNodeType.tokenizer]: {
|
||||
title: 'Tokenizer',
|
||||
icon: <ListPlus size={13} />,
|
||||
clickable: false,
|
||||
type: TimelineNodeType.indexer,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: 'Complete',
|
||||
icon: <CheckLine size={13} />,
|
||||
clickable: false,
|
||||
type: TimelineNodeType.complete,
|
||||
},
|
||||
];
|
||||
|
||||
};
|
||||
// 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;
|
||||
data: IPipelineFileLogDetail;
|
||||
timelineNodes: TimelineNode[];
|
||||
}
|
||||
const TimelineDataFlow = ({ activeFunc, activeId }: TimelineDataFlowProps) => {
|
||||
// const [activeStep, setActiveStep] = useState(2);
|
||||
const timelineNodes: TimelineNode[] = useMemo(() => {
|
||||
const nodes: TimelineNode[] = [];
|
||||
TimelineNodeArr.forEach((node) => {
|
||||
nodes.push({
|
||||
...node,
|
||||
className: 'w-32',
|
||||
completed: false,
|
||||
});
|
||||
});
|
||||
return nodes;
|
||||
}, []);
|
||||
const TimelineDataFlow = ({
|
||||
activeFunc,
|
||||
activeId,
|
||||
data,
|
||||
timelineNodes,
|
||||
}: TimelineDataFlowProps) => {
|
||||
// const [timelineNodeArr,setTimelineNodeArr] = useState<ITimelineNodeObj & {id: number | string}>()
|
||||
|
||||
const activeStep = useMemo(() => {
|
||||
const index = timelineNodes.findIndex((node) => node.id === activeId);
|
||||
return index > -1 ? index + 1 : 0;
|
||||
}, [activeId, timelineNodes]);
|
||||
const handleStepChange = (step: number, id: string | number) => {
|
||||
// setActiveStep(step);
|
||||
activeFunc?.(
|
||||
id,
|
||||
timelineNodes.find((node) => node.id === activeStep) as TimelineNode,
|
||||
);
|
||||
console.log(step, id);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -2,3 +2,14 @@ export enum ChunkTextMode {
|
||||
Full = 'full',
|
||||
Ellipse = 'ellipse',
|
||||
}
|
||||
|
||||
export enum TimelineNodeType {
|
||||
begin = 'file',
|
||||
parser = 'parser',
|
||||
splitter = 'splitter',
|
||||
contextGenerator = 'contextGenerator',
|
||||
titleSplitter = 'titleSplitter',
|
||||
characterSplitter = 'characterSplitter',
|
||||
tokenizer = 'tokenizer',
|
||||
end = 'end',
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { TimelineNode } from '@/components/originui/timeline';
|
||||
import {
|
||||
useCreateChunk,
|
||||
useDeleteChunk,
|
||||
@ -8,40 +8,17 @@ import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
|
||||
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
||||
import { IChunk } from '@/interfaces/database/knowledge';
|
||||
import kbService from '@/services/knowledge-service';
|
||||
import { formatSecondsToHumanReadable } from '@/utils/date';
|
||||
import { buildChunkHighlights } from '@/utils/document-util';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { camelCase, upperFirst } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IHighlight } from 'react-pdf-highlighter';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import { ChunkTextMode } from './constant';
|
||||
import { ITimelineNodeObj, TimelineNodeObj } from './components/time-line';
|
||||
import { ChunkTextMode, TimelineNodeType } from './constant';
|
||||
import { IDslComponent, IPipelineFileLogDetail } from './interface';
|
||||
|
||||
export interface IPipelineFileLogDetail {
|
||||
avatar: string;
|
||||
create_date: string;
|
||||
create_time: number;
|
||||
document_id: string;
|
||||
document_name: string;
|
||||
document_suffix: string;
|
||||
document_type: string;
|
||||
dsl: string;
|
||||
id: string;
|
||||
kb_id: string;
|
||||
operation_status: string;
|
||||
parser_id: string;
|
||||
pipeline_id: string;
|
||||
pipeline_title: string;
|
||||
process_begin_at: string;
|
||||
process_duration: number;
|
||||
progress: number;
|
||||
progress_msg: string;
|
||||
source_from: string;
|
||||
status: string;
|
||||
task_type: string;
|
||||
tenant_id: string;
|
||||
update_date: string;
|
||||
update_time: number;
|
||||
}
|
||||
export const useFetchPipelineFileLogDetail = (props?: {
|
||||
isEdit?: boolean;
|
||||
refreshCount?: number;
|
||||
@ -199,52 +176,105 @@ export const useFetchParserList = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useRerunDataflow = () => {
|
||||
export const useRerunDataflow = ({
|
||||
data,
|
||||
}: {
|
||||
data: IPipelineFileLogDetail;
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isChange, setIsChange] = useState(false);
|
||||
const handleReRunFunc = useCallback(
|
||||
(newData: { value: IDslComponent; key: string }) => {
|
||||
const newDsl = {
|
||||
...data.dsl,
|
||||
components: {
|
||||
...data.dsl.components,
|
||||
[newData.key]: newData.value,
|
||||
},
|
||||
};
|
||||
|
||||
// this Data provided to the interface
|
||||
const params = {
|
||||
id: data.id,
|
||||
dsl: newDsl,
|
||||
compenent_id: newData.key,
|
||||
};
|
||||
console.log('newDsl', newDsl, params);
|
||||
},
|
||||
[data],
|
||||
);
|
||||
|
||||
return {
|
||||
loading,
|
||||
setLoading,
|
||||
isChange,
|
||||
setIsChange,
|
||||
handleReRunFunc,
|
||||
};
|
||||
};
|
||||
|
||||
export const useFetchPaserText = () => {
|
||||
const initialText =
|
||||
'第一行文本\n\t第二行缩进文本\n第三行 多个空格 第一行文本\n\t第二行缩进文本\n第三行 ' +
|
||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 ' +
|
||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 ' +
|
||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 ' +
|
||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 ' +
|
||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 ' +
|
||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格';
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<string>(initialText);
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
|
||||
const timelineNodes: TimelineNode[] = useMemo(() => {
|
||||
const nodes: Array<ITimelineNodeObj & { id: number | string }> = [];
|
||||
console.log('time-->', data);
|
||||
const times = data?.dsl?.components;
|
||||
if (times) {
|
||||
const getNode = (
|
||||
key: string,
|
||||
index: number,
|
||||
type:
|
||||
| TimelineNodeType.begin
|
||||
| TimelineNodeType.parser
|
||||
| TimelineNodeType.splitter
|
||||
| TimelineNodeType.tokenizer
|
||||
| TimelineNodeType.characterSplitter
|
||||
| TimelineNodeType.titleSplitter,
|
||||
) => {
|
||||
const node = times[key].obj;
|
||||
const name = camelCase(
|
||||
node.component_name,
|
||||
) as keyof typeof TimelineNodeObj;
|
||||
|
||||
const {
|
||||
// data,
|
||||
// isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['createChunk'],
|
||||
mutationFn: async (payload: any) => {
|
||||
// let service = kbService.create_chunk;
|
||||
// if (payload.chunk_id) {
|
||||
// service = kbService.set_chunk;
|
||||
// }
|
||||
// const { data } = await service(payload);
|
||||
// if (data.code === 0) {
|
||||
message.success(t('message.created'));
|
||||
setTimeout(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchChunkList'] });
|
||||
}, 1000); // Delay to ensure the list is updated
|
||||
// }
|
||||
// return data?.code;
|
||||
},
|
||||
});
|
||||
let tempType = type;
|
||||
if (name === TimelineNodeType.parser) {
|
||||
tempType = TimelineNodeType.parser;
|
||||
} else if (name === TimelineNodeType.tokenizer) {
|
||||
tempType = TimelineNodeType.tokenizer;
|
||||
} else if (
|
||||
name === TimelineNodeType.characterSplitter ||
|
||||
name === TimelineNodeType.titleSplitter ||
|
||||
name === TimelineNodeType.splitter
|
||||
) {
|
||||
tempType = TimelineNodeType.splitter;
|
||||
}
|
||||
const timeNode = {
|
||||
...TimelineNodeObj[name],
|
||||
clickable: true,
|
||||
id: index,
|
||||
className: 'w-32',
|
||||
completed: false,
|
||||
date: formatSecondsToHumanReadable(
|
||||
node.params?.outputs?._elapsed_time?.value || 0,
|
||||
),
|
||||
type: tempType,
|
||||
detail: { value: times[key], key: key },
|
||||
};
|
||||
console.log('timeNodetype-->', type);
|
||||
nodes.push(timeNode);
|
||||
|
||||
return { data, loading, rerun: mutateAsync };
|
||||
if (times[key].downstream && times[key].downstream.length > 0) {
|
||||
const nextKey = times[key].downstream[0];
|
||||
|
||||
// nodes.push(timeNode);
|
||||
getNode(nextKey, index + 1, tempType);
|
||||
}
|
||||
};
|
||||
getNode(upperFirst(TimelineNodeType.begin), 1, TimelineNodeType.begin);
|
||||
// setTimelineNodeArr(nodes as unknown as ITimelineNodeObj & {id: number | string})
|
||||
}
|
||||
return nodes;
|
||||
}, [data]);
|
||||
return {
|
||||
timelineNodes,
|
||||
};
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
useGetChunkHighlights,
|
||||
useHandleChunkCardClick,
|
||||
useRerunDataflow,
|
||||
useTimelineDataFlow,
|
||||
} from './hooks';
|
||||
|
||||
import DocumentHeader from './components/document-preview/document-header';
|
||||
@ -29,13 +30,11 @@ import {
|
||||
useNavigatePage,
|
||||
} from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
||||
import { ChunkerContainer } from './chunker';
|
||||
import { useGetDocumentUrl } from './components/document-preview/hooks';
|
||||
import TimelineDataFlow, {
|
||||
TimelineNodeArr,
|
||||
TimelineNodeType,
|
||||
} from './components/time-line';
|
||||
import TimelineDataFlow from './components/time-line';
|
||||
import { TimelineNodeType } from './constant';
|
||||
import styles from './index.less';
|
||||
import { IDslComponent } from './interface';
|
||||
import ParserContainer from './parser';
|
||||
|
||||
const Chunk = () => {
|
||||
@ -45,9 +44,10 @@ const Chunk = () => {
|
||||
const { selectedChunkId } = useHandleChunkCardClick();
|
||||
const [activeStepId, setActiveStepId] = useState<number | string>(0);
|
||||
const { data: dataset } = useFetchPipelineFileLogDetail();
|
||||
const { isChange, setIsChange } = useRerunDataflow();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { timelineNodes } = useTimelineDataFlow(dataset);
|
||||
|
||||
const { navigateToDataset, getQueryString, navigateToDatasetList } =
|
||||
useNavigatePage();
|
||||
const fileUrl = useGetDocumentUrl();
|
||||
@ -69,8 +69,15 @@ const Chunk = () => {
|
||||
return 'unknown';
|
||||
}, [documentInfo]);
|
||||
|
||||
const {
|
||||
handleReRunFunc,
|
||||
isChange,
|
||||
setIsChange,
|
||||
loading: reRunLoading,
|
||||
} = useRerunDataflow({
|
||||
data: dataset,
|
||||
});
|
||||
const handleStepChange = (id: number | string, step: TimelineNode) => {
|
||||
console.log(id, step);
|
||||
if (isChange) {
|
||||
Modal.show({
|
||||
visible: true,
|
||||
@ -114,12 +121,14 @@ const Chunk = () => {
|
||||
};
|
||||
|
||||
const { type } = useGetKnowledgeSearchParams();
|
||||
|
||||
const currentTimeNode: TimelineNode = useMemo(() => {
|
||||
return (
|
||||
TimelineNodeArr.find((node) => node.id === activeStepId) ||
|
||||
timelineNodes.find((node) => node.id === activeStepId) ||
|
||||
({} as TimelineNode)
|
||||
);
|
||||
}, [activeStepId]);
|
||||
}, [activeStepId, timelineNodes]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
@ -134,10 +143,10 @@ const Chunk = () => {
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
onClick={navigateToDataset(
|
||||
getQueryString(QueryStringMap.id) as string,
|
||||
getQueryString(QueryStringMap.KnowledgeId) as string,
|
||||
)}
|
||||
>
|
||||
{dataset.name}
|
||||
{t('knowledgeDetails.overview')}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
@ -152,6 +161,8 @@ const Chunk = () => {
|
||||
<TimelineDataFlow
|
||||
activeFunc={handleStepChange}
|
||||
activeId={activeStepId}
|
||||
data={dataset}
|
||||
timelineNodes={timelineNodes}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -173,20 +184,31 @@ const Chunk = () => {
|
||||
</div>
|
||||
<div className="h-dvh border-r -mt-3"></div>
|
||||
<div className="w-3/5 h-full">
|
||||
{currentTimeNode?.type === TimelineNodeType.chunk && (
|
||||
{/* {currentTimeNode?.type === TimelineNodeType.splitter && (
|
||||
<ChunkerContainer
|
||||
isChange={isChange}
|
||||
setIsChange={setIsChange}
|
||||
step={currentTimeNode as TimelineNode}
|
||||
/>
|
||||
)}
|
||||
{currentTimeNode?.type === TimelineNodeType.parser && (
|
||||
)} */}
|
||||
{/* {currentTimeNode?.type === TimelineNodeType.parser && ( */}
|
||||
{(currentTimeNode?.type === TimelineNodeType.parser ||
|
||||
currentTimeNode?.type === TimelineNodeType.splitter) && (
|
||||
<ParserContainer
|
||||
isChange={isChange}
|
||||
reRunLoading={reRunLoading}
|
||||
setIsChange={setIsChange}
|
||||
step={currentTimeNode as TimelineNode}
|
||||
data={
|
||||
currentTimeNode.detail as {
|
||||
value: IDslComponent;
|
||||
key: string;
|
||||
}
|
||||
}
|
||||
reRunFunc={handleReRunFunc}
|
||||
/>
|
||||
)}
|
||||
{/* )} */}
|
||||
<Spotlight opcity={0.6} coverage={60} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
62
web/src/pages/dataflow-result/interface.ts
Normal file
62
web/src/pages/dataflow-result/interface.ts
Normal file
@ -0,0 +1,62 @@
|
||||
interface ComponentParams {
|
||||
debug_inputs: Record<string, any>;
|
||||
delay_after_error: number;
|
||||
description: string;
|
||||
exception_default_value: any;
|
||||
exception_goto: any;
|
||||
exception_method: any;
|
||||
inputs: Record<string, any>;
|
||||
max_retries: number;
|
||||
message_history_window_size: number;
|
||||
outputs: {
|
||||
_created_time: Record<string, any>;
|
||||
_elapsed_time: Record<string, any>;
|
||||
name: Record<string, any>;
|
||||
output_format: { type: string; value: string };
|
||||
json: { type: string; value: string };
|
||||
};
|
||||
persist_logs: boolean;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
interface ComponentObject {
|
||||
component_name: string;
|
||||
params: ComponentParams;
|
||||
}
|
||||
export interface IDslComponent {
|
||||
downstream: Array<string>;
|
||||
obj: ComponentObject;
|
||||
upstream: Array<string>;
|
||||
}
|
||||
export interface IPipelineFileLogDetail {
|
||||
avatar: string;
|
||||
create_date: string;
|
||||
create_time: number;
|
||||
document_id: string;
|
||||
document_name: string;
|
||||
document_suffix: string;
|
||||
document_type: string;
|
||||
dsl: {
|
||||
components: {
|
||||
[key: string]: IDslComponent;
|
||||
};
|
||||
task_id: string;
|
||||
path: Array<string>;
|
||||
};
|
||||
id: string;
|
||||
kb_id: string;
|
||||
operation_status: string;
|
||||
parser_id: string;
|
||||
pipeline_id: string;
|
||||
pipeline_title: string;
|
||||
process_begin_at: string;
|
||||
process_duration: number;
|
||||
progress: number;
|
||||
progress_msg: string;
|
||||
source_from: string;
|
||||
status: string;
|
||||
task_type: string;
|
||||
tenant_id: string;
|
||||
update_date: string;
|
||||
update_time: number;
|
||||
}
|
||||
@ -2,41 +2,121 @@ import { TimelineNode } from '@/components/originui/timeline';
|
||||
import Spotlight from '@/components/spotlight';
|
||||
import { Spin } from '@/components/ui/spin';
|
||||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import FormatPreserveEditor from './components/parse-editer';
|
||||
import CheckboxSets from './components/chunk-result-bar/checkbox-sets';
|
||||
import FormatPreserEditor from './components/parse-editer';
|
||||
import RerunButton from './components/rerun-button';
|
||||
import { useFetchParserList, useFetchPaserText } from './hooks';
|
||||
import { TimelineNodeType } from './constant';
|
||||
import { useFetchParserList } from './hooks';
|
||||
import { IDslComponent } from './interface';
|
||||
interface IProps {
|
||||
isChange: boolean;
|
||||
setIsChange: (isChange: boolean) => void;
|
||||
step?: TimelineNode;
|
||||
data: { value: IDslComponent; key: string };
|
||||
reRunLoading: boolean;
|
||||
reRunFunc: (data: { value: IDslComponent; key: string }) => void;
|
||||
}
|
||||
const ParserContainer = (props: IProps) => {
|
||||
const { isChange, setIsChange, step } = props;
|
||||
const { data: initialValue, rerun: onSave } = useFetchPaserText();
|
||||
const { isChange, setIsChange, step, data, reRunFunc, reRunLoading } = props;
|
||||
const { t } = useTranslation();
|
||||
const { loading } = useFetchParserList();
|
||||
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
|
||||
const initialValue = useMemo(() => {
|
||||
const outputs = data?.value?.obj?.params?.outputs;
|
||||
const key = outputs?.output_format?.value;
|
||||
const value = outputs[key]?.value;
|
||||
const type = outputs[key]?.type;
|
||||
console.log('outputs-->', outputs);
|
||||
return {
|
||||
key,
|
||||
type,
|
||||
value,
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
const [initialText, setInitialText] = useState(initialValue);
|
||||
const handleSave = (newContent: string) => {
|
||||
console.log('保存内容:', newContent);
|
||||
if (newContent !== initialText) {
|
||||
const handleSave = (newContent: any) => {
|
||||
console.log('newContent-change-->', newContent, initialValue);
|
||||
if (JSON.stringify(newContent) !== JSON.stringify(initialValue)) {
|
||||
setIsChange(true);
|
||||
onSave(newContent);
|
||||
setInitialText(newContent);
|
||||
} else {
|
||||
setIsChange(false);
|
||||
}
|
||||
// Here, the API is called to send newContent to the backend
|
||||
};
|
||||
const handleReRunFunc = () => {
|
||||
setIsChange(false);
|
||||
|
||||
const handleReRunFunc = useCallback(() => {
|
||||
const newData: { value: IDslComponent; key: string } = {
|
||||
...data,
|
||||
value: {
|
||||
...data.value,
|
||||
obj: {
|
||||
...data.value.obj,
|
||||
params: {
|
||||
...(data.value?.obj?.params || {}),
|
||||
outputs: {
|
||||
...(data.value?.obj?.params?.outputs || {}),
|
||||
[initialText.key]: {
|
||||
type: initialText.type,
|
||||
value: initialText.value,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
reRunFunc(newData);
|
||||
setIsChange(false);
|
||||
}, [data, initialText, reRunFunc, setIsChange]);
|
||||
|
||||
const handleRemoveChunk = useCallback(async () => {
|
||||
if (selectedChunkIds.length > 0) {
|
||||
initialText.value = initialText.value.filter(
|
||||
(item: any, index: number) => !selectedChunkIds.includes(index + ''),
|
||||
);
|
||||
setSelectedChunkIds([]);
|
||||
}
|
||||
}, [selectedChunkIds, initialText]);
|
||||
|
||||
const handleCheckboxClick = useCallback(
|
||||
(id: string | number, checked: boolean) => {
|
||||
console.log('handleCheckboxClick', id, checked, selectedChunkIds);
|
||||
setSelectedChunkIds((prev) => {
|
||||
if (checked) {
|
||||
return [...prev, id.toString()];
|
||||
} else {
|
||||
return prev.filter((item) => item.toString() !== id.toString());
|
||||
}
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const selectAllChunk = useCallback(
|
||||
(checked: boolean) => {
|
||||
setSelectedChunkIds(
|
||||
checked ? initialText.value.map((x, index: number) => index) : [],
|
||||
);
|
||||
},
|
||||
[initialText.value],
|
||||
);
|
||||
|
||||
const isChunck =
|
||||
step?.type === TimelineNodeType.characterSplitter ||
|
||||
step?.type === TimelineNodeType.titleSplitter ||
|
||||
step?.type === TimelineNodeType.splitter;
|
||||
return (
|
||||
<>
|
||||
{isChange && (
|
||||
<div className=" absolute top-2 right-6">
|
||||
<RerunButton step={step} onRerun={handleReRunFunc} />
|
||||
<RerunButton
|
||||
step={step}
|
||||
onRerun={handleReRunFunc}
|
||||
loading={reRunLoading}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={classNames('flex flex-col w-full')}>
|
||||
@ -51,11 +131,33 @@ const ParserContainer = (props: IProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className=" border rounded-lg p-[20px] box-border h-[calc(100vh-180px)] overflow-auto scrollbar-none">
|
||||
<FormatPreserveEditor
|
||||
|
||||
{isChunck && (
|
||||
<div className="pt-[5px] pb-[5px]">
|
||||
<CheckboxSets
|
||||
selectAllChunk={selectAllChunk}
|
||||
removeChunk={handleRemoveChunk}
|
||||
checked={selectedChunkIds.length === initialText.value.length}
|
||||
selectedChunkIds={selectedChunkIds}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className=" border rounded-lg p-[20px] box-border h-[calc(100vh-180px)] w-[calc(100%-20px)] overflow-auto scrollbar-none">
|
||||
<FormatPreserEditor
|
||||
initialValue={initialText}
|
||||
onSave={handleSave}
|
||||
className="!h-[calc(100vh-220px)]"
|
||||
className={
|
||||
initialText.key !== 'json' ? '!h-[calc(100vh-220px)]' : ''
|
||||
}
|
||||
isChunck={isChunck}
|
||||
isDelete={
|
||||
step?.type === TimelineNodeType.characterSplitter ||
|
||||
step?.type === TimelineNodeType.titleSplitter ||
|
||||
step?.type === TimelineNodeType.splitter
|
||||
}
|
||||
handleCheckboxClick={handleCheckboxClick}
|
||||
selectedChunkIds={selectedChunkIds}
|
||||
/>
|
||||
<Spotlight opcity={0.6} coverage={60} />
|
||||
</div>
|
||||
|
||||
@ -4,10 +4,12 @@ import {
|
||||
} from '@/hooks/logic-hooks';
|
||||
import kbService, {
|
||||
listDataPipelineLogDocument,
|
||||
listPipelineDatasetLogs,
|
||||
} from '@/services/knowledge-service';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import { LogTabs } from './dataset-common';
|
||||
|
||||
export interface IOverviewTital {
|
||||
cancelled: number;
|
||||
@ -61,7 +63,7 @@ export interface IFileLogItem {
|
||||
update_time: number;
|
||||
}
|
||||
export interface IFileLogList {
|
||||
docs: IFileLogItem[];
|
||||
logs: IFileLogItem[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
@ -70,7 +72,14 @@ const useFetchFileLogList = () => {
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
const { id } = useParams();
|
||||
const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>(
|
||||
LogTabs.FILE_LOGS,
|
||||
);
|
||||
const knowledgeBaseId = searchParams.get('id') || id;
|
||||
const fetchFunc =
|
||||
active === LogTabs.DATASET_LOGS
|
||||
? listPipelineDatasetLogs
|
||||
: listDataPipelineLogDocument;
|
||||
const { data } = useQuery<IFileLogList>({
|
||||
queryKey: [
|
||||
'fileLogList',
|
||||
@ -78,9 +87,10 @@ const useFetchFileLogList = () => {
|
||||
pagination.current,
|
||||
pagination.pageSize,
|
||||
searchString,
|
||||
active,
|
||||
],
|
||||
queryFn: async () => {
|
||||
const { data: res = {} } = await listDataPipelineLogDocument({
|
||||
const { data: res = {} } = await fetchFunc({
|
||||
kb_id: knowledgeBaseId,
|
||||
page: pagination.current,
|
||||
page_size: pagination.pageSize,
|
||||
@ -102,6 +112,8 @@ const useFetchFileLogList = () => {
|
||||
searchString,
|
||||
handleInputChange: onInputChange,
|
||||
pagination: { ...pagination, total: data?.total },
|
||||
active,
|
||||
setActive,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -71,9 +71,7 @@ const CardFooterProcess: FC<CardFooterProcessProps> = ({
|
||||
};
|
||||
const FileLogsPage: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>(
|
||||
LogTabs.FILE_LOGS,
|
||||
);
|
||||
|
||||
const [topAllData, setTopAllData] = useState({
|
||||
totalFiles: {
|
||||
value: 0,
|
||||
@ -111,12 +109,14 @@ const FileLogsPage: FC = () => {
|
||||
searchString,
|
||||
handleInputChange,
|
||||
pagination,
|
||||
active,
|
||||
setActive,
|
||||
} = useFetchFileLogList();
|
||||
|
||||
const tableList = useMemo(() => {
|
||||
console.log('tableList', tableOriginData);
|
||||
if (tableOriginData && tableOriginData.docs?.length) {
|
||||
return tableOriginData.docs.map((item) => {
|
||||
if (tableOriginData && tableOriginData.logs?.length) {
|
||||
return tableOriginData.logs.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
fileName: item.document_name,
|
||||
|
||||
@ -31,6 +31,7 @@ import {
|
||||
import { TFunction } from 'i18next';
|
||||
import { ClipboardList, Eye } from 'lucide-react';
|
||||
import { FC, useMemo, useState } from 'react';
|
||||
import { useParams } from 'umi';
|
||||
import { RunningStatus } from '../dataset/constant';
|
||||
import ProcessLogModal from '../process-log-modal';
|
||||
import { LogTabs, ProcessingType } from './dataset-common';
|
||||
@ -58,9 +59,11 @@ interface FileLogsTableProps {
|
||||
export const getFileLogsTableColumns = (
|
||||
t: TFunction<'translation', string>,
|
||||
showLog: (row: Row<IFileLogItem & DocumentLog>, active: LogTabs) => void,
|
||||
kowledgeId: string,
|
||||
navigateToDataflowResult: (
|
||||
id: string,
|
||||
knowledgeId?: string | undefined,
|
||||
knowledgeId: string,
|
||||
doc_id?: string,
|
||||
) => () => void,
|
||||
) => {
|
||||
// const { t } = useTranslate('knowledgeDetails');
|
||||
@ -175,7 +178,11 @@ export const getFileLogsTableColumns = (
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="p-1"
|
||||
onClick={navigateToDataflowResult(row.original.id)}
|
||||
onClick={navigateToDataflowResult(
|
||||
row.original.id,
|
||||
kowledgeId,
|
||||
row.original.document_id,
|
||||
)}
|
||||
>
|
||||
<ClipboardList />
|
||||
</Button>
|
||||
@ -288,7 +295,8 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const { navigateToDataflowResult } = useNavigatePage();
|
||||
const [logInfo, setLogInfo] = useState<IFileLogItem>({});
|
||||
const showLog = (row: Row<IFileLogItem & DocumentLog>, active: LogTabs) => {
|
||||
const kowledgeId = useParams().id;
|
||||
const showLog = (row: Row<IFileLogItem & DocumentLog>) => {
|
||||
const logDetail = {
|
||||
taskId: row.original.id,
|
||||
fileName: row.original.document_name,
|
||||
@ -306,7 +314,12 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return active === LogTabs.FILE_LOGS
|
||||
? getFileLogsTableColumns(t, showLog, navigateToDataflowResult)
|
||||
? getFileLogsTableColumns(
|
||||
t,
|
||||
showLog,
|
||||
kowledgeId || '',
|
||||
navigateToDataflowResult,
|
||||
)
|
||||
: getDatasetLogsTableColumns(t, showLog);
|
||||
}, [active, t]);
|
||||
|
||||
|
||||
@ -80,7 +80,7 @@ export const useShowLog = (documents: IDocumentInfo[]) => {
|
||||
fileSize: findRecord.size + '',
|
||||
source: findRecord.source_type,
|
||||
task: findRecord.status,
|
||||
state: findRecord.run,
|
||||
status: findRecord.run,
|
||||
startTime: findRecord.process_begin_at,
|
||||
endTime: findRecord.process_begin_at,
|
||||
duration: findRecord.process_duration + 's',
|
||||
|
||||
@ -7,11 +7,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '@/components/ui/hover-card';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
@ -19,7 +14,7 @@ import { CircleX } from 'lucide-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DocumentType, RunningStatus } from './constant';
|
||||
import { ParsingCard, PopoverContent } from './parsing-card';
|
||||
import { ParsingCard } from './parsing-card';
|
||||
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
|
||||
import { useHandleRunDocumentByIds } from './use-run-document';
|
||||
import { UseSaveMetaShowType } from './use-save-meta';
|
||||
@ -122,23 +117,13 @@ export function ParsingStatusCell({
|
||||
)}
|
||||
{isParserRunning(run) ? (
|
||||
<>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<div
|
||||
className="flex items-center gap-1"
|
||||
className="flex items-center gap-1 cursor-pointer"
|
||||
onClick={() => handleShowLog(record)}
|
||||
>
|
||||
<Progress value={p} className="h-1 flex-1 min-w-10" />
|
||||
{p}%
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-[40vw]">
|
||||
<PopoverContent
|
||||
record={record}
|
||||
handleShowLog={handleShowLog}
|
||||
></PopoverContent>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
<div
|
||||
className="cursor-pointer flex items-center gap-3"
|
||||
onClick={
|
||||
|
||||
@ -41,6 +41,7 @@ const {
|
||||
retrievalTestShare,
|
||||
getKnowledgeBasicInfo,
|
||||
fetchDataPipelineLog,
|
||||
fetchPipelineDatasetLogs,
|
||||
} = api;
|
||||
|
||||
const methods = {
|
||||
@ -179,6 +180,10 @@ const methods = {
|
||||
url: fetchDataPipelineLog,
|
||||
method: 'post',
|
||||
},
|
||||
fetchPipelineDatasetLogs: {
|
||||
url: fetchPipelineDatasetLogs,
|
||||
method: 'post',
|
||||
},
|
||||
get_pipeline_detail: {
|
||||
url: api.get_pipeline_detail,
|
||||
method: 'get',
|
||||
@ -223,5 +228,9 @@ export const listDataPipelineLogDocument = (
|
||||
params?: IFetchKnowledgeListRequestParams,
|
||||
body?: IFetchDocumentListRequestBody,
|
||||
) => request.post(api.fetchDataPipelineLog, { data: body || {}, params });
|
||||
export const listPipelineDatasetLogs = (
|
||||
params?: IFetchKnowledgeListRequestParams,
|
||||
body?: IFetchDocumentListRequestBody,
|
||||
) => request.post(api.fetchPipelineDatasetLogs, { data: body || {}, params });
|
||||
|
||||
export default kbService;
|
||||
|
||||
@ -49,6 +49,7 @@ export default {
|
||||
// data pipeline log
|
||||
fetchDataPipelineLog: `${api_host}/kb/list_pipeline_logs`,
|
||||
get_pipeline_detail: `${api_host}/kb/pipeline_log_detail`,
|
||||
fetchPipelineDatasetLogs: `${api_host}/kb/list_pipeline_dataset_logs`,
|
||||
|
||||
// tags
|
||||
listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import dayjs from 'dayjs';
|
||||
import { toFixed } from './common-util';
|
||||
|
||||
export function formatDate(date: any) {
|
||||
if (!date) {
|
||||
@ -43,3 +44,20 @@ export function formatStandardDate(date: any) {
|
||||
}
|
||||
return parsedDate.format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
export function formatSecondsToHumanReadable(seconds: number): string {
|
||||
if (isNaN(seconds) || seconds < 0) {
|
||||
return '0s';
|
||||
}
|
||||
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = toFixed(seconds % 60, 3);
|
||||
|
||||
const parts = [];
|
||||
if (h > 0) parts.push(`${h}h`);
|
||||
if (m > 0) parts.push(`${m}m`);
|
||||
if (s || parts.length === 0) parts.push(`${s}s`);
|
||||
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user