Feat: Displays the loading status of the data flow log #9869 (#10347)

### What problem does this PR solve?

Feat: Displays the loading status of the data flow log #9869

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-09-28 19:38:46 +08:00
committed by GitHub
parent f4cc4dbd30
commit 664bc0b961
9 changed files with 121 additions and 56 deletions

View File

@ -416,13 +416,11 @@ export const useUploadCanvasFileWithProgress = (
return { data, loading, uploadCanvasFile: mutateAsync }; return { data, loading, uploadCanvasFile: mutateAsync };
}; };
export const useFetchMessageTrace = ( export const useFetchMessageTrace = (canvasId?: string) => {
isStopFetchTrace: boolean,
canvasId?: string,
) => {
const { id } = useParams(); const { id } = useParams();
const queryId = id || canvasId; const queryId = id || canvasId;
const [messageId, setMessageId] = useState(''); const [messageId, setMessageId] = useState('');
const [isStopFetchTrace, setISStopFetchTrace] = useState(false);
const { const {
data, data,
@ -442,11 +440,19 @@ export const useFetchMessageTrace = (
message_id: messageId, message_id: messageId,
}); });
return data?.data ?? []; return Array.isArray(data?.data) ? data?.data : [];
}, },
}); });
return { data, loading, refetch, setMessageId, messageId }; return {
data,
loading,
refetch,
setMessageId,
messageId,
isStopFetchTrace,
setISStopFetchTrace,
};
}; };
export const useTestDbConnect = () => { export const useTestDbConnect = () => {

View File

@ -1771,6 +1771,12 @@ Important structured information may include: names, dates, locations, events, k
cancel: 'Cancel', cancel: 'Cancel',
swicthPromptMessage: swicthPromptMessage:
'The prompt word will change. Please confirm whether to abandon the existing prompt word?', 'The prompt word will change. Please confirm whether to abandon the existing prompt word?',
tokenizerFieldsOptions: {
text: 'Text',
keywords: 'Keywords',
questions: 'Questions',
summary: 'Augmented Context',
},
}, },
}, },
}; };

View File

