fix(ai): 图片创作重跑三连修——丢参考图/裂新批次/提示词矛盾
① 重跑丢参考图:「重跑这张/重跑整批」原来不带 refs → 后端收不到 reference_image_ids 落到纯文生图,出图与上传素材无关。现 GenBatch.refs 存 assetId(上传后回写;恢复批次由 tasks 接口新带的 id 还原),重跑原样带回。 ② 重跑裂新聊天记录:enqueue 每次新铸 batch_id,后端没有「补进原批次」 概念,刷新/切对话后重跑图裂成新卡、原卡失败格还挂着。现生成接口收/回 batch_id(UUID 校验,非法兜底新铸),重跑沿用原批次并打 batch_append 标记; 前端批次卡存 backendBatchId(提交回调即落,整批全失败也不丢),重跑带回。 恢复分组时 rerun 任务不计入「应出张数」→ 补图成功后失败格自然收掉; 批次状态改按「有任务在跑」判定,已有好图+补图在途也能接续轮询。 ③ 同批换装一半不换:带参考图模板「严格保留款式/不要换款」与用户要求 (如"生成现代服装的穿着")直接矛盾,模型每张随机听一边。改为只锁主体身份 (人物五官/发型/体型,商品品类/外形/Logo),用户明确要求改变的以用户要求 为最高优先级,未提及的才默认与参考图一致。 测试:apps 全量 214 过(需 --settings=airshelf.settings.test); 新增 test_rerun_with_batch_id_reuses_batch_and_marks_append。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,8 @@ class GenerateImageView(APIView):
|
||||
# 平台套图:前端传规范化平台 id(taobao/douyin/…),用于后端注入平台版式块(优化版)
|
||||
platform_id = str(request.data.get("platform_id") or "").strip() or None
|
||||
conversation_id = str(request.data.get("conversation_id") or "").strip() or None
|
||||
# 重跑/补图:前端带原批次 batch_id → enqueue 沿用(UUID 校验),记录归回原批次不裂新卡
|
||||
batch_id = str(request.data.get("batch_id") or "").strip() or None
|
||||
# 用户在图片创作里上传的参考图(已先传成 Asset),按 id 列表带入 → 生成时作多图参考(image_edit)
|
||||
raw_refs = request.data.get("reference_image_ids") or []
|
||||
if isinstance(raw_refs, str):
|
||||
@@ -60,13 +62,18 @@ class GenerateImageView(APIView):
|
||||
title=(prompt[:24] or "默认创作"),
|
||||
)
|
||||
try:
|
||||
tasks = enqueue_standalone_images(team=team, user=request.user, prompt=prompt, mode=mode, count=count, product_id=product_id, reference_product=reference_product, model_id=model_id, model_entity_id=model_entity_id, ratio=ratio, image_model=image_model, conversation=conversation, reference_image_ids=reference_image_ids, platform_id=platform_id)
|
||||
tasks = enqueue_standalone_images(team=team, user=request.user, prompt=prompt, mode=mode, count=count, product_id=product_id, reference_product=reference_product, model_id=model_id, model_entity_id=model_entity_id, ratio=ratio, image_model=image_model, conversation=conversation, reference_image_ids=reference_image_ids, platform_id=platform_id, batch_id=batch_id)
|
||||
except ValueError as exc: # 无可用模型 / 余额不足等,立即反馈
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
# 本次提交即刷新对话活跃时间,左栏「最近」据此置顶
|
||||
ImageConversation.objects.filter(id=conversation.id).update(last_active_at=timezone.now())
|
||||
# batch_id 回传给前端存进批次卡:后续「重跑这张 / 重跑整批」带它回来即可归回原批次
|
||||
return Response(
|
||||
{"conversation_id": str(conversation.id), "tasks": [{"id": str(t.id), "status": t.status} for t in tasks]},
|
||||
{
|
||||
"conversation_id": str(conversation.id),
|
||||
"batch_id": (tasks[0].request_payload or {}).get("batch_id") if tasks else None,
|
||||
"tasks": [{"id": str(t.id), "status": t.status} for t in tasks],
|
||||
},
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
|
||||
@@ -177,6 +184,8 @@ class AITaskViewSet(TeamScopedViewSetMixin, ReadOnlyModelViewSet):
|
||||
rp_platform_id=KeyTextTransform("platform_id", "request_payload"),
|
||||
rp_model_id=KeyTextTransform("model_id", "request_payload"),
|
||||
rp_model_entity_id=KeyTextTransform("model_entity_id", "request_payload"),
|
||||
# 只在重跑任务里落此键(值恒为 True);键不存在 → NULL → 假值,存在 → "true"/"1" → 真值
|
||||
rp_batch_append=KeyTextTransform("batch_append", "request_payload"),
|
||||
)
|
||||
.filter(request_payload__mode=mode) # 路径查找,不能用 rp_mode 注解比较(MySQL 3141,见 _unread_base)
|
||||
)
|
||||
@@ -197,6 +206,7 @@ class AITaskViewSet(TeamScopedViewSetMixin, ReadOnlyModelViewSet):
|
||||
"model_id": t.rp_model_id or "",
|
||||
"model_entity_id": t.rp_model_entity_id or "",
|
||||
"platform_id": t.rp_platform_id or "",
|
||||
"rerun": bool(t.rp_batch_append),
|
||||
"created_at": t.created_at,
|
||||
# 软删的图不再出现在工作台记录里(R109:删除资产库图片 → 任务记录联动)
|
||||
"assets": AssetSerializer(
|
||||
@@ -271,7 +281,8 @@ class ImageConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
rid = str(rid)
|
||||
if rid not in ref_cache:
|
||||
a = Asset.objects.filter(id=rid).prefetch_related("files").first()
|
||||
ref_cache[rid] = {"name": a.name, "url": _asset_preview(a)} if a else None
|
||||
# 带上 id:前端重跑时凭它原样复用参考图(否则刷新恢复的批次只有 {name,url},重跑丢参考)
|
||||
ref_cache[rid] = {"id": rid, "name": a.name, "url": _asset_preview(a)} if a else None
|
||||
if ref_cache[rid]:
|
||||
out.append(ref_cache[rid])
|
||||
return out
|
||||
@@ -284,6 +295,8 @@ class ImageConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
"prompt": (t.request_payload or {}).get("prompt", ""),
|
||||
"batch_id": (t.request_payload or {}).get("batch_id", ""),
|
||||
"ratio": (t.request_payload or {}).get("ratio") or "",
|
||||
# 重跑/补图任务:不计入批次「应出张数」,前端据此正确渲染失败格数量
|
||||
"rerun": bool((t.request_payload or {}).get("batch_append")),
|
||||
"reference_images": resolve_refs((t.request_payload or {}).get("reference_image_ids")),
|
||||
"created_at": t.created_at,
|
||||
"assets": AssetSerializer(
|
||||
|
||||
Reference in New Issue
Block a user