mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### 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)
This commit is contained in:
@ -28,7 +28,7 @@ const DualRangeSlider = React.forwardRef<
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
|
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-border-button">
|
||||||
<SliderPrimitive.Range className="absolute h-full bg-accent-primary" />
|
<SliderPrimitive.Range className="absolute h-full bg-accent-primary" />
|
||||||
</SliderPrimitive.Track>
|
</SliderPrimitive.Track>
|
||||||
{initialValue.map((value, index) => (
|
{initialValue.map((value, index) => (
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export const enum AgentApiAction {
|
|||||||
FetchExternalAgentInputs = 'fetchExternalAgentInputs',
|
FetchExternalAgentInputs = 'fetchExternalAgentInputs',
|
||||||
SetAgentSetting = 'setAgentSetting',
|
SetAgentSetting = 'setAgentSetting',
|
||||||
FetchPrompt = 'fetchPrompt',
|
FetchPrompt = 'fetchPrompt',
|
||||||
|
CancelDataflow = 'cancelDataflow',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmptyDsl = {
|
export const EmptyDsl = {
|
||||||
@ -387,7 +388,7 @@ export const useUploadCanvasFileWithProgress = (
|
|||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
onError(file, error as Error);
|
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 = () => {
|
export const useTestDbConnect = () => {
|
||||||
@ -571,7 +572,6 @@ export const useFetchAgentLog = (searchParams: IAgentLogsRequest) => {
|
|||||||
initialData: {} as IAgentLogsResponse,
|
initialData: {} as IAgentLogsResponse,
|
||||||
gcTime: 0,
|
gcTime: 0,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
console.log('useFetchAgentLog', searchParams);
|
|
||||||
const { data } = await fetchAgentLogsByCanvasId(id as string, {
|
const { data } = await fetchAgentLogsByCanvasId(id as string, {
|
||||||
...searchParams,
|
...searchParams,
|
||||||
});
|
});
|
||||||
@ -678,3 +678,24 @@ export const useFetchAgentList = ({
|
|||||||
|
|
||||||
return { data, loading };
|
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 };
|
||||||
|
};
|
||||||
|
|||||||
@ -1579,6 +1579,7 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
sqlStatementTip:
|
sqlStatementTip:
|
||||||
'Write your SQL query here. You can use variables, raw SQL, or mix both using variable syntax.',
|
'Write your SQL query here. You can use variables, raw SQL, or mix both using variable syntax.',
|
||||||
frameworkPrompts: 'Framework',
|
frameworkPrompts: 'Framework',
|
||||||
|
release: 'Publish',
|
||||||
},
|
},
|
||||||
llmTools: {
|
llmTools: {
|
||||||
bad_calculator: {
|
bad_calculator: {
|
||||||
@ -1702,6 +1703,7 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
begin: 'File',
|
begin: 'File',
|
||||||
parserMethod: 'Parser method',
|
parserMethod: 'Parser method',
|
||||||
exportJson: 'Export JSON',
|
exportJson: 'Export JSON',
|
||||||
|
viewResult: 'View Result',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1490,6 +1490,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
sqlStatementTip:
|
sqlStatementTip:
|
||||||
'在此处编写您的 SQL 查询。您可以使用变量、原始 SQL,或使用变量语法混合使用两者。',
|
'在此处编写您的 SQL 查询。您可以使用变量、原始 SQL,或使用变量语法混合使用两者。',
|
||||||
frameworkPrompts: '框架',
|
frameworkPrompts: '框架',
|
||||||
|
release: '发布',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
@ -1620,6 +1621,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
begin: '文件',
|
begin: '文件',
|
||||||
parserMethod: '解析方法',
|
parserMethod: '解析方法',
|
||||||
exportJson: '导出 JSON',
|
exportJson: '导出 JSON',
|
||||||
|
viewResult: '查看结果',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -35,7 +35,6 @@ import {
|
|||||||
useHideFormSheetOnNodeDeletion,
|
useHideFormSheetOnNodeDeletion,
|
||||||
useShowDrawer,
|
useShowDrawer,
|
||||||
} from '../hooks/use-show-drawer';
|
} from '../hooks/use-show-drawer';
|
||||||
import { LogSheet } from '../log-sheet';
|
|
||||||
import RunSheet from '../run-sheet';
|
import RunSheet from '../run-sheet';
|
||||||
import { ButtonEdge } from './edge';
|
import { ButtonEdge } from './edge';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
@ -65,9 +64,10 @@ const edgeTypes = {
|
|||||||
interface IProps {
|
interface IProps {
|
||||||
drawerVisible: boolean;
|
drawerVisible: boolean;
|
||||||
hideDrawer(): void;
|
hideDrawer(): void;
|
||||||
|
showLogSheet(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
function DataFlowCanvas({ drawerVisible, hideDrawer, showLogSheet }: IProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
nodes,
|
nodes,
|
||||||
@ -147,17 +147,10 @@ function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
clearActiveDropdown,
|
clearActiveDropdown,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const {
|
const { run, loading: running } = useRunDataflow(
|
||||||
visible: logSheetVisible,
|
showLogSheet!,
|
||||||
showModal: showLogSheet,
|
hideRunOrChatDrawer,
|
||||||
hideModal: hideLogSheet,
|
);
|
||||||
} = useSetModalState();
|
|
||||||
|
|
||||||
const {
|
|
||||||
run,
|
|
||||||
loading: running,
|
|
||||||
messageId,
|
|
||||||
} = useRunDataflow(showLogSheet!, hideRunOrChatDrawer);
|
|
||||||
|
|
||||||
const onConnect = (connection: Connection) => {
|
const onConnect = (connection: Connection) => {
|
||||||
originalOnConnect(connection);
|
originalOnConnect(connection);
|
||||||
@ -311,9 +304,7 @@ function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
loading={running}
|
loading={running}
|
||||||
></RunSheet>
|
></RunSheet>
|
||||||
)}
|
)}
|
||||||
{logSheetVisible && (
|
{/* {logSheetVisible && <LogSheet hideModal={hideLogSheet}></LogSheet>} */}
|
||||||
<LogSheet hideModal={hideLogSheet} messageId={messageId}></LogSheet>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,3 +48,10 @@ export type HandleContextType = {
|
|||||||
export const HandleContext = createContext<HandleContextType>(
|
export const HandleContext = createContext<HandleContextType>(
|
||||||
{} as HandleContextType,
|
{} as HandleContextType,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type LogContextType = {
|
||||||
|
messageId: string;
|
||||||
|
setMessageId: (messageId: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LogContext = createContext<LogContextType>({} as LogContextType);
|
||||||
|
|||||||
24
web/src/pages/data-flow/hooks/use-cancel-dataflow.ts
Normal file
24
web/src/pages/data-flow/hooks/use-cancel-dataflow.ts
Normal file
@ -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 };
|
||||||
|
}
|
||||||
30
web/src/pages/data-flow/hooks/use-fetch-log.ts
Normal file
30
web/src/pages/data-flow/hooks/use-fetch-log.ts
Normal file
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { useSendMessageBySSE } from '@/hooks/use-send-message';
|
import { useSendMessageBySSE } from '@/hooks/use-send-message';
|
||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
|
import { LogContext } from '../context';
|
||||||
import { useSaveGraphBeforeOpeningDebugDrawer } from './use-save-graph';
|
import { useSaveGraphBeforeOpeningDebugDrawer } from './use-save-graph';
|
||||||
|
|
||||||
export function useRunDataflow(
|
export function useRunDataflow(
|
||||||
@ -11,7 +12,7 @@ export function useRunDataflow(
|
|||||||
) {
|
) {
|
||||||
const { send } = useSendMessageBySSE(api.runCanvas);
|
const { send } = useSendMessageBySSE(api.runCanvas);
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const [messageId, setMessageId] = useState();
|
const { setMessageId } = useContext(LogContext);
|
||||||
|
|
||||||
const { handleRun: saveGraph, loading } =
|
const { handleRun: saveGraph, loading } =
|
||||||
useSaveGraphBeforeOpeningDebugDrawer(showLogSheet!);
|
useSaveGraphBeforeOpeningDebugDrawer(showLogSheet!);
|
||||||
@ -39,10 +40,10 @@ export function useRunDataflow(
|
|||||||
return msgId;
|
return msgId;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[hideRunOrChatDrawer, id, saveGraph, send],
|
[hideRunOrChatDrawer, id, saveGraph, send, setMessageId],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { run, loading: loading, messageId };
|
return { run, loading: loading };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RunDataflowType = ReturnType<typeof useRunDataflow>;
|
export type RunDataflowType = ReturnType<typeof useRunDataflow>;
|
||||||
|
|||||||
@ -30,13 +30,17 @@ import { ComponentPropsWithoutRef, useCallback } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import DataFlowCanvas from './canvas';
|
import DataFlowCanvas from './canvas';
|
||||||
import { DropdownProvider } from './canvas/context';
|
import { DropdownProvider } from './canvas/context';
|
||||||
|
import { LogContext } from './context';
|
||||||
|
import { useCancelCurrentDataflow } from './hooks/use-cancel-dataflow';
|
||||||
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
|
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
|
||||||
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
||||||
|
import { useFetchLog } from './hooks/use-fetch-log';
|
||||||
import {
|
import {
|
||||||
useSaveGraph,
|
useSaveGraph,
|
||||||
useSaveGraphBeforeOpeningDebugDrawer,
|
useSaveGraphBeforeOpeningDebugDrawer,
|
||||||
useWatchAgentChange,
|
useWatchAgentChange,
|
||||||
} from './hooks/use-save-graph';
|
} from './hooks/use-save-graph';
|
||||||
|
import { LogSheet } from './log-sheet';
|
||||||
import { SettingDialog } from './setting-dialog';
|
import { SettingDialog } from './setting-dialog';
|
||||||
import { useAgentHistoryManager } from './use-agent-history-manager';
|
import { useAgentHistoryManager } from './use-agent-history-manager';
|
||||||
import { VersionDialog } from './version-dialog';
|
import { VersionDialog } from './version-dialog';
|
||||||
@ -65,9 +69,7 @@ export default function DataFlow() {
|
|||||||
const { saveGraph, loading } = useSaveGraph();
|
const { saveGraph, loading } = useSaveGraph();
|
||||||
const { flowDetail: agentDetail } = useFetchDataOnMount();
|
const { flowDetail: agentDetail } = useFetchDataOnMount();
|
||||||
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
|
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
|
||||||
const handleRunAgent = useCallback(() => {
|
|
||||||
handleRun();
|
|
||||||
}, [handleRun]);
|
|
||||||
const {
|
const {
|
||||||
visible: versionDialogVisible,
|
visible: versionDialogVisible,
|
||||||
hideModal: hideVersionDialog,
|
hideModal: hideVersionDialog,
|
||||||
@ -80,6 +82,29 @@ export default function DataFlow() {
|
|||||||
showModal: showSettingDialog,
|
showModal: showSettingDialog,
|
||||||
} = useSetModalState();
|
} = 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);
|
const time = useWatchAgentChange(chatDrawerVisible);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -112,14 +137,17 @@ export default function DataFlow() {
|
|||||||
<LaptopMinimalCheck /> {t('flow.save')}
|
<LaptopMinimalCheck /> {t('flow.save')}
|
||||||
</ButtonLoading>
|
</ButtonLoading>
|
||||||
<Button variant={'secondary'} onClick={handleRunAgent}>
|
<Button variant={'secondary'} onClick={handleRunAgent}>
|
||||||
<CirclePlay />
|
<CirclePlay className={isParsing ? 'animate-spin' : ''} />
|
||||||
{t('flow.run')}
|
{isParsing ? 'running' : t('flow.run')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant={'secondary'} onClick={showVersionDialog}>
|
<Button variant={'secondary'} onClick={showVersionDialog}>
|
||||||
<History />
|
<History />
|
||||||
{t('flow.historyversion')}
|
{t('flow.historyversion')}
|
||||||
</Button>
|
</Button>
|
||||||
|
{/* <Button variant={'secondary'}>
|
||||||
|
<Send />
|
||||||
|
{t('flow.release')}
|
||||||
|
</Button> */}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant={'secondary'}>
|
<Button variant={'secondary'}>
|
||||||
@ -140,15 +168,17 @@ export default function DataFlow() {
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<ReactFlowProvider>
|
<LogContext.Provider value={{ messageId, setMessageId }}>
|
||||||
<DropdownProvider>
|
<ReactFlowProvider>
|
||||||
<DataFlowCanvas
|
<DropdownProvider>
|
||||||
drawerVisible={chatDrawerVisible}
|
<DataFlowCanvas
|
||||||
hideDrawer={hideChatDrawer}
|
drawerVisible={chatDrawerVisible}
|
||||||
></DataFlowCanvas>
|
hideDrawer={hideChatDrawer}
|
||||||
</DropdownProvider>
|
showLogSheet={showLogSheet}
|
||||||
</ReactFlowProvider>
|
></DataFlowCanvas>
|
||||||
|
</DropdownProvider>
|
||||||
|
</ReactFlowProvider>
|
||||||
|
</LogContext.Provider>
|
||||||
{versionDialogVisible && (
|
{versionDialogVisible && (
|
||||||
<DropdownProvider>
|
<DropdownProvider>
|
||||||
<VersionDialog hideModal={hideVersionDialog}></VersionDialog>
|
<VersionDialog hideModal={hideVersionDialog}></VersionDialog>
|
||||||
@ -157,6 +187,14 @@ export default function DataFlow() {
|
|||||||
{settingDialogVisible && (
|
{settingDialogVisible && (
|
||||||
<SettingDialog hideModal={hideSettingDialog}></SettingDialog>
|
<SettingDialog hideModal={hideSettingDialog}></SettingDialog>
|
||||||
)}
|
)}
|
||||||
|
{logSheetVisible && (
|
||||||
|
<LogSheet
|
||||||
|
hideModal={hideLogSheet}
|
||||||
|
isParsing={isParsing}
|
||||||
|
logs={data}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
></LogSheet>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,10 @@ import {
|
|||||||
TimelineSeparator,
|
TimelineSeparator,
|
||||||
TimelineTitle,
|
TimelineTitle,
|
||||||
} from '@/components/originui/timeline';
|
} from '@/components/originui/timeline';
|
||||||
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { ITraceData } from '@/interfaces/database/agent';
|
import { ITraceData } from '@/interfaces/database/agent';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { File } from 'lucide-react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
import OperatorIcon from '../operator-icon';
|
import OperatorIcon from '../operator-icon';
|
||||||
@ -17,6 +20,8 @@ export type DataflowTimelineProps = {
|
|||||||
traceList?: ITraceData[];
|
traceList?: ITraceData[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const END = 'END';
|
||||||
|
|
||||||
interface DataflowTrace {
|
interface DataflowTrace {
|
||||||
datetime: string;
|
datetime: string;
|
||||||
elapsed_time: number;
|
elapsed_time: number;
|
||||||
@ -48,43 +53,66 @@ export function DataflowTimeline({ traceList }: DataflowTimelineProps) {
|
|||||||
const traces = item.trace as DataflowTrace[];
|
const traces = item.trace as DataflowTrace[];
|
||||||
const nodeLabel = getNodeLabel(item.component_id);
|
const nodeLabel = getNodeLabel(item.component_id);
|
||||||
|
|
||||||
|
const latest = traces[traces.length - 1];
|
||||||
|
const progress = latest.progress * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimelineItem
|
<TimelineItem
|
||||||
key={item.component_id}
|
key={item.component_id}
|
||||||
step={index}
|
step={index}
|
||||||
className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8"
|
className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8 pb-6"
|
||||||
>
|
>
|
||||||
<TimelineHeader>
|
<TimelineHeader>
|
||||||
<TimelineSeparator className="group-data-[orientation=vertical]/timeline:-left-7 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem-0.25rem)] group-data-[orientation=vertical]/timeline:translate-y-7 bg-accent-primary" />
|
<TimelineSeparator className="group-data-[orientation=vertical]/timeline:-left-7 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem-0.25rem)] group-data-[orientation=vertical]/timeline:translate-y-7 bg-accent-primary" />
|
||||||
<TimelineTitle className="">
|
<TimelineTitle className="">
|
||||||
<TimelineContent className="text-foreground mt-2 rounded-lg border px-4 py-3">
|
<TimelineContent
|
||||||
<p className="mb-2">
|
className={cn(
|
||||||
{getNodeData(item.component_id)?.name || 'END'}
|
'text-foreground rounded-lg border px-4 py-3',
|
||||||
</p>
|
)}
|
||||||
|
>
|
||||||
|
<section className="flex items-center justify-between mb-2">
|
||||||
|
<span className="flex-1 truncate">
|
||||||
|
{getNodeData(item.component_id)?.name || END}
|
||||||
|
</span>
|
||||||
|
<div className="flex-1 flex items-center gap-5">
|
||||||
|
<Progress value={progress} className="h-1 flex-1" />
|
||||||
|
<span className="text-accent-primary text-xs">
|
||||||
|
{progress}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<div className="divide-y space-y-1">
|
<div className="divide-y space-y-1">
|
||||||
{traces.map((x, idx) => (
|
{traces.map((x, idx) => (
|
||||||
<section
|
<section
|
||||||
key={idx}
|
key={idx}
|
||||||
className="text-text-secondary text-xs"
|
className="text-text-secondary text-xs space-x-2 py-2.5 !m-0"
|
||||||
>
|
>
|
||||||
<div className="space-x-2">
|
<span>{x.datetime}</span>
|
||||||
<span>{x.datetime}</span>
|
|
||||||
<span>{x.progress * 100}%</span>
|
|
||||||
<span>{x.elapsed_time.toString().slice(0, 6)}</span>
|
|
||||||
</div>
|
|
||||||
{item.component_id !== 'END' && (
|
{item.component_id !== 'END' && (
|
||||||
<div>{x.message}</div>
|
<span>{x.message}</span>
|
||||||
)}
|
)}
|
||||||
|
<span>{x.elapsed_time.toString().slice(0, 6)}</span>
|
||||||
</section>
|
</section>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</TimelineContent>
|
</TimelineContent>
|
||||||
</TimelineTitle>
|
</TimelineTitle>
|
||||||
<TimelineIndicator className="border border-accent-primary group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-6 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7">
|
<TimelineIndicator
|
||||||
{nodeLabel && (
|
className={cn(
|
||||||
|
'border border-accent-primary group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-5 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7',
|
||||||
|
{
|
||||||
|
'rounded bg-accent-primary': nodeLabel === Operator.Begin,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.component_id === END ? (
|
||||||
|
<span className="rounded-full inline-block size-2 bg-accent-primary"></span>
|
||||||
|
) : nodeLabel === Operator.Begin ? (
|
||||||
|
<File className="size-3.5 text-bg-base"></File>
|
||||||
|
) : (
|
||||||
<OperatorIcon
|
<OperatorIcon
|
||||||
name={nodeLabel}
|
name={nodeLabel}
|
||||||
className="size-6 rounded-full"
|
className="size-3.5 rounded-full"
|
||||||
></OperatorIcon>
|
></OperatorIcon>
|
||||||
)}
|
)}
|
||||||
</TimelineIndicator>
|
</TimelineIndicator>
|
||||||
|
|||||||
@ -5,11 +5,15 @@ import {
|
|||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from '@/components/ui/sheet';
|
} from '@/components/ui/sheet';
|
||||||
import { useFetchMessageTrace } from '@/hooks/use-agent-request';
|
|
||||||
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 { NotebookText, SquareArrowOutUpRight } from 'lucide-react';
|
import {
|
||||||
import { useEffect } from 'react';
|
ArrowUpRight,
|
||||||
|
CirclePause,
|
||||||
|
Logs,
|
||||||
|
SquareArrowOutUpRight,
|
||||||
|
} from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import 'react18-json-view/src/style.css';
|
import 'react18-json-view/src/style.css';
|
||||||
import {
|
import {
|
||||||
@ -18,39 +22,53 @@ import {
|
|||||||
} from '../hooks/use-download-output';
|
} from '../hooks/use-download-output';
|
||||||
import { DataflowTimeline } from './dataflow-timeline';
|
import { DataflowTimeline } from './dataflow-timeline';
|
||||||
|
|
||||||
type LogSheetProps = IModalProps<any> & { messageId?: string };
|
type LogSheetProps = IModalProps<any> & {
|
||||||
|
isParsing: boolean;
|
||||||
|
handleCancel(): void;
|
||||||
|
logs?: ITraceData[];
|
||||||
|
};
|
||||||
|
|
||||||
export function LogSheet({ hideModal, messageId }: LogSheetProps) {
|
export function LogSheet({
|
||||||
|
hideModal,
|
||||||
|
isParsing,
|
||||||
|
logs,
|
||||||
|
handleCancel,
|
||||||
|
}: LogSheetProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setMessageId, data } = useFetchMessageTrace(false);
|
|
||||||
|
|
||||||
const { handleDownloadJson } = useDownloadOutput(data);
|
const { handleDownloadJson } = useDownloadOutput(logs);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (messageId) {
|
|
||||||
setMessageId(messageId);
|
|
||||||
}
|
|
||||||
}, [messageId, setMessageId]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open onOpenChange={hideModal} modal={false}>
|
<Sheet open onOpenChange={hideModal} modal={false}>
|
||||||
<SheetContent className={cn('top-20')}>
|
<SheetContent className={cn('top-20')}>
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle className="flex items-center gap-1">
|
<SheetTitle className="flex items-center gap-2.5">
|
||||||
<NotebookText className="size-4" /> {t('flow.log')}
|
<Logs className="size-4" /> {t('flow.log')}
|
||||||
|
<Button variant={'ghost'}>
|
||||||
|
{t('dataflow.viewResult')} <ArrowUpRight />
|
||||||
|
</Button>
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<section className="max-h-[82vh] overflow-auto mt-6">
|
<section className="max-h-[82vh] overflow-auto mt-6">
|
||||||
<DataflowTimeline traceList={data}></DataflowTimeline>
|
<DataflowTimeline traceList={logs}></DataflowTimeline>
|
||||||
</section>
|
</section>
|
||||||
<Button
|
{isParsing ? (
|
||||||
onClick={handleDownloadJson}
|
<Button
|
||||||
disabled={isEndOutputEmpty(data)}
|
className="w-full mt-8 bg-state-error/10 text-state-error"
|
||||||
className="w-full mt-8"
|
onClick={handleCancel}
|
||||||
>
|
>
|
||||||
<SquareArrowOutUpRight />
|
<CirclePause /> Cancel
|
||||||
{t('dataflow.exportJson')}
|
</Button>
|
||||||
</Button>
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={handleDownloadJson}
|
||||||
|
disabled={isEndOutputEmpty(logs)}
|
||||||
|
className="w-full mt-8"
|
||||||
|
>
|
||||||
|
<SquareArrowOutUpRight />
|
||||||
|
{t('dataflow.exportJson')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import { IconFont } from '@/components/icon-font';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
Blocks,
|
Blocks,
|
||||||
|
File,
|
||||||
FileChartColumnIncreasing,
|
FileChartColumnIncreasing,
|
||||||
Heading,
|
Heading,
|
||||||
HousePlus,
|
|
||||||
ListMinus,
|
ListMinus,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Operator } from './constant';
|
import { Operator } from './constant';
|
||||||
@ -15,11 +15,11 @@ interface IProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const OperatorIconMap = {
|
export const OperatorIconMap = {
|
||||||
[Operator.Begin]: 'house-plus',
|
|
||||||
[Operator.Note]: 'notebook-pen',
|
[Operator.Note]: 'notebook-pen',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SVGIconMap = {
|
export const SVGIconMap = {
|
||||||
|
[Operator.Begin]: File,
|
||||||
[Operator.Parser]: FileChartColumnIncreasing,
|
[Operator.Parser]: FileChartColumnIncreasing,
|
||||||
[Operator.Tokenizer]: ListMinus,
|
[Operator.Tokenizer]: ListMinus,
|
||||||
[Operator.Splitter]: Blocks,
|
[Operator.Splitter]: Blocks,
|
||||||
@ -42,7 +42,7 @@ const OperatorIcon = ({ name, className }: IProps) => {
|
|||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<HousePlus className="rounded size-3" />
|
<File className="rounded size-3" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ const OperatorIcon = ({ name, className }: IProps) => {
|
|||||||
return typeof Icon === 'string' ? (
|
return typeof Icon === 'string' ? (
|
||||||
<IconFont name={Icon} className={cn('size-5 ', className)}></IconFont>
|
<IconFont name={Icon} className={cn('size-5 ', className)}></IconFont>
|
||||||
) : (
|
) : (
|
||||||
<SvgIcon className="size-5"></SvgIcon>
|
<SvgIcon className={cn('size-5', className)}></SvgIcon>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ const {
|
|||||||
fetchAgentLogs,
|
fetchAgentLogs,
|
||||||
fetchExternalAgentInputs,
|
fetchExternalAgentInputs,
|
||||||
prompt,
|
prompt,
|
||||||
|
cancelDataflow,
|
||||||
} = api;
|
} = api;
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
@ -120,6 +121,10 @@ const methods = {
|
|||||||
url: prompt,
|
url: prompt,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
},
|
},
|
||||||
|
cancelDataflow: {
|
||||||
|
url: cancelDataflow,
|
||||||
|
method: 'put',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const agentService = registerNextServer<keyof typeof methods>(methods);
|
const agentService = registerNextServer<keyof typeof methods>(methods);
|
||||||
|
|||||||
@ -169,6 +169,7 @@ export default {
|
|||||||
fetchExternalAgentInputs: (canvasId: string) =>
|
fetchExternalAgentInputs: (canvasId: string) =>
|
||||||
`${ExternalApi}${api_host}/agentbots/${canvasId}/inputs`,
|
`${ExternalApi}${api_host}/agentbots/${canvasId}/inputs`,
|
||||||
prompt: `${api_host}/canvas/prompts`,
|
prompt: `${api_host}/canvas/prompts`,
|
||||||
|
cancelDataflow: (id: string) => `${api_host}/canvas/cancel/${id}`,
|
||||||
|
|
||||||
// mcp server
|
// mcp server
|
||||||
listMcpServer: `${api_host}/mcp_server/list`,
|
listMcpServer: `${api_host}/mcp_server/list`,
|
||||||
|
|||||||
@ -28,7 +28,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: 'var(--colors-outline-neutral-strong)',
|
border: 'var(--border-default)',
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
background: 'var(--background)',
|
background: 'var(--background)',
|
||||||
|
|||||||
@ -100,7 +100,6 @@
|
|||||||
--bg-card: rgba(0, 0, 0, 0.05);
|
--bg-card: rgba(0, 0, 0, 0.05);
|
||||||
--bg-component: #ffffff;
|
--bg-component: #ffffff;
|
||||||
--bg-input: rgba(255, 255, 255, 0);
|
--bg-input: rgba(255, 255, 255, 0);
|
||||||
--bg-accent: rgba(76, 164, 231, 0.05);
|
|
||||||
/* Button ,Body text, Input completed text */
|
/* Button ,Body text, Input completed text */
|
||||||
--text-primary: #161618;
|
--text-primary: #161618;
|
||||||
--text-secondary: #75787a;
|
--text-secondary: #75787a;
|
||||||
|
|||||||
Reference in New Issue
Block a user