feat: 出图报错捕获真因 + 翻成前端友好提示(故事板)

老板/研发要求:有报错时把真因抓出来,做成前端友好提示。

- 捕获真实错误体(openai_compatible._raise_with_body):取代 raise_for_status()。
  yunqi 的真因(moderation_blocked / safety_violations=[sexual])只在 body 里,
  原来只剩状态行「400 Bad Request for url」→ 真因长期不可见。现在完整抓出。
- friendly_generation_error():把原始报错翻成中文友好提示(内容审核/超时/限流/
  凭证/参考图被拒…),前端直接展示;原始报错留 task.error_message 供研发排查。
- 故事板失败:友好提示存 shot.error_message;并撤回「重试 400」——审核拦截重试
  也是白拦,改成快速失败+提示,引导用户改措辞(如避免「胸罩/内衣/抚摸/贴身」)。
- 前端:失败帧缩略图加红色「⚠失败」角标(hover 看原因),主视图显示完整友好提示;
  不再灰着没反应。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-26 17:03:02 +08:00
co-authored by Claude Opus 4.8
parent d26500bdbd
commit 4c8b2d1902
3 changed files with 64 additions and 7 deletions
@@ -30,6 +30,26 @@ def _downscale_ref_for_edit(data: bytes, content_type: str, max_edge: int = 2048
return data, content_type
def _raise_with_body(response: requests.Response) -> None:
"""非 2xx 时抛出**含响应体**的错误。中转站(yunqi 等)的真实原因(如 OpenAI 风格
{"error":{"code":"moderation_blocked","message":"...safety_violations=[sexual]..."}})只在 body 里;
原来的 raise_for_status() 只带状态行「400 Bad Request for url」→ 真因长期不可见。这里把 body 一并抛出,
供上层记日志 + 翻成前端友好提示。"""
if response.ok:
return
detail = ""
try:
data = response.json()
err = data.get("error") if isinstance(data, dict) else None
if isinstance(err, dict):
detail = " ".join(str(err.get(k)) for k in ("code", "message") if err.get(k)).strip()
if not detail:
detail = str(data)[:500]
except Exception: # noqa: BLE001 — body 非 JSON / 解析失败:退回纯文本截断
detail = (response.text or "")[:500]
raise requests.HTTPError(f"{response.status_code} {detail}".strip(), response=response)
class OpenAICompatibleProvider(VolcanoArkProvider):
"""通用 OpenAI 兼容中转站适配器(tokenssr / yunqi / 任意 New-API 网关)。
@@ -80,7 +100,7 @@ class OpenAICompatibleProvider(VolcanoArkProvider):
json=body,
timeout=300,
)
response.raise_for_status()
_raise_with_body(response)
return response.json()
def image_edit(
@@ -121,5 +141,5 @@ class OpenAICompatibleProvider(VolcanoArkProvider):
data=data,
timeout=300,
)
response.raise_for_status()
_raise_with_body(response)
return response.json()