From 675d0fc5ef4f488ddaf9b58bbd5afa2f4185b785 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Tue, 7 Apr 2026 14:08:26 +0800 Subject: [PATCH] feat(web): workflow run time ui --- web/src/components/Markdown/CodeBlock.tsx | 10 +- .../Workflow/components/Chat/Runtime.tsx | 160 +++++++++++------- .../Workflow/components/Chat/chat.module.css | 7 +- 3 files changed, 112 insertions(+), 65 deletions(-) diff --git a/web/src/components/Markdown/CodeBlock.tsx b/web/src/components/Markdown/CodeBlock.tsx index a830827d..f863cc48 100644 --- a/web/src/components/Markdown/CodeBlock.tsx +++ b/web/src/components/Markdown/CodeBlock.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-02 15:15:11 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-02 15:15:11 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-04-07 14:04:33 */ /** * CodeBlock Component @@ -27,6 +27,7 @@ type ICodeBlockProps = { needCopy?: boolean; size?: 'small' | 'default'; showLineNumbers?: boolean; + background?: string; } /** Code block component for displaying formatted code with optional copy functionality */ @@ -34,7 +35,8 @@ const CodeBlock: FC = ({ value, needCopy = true, size = 'default', - showLineNumbers = false + showLineNumbers = false, + background = '#F0F3F8' }) => { return ( @@ -43,7 +45,7 @@ const CodeBlock: FC = ({ style={atelierHeathLight} customStyle={{ padding: '8px 12px 8px 12px', - backgroundColor: '#F0F3F8', + backgroundColor: background, borderRadius: 8, fontSize: size === 'small' ? 12 : 14, wordBreak: 'break-all' diff --git a/web/src/views/Workflow/components/Chat/Runtime.tsx b/web/src/views/Workflow/components/Chat/Runtime.tsx index 7da550e0..68bdc452 100644 --- a/web/src/views/Workflow/components/Chat/Runtime.tsx +++ b/web/src/views/Workflow/components/Chat/Runtime.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-24 17:57:08 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-12 13:39:24 + * @Last Modified time: 2026-04-07 14:05:50 */ /* * Runtime Component @@ -18,13 +18,15 @@ import { type FC, useState } from 'react' import { useTranslation } from 'react-i18next' import clsx from 'clsx' -import { Space, Button, Collapse, Flex } from 'antd' +import { App, Button, Collapse, Flex } from 'antd' import { CheckCircleFilled, CloseCircleFilled, LoadingOutlined, RightOutlined, ArrowLeftOutlined } from '@ant-design/icons' +import copy from 'copy-to-clipboard' import styles from './chat.module.css' import type { ChatItem } from '@/components/Chat/types' import Markdown from '@/components/Markdown' import CodeBlock from '@/components/Markdown/CodeBlock' +import RbAlert from '@/components/RbAlert' /** * Runtime component props @@ -36,10 +38,12 @@ const Runtime: FC<{ item: ChatItem; index: number;}> = ({ index }) => { const { t } = useTranslation() + const { message } = App.useApp() // Stores the currently selected detail view (for nested loop/iteration exploration) const [detail, setDetail] = useState(null) // Tracks whether the current detail view is for a loop (true) or iteration (false) const [loop, setLoop] = useState(null) + const [expanded, setExpanded] = useState(false) /** * Handles navigation into nested loop/iteration details @@ -57,7 +61,7 @@ const Runtime: FC<{ item: ChatItem; index: number;}> = ({ * @returns Tailwind CSS class for appropriate color */ const getStatus = (status?: string) => { - return status === 'completed' ? 'rb:text-[#369F21]' : status === 'failed' ? 'rb:text-[#FF5D34]' : 'rb:text-[#5B6167]' + return status === 'completed' ? 'rb:text-[#369F21]!' : status === 'failed' ? 'rb:text-[#FF5D34]!' : 'rb:text-[#5B6167]!' } /** @@ -76,7 +80,7 @@ const Runtime: FC<{ item: ChatItem; index: number;}> = ({ return ( - + {Object.entries(groupedByCycle).map(([cycleIdx, items]: [string, any]) => { return ( = ({ /> ) })} - + ) } @@ -103,7 +107,7 @@ const Runtime: FC<{ item: ChatItem; index: number;}> = ({ */ const renderChild = (list: any) => { if (Array.isArray(list)) { - return + return {list?.map(vo => { const isLoop = vo.node_type === 'loop'; // Render cycle variables for loop nodes without node_name @@ -114,6 +118,7 @@ const Runtime: FC<{ item: ChatItem; index: number;}> = ({
@@ -133,35 +138,44 @@ const Runtime: FC<{ item: ChatItem; index: number;}> = ({ return ( -
- {vo.icon &&
} -
{vo.node_name}
-
- + label:
+ + {vo.icon &&
} +
{vo.node_name}
+ + {typeof vo.elapsed_time == 'number' && <>{vo.elapsed_time?.toFixed(3)}ms} - {vo.status === 'completed' ? : vo.status === 'failed' ? : } - + {vo.status === 'completed' + ? + : vo.status === 'failed' + ? + : + } +
, className: styles.collapseItem, children: ( - + {/* Display error message for failed nodes */} - {vo.status === 'failed' && -
-
- {t(`workflow.error`)} - -
-
+ + {item.error && + + + + {t(`workflow.error`)} + + -
-
+
+ } {/* Display navigation to nested cycles if subContent exists */} {vo.subContent?.length > 0 && ( @@ -172,12 +186,13 @@ const Runtime: FC<{ item: ChatItem; index: number;}> = ({ )} {/* Display input and output data as JSON code blocks */} {['input', 'output'].map(key => ( -
+
{isLoop ? t(`workflow.runtime.${key}_cycle_vars`) : t(`workflow.${key}`)}
@@ -186,55 +201,80 @@ const Runtime: FC<{ item: ChatItem; index: number;}> = ({ value={typeof vo.content === 'object' && vo.content?.[key] ? JSON.stringify(vo.content[key], null, 2) : '{}'} needCopy={false} showLineNumbers={true} + background="#EBEBEB" />
))} - + ) }]} /> ) })} - + } return
} + /** Copy value to clipboard and show success message */ + const handleCopy = (value: string) => { + copy(value) + message.success(t('common.copySuccess')) + } + return ( -
- - {item.status === 'completed' ? : item.status === 'failed' ? : } - {t('application.workflow')} -
, - className: styles.collapseItem, - children: ( - detail - ? ( -
- - {renderDetailChild(detail.subContent)} -
- ) - : <> - {item.error && -
- -
- } - {renderChild(item.subContent)} - +
+ + + {item.status === 'completed' + ? + : item.status === 'failed' + ? + : + } + {t('application.workflow')} + + { setExpanded(v => !v); setDetail(null) }} + > +
+ + + {expanded && ( + detail + ? ( +
+ + {renderDetailChild(detail.subContent)} +
) - }]} - /> + :
+ {item.error && + + } + {renderChild(item.subContent)} +
+ )}
) } diff --git a/web/src/views/Workflow/components/Chat/chat.module.css b/web/src/views/Workflow/components/Chat/chat.module.css index 46360d47..1754aa71 100644 --- a/web/src/views/Workflow/components/Chat/chat.module.css +++ b/web/src/views/Workflow/components/Chat/chat.module.css @@ -21,7 +21,9 @@ line-height: 16px; } .collapse-item:global(.ant-collapse-item>.ant-collapse-header) { - padding: 8px 12px; + padding: 8px 16px 8px 10px; + display: flex; + align-items: center; } .collapse-item:global(.ant-collapse-item>.ant-collapse-header .ant-collapse-expand-icon) { height: 16px; @@ -45,4 +47,7 @@ } .collapse-item :global(.ant-collapse .ant-collapse-content>.ant-collapse-content-box) { padding: 0 4px 4px 4px; +} +:global(.ant-collapse-borderless)>.collapse-item:global(.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box) { + padding: 0 12px 12px 12px; } \ No newline at end of file