fix: 测试清单一轮bug修复(商品库/视频项目/设置/消费/平台套图)

- 商品库编辑删图假成功+悬停无删除图标;商品三视图错显角色三视图
- 视频项目演员删除入口;角色三视图防呆;故事板审核失败原因透出
- 设置:管理团队死按钮/通知未接入项屏蔽/邮箱验证链路
- 消费:账单流水切换类型重置分页+独立总页数
- 资产库上传按钮隐藏;月限额两处对齐
- 平台套图:提示词框放大/选模型胶囊内嵌/未读任务角标/工作台记录持久化
- 新建商品独立添加卖点按钮;商品库超1920自适应

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-30 19:46:35 +08:00
co-authored by Claude Opus 4.8
parent b17a716c06
commit 22268d608b
14 changed files with 261 additions and 107 deletions
+50 -2
View File
@@ -1248,7 +1248,7 @@ def _run_image_with_retry(make, *, attempts: int = _IMAGE_GEN_ATTEMPTS):
time.sleep(2 * (i + 1))
def generate_base_asset(*, project, user, kind: str, prompt: str, label: str = "", group_id: str | None = None, reference_asset_id: str | None = None) -> AITask:
def generate_base_asset(*, project, user, kind: str, prompt: str, label: str = "", group_id: str | None = None, reference_asset_id: str | None = None, auto_triview: bool = False) -> AITask:
"""提交基础资产生成(**异步**):Web 请求只建 RESERVED 任务 + 预留额度(秒级),
慢出图(文生图 / 商品 image_edit)交给 Celery worker(run_base_asset_task)跑。
@@ -1293,6 +1293,9 @@ def generate_base_asset(*, project, user, kind: str, prompt: str, label: str = "
"model": model_config.name, "endpoint": model_config.endpoint, "prompt": gen_prompt,
"kind": kind, "label": label or "", "group_id": str(group_id) if group_id else "",
"use_edit": use_edit, "reference_image": ref_url,
# 角色立绘出图成功后,worker 内自动接力生成它配套的三视图(ZWQ#3)。仅人物立绘生效;
# 默认关闭(显式由调用方传 True),避免给商品/场景或不需要三视图的流程白白多扣一次费。
"auto_triview": bool(auto_triview) and kind == BaseAssetGroup.Kind.PERSON,
}
task = create_ai_task(
project=project,
@@ -1397,6 +1400,17 @@ def run_base_asset_task(*, task_id: str) -> None:
from apps.assets.review import submit_asset_for_review
transaction.on_commit(lambda a=asset: submit_asset_for_review(a))
# ZWQ#3 · 角色立绘出图成功后自动接力生成它配套的三视图(opt-in;商品/场景不触发)。
# 放 on_commit:确保立绘已落库再据它发起三视图(image_edit 以该立绘为参考)。
# best-effort:三视图发起失败(额度不足/模型不支持 image_edit 等)不影响立绘本身已成功。
if kind == BaseAssetGroup.Kind.PERSON and payload.get("auto_triview"):
def _kickoff_triview(portrait=asset, proj=project, usr=user):
try:
generate_person_triview(project=proj, user=usr, portrait_asset=portrait)
except Exception: # noqa: BLE001 — 三视图接力是附加能力,失败绝不回头弄挂立绘流程
logger.exception("auto triview kickoff failed for portrait %s", getattr(portrait, "id", "?"))
transaction.on_commit(_kickoff_triview)
except Exception as exc: # noqa: BLE001 — 失败要退费并把错误记进 AITask 供前端轮询读取;不向上抛(避免 celery 重试二次扣费)
task.status = AITask.Status.FAILED
task.error_message = str(exc)
@@ -1807,12 +1821,46 @@ def _is_transient_error(exc: Exception) -> bool:
)
# 审核类别英文 → 给用户的中文说明(摘自第三方 safety_violations 字段)。命不中则原样保留英文。
_MODERATION_CATEGORY_CN = {
"sexual": "性暗示 / 露骨",
"sexual/minors": "涉及未成年的性内容",
"violence": "暴力",
"violence/graphic": "血腥暴力",
"self-harm": "自残",
"hate": "仇恨",
"harassment": "骚扰",
"illicit": "违禁",
}
def _extract_moderation_categories(raw: str) -> list[str]:
"""从原始报错里抽出被审核命中的类别(如 safety_violations=[sexual] / "categories":["sexual"]),
译成中文标签。抽不到返回 []。供友好提示点名真实类别,而非泛化的「疑似敏感内容」。"""
cats: list[str] = []
seen: set[str] = set()
# 抓 safety_violations / categories / category 后面的值块,到 ] / } / 换行 / 句末为止。
# 兼容 [sexual] / ["sexual","violence"] / sexual,violence / : "sexual" 等多种写法。
for m in re.finditer(
r"(?:safety_violations|categories|violation_categor(?:y|ies)|category)\s*[=:]\s*\[?\s*([^\]\}\n]+)",
raw or "",
):
for tok in re.split(r"[\s,;\"']+", m.group(1)):
key = tok.strip().strip("\"'[]").lower()
if key and key not in seen:
seen.add(key)
cats.append(_MODERATION_CATEGORY_CN.get(key, key))
return cats
def friendly_generation_error(raw: str) -> str:
"""把模型/中转站的原始报错翻成给用户的中文友好提示(前端直接展示)。原始报错仍记进 AITask 供排查。
覆盖:内容审核拦截 / 超时 / 限流 / 凭证 / 参考图被拒 等;命不中给通用兜底。"""
s = (raw or "").lower()
if any(k in s for k in ("moderation_blocked", "safety system", "safety_violation", "content_policy", "content policy")):
return "画面或文案被内容审核拦截(疑似敏感内容)。请调整脚本措辞(如避免「胸罩 / 内衣 / 抚摸 / 贴身」等直白表述,改用「产品 / 包装展示」),或更换参考图后重试。"
cats = _extract_moderation_categories(raw)
cat_note = f"(命中类别:{(''.join(cats))})" if cats else "(疑似敏感内容)"
return f"画面或文案被内容审核拦截{cat_note}。请调整脚本措辞(如避免「胸罩 / 内衣 / 抚摸 / 贴身」等直白表述,改用「产品 / 包装展示」),或更换参考图后重试。"
if any(k in s for k in ("timed out", "timeout", "read timed out")):
return "生成超时,可能是网络波动或模型繁忙,请稍后重试。"
if any(k in s for k in ("429", "too many requests", "rate limit", "forbidden", "403")):