@ -23,7 +23,7 @@ import { ITraceData } from '@/interfaces/database/agent';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { t } from 'i18next'; import { t } from 'i18next';
import { get, isEmpty, isEqual, uniqWith } from 'lodash'; import { get, isEmpty, isEqual, uniqWith } from 'lodash';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import JsonView from 'react18-json-view'; import JsonView from 'react18-json-view';
import { Operator } from '../constant'; import { Operator } from '../constant';
import { useCacheChatLog } from '../hooks/use-cache-chat-log'; import { useCacheChatLog } from '../hooks/use-cache-chat-log';
@ -116,12 +116,12 @@ export const WorkFlowTimeline = ({
isShare, isShare,
}: LogFlowTimelineProps) => { }: LogFlowTimelineProps) => {
// const getNode = useGraphStore((state) => state.getNode); // const getNode = useGraphStore((state) => state.getNode);
const [isStopFetchTrace, setISStopFetchTrace] = useState(false);
const { data: traceData, setMessageId } = useFetchMessageTrace( const {
isStopFetchTrace, data: traceData,
canvasId, setMessageId,
); setISStopFetchTrace,
} = useFetchMessageTrace(canvasId);
useEffect(() => { useEffect(() => {
setMessageId(currentMessageId); setMessageId(currentMessageId);
@ -133,7 +133,7 @@ export const WorkFlowTimeline = ({
useEffect(() => { useEffect(() => {
setISStopFetchTrace(!sendLoading); setISStopFetchTrace(!sendLoading);
}, [sendLoading]); }, [sendLoading, setISStopFetchTrace]);
const startedNodeList = useMemo(() => { const startedNodeList = useMemo(() => {
const finish = currentEventListWithoutMessage?.some( const finish = currentEventListWithoutMessage?.some(
@ -151,7 +151,7 @@ export const WorkFlowTimeline = ({
} }
return pre; return pre;
}, []); }, []);
}, [currentEventListWithoutMessage, sendLoading]); }, [currentEventListWithoutMessage, sendLoading, setISStopFetchTrace]);
const getElapsedTime = (nodeId: string) => { const getElapsedTime = (nodeId: string) => {
if (nodeId === 'begin') { if (nodeId === 'begin') {

View File

@ -31,12 +31,16 @@ export const FormSchema = z.object({
const SearchMethodOptions = buildOptions(TokenizerSearchMethod); const SearchMethodOptions = buildOptions(TokenizerSearchMethod);
const FieldsOptions = buildOptions(TokenizerFields);
const TokenizerForm = ({ node }: INextOperatorForm) => { const TokenizerForm = ({ node }: INextOperatorForm) => {
const { t } = useTranslation(); const { t } = useTranslation();
const defaultValues = useFormValues(initialTokenizerValues, node); const defaultValues = useFormValues(initialTokenizerValues, node);
const FieldsOptions = buildOptions(
TokenizerFields,
t,
'dataflow.tokenizerFieldsOptions',
);
const form = useForm<z.infer<typeof FormSchema>>({ const form = useForm<z.infer<typeof FormSchema>>({
defaultValues, defaultValues,
resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema),

View File

@ -3,19 +3,19 @@ import { useCallback } from 'react';
export function useCancelCurrentDataflow({ export function useCancelCurrentDataflow({
messageId, messageId,
setMessageId, stopFetchTrace,
}: { }: {
messageId: string; messageId: string;
setMessageId: (messageId: string) => void; stopFetchTrace(): void;
}) { }) {
const { cancelDataflow } = useCancelDataflow(); const { cancelDataflow } = useCancelDataflow();
const handleCancel = useCallback(async () => { const handleCancel = useCallback(async () => {
const code = await cancelDataflow(messageId); const code = await cancelDataflow(messageId);
if (code === 0) { if (code === 0) {
setMessageId(''); stopFetchTrace();
} }
}, [cancelDataflow, messageId, setMessageId]); }, [cancelDataflow, messageId, stopFetchTrace]);
return { handleCancel }; return { handleCancel };
} }

View File

@ -1,10 +1,16 @@
import { useFetchMessageTrace } from '@/hooks/use-agent-request'; import { useFetchMessageTrace } from '@/hooks/use-agent-request';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
export function useFetchLog() { export function useFetchLog(logSheetVisible: boolean) {
const { setMessageId, data, loading, messageId } = const {
useFetchMessageTrace(false); setMessageId,
data,
loading,
messageId,
setISStopFetchTrace,
isStopFetchTrace,
} = useFetchMessageTrace();
const isCompleted = useMemo(() => { const isCompleted = useMemo(() => {
if (Array.isArray(data)) { if (Array.isArray(data)) {
@ -13,18 +19,38 @@ export function useFetchLog() {
latest?.component_id === 'END' && !isEmpty(latest?.trace[0].message) latest?.component_id === 'END' && !isEmpty(latest?.trace[0].message)
); );
} }
return true; return false;
}, [data]); }, [data]);
const isLogEmpty = !data || !data.length; const isLogEmpty = !data || !data.length;
const stopFetchTrace = useCallback(() => {
setISStopFetchTrace(true);
}, [setISStopFetchTrace]);
// cancel request
useEffect(() => {
if (isCompleted) {
stopFetchTrace();
}
}, [isCompleted, stopFetchTrace]);
useEffect(() => {
if (logSheetVisible) {
setISStopFetchTrace(false);
}
}, [logSheetVisible, setISStopFetchTrace]);
return { return {
data, logs: data,
isLogEmpty, isLogEmpty,
isCompleted, isCompleted,
loading, loading,
isParsing: !isLogEmpty && !isCompleted, isParsing: !isLogEmpty && !isCompleted && !isStopFetchTrace,
messageId, messageId,
setMessageId, setMessageId,
stopFetchTrace,
}; };
} }
export type UseFetchLogReturnType = ReturnType<typeof useFetchLog>;

View File

@ -89,20 +89,29 @@ export default function DataFlow() {
hideModal: hideLogSheet, hideModal: hideLogSheet,
} = useSetModalState(); } = useSetModalState();
const { isParsing, data, messageId, setMessageId } = useFetchLog(); const {
isParsing,
logs,
messageId,
setMessageId,
isCompleted,
stopFetchTrace,
isLogEmpty,
} = useFetchLog(logSheetVisible);
const handleRunAgent = useCallback(() => { const handleRunAgent = useCallback(() => {
if (isParsing) { if (isParsing) {
// show log sheet // show log sheet
showLogSheet(); showLogSheet();
} else { } else {
hideLogSheet();
handleRun(); handleRun();
} }
}, [handleRun, isParsing, showLogSheet]); }, [handleRun, hideLogSheet, isParsing, showLogSheet]);
const { handleCancel } = useCancelCurrentDataflow({ const { handleCancel } = useCancelCurrentDataflow({
messageId, messageId,
setMessageId, stopFetchTrace,
}); });
const time = useWatchAgentChange(chatDrawerVisible); const time = useWatchAgentChange(chatDrawerVisible);
@ -139,7 +148,6 @@ export default function DataFlow() {
<ButtonLoading <ButtonLoading
variant={'secondary'} variant={'secondary'}
onClick={handleRunAgent} onClick={handleRunAgent}
disabled={isParsing}
loading={running} loading={running}
> >
{running || ( {running || (
@ -199,7 +207,9 @@ export default function DataFlow() {
<LogSheet <LogSheet
hideModal={hideLogSheet} hideModal={hideLogSheet}
isParsing={isParsing} isParsing={isParsing}
logs={data} isCompleted={isCompleted}
isLogEmpty={isLogEmpty}
logs={logs}
handleCancel={handleCancel} handleCancel={handleCancel}
></LogSheet> ></LogSheet>
)} )}

View File

@ -10,6 +10,7 @@ import {
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { ITraceData } from '@/interfaces/database/agent'; import { ITraceData } from '@/interfaces/database/agent';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { isEmpty } from 'lodash';
import { File } from 'lucide-react'; import { File } from 'lucide-react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { Operator } from '../constant'; import { Operator } from '../constant';
@ -82,7 +83,9 @@ export function DataflowTimeline({ traceList }: DataflowTimelineProps) {
</div> </div>
</section> </section>
<div className="divide-y space-y-1"> <div className="divide-y space-y-1">
{traces.map((x, idx) => ( {traces
.filter((x) => !isEmpty(x.message))
.map((x, idx) => (
<section <section
key={idx} key={idx}
className="text-text-secondary text-xs space-x-2 py-2.5 !m-0" className="text-text-secondary text-xs space-x-2 py-2.5 !m-0"
@ -98,7 +101,9 @@ export function DataflowTimeline({ traceList }: DataflowTimelineProps) {
{x.message} {x.message}
</span> </span>
)} )}
<span>{x.elapsed_time.toString().slice(0, 6)}s</span> <span>
{x.elapsed_time.toString().slice(0, 6)}s
</span>
</section> </section>
))} ))}
</div> </div>

View File

@ -1,3 +1,4 @@
import { SkeletonCard } from '@/components/skeleton-card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { import {
Sheet, Sheet,
@ -6,7 +7,6 @@ import {
SheetTitle, SheetTitle,
} from '@/components/ui/sheet'; } from '@/components/ui/sheet';
import { IModalProps } from '@/interfaces/common'; import { IModalProps } from '@/interfaces/common';
import { ITraceData } from '@/interfaces/database/agent';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { import {
ArrowUpRight, ArrowUpRight,
@ -20,19 +20,23 @@ import {
isEndOutputEmpty, isEndOutputEmpty,
useDownloadOutput, useDownloadOutput,
} from '../hooks/use-download-output'; } from '../hooks/use-download-output';
import { UseFetchLogReturnType } from '../hooks/use-fetch-log';
import { DataflowTimeline } from './dataflow-timeline'; import { DataflowTimeline } from './dataflow-timeline';
type LogSheetProps = IModalProps<any> & { type LogSheetProps = IModalProps<any> & {
isParsing: boolean;
handleCancel(): void; handleCancel(): void;
logs?: ITraceData[]; } & Pick<
}; UseFetchLogReturnType,
'isCompleted' | 'isLogEmpty' | 'isParsing' | 'logs'
>;
export function LogSheet({ export function LogSheet({
hideModal, hideModal,
isParsing, isParsing,
logs, logs,
handleCancel, handleCancel,
isCompleted,
isLogEmpty,
}: LogSheetProps) { }: LogSheetProps) {
const { t } = useTranslation(); const { t } = useTranslation();
@ -47,13 +51,17 @@ export function LogSheet({
<SheetHeader> <SheetHeader>
<SheetTitle className="flex items-center gap-2.5"> <SheetTitle className="flex items-center gap-2.5">
<Logs className="size-4" /> {t('flow.log')} <Logs className="size-4" /> {t('flow.log')}
<Button variant={'ghost'}> <Button variant={'ghost'} disabled={!isCompleted}>
{t('dataflow.viewResult')} <ArrowUpRight /> {t('dataflow.viewResult')} <ArrowUpRight />
</Button> </Button>
</SheetTitle> </SheetTitle>
</SheetHeader> </SheetHeader>
<section className="max-h-[82vh] overflow-auto mt-6"> <section className="max-h-[82vh] overflow-auto mt-6">
{isLogEmpty ? (
<SkeletonCard className="mt-2" />
) : (
<DataflowTimeline traceList={logs}></DataflowTimeline> <DataflowTimeline traceList={logs}></DataflowTimeline>
)}
</section> </section>
{isParsing ? ( {isParsing ? (
<Button <Button