From aa7c8856fb497e5c41b78e2362e273328788aca6 Mon Sep 17 00:00:00 2001 From: 2910410219 <2910410219@qq.com> Date: Tue, 1 Sep 2026 15:39:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81=E5=A4=B1=E8=B4=A5=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E3=80=8C=E6=9F=A5=E7=9C=8B=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E5=8E=9F=E5=9B=A0=E3=80=8D=E6=8C=89=E9=92=AE=EF=BC=88=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E6=94=B6=E8=B5=B7=E3=80=81=E7=82=B9=E5=87=BB=E5=B1=95?= =?UTF-8?q?=E5=BC=80=EF=BC=8C=E7=BB=91=E5=AE=9A=20error=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=EF=BC=89=EF=BC=9B=E5=AE=9E=E6=97=B6=E4=B8=8E=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E5=9B=9E=E6=98=BE=E5=9D=87=E5=B1=95=E7=A4=BA=E8=AF=A6?= =?UTF-8?q?=E7=BB=86=E5=A4=B1=E8=B4=A5=E5=8E=9F=E5=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- src/api/settings/workflow/session.ts | 2 + .../home/components/WorkflowFormCard.vue | 50 ++++++++++++++++--- src/views/home/index.vue | 14 +++--- src/views/home/utils/wsMessage.ts | 13 +++++ 4 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/api/settings/workflow/session.ts b/src/api/settings/workflow/session.ts index fc44074..01fb2e9 100644 --- a/src/api/settings/workflow/session.ts +++ b/src/api/settings/workflow/session.ts @@ -24,6 +24,8 @@ export interface VOSessionInfoResult { totalTokens: number; totalFee: number; errorMsg: string; + // 失败详细原因(工作流节点失败详情,如「执行工作流失败: ...节点:生成视频, 失败原因:...」),比 errorMsg 更具体 + error?: string; createdAt: string; } diff --git a/src/views/home/components/WorkflowFormCard.vue b/src/views/home/components/WorkflowFormCard.vue index ea224e3..fe80a65 100644 --- a/src/views/home/components/WorkflowFormCard.vue +++ b/src/views/home/components/WorkflowFormCard.vue @@ -171,19 +171,26 @@ @@ -268,6 +275,15 @@ const loadChargeModes = async () => { const isFailed = computed(() => !!props.formError); const isDisabled = computed(() => (props.readonly && !props.editable) || props.submitting); +// 失败原因默认收起:新失败时重置为不展示,用户点「查看失败原因」才展开 +const showError = ref(false); +watch( + () => props.formError, + () => { + showError.value = false; + } +); + // 表单收起/展开:editing(待提交/回显)默认展开;提交(running)自动收起; // done/failed 保持提交时状态(默认收起,用户可手动展开查看已填值) const collapsed = ref(false); @@ -798,19 +814,41 @@ watch( /* 底部操作区 */ .workflow-form-footer { display: flex; + flex-wrap: wrap; /* 失败原因详情换行到状态行下方 */ align-items: center; gap: 12px; padding: 12px 20px; border-top: 1px solid #f1f5f9; background: #fafbfc; flex-shrink: 0; - .form-error-text { + .form-status-row { + display: flex; + align-items: center; + gap: 8px; flex: 1; + min-width: 0; + } + .form-error-toggle { + padding: 0; + height: auto; + font-size: 12px; + color: #ef4444; + .error-arrow { + transition: transform 0.2s; + &.is-expanded { transform: rotate(180deg); } + } + } + .form-error-text { + flex-basis: 100%; /* 占整行,显示在状态行下方 */ font-size: 12px; color: #ef4444; line-height: 1.5; white-space: pre-wrap; /* 失败详情可能含换行(error 字段多行原因),允许换行展示 */ word-break: break-word; + background: #fef2f2; + border: 1px solid #fee2e2; + border-radius: 6px; + padding: 8px 12px; } .form-edit-tip { flex: 1; diff --git a/src/views/home/index.vue b/src/views/home/index.vue index 79b7743..e57bfe2 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -105,7 +105,7 @@ import { applyHomeFormValues, buildOutputsFromUrls } from './utils/flowDsl'; import type { WorkflowOutput } from './utils/flowDsl'; import { getChatModel, listModelManage } from '/@/api/settings/modelConfigV2'; import { connectSessionSocket, sendAgentStart, sendWorkflowStart, sendCancel } from './utils/wsExecute'; -import { parseWsMessage, getDelta, getAnswer, getRecordId, getErrorText, getNodeEvent, getToolCallName, getToolResultText, type WorkflowNodeProgress } from './utils/wsMessage'; +import { parseWsMessage, getDelta, getAnswer, getRecordId, getErrorText, getErrorDetail, getNodeEvent, getToolCallName, getToolResultText, type WorkflowNodeProgress } from './utils/wsMessage'; import { getApiErrorMessage } from '/@/utils/request'; import { getWorkflowDetail, @@ -1140,13 +1140,14 @@ const runWorkflow = async ( formMsg.recordType = 'workflow'; } } else if (msg.type === 'error') { - // 失败:执行中的节点标记为失败,执行过程区展示中断位置 + // 失败:执行中的节点标记为失败,执行过程区展示中断位置; + // 失败原因绑定 error 字段(节点失败详情),供卡片「查看失败原因」手动展开 if (formMsg.formProgress) { formMsg.formProgress.nodes.forEach((n) => { if (n.status === 'running') n.status = 'failed'; }); } - finishExec(false, getErrorText(msg)); + finishExec(false, getErrorDetail(msg)); } else if (msg.type === 'flow_complete') { // flow_complete 为后端新增的工作流完成信号,事件自带产出文件 URL 列表 → 直接渲染产出卡片; // 以 round_start 首帧填充的 roundRecordId 作 backendId,保证实时产出可删除(与回显路径一致) @@ -1657,8 +1658,8 @@ const loadWorkflowSessionMessages = async (sid: string, results: VOSessionInfoRe if (q) msgs.push({ id: 'rq-' + r.id, content: String(q), time: r.createdAt || '', isUser: true, recordId: String(r.id), recordType: r.type }); if (r.resultContent) msgs.push({ id: 'ra-' + r.id, content: String(r.resultContent), time: r.createdAt || '', isUser: false, recordId: String(r.id), recordType: r.type }); } else if (r.type === 'workflow') { - // 失败判断:以 errorMsg 为准(部分失败记录 status 仍是 1,仅凭 status 会误判为成功) - const failed = !!r.errorMsg; + // 失败判断:error / errorMsg 任一有值即失败(部分失败记录 status 仍是 1,仅凭 status 会误判为成功) + const failed = !!(r.errorMsg || r.error); // 每次执行都渲染带值表单卡片(requestParams.nodes 即该次提交的表单值快照); // form 补 id=flowId,供失败卡片「重新编辑并执行」时作为启动 flowId if (Array.isArray(r.requestParams?.nodes)) { @@ -1675,7 +1676,8 @@ const loadWorkflowSessionMessages = async (sid: string, results: VOSessionInfoRe flowContent: r.requestParams, }, formStatus: failed ? 'failed' : 'done', - formError: failed ? r.errorMsg || '执行失败' : '', + // 失败原因绑定 error 字段(节点失败详情);errorMsg(如「用户已终止执行」)仅作兜底 + formError: failed ? r.error || r.errorMsg || '执行失败' : '', }); } // 结果消息:成功记录直接带产出(session/get 记录自带的 resultFileUrl),不再调 execution/get diff --git a/src/views/home/utils/wsMessage.ts b/src/views/home/utils/wsMessage.ts index 02a3f7d..9c8d807 100644 --- a/src/views/home/utils/wsMessage.ts +++ b/src/views/home/utils/wsMessage.ts @@ -169,3 +169,16 @@ export function getErrorText(msg: WsStreamMessage): string { } return '执行失败,请重试'; } + +/** error 详细原因(error 字段优先展示失败详情;message 仅在 error 缺失时兜底)——供工作流失败卡片「查看失败原因」使用 */ +export function getErrorDetail(msg: WsStreamMessage): string { + if (typeof msg?.error === 'string' && msg.error) return msg.error; + if (typeof msg?.message === 'string' && msg.message) return msg.message; + const d = msg?.data; + if (typeof d === 'string') return d; + if (d && typeof d === 'object') { + const nested = d.message ?? d.error ?? d.msg; + if (typeof nested === 'string' && nested) return nested; + } + return '执行失败,请重试'; +}