diff --git a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx index b412b2bbcc..e4fae3fbc1 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx @@ -191,7 +191,7 @@ const SettingBuiltInTool: FC = ({
{currTool?.label[language]}
diff --git a/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx b/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx new file mode 100644 index 0000000000..941d5676d1 --- /dev/null +++ b/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx @@ -0,0 +1,29 @@ +import { RiArrowRightLine } from '@remixicon/react' + +type AgentLogTriggerProps = { + onDetail?: () => void +} +const AgentLogTrigger = ({ + onDetail, +}: AgentLogTriggerProps) => { + return ( +
+
+ Agent strategy +
+
+
+
+
+ Detail + +
+
+
+ ) +} + +export default AgentLogTrigger diff --git a/web/app/components/workflow/run/agent-log/agent-result-panel.tsx b/web/app/components/workflow/run/agent-log/agent-result-panel.tsx new file mode 100644 index 0000000000..53eef73b3c --- /dev/null +++ b/web/app/components/workflow/run/agent-log/agent-result-panel.tsx @@ -0,0 +1,43 @@ +import Button from '@/app/components/base/button' +import { RiArrowLeftLine } from '@remixicon/react' +import TracingPanel from '../tracing-panel' + +type AgentResultPanelProps = { + onBack: () => void +} +const AgentResultPanel = ({ + onBack, +}: AgentResultPanelProps) => { + return ( +
+
+ +
/
+
+ Agent strategy +
+ +
+ +
+ ) +} + +export default AgentResultPanel diff --git a/web/app/components/workflow/run/agent-log/tool-call-result-panel.tsx b/web/app/components/workflow/run/agent-log/tool-call-result-panel.tsx new file mode 100644 index 0000000000..f10e314770 --- /dev/null +++ b/web/app/components/workflow/run/agent-log/tool-call-result-panel.tsx @@ -0,0 +1,45 @@ +import Button from '@/app/components/base/button' +import { RiArrowLeftLine } from '@remixicon/react' +import TracingPanel from '../tracing-panel' + +type ToolCallResultPanelProps = { + onBack: () => void + onClose: () => void +} +const ToolCallResultPanel = ({ + onBack, + onClose, +}: ToolCallResultPanelProps) => { + return ( +
+
+ +
/
+
+ 10 Logs +
+ +
+ +
+ ) +} + +export default ToolCallResultPanel diff --git a/web/app/components/workflow/run/hooks.ts b/web/app/components/workflow/run/hooks.ts new file mode 100644 index 0000000000..07534b911d --- /dev/null +++ b/web/app/components/workflow/run/hooks.ts @@ -0,0 +1,48 @@ +import { + useCallback, + useState, +} from 'react' +import { useBoolean } from 'ahooks' +import type { IterationDurationMap, NodeTracing } from '@/types/workflow' + +export const useLogs = () => { + const [showRetryDetail, { + setTrue: setShowRetryDetailTrue, + setFalse: setShowRetryDetailFalse, + }] = useBoolean(false) + const [retryResultList, setRetryResultList] = useState([]) + const handleShowRetryResultList = useCallback((detail: NodeTracing[]) => { + setShowRetryDetailTrue() + setRetryResultList(detail) + }, [setShowRetryDetailTrue, setRetryResultList]) + + const [showIteratingDetail, { + setTrue: setShowIteratingDetailTrue, + setFalse: setShowIteratingDetailFalse, + }] = useBoolean(false) + const [iterationResultList, setIterationResultList] = useState([]) + const [iterationResultDurationMap, setIterationResultDurationMap] = useState({}) + const handleShowIterationResultList = useCallback((detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => { + setShowIteratingDetailTrue() + setIterationResultList(detail) + setIterationResultDurationMap(iterDurationMap) + }, [setShowIteratingDetailTrue, setIterationResultList, setIterationResultDurationMap]) + + return { + showSpecialResultPanel: !showRetryDetail && !showIteratingDetail, + showRetryDetail, + setShowRetryDetailTrue, + setShowRetryDetailFalse, + retryResultList, + setRetryResultList, + handleShowRetryResultList, + showIteratingDetail, + setShowIteratingDetailTrue, + setShowIteratingDetailFalse, + iterationResultList, + setIterationResultList, + iterationResultDurationMap, + setIterationResultDurationMap, + handleShowIterationResultList, + } +} diff --git a/web/app/components/workflow/run/index.tsx b/web/app/components/workflow/run/index.tsx index 5bfcf56dad..f81c2defe7 100644 --- a/web/app/components/workflow/run/index.tsx +++ b/web/app/components/workflow/run/index.tsx @@ -3,17 +3,16 @@ 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 OutputPanel from './output-panel' import ResultPanel from './result-panel' import TracingPanel from './tracing-panel' -import IterationResultPanel from './iteration-result-panel' -import RetryResultPanel from './retry-result-panel' +import SpecialResultPanel from './special-result-panel' +import { useLogs } from './hooks' 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 { IterationDurationMap, NodeTracing } from '@/types/workflow' +import type { NodeTracing } from '@/types/workflow' import type { WorkflowRunDetailResponse } from '@/models/log' import { useStore as useAppStore } from '@/app/components/app/store' import formatNodeList from './utils/format-log' @@ -106,41 +105,18 @@ const RunPanel: FC = ({ hideResult, activeTab = 'RESULT', runID, getRe adjustResultHeight() }, [loading]) - const [iterationRunResult, setIterationRunResult] = useState([]) - const [iterDurationMap, setIterDurationMap] = useState({}) - const [retryRunResult, setRetryRunResult] = useState([]) - const [isShowIterationDetail, { - setTrue: doShowIterationDetail, - setFalse: doHideIterationDetail, - }] = useBoolean(false) - const [isShowRetryDetail, { - setTrue: doShowRetryDetail, - setFalse: doHideRetryDetail, - }] = useBoolean(false) - - const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => { - setIterationRunResult(detail) - doShowIterationDetail() - setIterDurationMap(iterDurationMap) - }, [doShowIterationDetail, setIterationRunResult, setIterDurationMap]) - - const handleShowRetryDetail = useCallback((detail: NodeTracing[]) => { - setRetryRunResult(detail) - doShowRetryDetail() - }, [doShowRetryDetail, setRetryRunResult]) - - if (isShowIterationDetail) { - return ( -
- -
- ) - } + const { + showRetryDetail, + setShowRetryDetailFalse, + retryResultList, + handleShowRetryResultList, + showIteratingDetail, + setShowIteratingDetailFalse, + iterationResultList, + iterationResultDurationMap, + handleShowIterationResultList, + showSpecialResultPanel, + } = useLogs() return (
@@ -198,19 +174,25 @@ const RunPanel: FC = ({ hideResult, activeTab = 'RESULT', runID, getRe exceptionCounts={runDetail.exceptions_count} /> )} - {!loading && currentTab === 'TRACING' && !isShowRetryDetail && ( + {!loading && currentTab === 'TRACING' && !showSpecialResultPanel && ( )} { - !loading && currentTab === 'TRACING' && isShowRetryDetail && ( - ) } diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index 2081c61e60..25e3802107 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -187,10 +187,10 @@ const NodePanel: FC = ({ onClick={handleOnShowRetryDetail} >
- + {t('workflow.nodes.common.retry.retries', { num: nodeInfo.retryDetail?.length })}
- + )}
diff --git a/web/app/components/workflow/run/special-result-panel.tsx b/web/app/components/workflow/run/special-result-panel.tsx new file mode 100644 index 0000000000..c8918e897e --- /dev/null +++ b/web/app/components/workflow/run/special-result-panel.tsx @@ -0,0 +1,49 @@ +import RetryResultPanel from './retry-result-panel' +import IterationResultPanel from './iteration-result-panel' +import type { IterationDurationMap, NodeTracing } from '@/types/workflow' + +type SpecialResultPanelProps = { + showRetryDetail: boolean + setShowRetryDetailFalse: () => void + retryResultList: NodeTracing[] + + showIteratingDetail: boolean + setShowIteratingDetailFalse: () => void + iterationResultList: NodeTracing[][] + iterationResultDurationMap: IterationDurationMap +} +const SpecialResultPanel = ({ + showRetryDetail, + setShowRetryDetailFalse, + retryResultList, + + showIteratingDetail, + setShowIteratingDetailFalse, + iterationResultList, + iterationResultDurationMap, +}: SpecialResultPanelProps) => { + return ( + <> + { + showRetryDetail && ( + + ) + } + { + showIteratingDetail && ( + + ) + } + + ) +} + +export default SpecialResultPanel diff --git a/web/app/components/workflow/run/utils/format-log/iteration/data.ts b/web/app/components/workflow/run/utils/format-log/iteration/data.ts index 54ad8c4dd1..0d08cd5350 100644 --- a/web/app/components/workflow/run/utils/format-log/iteration/data.ts +++ b/web/app/components/workflow/run/utils/format-log/iteration/data.ts @@ -178,7 +178,7 @@ export const simpleIterationData = (() => { return { in: [startNode, outputArrayNode, iterationNode, ...iterations, endNode], - output: [startNode, outputArrayNode, { + expect: [startNode, outputArrayNode, { ...iterationNode, details: [ [iterations[0]], diff --git a/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts b/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts index 245263ef4a..77b776f12c 100644 --- a/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts +++ b/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts @@ -1,11 +1,11 @@ import format from '.' import { simpleIterationData } from './data' -describe('format api data to tracing panel data', () => { +describe('iteration', () => { test('result should have no nodes in iteration node', () => { expect(format(simpleIterationData.in as any).find(item => !!(item as any).execution_metadata?.iteration_id)).toBeUndefined() }) test('iteration should put nodes in details', () => { - expect(format(simpleIterationData.in as any)).toEqual(simpleIterationData.output) + expect(format(simpleIterationData.in as any)).toEqual(simpleIterationData.expect) }) }) diff --git a/web/app/components/workflow/run/utils/format-log/retry/data.ts b/web/app/components/workflow/run/utils/format-log/retry/data.ts new file mode 100644 index 0000000000..e22c8b8982 --- /dev/null +++ b/web/app/components/workflow/run/utils/format-log/retry/data.ts @@ -0,0 +1,133 @@ +export const simpleRetryData = (() => { + const startNode = { + id: 'f7938b2b-77cd-43f0-814c-2f0ade7cbc60', + index: 1, + predecessor_node_id: null, + node_id: '1735112903395', + node_type: 'start', + title: 'Start', + inputs: { + 'sys.files': [], + 'sys.user_id': '6d8ad01f-edf9-43a6-b863-a034b1828ac7', + 'sys.app_id': '6180ead7-2190-4a61-975c-ec3bf29653da', + 'sys.workflow_id': 'eef6da45-244b-4c79-958e-f3573f7c12bb', + 'sys.workflow_run_id': 'fc8970ef-1406-484e-afde-8567dc22f34c', + }, + process_data: null, + outputs: { + 'sys.files': [], + 'sys.user_id': '6d8ad01f-edf9-43a6-b863-a034b1828ac7', + 'sys.app_id': '6180ead7-2190-4a61-975c-ec3bf29653da', + 'sys.workflow_id': 'eef6da45-244b-4c79-958e-f3573f7c12bb', + 'sys.workflow_run_id': 'fc8970ef-1406-484e-afde-8567dc22f34c', + }, + status: 'succeeded', + error: null, + elapsed_time: 0.008715, + execution_metadata: null, + extras: {}, + created_at: 1735112940, + created_by_role: 'account', + created_by_account: { + id: '6d8ad01f-edf9-43a6-b863-a034b1828ac7', + name: '九彩拼盘', + email: 'iamjoel007@gmail.com', + }, + created_by_end_user: null, + finished_at: 1735112940, + } + + const httpNode = { + id: '50220407-3420-4ad4-89da-c6959710d1aa', + index: 2, + predecessor_node_id: '1735112903395', + node_id: '1735112908006', + node_type: 'http-request', + title: 'HTTP Request', + inputs: null, + process_data: { + request: 'GET / HTTP/1.1\r\nHost: 404\r\n\r\n', + }, + outputs: null, + status: 'failed', + error: 'timed out', + elapsed_time: 30.247757, + execution_metadata: null, + extras: {}, + created_at: 1735112940, + created_by_role: 'account', + created_by_account: { + id: '6d8ad01f-edf9-43a6-b863-a034b1828ac7', + name: '九彩拼盘', + email: 'iamjoel007@gmail.com', + }, + created_by_end_user: null, + finished_at: 1735112970, + } + + const retry1 = { + id: 'ed352b36-27fb-49c6-9e8f-cc755bfc25fc', + index: 3, + predecessor_node_id: '1735112903395', + node_id: '1735112908006', + node_type: 'http-request', + title: 'HTTP Request', + inputs: null, + process_data: null, + outputs: null, + status: 'retry', + error: 'timed out', + elapsed_time: 10.011833, + execution_metadata: { + iteration_id: null, + parallel_mode_run_id: null, + }, + extras: {}, + created_at: 1735112940, + created_by_role: 'account', + created_by_account: { + id: '6d8ad01f-edf9-43a6-b863-a034b1828ac7', + name: '九彩拼盘', + email: 'iamjoel007@gmail.com', + }, + created_by_end_user: null, + finished_at: 1735112950, + } + + const retry2 = { + id: '74dfb3d3-dacf-44f2-8784-e36bfa2d6c4e', + index: 4, + predecessor_node_id: '1735112903395', + node_id: '1735112908006', + node_type: 'http-request', + title: 'HTTP Request', + inputs: null, + process_data: null, + outputs: null, + status: 'retry', + error: 'timed out', + elapsed_time: 10.010368, + execution_metadata: { + iteration_id: null, + parallel_mode_run_id: null, + }, + extras: {}, + created_at: 1735112950, + created_by_role: 'account', + created_by_account: { + id: '6d8ad01f-edf9-43a6-b863-a034b1828ac7', + name: '九彩拼盘', + email: 'iamjoel007@gmail.com', + }, + created_by_end_user: null, + finished_at: 1735112960, + } + + return { + in: [startNode, httpNode, retry1, retry2], + expect: [startNode, { + ...httpNode, + retryDetail: [retry1, retry2], + }], + } +})() diff --git a/web/app/components/workflow/run/utils/format-log/retry/index.spec.ts b/web/app/components/workflow/run/utils/format-log/retry/index.spec.ts new file mode 100644 index 0000000000..5ae6c385fd --- /dev/null +++ b/web/app/components/workflow/run/utils/format-log/retry/index.spec.ts @@ -0,0 +1,11 @@ +import format from '.' +import { simpleRetryData } from './data' + +describe('retry', () => { + test('should have no retry status nodes', () => { + expect(format(simpleRetryData.in as any).find(item => (item as any).status === 'retry')).toBeUndefined() + }) + test('should put retry nodes in retryDetail', () => { + expect(format(simpleRetryData.in as any)).toEqual(simpleRetryData.expect) + }) +}) diff --git a/web/app/components/workflow/run/utils/format-log/retry/index.ts b/web/app/components/workflow/run/utils/format-log/retry/index.ts index 37e68099d6..e2a61dd6b2 100644 --- a/web/app/components/workflow/run/utils/format-log/retry/index.ts +++ b/web/app/components/workflow/run/utils/format-log/retry/index.ts @@ -1,7 +1,29 @@ +import { BlockEnum } from '@/app/components/workflow/types' import type { NodeTracing } from '@/types/workflow' const format = (list: NodeTracing[]): NodeTracing[] => { - return list + const retryNodes = list.filter((item) => { + const { execution_metadata } = item + const isInIteration = !!execution_metadata?.iteration_id + if (isInIteration || item.node_type === BlockEnum.Iteration) return false + return item.status === 'retry' + }) + + const retryNodeIds = retryNodes.map(item => item.node_id) + // move retry nodes to retryDetail + const result = list.filter((item) => { + return item.status !== 'retry' + }).map((item) => { + const isRetryBelongNode = retryNodeIds.includes(item.node_id) + if (isRetryBelongNode) { + return { + ...item, + retryDetail: list.filter(node => node.status === 'retry' && node.node_id === item.node_id), + } + } + return item + }) + return result } export default format