fix: 测试清单一轮bug修复(商品库/视频项目/设置/消费/平台套图)
- 商品库编辑删图假成功+悬停无删除图标;商品三视图错显角色三视图 - 视频项目演员删除入口;角色三视图防呆;故事板审核失败原因透出 - 设置:管理团队死按钮/通知未接入项屏蔽/邮箱验证链路 - 消费:账单流水切换类型重置分页+独立总页数 - 资产库上传按钮隐藏;月限额两处对齐 - 平台套图:提示词框放大/选模型胶囊内嵌/未读任务角标/工作台记录持久化 - 新建商品独立添加卖点按钮;商品库超1920自适应 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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")):
|
||||
|
||||
@@ -129,10 +129,20 @@ class AssetViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
if p.get("product"):
|
||||
# 资产归属商品:独立生图写 metadata.product_id;项目内生成回溯 origin_task→project→product;
|
||||
# 上传的商品图经 ProductImage 关联。三路并集 = 前端 ProductDetail 的 belongsToProduct。
|
||||
# ★ ZWQ#5:项目里同时会生成「角色立绘/三视图(person/tri_view)、场景图(scene)、分镜图(storyboard)」,
|
||||
# 这些都挂在同一 project 上但**不属于商品**。若 origin_task→project 这一路不限类目,商品库的
|
||||
# 「AI 生成三视图」会把项目里的角色三视图当成商品三视图错显。故 project 回溯路只认「确属商品」的类目
|
||||
# (商品图/模特上身图/平台套图/自由创作);角色/场景/分镜资产仍可经 metadata.product_id 显式归属命中。
|
||||
pid = p["product"]
|
||||
product_categories = (
|
||||
Asset.Category.PRODUCT_IMAGE,
|
||||
Asset.Category.MODEL_TRYON,
|
||||
Asset.Category.PLATFORM_KIT,
|
||||
Asset.Category.FREE_CREATE,
|
||||
)
|
||||
qs = qs.filter(
|
||||
Q(metadata__product_id=pid)
|
||||
| Q(origin_task__project__product_id=pid)
|
||||
| (Q(origin_task__project__product_id=pid) & Q(category__in=product_categories))
|
||||
| Q(product_images__product_id=pid)
|
||||
).distinct()
|
||||
if p.get("q"):
|
||||
|
||||
@@ -487,7 +487,17 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
if kind not in BaseAssetGroup.Kind.values:
|
||||
return Response({"detail": "invalid base asset kind"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
task = generate_base_asset(project=project, user=request.user, kind=kind, prompt=request.data.get("prompt", ""), label=request.data.get("label", ""), reference_asset_id=request.data.get("reference_asset_id") or None)
|
||||
task = generate_base_asset(
|
||||
project=project,
|
||||
user=request.user,
|
||||
kind=kind,
|
||||
prompt=request.data.get("prompt", ""),
|
||||
label=request.data.get("label", ""),
|
||||
reference_asset_id=request.data.get("reference_asset_id") or None,
|
||||
# ZWQ#3:角色立绘可让 worker 出图后自动接力生成三视图。前端勾选时传 auto_triview=true;
|
||||
# 不传则维持原行为(只出立绘,用户再手点「AI 生成三视图」)。
|
||||
auto_triview=bool(request.data.get("auto_triview")),
|
||||
)
|
||||
except ValueError as exc: # 无可用模型 / 余额不足等,立即反馈
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
stage, _ = ProjectStage.objects.get_or_create(project=project, stage=ProjectStage.Stage.BASE_ASSETS)
|
||||
|
||||
Reference in New Issue
Block a user