From 74ea4b81f2547f0b7a9f81f8eea278ce5e07f376 Mon Sep 17 00:00:00 2001 From: stupid-h4er Date: Wed, 27 May 2026 21:55:05 +0800 Subject: [PATCH] fix: improve seedance reference handling --- docs/pending-test.md | 1 + handler/ai.go | 52 ++++++++++++++++++- handler/ai_test.go | 20 +++++++ .../components/canvas-config-node-panel.tsx | 7 +-- .../components/canvas-node-generation.ts | 4 ++ web/src/app/(user)/video/page.tsx | 43 ++++++++++++--- web/src/lib/seedance-video.ts | 43 +++++++++++++++ web/src/services/api/video.ts | 6 ++- web/src/types/media.ts | 3 ++ 9 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 handler/ai_test.go diff --git a/docs/pending-test.md b/docs/pending-test.md index e313d2b..24a039d 100644 --- a/docs/pending-test.md +++ b/docs/pending-test.md @@ -1,5 +1,6 @@ # 待测试 +- Seedance 参考素材失败原因排查:后端会把火山上游错误摘要返回给前端;`/video` 和画布视频生成会按 `图片1/视频1/音频1` 自动编号参考素材,并在实际请求提示词中注入编号说明;参考视频会在请求前校验大小、时长、宽高、宽高比和像素总量。 - 画布项目导出改为下载 `.zip` 压缩包,包内包含 `projects.json` 和当前画布引用到的本地图片、视频文件,避免只导出 JSON 时丢失媒体内容。 - 画布库支持多选后一键导出多个画布项目,导出的压缩包可一次恢复多个项目。 - 画布项目导入改为读取新版 `.zip` 压缩包,会先按 `projects.json` 中的文件映射恢复图片、视频到本地存储,再插入画布项目,导入成功后仍停留在画布库。 diff --git a/handler/ai.go b/handler/ai.go index 045936c..c569c0a 100644 --- a/handler/ai.go +++ b/handler/ai.go @@ -119,12 +119,12 @@ func copyAIResponse(w http.ResponseWriter, request *http.Request, onFailure func defer response.Body.Close() if response.StatusCode >= http.StatusBadRequest { - _, _ = io.Copy(io.Discard, io.LimitReader(response.Body, 4096)) + body, _ := io.ReadAll(io.LimitReader(response.Body, 4096)) log.Printf("AI upstream error: url=%s status=%d", request.URL.String(), response.StatusCode) if onFailure != nil { onFailure() } - Fail(w, aiStatusMessage(response.StatusCode)) + Fail(w, aiUpstreamStatusMessage(response.StatusCode, body)) return } @@ -239,6 +239,54 @@ func aiStatusMessage(statusCode int) string { } } +func aiUpstreamStatusMessage(statusCode int, body []byte) string { + base := aiStatusMessage(statusCode) + detail := aiUpstreamErrorDetail(body) + if detail == "" { + return base + } + return base + ":" + detail +} + +func aiUpstreamErrorDetail(body []byte) string { + text := strings.TrimSpace(string(body)) + if text == "" { + return "" + } + var payload struct { + Msg string `json:"msg"` + Message string `json:"message"` + Error struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"error"` + } + if err := json.Unmarshal(body, &payload); err == nil { + if payload.Error.Message != "" { + if payload.Error.Code != "" { + return safeUpstreamText(payload.Error.Code + " " + payload.Error.Message) + } + return safeUpstreamText(payload.Error.Message) + } + if payload.Msg != "" { + return safeUpstreamText(payload.Msg) + } + if payload.Message != "" { + return safeUpstreamText(payload.Message) + } + } + return safeUpstreamText(text) +} + +func safeUpstreamText(text string) string { + text = strings.Join(strings.Fields(strings.TrimSpace(text)), " ") + runes := []rune(text) + if len(runes) > 300 { + return string(runes[:300]) + "..." + } + return text +} + type aiError struct { message string } diff --git a/handler/ai_test.go b/handler/ai_test.go new file mode 100644 index 0000000..2c4d8d4 --- /dev/null +++ b/handler/ai_test.go @@ -0,0 +1,20 @@ +package handler + +import ( + "strings" + "testing" +) + +func TestAIUpstreamErrorDetail(t *testing.T) { + got := aiUpstreamErrorDetail([]byte(`{"error":{"code":"InvalidParameter","message":"reference video fps is invalid"}}`)) + if got != "InvalidParameter reference video fps is invalid" { + t.Fatalf("detail = %q", got) + } +} + +func TestSafeUpstreamTextTruncates(t *testing.T) { + got := safeUpstreamText(strings.Repeat("错", 320)) + if len([]rune(got)) != 303 { + t.Fatalf("truncated rune length = %d", len([]rune(got))) + } +} diff --git a/web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx b/web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx index 8966229..f910ed7 100644 --- a/web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx +++ b/web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx @@ -9,6 +9,7 @@ import { ModelPicker } from "@/components/model-picker"; import { defaultConfig, useConfigStore, useEffectiveConfig, type AiConfig } from "@/stores/use-config-store"; import { CreditSymbol, requestCreditCost } from "@/constant/credits"; import { canvasThemes } from "@/lib/canvas-theme"; +import { seedanceReferenceLabel } from "@/lib/seedance-video"; import { useThemeStore } from "@/stores/use-theme-store"; import { CanvasImageSettingsPopover } from "./canvas-image-settings-popover"; import { CanvasVideoSettingsPopover } from "./canvas-video-settings-popover"; @@ -303,7 +304,7 @@ function ImageSortCard({
{input.title} - {imageIndex + 1} + {seedanceReferenceLabel("image", imageIndex)} onMove(input, offset)} />
@@ -328,7 +329,7 @@ function VideoSortCard({
@@ -358,7 +359,7 @@ function AudioSortCard({