'use client' import type { FC } from 'react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import { BlockEnum } from '../types' import OutputPanel from './output-panel' import ResultPanel from './result-panel' import TracingPanel from './tracing-panel' import IterationResultPanel from './iteration-result-panel' import cn from '@/utils/classnames' import { ToastContext } from '@/app/components/base/toast' import Loading from '@/app/components/base/loading' import { fetchRunDetail, fetchTracingList } from '@/service/log' import type { NodeTracing } from '@/types/workflow' import type { WorkflowRunDetailResponse } from '@/models/log' import { useStore as useAppStore } from '@/app/components/app/store' export type RunProps = { hideResult?: boolean activeTab?: 'RESULT' | 'DETAIL' | 'TRACING' runID: string getResultCallback?: (result: WorkflowRunDetailResponse) => void } const RunPanel: FC = ({ hideResult, activeTab = 'RESULT', runID, getResultCallback }) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const [currentTab, setCurrentTab] = useState(activeTab) const appDetail = useAppStore(state => state.appDetail) const [loading, setLoading] = useState(true) const [runDetail, setRunDetail] = useState() const [list, setList] = useState([]) const executor = useMemo(() => { if (runDetail?.created_by_role === 'account') return runDetail.created_by_account?.name || '' if (runDetail?.created_by_role === 'end_user') return runDetail.created_by_end_user?.session_id || '' return 'N/A' }, [runDetail]) const getResult = useCallback(async (appID: string, runID: string) => { try { const res = await fetchRunDetail({ appID, runID, }) setRunDetail(res) if (getResultCallback) getResultCallback(res) } catch (err) { notify({ type: 'error', message: `${err}`, }) } }, [notify, getResultCallback]) const formatNodeList = useCallback((list: NodeTracing[]) => { const allItems = [...list].reverse() const result: NodeTracing[] = [] const groupMap = new Map() const processIterationNode = (item: NodeTracing) => { result.push({ ...item, details: [], }) } const updateParallelModeGroup = (runId: string, item: NodeTracing, iterationNode: NodeTracing) => { if (!groupMap.has(runId)) groupMap.set(runId, [item]) else groupMap.get(runId)!.push(item) if (item.status === 'failed') { iterationNode.status = 'failed' iterationNode.error = item.error } iterationNode.details = Array.from(groupMap.values()) } const updateSequentialModeGroup = (index: number, item: NodeTracing, iterationNode: NodeTracing) => { const { details } = iterationNode if (details) { if (!details[index]) details[index] = [item] else details[index].push(item) } if (item.status === 'failed') { iterationNode.status = 'failed' iterationNode.error = item.error } } const processNonIterationNode = (item: NodeTracing) => { const { execution_metadata } = item if (!execution_metadata?.iteration_id) { result.push(item) return } const iterationNode = result.find(node => node.node_id === execution_metadata.iteration_id) if (!iterationNode || !Array.isArray(iterationNode.details)) return const { parallel_mode_run_id, iteration_index = 0 } = execution_metadata if (parallel_mode_run_id) updateParallelModeGroup(parallel_mode_run_id, item, iterationNode) else updateSequentialModeGroup(iteration_index, item, iterationNode) } allItems.forEach((item) => { item.node_type === BlockEnum.Iteration ? processIterationNode(item) : processNonIterationNode(item) }) return result }, []) const getTracingList = useCallback(async (appID: string, runID: string) => { try { const { data: nodeList } = await fetchTracingList({ url: `/apps/${appID}/workflow-runs/${runID}/node-executions`, }) setList(formatNodeList(nodeList)) } catch (err) { notify({ type: 'error', message: `${err}`, }) } }, [notify]) const getData = async (appID: string, runID: string) => { setLoading(true) await getResult(appID, runID) await getTracingList(appID, runID) setLoading(false) } const switchTab = async (tab: string) => { setCurrentTab(tab) if (tab === 'RESULT') appDetail?.id && await getResult(appDetail.id, runID) appDetail?.id && await getTracingList(appDetail.id, runID) } useEffect(() => { // fetch data if (appDetail && runID) getData(appDetail.id, runID) }, [appDetail, runID]) const [height, setHeight] = useState(0) const ref = useRef(null) const adjustResultHeight = () => { if (ref.current) setHeight(ref.current?.clientHeight - 16 - 16 - 2 - 1) } useEffect(() => { adjustResultHeight() }, [loading]) const [iterationRunResult, setIterationRunResult] = useState([]) const [isShowIterationDetail, { setTrue: doShowIterationDetail, setFalse: doHideIterationDetail, }] = useBoolean(false) const handleShowIterationDetail = useCallback((detail: NodeTracing[][]) => { setIterationRunResult(detail) doShowIterationDetail() }, [doShowIterationDetail]) if (isShowIterationDetail) { return (
) } return (
{/* tab */}
{!hideResult && (
switchTab('RESULT')} >{t('runLog.result')}
)}
switchTab('DETAIL')} >{t('runLog.detail')}
switchTab('TRACING')} >{t('runLog.tracing')}
{/* panel detail */}
{loading && (
)} {!loading && currentTab === 'RESULT' && runDetail && ( )} {!loading && currentTab === 'DETAIL' && runDetail && ( )} {!loading && currentTab === 'TRACING' && ( )}
) } export default RunPanel