本文目录10 节

最终结果错误,不代表最后一层代码有问题

聊天页选择角色的非首张图片生成视频后,最终视频和封面仍然使用角色首图。页面上看到的是两个异常:视频内容不对,封面也不对。

如果只看 UI,很容易同时去修改选图状态、视频封面和生成请求。但异步生成链路中,最终结果已经经过多个系统:页面状态、请求封装、后端任务创建、异步队列、外部生成服务、状态轮询和前端展示。任何一层都可能覆盖输入。

这类问题最重要的不是尽快找到一段可改的代码,而是用同一个任务把输入和输出对应起来。

先画出完整链路

我先把视频生成过程拆成:

用户选择角色图片
  -> 前端解析图片 URL
    -> createGenerateImageTask payload
      -> taskId
        -> getGenerateImageTaskStatus
          -> 最终 mp4
            -> 视频首帧封面

每一层都需要回答两个问题:这一层收到了什么,以及传给下一层的是什么。

页面展示异常只能证明链路末端有问题,不能直接证明是哪一层第一次偏离。

WebView 请求要用真实传输日志确认

在 Chrome DevTools Network 中,我能看到模板查询、任务状态和最终 mp4,但创建任务的 POST payload 并不总是稳定出现。原因是项目使用 Capacitor 原生 HTTP,请求不完全等同于页面内普通 fetch

因此我改用 Android Studio Logcat 查看 CapacitorHttp.request,确认创建任务时实际传入了:

  • 当前用户选择的图片 URL;
  • type: video
  • 当前角色标识;
  • 当前视频模板标识。

随后直接打开请求中的图片 URL,确认资源内容确实是非首张图片。到这里,前端选图状态、URL 解析和创建任务传参都有了可验证证据。

用 taskId 绑定输入和输出

只分别截一张请求日志和一段错误视频仍然不够,因为并发任务可能让证据错配。

我连续复现了两个独立任务,保存各自的 taskId,再查询对应状态和最终资源。两个任务都完成到 100%,返回不同的视频地址,但视频内容都使用角色首图。

这组结果把问题边界进一步缩小:

  • 前端请求中的图片正确;
  • 异步任务正常创建并完成;
  • 最终资源确实来自对应任务;
  • 错误已经存在于生成完成的视频内部。

因此不应该在没有新证据时继续修改前端选图逻辑。更可能的方向是后端创建任务后的参数处理、队列数据传递、模板任务处理或调用最终生成服务时重新选择了角色首图。

这里的“更可能”仍然只是基于证据的范围判断,不能写成已经确认的后端根因。只有服务端日志或代码能证明是否使用了 character.coverphotos[0] 或其他默认字段。

封面异常为什么不是第二个独立 Bug

任务状态接口没有稳定返回独立封面。前端在缺少 cover 时,会从最终视频中提取首帧作为封面。

因此链路实际上是:

最终视频错误使用角色首图
  -> 视频首帧也是角色首图
    -> 前端提取出的封面仍然是角色首图

这说明封面和视频内容异常大概率是同一个上游问题产生的两个表现。若此时单独替换前端 poster,只会让列表看起来正确,播放后仍然是错误内容。

另一类封面问题:视频首帧还没准备好

在 OC 生成视频卡片中,我也遇到过“生成完成后封面短暂空白”。这次链路不同:最终视频本身正确,只是 iOS WebView 加载 metadata、解码首帧需要时间。

处理方式是在任务结果中建立封面优先级:

  1. 后端返回的 postercoverthumbnail 等字段;
  2. 本次视频任务使用的参考图;
  3. 请求参数中的图片字段。

卡片先显示临时封面,<video> 在后面加载;收到 loadeddata 或开始播放后,再标记首帧 ready 并撤掉覆盖图。

同时,视频任务的最终结果只接受 videourlmp4,不再把 result.image 当成视频地址。这样后端未来同时返回封面图与视频时,前端不会误把图片字段当成最终媒体。

这是一项展示体验优化,不会让后端生成更快,也不会改变 taskId、轮询或扣费链路。复盘中必须把“生成慢”和“首帧显示慢”分开。

style 字段还暴露了 label 与 value 的边界

另一次 OC 生图测试中,前端请求已经带上 style,生成结果却和所选风格关联不明显。继续对比后发现,问题可能不是字段缺失,而是前端把本地化展示文案当成了接口值。

一个选择项通常至少包含两种语义:

{
  label: '动漫风',
  value: 'anime'
}

label 服务于用户和 i18n,value 服务于稳定协议。如果把翻译后的 label 直接传给后端,页面看起来选中了正确风格,后端却可能无法映射到对应 prompt。

在拿到后端支持的枚举表之前,我没有直接修改字段。更合理的协作方式是先确认接口契约,再保持 UI 文案不变,只调整稳定 value。

App 内相册和系统相册是两条链路

排查“生成视频后没有保存到相册”时,还需要先确认“相册”指什么:

  • 手机系统相册:前端调用原生插件下载 mp4,并写入 Android MediaStore;
  • App 内角色相册:生成任务完成后重新请求角色数据,依赖接口返回的 photos 中包含新视频。

两者名称相似,技术链路完全不同。

在 App 内角色相册的问题中,前端已经完成任务创建、状态轮询、聊天消息展示和 refreshCharacterPhotos(),但刷新后的接口数据仍然没有新视频。此时用前端本地补数据可以暂时改变 UI,却会掩盖服务端没有挂载媒体的真实问题。

继续排查时应该对比 iOS 与 Android 的 payload、header、bundle、source version 和接口响应,并让后端检查任务完成后的写入日志。

外部 Web 页面也要先确认归属

自定义生图和生视频页面中有少量按钮没有本地化。主应用语言包里已经存在对应翻译,但继续检查入口后发现,实际页面由外部 Web 链接承载,主应用只负责传递 language、cover、character 和 userId 等参数。

这时继续修改主应用 locale 不会影响外部页面。最终处理不是“修一段看起来相关的代码”,而是确认主应用传参正常,把问题记录给真正维护页面的一方。

一套可以复用的排查顺序

以后遇到 AI 异步生成问题,我会按下面的顺序收集证据:

  1. 固定复现步骤,保存用户当前选择;
  2. 确认实际发出的原生或 Web 请求 payload;
  3. 验证 URL 指向的资源内容,而不只看字符串;
  4. 保存 taskId,用它关联轮询和最终资源;
  5. 区分任务失败、资源错误和前端展示错误;
  6. 区分后端确认事实与前端基于现象的推测;
  7. 只有确认问题所在层级后,再选择修改代码或转交协作方。

这条流程会比直接试改更慢几分钟,却能显著减少误改正常逻辑、制造第二个问题的风险。

Recommended

继续阅读

  1. 一个一直显示“生成中”的任务,为什么不能直接重新提交工程笔记 · 8 分钟
  2. 修复 Bug 之后,怎样真正吃透相关代码工程笔记 · 7 分钟