From 6bf0cda16f495c4eb51ad60a6be3cfbca3c29237 Mon Sep 17 00:00:00 2001 From: balibabu Date: Wed, 24 Sep 2025 16:33:33 +0800 Subject: [PATCH] Feat: Cancel a running data flow test #9869 (#10257) ### What problem does this PR solve? Feat: Cancel a running data flow test #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/components/ui/dual-range-slider.tsx | 2 +- web/src/hooks/use-agent-request.ts | 27 +++++++- web/src/locales/en.ts | 2 + web/src/locales/zh.ts | 2 + web/src/pages/data-flow/canvas/index.tsx | 23 ++----- web/src/pages/data-flow/context.ts | 7 ++ .../data-flow/hooks/use-cancel-dataflow.ts | 24 +++++++ .../pages/data-flow/hooks/use-fetch-log.ts | 30 ++++++++ .../pages/data-flow/hooks/use-run-dataflow.ts | 9 +-- web/src/pages/data-flow/index.tsx | 68 +++++++++++++++---- .../data-flow/log-sheet/dataflow-timeline.tsx | 58 ++++++++++++---- web/src/pages/data-flow/log-sheet/index.tsx | 66 +++++++++++------- web/src/pages/data-flow/operator-icon.tsx | 8 +-- web/src/services/agent-service.ts | 5 ++ web/src/utils/api.ts | 1 + web/tailwind.config.js | 2 +- web/tailwind.css | 1 - 17 files changed, 251 insertions(+), 84 deletions(-) create mode 100644 web/src/pages/data-flow/hooks/use-cancel-dataflow.ts create mode 100644 web/src/pages/data-flow/hooks/use-fetch-log.ts diff --git a/web/src/components/ui/dual-range-slider.tsx b/web/src/components/ui/dual-range-slider.tsx index a0ddd83db..e2fbb303c 100644 --- a/web/src/components/ui/dual-range-slider.tsx +++ b/web/src/components/ui/dual-range-slider.tsx @@ -28,7 +28,7 @@ const DualRangeSlider = React.forwardRef< )} {...props} > - + {initialValue.map((value, index) => ( diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index 97b8e4ede..65c6fc4bf 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -52,6 +52,7 @@ export const enum AgentApiAction { FetchExternalAgentInputs = 'fetchExternalAgentInputs', SetAgentSetting = 'setAgentSetting', FetchPrompt = 'fetchPrompt', + CancelDataflow = 'cancelDataflow', } export const EmptyDsl = { @@ -387,7 +388,7 @@ export const useUploadCanvasFileWithProgress = ( files.forEach((file) => { onError(file, error as Error); }); - message.error(error?.message); + message.error((error as Error)?.message || 'Upload failed'); } }, }); @@ -425,7 +426,7 @@ export const useFetchMessageTrace = ( }, }); - return { data, loading, refetch, setMessageId }; + return { data, loading, refetch, setMessageId, messageId }; }; export const useTestDbConnect = () => { @@ -571,7 +572,6 @@ export const useFetchAgentLog = (searchParams: IAgentLogsRequest) => { initialData: {} as IAgentLogsResponse, gcTime: 0, queryFn: async () => { - console.log('useFetchAgentLog', searchParams); const { data } = await fetchAgentLogsByCanvasId(id as string, { ...searchParams, }); @@ -678,3 +678,24 @@ export const useFetchAgentList = ({ return { data, loading }; }; + +export const useCancelDataflow = () => { + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: [AgentApiAction.CancelDataflow], + mutationFn: async (taskId: string) => { + const ret = await agentService.cancelDataflow(taskId); + if (ret?.data?.code === 0) { + message.success('success'); + } else { + message.error(ret?.data?.data); + } + return ret?.data?.code; + }, + }); + + return { data, loading, cancelDataflow: mutateAsync }; +}; diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index f88e269b4..9de38e2be 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1579,6 +1579,7 @@ This delimiter is used to split the input text into several text pieces echo of sqlStatementTip: 'Write your SQL query here. You can use variables, raw SQL, or mix both using variable syntax.', frameworkPrompts: 'Framework', + release: 'Publish', }, llmTools: { bad_calculator: { @@ -1702,6 +1703,7 @@ This delimiter is used to split the input text into several text pieces echo of begin: 'File', parserMethod: 'Parser method', exportJson: 'Export JSON', + viewResult: 'View Result', }, }, }; diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index a430e8415..2d81f8426 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -1490,6 +1490,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 sqlStatementTip: '在此处编写您的 SQL 查询。您可以使用变量、原始 SQL,或使用变量语法混合使用两者。', frameworkPrompts: '框架', + release: '发布', }, footer: { profile: 'All rights reserved @ React', @@ -1620,6 +1621,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 begin: '文件', parserMethod: '解析方法', exportJson: '导出 JSON', + viewResult: '查看结果', }, }, }; diff --git a/web/src/pages/data-flow/canvas/index.tsx b/web/src/pages/data-flow/canvas/index.tsx index 31c58b5a2..0ea2e6d90 100644 --- a/web/src/pages/data-flow/canvas/index.tsx +++ b/web/src/pages/data-flow/canvas/index.tsx @@ -35,7 +35,6 @@ import { useHideFormSheetOnNodeDeletion, useShowDrawer, } from '../hooks/use-show-drawer'; -import { LogSheet } from '../log-sheet'; import RunSheet from '../run-sheet'; import { ButtonEdge } from './edge'; import styles from './index.less'; @@ -65,9 +64,10 @@ const edgeTypes = { interface IProps { drawerVisible: boolean; hideDrawer(): void; + showLogSheet(): void; } -function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) { +function DataFlowCanvas({ drawerVisible, hideDrawer, showLogSheet }: IProps) { const { t } = useTranslation(); const { nodes, @@ -147,17 +147,10 @@ function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) { clearActiveDropdown, ]); - const { - visible: logSheetVisible, - showModal: showLogSheet, - hideModal: hideLogSheet, - } = useSetModalState(); - - const { - run, - loading: running, - messageId, - } = useRunDataflow(showLogSheet!, hideRunOrChatDrawer); + const { run, loading: running } = useRunDataflow( + showLogSheet!, + hideRunOrChatDrawer, + ); const onConnect = (connection: Connection) => { originalOnConnect(connection); @@ -311,9 +304,7 @@ function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) { loading={running} > )} - {logSheetVisible && ( - - )} + {/* {logSheetVisible && } */} ); } diff --git a/web/src/pages/data-flow/context.ts b/web/src/pages/data-flow/context.ts index 6839554d3..5a765921d 100644 --- a/web/src/pages/data-flow/context.ts +++ b/web/src/pages/data-flow/context.ts @@ -48,3 +48,10 @@ export type HandleContextType = { export const HandleContext = createContext( {} as HandleContextType, ); + +export type LogContextType = { + messageId: string; + setMessageId: (messageId: string) => void; +}; + +export const LogContext = createContext({} as LogContextType); diff --git a/web/src/pages/data-flow/hooks/use-cancel-dataflow.ts b/web/src/pages/data-flow/hooks/use-cancel-dataflow.ts new file mode 100644 index 000000000..1099660e5 --- /dev/null +++ b/web/src/pages/data-flow/hooks/use-cancel-dataflow.ts @@ -0,0 +1,24 @@ +import { useCancelDataflow } from '@/hooks/use-agent-request'; +import { useCallback } from 'react'; + +export function useCancelCurrentDataflow({ + messageId, + setMessageId, + hideLogSheet, +}: { + messageId: string; + setMessageId: (messageId: string) => void; + hideLogSheet(): void; +}) { + const { cancelDataflow } = useCancelDataflow(); + + const handleCancel = useCallback(async () => { + const code = await cancelDataflow(messageId); + if (code === 0) { + setMessageId(''); + hideLogSheet(); + } + }, [cancelDataflow, hideLogSheet, messageId, setMessageId]); + + return { handleCancel }; +} diff --git a/web/src/pages/data-flow/hooks/use-fetch-log.ts b/web/src/pages/data-flow/hooks/use-fetch-log.ts new file mode 100644 index 000000000..095121323 --- /dev/null +++ b/web/src/pages/data-flow/hooks/use-fetch-log.ts @@ -0,0 +1,30 @@ +import { useFetchMessageTrace } from '@/hooks/use-agent-request'; +import { isEmpty } from 'lodash'; +import { useMemo } from 'react'; + +export function useFetchLog() { + const { setMessageId, data, loading, messageId } = + useFetchMessageTrace(false); + + const isCompleted = useMemo(() => { + if (Array.isArray(data)) { + const latest = data?.at(-1); + return ( + latest?.component_id === 'END' && !isEmpty(latest?.trace[0].message) + ); + } + return true; + }, [data]); + + const isLogEmpty = !data || !data.length; + + return { + data, + isLogEmpty, + isCompleted, + loading, + isParsing: !isLogEmpty && !isCompleted, + messageId, + setMessageId, + }; +} diff --git a/web/src/pages/data-flow/hooks/use-run-dataflow.ts b/web/src/pages/data-flow/hooks/use-run-dataflow.ts index 7e8471251..5144b80ff 100644 --- a/web/src/pages/data-flow/hooks/use-run-dataflow.ts +++ b/web/src/pages/data-flow/hooks/use-run-dataflow.ts @@ -1,8 +1,9 @@ import { useSendMessageBySSE } from '@/hooks/use-send-message'; import api from '@/utils/api'; import { get } from 'lodash'; -import { useCallback, useState } from 'react'; +import { useCallback, useContext } from 'react'; import { useParams } from 'umi'; +import { LogContext } from '../context'; import { useSaveGraphBeforeOpeningDebugDrawer } from './use-save-graph'; export function useRunDataflow( @@ -11,7 +12,7 @@ export function useRunDataflow( ) { const { send } = useSendMessageBySSE(api.runCanvas); const { id } = useParams(); - const [messageId, setMessageId] = useState(); + const { setMessageId } = useContext(LogContext); const { handleRun: saveGraph, loading } = useSaveGraphBeforeOpeningDebugDrawer(showLogSheet!); @@ -39,10 +40,10 @@ export function useRunDataflow( return msgId; } }, - [hideRunOrChatDrawer, id, saveGraph, send], + [hideRunOrChatDrawer, id, saveGraph, send, setMessageId], ); - return { run, loading: loading, messageId }; + return { run, loading: loading }; } export type RunDataflowType = ReturnType; diff --git a/web/src/pages/data-flow/index.tsx b/web/src/pages/data-flow/index.tsx index aa99e548c..02d8ce6ca 100644 --- a/web/src/pages/data-flow/index.tsx +++ b/web/src/pages/data-flow/index.tsx @@ -30,13 +30,17 @@ import { ComponentPropsWithoutRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import DataFlowCanvas from './canvas'; import { DropdownProvider } from './canvas/context'; +import { LogContext } from './context'; +import { useCancelCurrentDataflow } from './hooks/use-cancel-dataflow'; import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; import { useFetchDataOnMount } from './hooks/use-fetch-data'; +import { useFetchLog } from './hooks/use-fetch-log'; import { useSaveGraph, useSaveGraphBeforeOpeningDebugDrawer, useWatchAgentChange, } from './hooks/use-save-graph'; +import { LogSheet } from './log-sheet'; import { SettingDialog } from './setting-dialog'; import { useAgentHistoryManager } from './use-agent-history-manager'; import { VersionDialog } from './version-dialog'; @@ -65,9 +69,7 @@ export default function DataFlow() { const { saveGraph, loading } = useSaveGraph(); const { flowDetail: agentDetail } = useFetchDataOnMount(); const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer); - const handleRunAgent = useCallback(() => { - handleRun(); - }, [handleRun]); + const { visible: versionDialogVisible, hideModal: hideVersionDialog, @@ -80,6 +82,29 @@ export default function DataFlow() { showModal: showSettingDialog, } = useSetModalState(); + const { + visible: logSheetVisible, + showModal: showLogSheet, + hideModal: hideLogSheet, + } = useSetModalState(); + + const { isParsing, data, messageId, setMessageId } = useFetchLog(); + + const handleRunAgent = useCallback(() => { + if (isParsing) { + // show log sheet + showLogSheet(); + } else { + handleRun(); + } + }, [handleRun, isParsing, showLogSheet]); + + const { handleCancel } = useCancelCurrentDataflow({ + messageId, + setMessageId, + hideLogSheet, + }); + const time = useWatchAgentChange(chatDrawerVisible); return ( @@ -112,14 +137,17 @@ export default function DataFlow() { {t('flow.save')} - + {/* */}
- +
- + {isParsing ? ( + + ) : ( + + )} ); diff --git a/web/src/pages/data-flow/operator-icon.tsx b/web/src/pages/data-flow/operator-icon.tsx index 6db9db9d4..7af1719e8 100644 --- a/web/src/pages/data-flow/operator-icon.tsx +++ b/web/src/pages/data-flow/operator-icon.tsx @@ -2,9 +2,9 @@ import { IconFont } from '@/components/icon-font'; import { cn } from '@/lib/utils'; import { Blocks, + File, FileChartColumnIncreasing, Heading, - HousePlus, ListMinus, } from 'lucide-react'; import { Operator } from './constant'; @@ -15,11 +15,11 @@ interface IProps { } export const OperatorIconMap = { - [Operator.Begin]: 'house-plus', [Operator.Note]: 'notebook-pen', }; export const SVGIconMap = { + [Operator.Begin]: File, [Operator.Parser]: FileChartColumnIncreasing, [Operator.Tokenizer]: ListMinus, [Operator.Splitter]: Blocks, @@ -42,7 +42,7 @@ const OperatorIcon = ({ name, className }: IProps) => { className, )} > - + ); } @@ -50,7 +50,7 @@ const OperatorIcon = ({ name, className }: IProps) => { return typeof Icon === 'string' ? ( ) : ( - + ); }; diff --git a/web/src/services/agent-service.ts b/web/src/services/agent-service.ts index 367cfa8be..b2c60b434 100644 --- a/web/src/services/agent-service.ts +++ b/web/src/services/agent-service.ts @@ -29,6 +29,7 @@ const { fetchAgentLogs, fetchExternalAgentInputs, prompt, + cancelDataflow, } = api; const methods = { @@ -120,6 +121,10 @@ const methods = { url: prompt, method: 'get', }, + cancelDataflow: { + url: cancelDataflow, + method: 'put', + }, } as const; const agentService = registerNextServer(methods); diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index 716f5c22f..a70ea1acc 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -169,6 +169,7 @@ export default { fetchExternalAgentInputs: (canvasId: string) => `${ExternalApi}${api_host}/agentbots/${canvasId}/inputs`, prompt: `${api_host}/canvas/prompts`, + cancelDataflow: (id: string) => `${api_host}/canvas/cancel/${id}`, // mcp server listMcpServer: `${api_host}/mcp_server/list`, diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 34b888a54..fb209297e 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -28,7 +28,7 @@ module.exports = { }, extend: { colors: { - border: 'var(--colors-outline-neutral-strong)', + border: 'var(--border-default)', input: 'hsl(var(--input))', ring: 'hsl(var(--ring))', background: 'var(--background)', diff --git a/web/tailwind.css b/web/tailwind.css index b1ccf1fa7..745d78e9a 100644 --- a/web/tailwind.css +++ b/web/tailwind.css @@ -100,7 +100,6 @@ --bg-card: rgba(0, 0, 0, 0.05); --bg-component: #ffffff; --bg-input: rgba(255, 255, 255, 0); - --bg-accent: rgba(76, 164, 231, 0.05); /* Button ,Body text, Input completed text */ --text-primary: #161618; --text-secondary: #75787a;