diff --git a/pages/assistant/index.vue b/pages/assistant/index.vue index de6041b..fe21837 100644 --- a/pages/assistant/index.vue +++ b/pages/assistant/index.vue @@ -800,6 +800,37 @@ return String(value ?? '') } }, + /** + * 取载荷里的工作流执行状态。 + * 后端会把 status 放在顶层;兼容 data.status 的嵌套写法。 + */ + getPayloadStatus(payload) { + if (!payload || typeof payload !== 'object') { + return '' + } + const status = + payload.status !== undefined && payload.status !== null + ? payload.status + : payload.data && payload.data.status + return String(status || '').trim().toLowerCase() + }, + /** + * 工作流是否以失败收场。 + * 只在拿到明确 failed 时为真,状态缺失视为未失败,避免误伤正常响应。 + */ + isFailedPayload(payload) { + return this.getPayloadStatus(payload) === 'failed' + }, + /** + * 兜底文案:解析不出业务字段时的可读提示。 + * + *

绝不能退回 JSON.stringify 整包 —— 那会把 workflow_id / req_id + * 等内部标识符与上游错误栈直接贴进聊天气泡(见 issue 01)。 + * 只有调用方明确传入字符串时才原样使用。 + */ + buildFallbackText() { + return '接口已返回,但未解析到可展示的内容。' + }, pickString(...values) { for (let index = 0; index < values.length; index += 1) { const value = values[index] @@ -867,7 +898,7 @@ if (title || body) { return `${title || this.buildManuscriptPayload(query).theme}\n\n${body}` } - return this.stringifyUnknown(payload) + return this.buildFallbackText() }, buildOutlinePayload(query) { const fields = this.parseKeyValueInput(query) @@ -882,6 +913,11 @@ } }, formatOutlineResult(payload) { + // 与创意链路同理:失败态直接给可读文案,不落到整包兜底, + // 也不信任 payload.answer(失败时它可能是 UUID)。 + if (this.isFailedPayload(payload)) { + return '生成失败,请稍后重试' + } const data = payload || {} return ( this.pickString( @@ -892,7 +928,7 @@ data.raw && data.raw.result, data.raw && data.raw.content, data.raw && data.raw.message - ) || this.stringifyUnknown(payload) + ) || this.buildFallbackText() ) }, getCreativeOutputs(payload) { @@ -961,6 +997,13 @@ return `\n\n### ${title}\n${lines.join('\n\n')}` }, formatCreativeResult(payload) { + // 失败态优先:此时 outputs 为空、拿不到任何业务字段, + // 必须直接给可读文案,不能落到下面的整包兜底(issue 01)。 + // 注意不能信任 payload.answer:失败报文中它可能是 workflow_run_id + // 这类 UUID(后端 extractAnswer 的伴生现象),必须换成固定文案。 + if (this.isFailedPayload(payload)) { + return '生成失败,请稍后重试' + } const outputs = this.getCreativeOutputs(payload) const script = this.pickString(outputs.Script_result, outputs.script, outputs.text) const videoMessage = this.pickString(outputs.video_message) @@ -985,7 +1028,7 @@ ] .filter(Boolean) .join('\n\n') - return text || this.stringifyUnknown(payload) + return text || this.pickString(payload && payload.answer) || this.buildFallbackText() }, async sendModeMessage(query, assistantId) { const target = this.messageList.find((item) => item.id === assistantId) diff --git a/pages/creative-assistant/index.vue b/pages/creative-assistant/index.vue index 4efe242..883ad43 100644 --- a/pages/creative-assistant/index.vue +++ b/pages/creative-assistant/index.vue @@ -63,8 +63,8 @@ 工作流结果 - - {{ loading ? '执行中' : hasAnyResult ? '已完成' : '待执行' }} + + {{ loading ? '执行中' : workflowFailed ? '执行失败' : hasAnyResult ? '已完成' : '待执行' }} @@ -88,6 +88,10 @@ + + {{ (workflowResponse && workflowResponse.answer) || '工作流执行失败,请稍后重试' }} + + 填写文本指令并上传附件后执行工作流 @@ -248,6 +252,15 @@ this.photoFiles.length ) }, + /** + * 后端判定工作流失败时不再下发 outputs/raw,只给 status + 可读 answer。 + * 这里据此提示失败,避免落到「待执行」空态误导用户(见 issue 01)。 + */ + workflowFailed() { + return String((this.workflowResponse && this.workflowResponse.status) || '') + .trim() + .toLowerCase() === 'failed' + }, loadingTitle() { if (this.uploadedFiles.length && String(this.content || '').trim()) { return '正在分析指令并处理附件' @@ -443,7 +456,11 @@ const result = await creativeAssistant(this.buildRequestPayload()) this.workflowResponse = result || null this.workflowOutputs = this.normalizeOutputPayload(result || null) - if (!this.hasAnyResult) { + if (this.workflowFailed) { + this.showToast( + (result && result.answer) || '工作流执行失败,请稍后重试' + ) + } else if (!this.hasAnyResult) { this.showToast('接口已返回,但未识别到文档约定的输出字段') } else { this.showToast('工作流执行完成') @@ -787,6 +804,11 @@ color: #1f8f49; } + .result-status--failed { + background: #fdeeee; + color: #c0392b; + } + .result-empty { margin-top: 22rpx; padding: 32rpx 24rpx;