feat: 生成视频任务失败时记录并在用户前台-对话记录、管理后台-订单显示
This commit is contained in:
parent
85dc61b07d
commit
b9e08938e0
|
|
@ -123,7 +123,21 @@
|
|||
@click="playVideo(scope.row.result)"
|
||||
/>
|
||||
</template>
|
||||
<!-- 非链接内容 -->
|
||||
<!-- 视频任务失败等场景:result 为 VideoTaskError JSON -->
|
||||
<template v-else-if="parseVolcTaskErrorJson(scope.row.result)">
|
||||
<div class="order-result-error">
|
||||
<div
|
||||
v-if="parseVolcTaskErrorJson(scope.row.result).code"
|
||||
class="order-result-error-line"
|
||||
>
|
||||
编号:{{ volcFailureCodeWithHint(parseVolcTaskErrorJson(scope.row.result).code) }}
|
||||
</div>
|
||||
<div class="order-result-error-line">
|
||||
信息:{{ parseVolcTaskErrorJson(scope.row.result).message || '—' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 非链接内容(如任务 id,可点击尝试拉取视频) -->
|
||||
<span v-else @click="handleOtherEvent(scope.row)">{{ scope.row.result }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
|
@ -309,6 +323,39 @@ export default {
|
|||
const value = String(str || "").trim();
|
||||
return /^https?:\/\//i.test(value);
|
||||
},
|
||||
/** 已知火山错误码后附中文说明(括号) */
|
||||
volcFailureCodeWithHint(code) {
|
||||
const c = String(code || "").trim();
|
||||
if (!c) return "";
|
||||
const hints = {
|
||||
OutputVideoSensitiveContentDetected: "输出视频可能包含敏感信息",
|
||||
InvalidParameter: "请求参数无效"
|
||||
};
|
||||
const hint = hints[c];
|
||||
return hint ? `${c}(${hint})` : c;
|
||||
},
|
||||
/** result 为火山回调失败写入的 VideoTaskError JSON 时解析为 { code, message } */
|
||||
parseVolcTaskErrorJson(str) {
|
||||
const s = String(str || "").trim();
|
||||
if (!s || s[0] !== "{") return null;
|
||||
try {
|
||||
const o = JSON.parse(s);
|
||||
if (
|
||||
o &&
|
||||
typeof o === "object" &&
|
||||
!Array.isArray(o) &&
|
||||
("code" in o || "message" in o)
|
||||
) {
|
||||
return {
|
||||
code: o.code != null ? String(o.code) : "",
|
||||
message: o.message != null ? String(o.message) : ""
|
||||
};
|
||||
}
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// 判断是否为图片结果(与 portal-ui GeneratedAssets 一致)
|
||||
isImage(url) {
|
||||
const value = String(url || "").trim();
|
||||
|
|
@ -333,6 +380,7 @@ export default {
|
|||
},
|
||||
// 非链接内容点击事件
|
||||
async handleOtherEvent(row) {
|
||||
if (this.parseVolcTaskErrorJson(row.result)) return;
|
||||
// 防止重复点击
|
||||
if (row.isDownloading) return;
|
||||
const originalResult = row.result;
|
||||
|
|
@ -509,6 +557,19 @@ export default {
|
|||
height: auto;
|
||||
}
|
||||
|
||||
.order-result-error {
|
||||
text-align: left;
|
||||
max-width: 280px;
|
||||
margin: 0 auto;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: #c45656;
|
||||
}
|
||||
|
||||
.order-result-error-line {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.text-ellipsis-two-lines {
|
||||
/* 必须设置宽度(继承表格列宽,也可手动指定) */
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -126,6 +126,19 @@
|
|||
{{ taskStatusText(row) }}
|
||||
</span>
|
||||
</div>
|
||||
<template v-for="(fe, feIdx) in [taskRowFailureError(row)]">
|
||||
<div
|
||||
v-if="fe"
|
||||
:key="`${row.id || 'row'}-fe-${feIdx}`"
|
||||
class="vg-chat-failure-detail">
|
||||
<div v-if="fe.code" class="vg-chat-failure-line">
|
||||
错误编号:{{ volcFailureCodeWithHint(fe.code) }}
|
||||
</div>
|
||||
<div v-if="fe.message" class="vg-chat-failure-line">
|
||||
信息:{{ fe.message }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -573,6 +586,51 @@ export default {
|
|||
el.scrollTop = el.scrollHeight
|
||||
},
|
||||
|
||||
/** 解析失败订单存库的 VideoTaskError JSON(兼容旧数据、非 JSON 的 result) */
|
||||
parseTaskResultErrorJson(result) {
|
||||
const s = String(result ?? '').trim()
|
||||
if (!s || s[0] !== '{') return null
|
||||
try {
|
||||
const o = JSON.parse(s)
|
||||
if (
|
||||
o &&
|
||||
typeof o === 'object' &&
|
||||
!Array.isArray(o) &&
|
||||
('code' in o || 'message' in o)
|
||||
) {
|
||||
return {
|
||||
code: o.code != null ? String(o.code) : '',
|
||||
message: o.message != null ? String(o.message) : ''
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
/** status=2 且 result 为错误 JSON 时返回明细,否则 null */
|
||||
taskRowFailureError(row) {
|
||||
const st = row?.status
|
||||
if (st !== 2 && st !== '2') return null
|
||||
const parsed = this.parseTaskResultErrorJson(row?.result)
|
||||
if (!parsed) return null
|
||||
if (!parsed.code && !parsed.message) return null
|
||||
return parsed
|
||||
},
|
||||
|
||||
/** 已知火山错误码后附中文说明(括号) */
|
||||
volcFailureCodeWithHint(code) {
|
||||
const c = String(code || '').trim()
|
||||
if (!c) return ''
|
||||
const hints = {
|
||||
OutputVideoSensitiveContentDetected: '输出视频可能包含敏感信息',
|
||||
InvalidParameter: '请求参数无效'
|
||||
}
|
||||
const hint = hints[c]
|
||||
return hint ? `${c}(${hint})` : c
|
||||
},
|
||||
|
||||
taskRowResultTrim(row) {
|
||||
let r = String(row?.result ?? '').trim()
|
||||
if (r) return r
|
||||
|
|
@ -1969,11 +2027,26 @@ export default {
|
|||
|
||||
.vg-chat-user-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vg-chat-failure-detail {
|
||||
flex: 1 1 100%;
|
||||
margin-top: 4px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: rgba(255, 180, 180, 0.92);
|
||||
}
|
||||
|
||||
.vg-chat-failure-line {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.vg-chat-user-col-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import com.ruoyi.common.core.controller.BaseController;
|
|||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.request.video.dto.VideoTaskCallBackRequest;
|
||||
import com.ruoyi.common.core.response.video.GetVideoGenerationTaskResponse;
|
||||
import com.ruoyi.common.core.response.video.dto.VideoTaskError;
|
||||
import com.ruoyi.common.enums.AiOrderStatusType;
|
||||
import com.ruoyi.common.enums.VideoTaskStatusType;
|
||||
import com.ruoyi.common.utils.JsonUtils;
|
||||
import com.ruoyi.common.utils.RandomStringUtil;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.TencentCosUtil;
|
||||
|
|
@ -398,6 +400,9 @@ public class ByteApiController extends BaseController {
|
|||
return aiOrderService.volcCallbackSuccessProcess(request, taskResp, order);
|
||||
} else {
|
||||
// 前面已判断过status的合法性,并处理了三种非失败的状态,所以可以确定是取消、失败、超时
|
||||
if (taskResp.getError() != null) {
|
||||
order.setResult(JsonUtils.toJson(taskResp.getError()));
|
||||
}
|
||||
aiOrderService.orderFailure(order);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue