| | |
| | | memo, |
| | | useCallback, |
| | | useEffect, |
| | | // useRef, |
| | | useState, |
| | | } from 'react' |
| | | import { |
| | |
| | | } from '@remixicon/react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import copy from 'copy-to-clipboard' |
| | | import { useBoolean } from 'ahooks' |
| | | import ResultText from '../run/result-text' |
| | | import ResultPanel from '../run/result-panel' |
| | | import TracingPanel from '../run/tracing-panel' |
| | |
| | | import { |
| | | WorkflowRunningStatus, |
| | | } from '../types' |
| | | import { SimpleBtn } from '../../app/text-generate/item' |
| | | import Toast from '../../base/toast' |
| | | import IterationResultPanel from '../run/iteration-result-panel' |
| | | import RetryResultPanel from '../run/retry-result-panel' |
| | | import InputsPanel from './inputs-panel' |
| | | import cn from '@/utils/classnames' |
| | | import Loading from '@/app/components/base/loading' |
| | | import Button from '@/app/components/base/button' |
| | | import type { IterationDurationMap, NodeTracing } from '@/types/workflow' |
| | | |
| | | const WorkflowPreview = () => { |
| | | const { t } = useTranslation() |
| | |
| | | switchTab('DETAIL') |
| | | }, [workflowRunningData]) |
| | | |
| | | const [panelWidth, setPanelWidth] = useState(420) |
| | | const [isResizing, setIsResizing] = useState(false) |
| | | const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([]) |
| | | const [retryRunResult, setRetryRunResult] = useState<NodeTracing[]>([]) |
| | | const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({}) |
| | | const [isShowIterationDetail, { |
| | | setTrue: doShowIterationDetail, |
| | | setFalse: doHideIterationDetail, |
| | | }] = useBoolean(false) |
| | | const [isShowRetryDetail, { |
| | | setTrue: doShowRetryDetail, |
| | | setFalse: doHideRetryDetail, |
| | | }] = useBoolean(false) |
| | | |
| | | const startResizing = useCallback((e: React.MouseEvent) => { |
| | | e.preventDefault() |
| | | setIsResizing(true) |
| | | }, []) |
| | | const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterationDurationMap: IterationDurationMap) => { |
| | | setIterDurationMap(iterationDurationMap) |
| | | setIterationRunResult(detail) |
| | | doShowIterationDetail() |
| | | }, [doShowIterationDetail]) |
| | | |
| | | const stopResizing = useCallback(() => { |
| | | setIsResizing(false) |
| | | }, []) |
| | | const handleRetryDetail = useCallback((detail: NodeTracing[]) => { |
| | | setRetryRunResult(detail) |
| | | doShowRetryDetail() |
| | | }, [doShowRetryDetail]) |
| | | |
| | | const resize = useCallback((e: MouseEvent) => { |
| | | if (isResizing) { |
| | | const newWidth = window.innerWidth - e.clientX |
| | | if (newWidth > 420 && newWidth < 1024) |
| | | setPanelWidth(newWidth) |
| | | } |
| | | }, [isResizing]) |
| | | |
| | | useEffect(() => { |
| | | window.addEventListener('mousemove', resize) |
| | | window.addEventListener('mouseup', stopResizing) |
| | | return () => { |
| | | window.removeEventListener('mousemove', resize) |
| | | window.removeEventListener('mouseup', stopResizing) |
| | | } |
| | | }, [resize, stopResizing]) |
| | | if (isShowIterationDetail) { |
| | | return ( |
| | | <div className={` |
| | | flex flex-col w-[420px] h-full rounded-l-2xl border-[0.5px] border-gray-200 shadow-xl bg-white |
| | | `}> |
| | | <IterationResultPanel |
| | | list={iterationRunResult} |
| | | onHide={doHideIterationDetail} |
| | | onBack={doHideIterationDetail} |
| | | iterDurationMap={iterDurationMap} |
| | | /> |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | return ( |
| | | <div className={` |
| | | relative flex h-full flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl |
| | | `} |
| | | style={{ width: `${panelWidth}px` }} |
| | | > |
| | | <div |
| | | className="absolute bottom-0 left-[3px] top-1/2 z-50 h-6 w-[3px] cursor-col-resize rounded bg-gray-300" |
| | | onMouseDown={startResizing} |
| | | /> |
| | | <div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'> |
| | | flex flex-col w-[420px] h-full rounded-l-2xl border-[0.5px] border-gray-200 shadow-xl bg-white |
| | | `}> |
| | | <div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-gray-900'> |
| | | {`Test Run${!workflowRunningData?.result.sequence_number ? '' : `#${workflowRunningData?.result.sequence_number}`}`} |
| | | <div className='cursor-pointer p-1' onClick={() => handleCancelDebugAndPreviewPanel()}> |
| | | <RiCloseLine className='h-4 w-4 text-text-tertiary' /> |
| | | <div className='p-1 cursor-pointer' onClick={() => handleCancelDebugAndPreviewPanel()}> |
| | | <RiCloseLine className='w-4 h-4 text-gray-500' /> |
| | | </div> |
| | | </div> |
| | | <div className='relative flex grow flex-col'> |
| | | <div className='flex shrink-0 items-center border-b-[0.5px] border-divider-subtle px-4'> |
| | | {showInputsPanel && ( |
| | | <div |
| | | className={cn( |
| | | 'mr-6 cursor-pointer border-b-2 border-transparent py-3 text-[13px] font-semibold leading-[18px] text-text-tertiary', |
| | | currentTab === 'INPUT' && '!border-[rgb(21,94,239)] text-text-secondary', |
| | | )} |
| | | onClick={() => switchTab('INPUT')} |
| | | >{t('runLog.input')}</div> |
| | | )} |
| | | <div |
| | | className={cn( |
| | | 'mr-6 cursor-pointer border-b-2 border-transparent py-3 text-[13px] font-semibold leading-[18px] text-text-tertiary', |
| | | currentTab === 'RESULT' && '!border-[rgb(21,94,239)] text-text-secondary', |
| | | !workflowRunningData && '!cursor-not-allowed opacity-30', |
| | | )} |
| | | onClick={() => { |
| | | if (!workflowRunningData) |
| | | return |
| | | switchTab('RESULT') |
| | | }} |
| | | >{t('runLog.result')}</div> |
| | | <div |
| | | className={cn( |
| | | 'mr-6 cursor-pointer border-b-2 border-transparent py-3 text-[13px] font-semibold leading-[18px] text-text-tertiary', |
| | | currentTab === 'DETAIL' && '!border-[rgb(21,94,239)] text-text-secondary', |
| | | !workflowRunningData && '!cursor-not-allowed opacity-30', |
| | | )} |
| | | onClick={() => { |
| | | if (!workflowRunningData) |
| | | return |
| | | switchTab('DETAIL') |
| | | }} |
| | | >{t('runLog.detail')}</div> |
| | | <div |
| | | className={cn( |
| | | 'mr-6 cursor-pointer border-b-2 border-transparent py-3 text-[13px] font-semibold leading-[18px] text-text-tertiary', |
| | | currentTab === 'TRACING' && '!border-[rgb(21,94,239)] text-text-secondary', |
| | | !workflowRunningData && '!cursor-not-allowed opacity-30', |
| | | )} |
| | | onClick={() => { |
| | | if (!workflowRunningData) |
| | | return |
| | | switchTab('TRACING') |
| | | }} |
| | | >{t('runLog.tracing')}</div> |
| | | </div> |
| | | <div className={cn( |
| | | 'h-0 grow overflow-y-auto rounded-b-2xl bg-components-panel-bg', |
| | | (currentTab === 'RESULT' || currentTab === 'TRACING') && '!bg-background-section-burn', |
| | | )}> |
| | | {currentTab === 'INPUT' && showInputsPanel && ( |
| | | <InputsPanel onRun={() => switchTab('RESULT')} /> |
| | | )} |
| | | {currentTab === 'RESULT' && ( |
| | | <div className='grow relative flex flex-col'> |
| | | {isShowIterationDetail |
| | | ? ( |
| | | <IterationResultPanel |
| | | list={iterationRunResult} |
| | | onHide={doHideIterationDetail} |
| | | onBack={doHideIterationDetail} |
| | | iterDurationMap={iterDurationMap} |
| | | /> |
| | | ) |
| | | : ( |
| | | <> |
| | | <ResultText |
| | | isRunning={workflowRunningData?.result?.status === WorkflowRunningStatus.Running || !workflowRunningData?.result} |
| | | outputs={workflowRunningData?.resultText} |
| | | allFiles={workflowRunningData?.result?.files} |
| | | error={workflowRunningData?.result?.error} |
| | | onClick={() => switchTab('DETAIL')} |
| | | /> |
| | | {(workflowRunningData?.result.status === WorkflowRunningStatus.Succeeded && workflowRunningData?.resultText && typeof workflowRunningData?.resultText === 'string') && ( |
| | | <Button |
| | | className={cn('mb-4 ml-4 space-x-1')} |
| | | <div className='shrink-0 flex items-center px-4 border-b-[0.5px] border-[rgba(0,0,0,0.05)]'> |
| | | {showInputsPanel && ( |
| | | <div |
| | | className={cn( |
| | | 'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer', |
| | | currentTab === 'INPUT' && '!border-[rgb(21,94,239)] text-gray-700', |
| | | )} |
| | | onClick={() => switchTab('INPUT')} |
| | | >{t('runLog.input')}</div> |
| | | )} |
| | | <div |
| | | className={cn( |
| | | 'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer', |
| | | currentTab === 'RESULT' && '!border-[rgb(21,94,239)] text-gray-700', |
| | | !workflowRunningData && 'opacity-30 !cursor-not-allowed', |
| | | )} |
| | | onClick={() => { |
| | | const content = workflowRunningData?.resultText |
| | | if (typeof content === 'string') |
| | | copy(content) |
| | | else |
| | | copy(JSON.stringify(content)) |
| | | Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) |
| | | }}> |
| | | <RiClipboardLine className='h-3.5 w-3.5' /> |
| | | <div>{t('common.operation.copy')}</div> |
| | | </Button> |
| | | )} |
| | | if (!workflowRunningData) |
| | | return |
| | | switchTab('RESULT') |
| | | }} |
| | | >{t('runLog.result')}</div> |
| | | <div |
| | | className={cn( |
| | | 'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer', |
| | | currentTab === 'DETAIL' && '!border-[rgb(21,94,239)] text-gray-700', |
| | | !workflowRunningData && 'opacity-30 !cursor-not-allowed', |
| | | )} |
| | | onClick={() => { |
| | | if (!workflowRunningData) |
| | | return |
| | | switchTab('DETAIL') |
| | | }} |
| | | >{t('runLog.detail')}</div> |
| | | <div |
| | | className={cn( |
| | | 'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer', |
| | | currentTab === 'TRACING' && '!border-[rgb(21,94,239)] text-gray-700', |
| | | !workflowRunningData && 'opacity-30 !cursor-not-allowed', |
| | | )} |
| | | onClick={() => { |
| | | if (!workflowRunningData) |
| | | return |
| | | switchTab('TRACING') |
| | | }} |
| | | >{t('runLog.tracing')}</div> |
| | | </div> |
| | | <div className={cn( |
| | | 'grow bg-components-panel-bg h-0 overflow-y-auto rounded-b-2xl', |
| | | (currentTab === 'RESULT' || currentTab === 'TRACING') && '!bg-background-section-burn', |
| | | )}> |
| | | {currentTab === 'INPUT' && showInputsPanel && ( |
| | | <InputsPanel onRun={() => switchTab('RESULT')} /> |
| | | )} |
| | | {currentTab === 'RESULT' && ( |
| | | <> |
| | | <ResultText |
| | | isRunning={workflowRunningData?.result?.status === WorkflowRunningStatus.Running || !workflowRunningData?.result} |
| | | outputs={workflowRunningData?.resultText} |
| | | allFiles={workflowRunningData?.result?.files as any} |
| | | error={workflowRunningData?.result?.error} |
| | | onClick={() => switchTab('DETAIL')} |
| | | /> |
| | | {(workflowRunningData?.result.status === WorkflowRunningStatus.Succeeded && workflowRunningData?.resultText && typeof workflowRunningData?.resultText === 'string') && ( |
| | | <SimpleBtn |
| | | className={cn('ml-4 mb-4 inline-flex space-x-1')} |
| | | onClick={() => { |
| | | const content = workflowRunningData?.resultText |
| | | if (typeof content === 'string') |
| | | copy(content) |
| | | else |
| | | copy(JSON.stringify(content)) |
| | | Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) |
| | | }}> |
| | | <RiClipboardLine className='w-3.5 h-3.5' /> |
| | | <div>{t('common.operation.copy')}</div> |
| | | </SimpleBtn> |
| | | )} |
| | | </> |
| | | )} |
| | | {currentTab === 'DETAIL' && ( |
| | | <ResultPanel |
| | | inputs={workflowRunningData?.result?.inputs} |
| | | outputs={workflowRunningData?.result?.outputs} |
| | | status={workflowRunningData?.result?.status || ''} |
| | | error={workflowRunningData?.result?.error} |
| | | elapsed_time={workflowRunningData?.result?.elapsed_time} |
| | | total_tokens={workflowRunningData?.result?.total_tokens} |
| | | created_at={workflowRunningData?.result?.created_at} |
| | | created_by={(workflowRunningData?.result?.created_by as any)?.name} |
| | | steps={workflowRunningData?.result?.total_steps} |
| | | exceptionCounts={workflowRunningData?.result?.exceptions_count} |
| | | /> |
| | | )} |
| | | {currentTab === 'DETAIL' && !workflowRunningData?.result && ( |
| | | <div className='flex h-full items-center justify-center bg-components-panel-bg'> |
| | | <Loading /> |
| | | </div> |
| | | )} |
| | | {currentTab === 'TRACING' && !isShowRetryDetail && ( |
| | | <TracingPanel |
| | | className='bg-background-section-burn' |
| | | list={workflowRunningData?.tracing || []} |
| | | onShowIterationDetail={handleShowIterationDetail} |
| | | onShowRetryDetail={handleRetryDetail} |
| | | /> |
| | | )} |
| | | {currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && ( |
| | | <div className='flex h-full items-center justify-center !bg-background-section-burn'> |
| | | <Loading /> |
| | | </div> |
| | | )} |
| | | { |
| | | currentTab === 'TRACING' && isShowRetryDetail && ( |
| | | <RetryResultPanel |
| | | list={retryRunResult} |
| | | onBack={doHideRetryDetail} |
| | | /> |
| | | ) |
| | | } |
| | | </div> |
| | | </> |
| | | )} |
| | | {currentTab === 'DETAIL' && ( |
| | | <ResultPanel |
| | | inputs={workflowRunningData?.result?.inputs} |
| | | outputs={workflowRunningData?.result?.outputs} |
| | | status={workflowRunningData?.result?.status || ''} |
| | | error={workflowRunningData?.result?.error} |
| | | elapsed_time={workflowRunningData?.result?.elapsed_time} |
| | | total_tokens={workflowRunningData?.result?.total_tokens} |
| | | created_at={workflowRunningData?.result?.created_at} |
| | | created_by={(workflowRunningData?.result?.created_by as any)?.name} |
| | | steps={workflowRunningData?.result?.total_steps} |
| | | exceptionCounts={workflowRunningData?.result?.exceptions_count} |
| | | /> |
| | | )} |
| | | {currentTab === 'DETAIL' && !workflowRunningData?.result && ( |
| | | <div className='flex h-full items-center justify-center bg-components-panel-bg'> |
| | | <Loading /> |
| | | </div> |
| | | )} |
| | | {currentTab === 'TRACING' && ( |
| | | <TracingPanel |
| | | className='bg-background-section-burn' |
| | | list={workflowRunningData?.tracing || []} |
| | | /> |
| | | )} |
| | | {currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && ( |
| | | <div className='flex h-full items-center justify-center !bg-background-section-burn'> |
| | | <Loading /> |
| | | </div> |
| | | )} |
| | | </div> |
| | | |
| | | </div> |
| | | </div> |
| | | ) |