feat(images): 期2 图片趴三类 — 模特上身图/平台套图/自由创作 归类+成组+接模特库

后端:
- 独立生图三类归类:model+product→model_tryon(模特上身图,引用模特库,不送审) / cover→platform_kit(平台套图) / image→free_create(自由创作);生成演员(model 无 product)仍→person(视频角色,留期3)
- 成组:每次提交一个 batch_id 串起整批,落 asset.metadata;另记 mode + model_entity_id(上身图溯源模特库)
- generate-image 端点透传 model_entity_id;资产库 _tab_q+summary 加 tryon/kits/creations 三类桶
- 单测 StandaloneCategoryTests 直跑 worker 验四态归类全绿(绕开异步 .delay)

前端:
- 资产库 library.tsx 加三类 tab(模特上身图/平台套图/自由创作)+ 计数 + 筛选维度
- ai-tools 模特选择器数据源 person→模特库(listModels 映射,选中传形象图当参考 = 引用模特库),ActorLibrary 同源
- api.ts submitGenerateImage 加 model_entity_id

验收:tsc+build 全绿;无头 0 console error(资产库三类 tab 各归类正确、模特选择器 5 张取自模特库)
基线既有 3 失败(StandaloneImageReferenceTests 异步漂移)零新增

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-21 16:36:29 +08:00
co-authored by Claude Opus 4.8
parent 44e90233ff
commit c8f3f91d38
8 changed files with 187 additions and 29 deletions
+12 -2
View File
@@ -30,7 +30,11 @@ class AssetPagination(PageNumberPagination):
# 资产库 tab → 查询条件。与前端 assetTab(library.tsx)完全一致,供服务端按页过滤/计数。
_KNOWN_CATS = ["person", "scene", "product_image", "final_video", "upload"]
# 图片趴三类(期2):tryon=模特上身图 / kits=平台套图 / creations=自由创作。
_KNOWN_CATS = [
"person", "scene", "product_image", "final_video", "upload",
"model_tryon", "platform_kit", "free_create",
]
def _tab_q(tab: str) -> Q:
@@ -40,6 +44,12 @@ def _tab_q(tab: str) -> Q:
return Q(category="scene")
if tab == "products":
return Q(category="product_image")
if tab == "tryon": # 模特上身图(图片趴)
return Q(category="model_tryon")
if tab == "kits": # 平台套图(图片趴)
return Q(category="platform_kit")
if tab == "creations": # 自由创作(图片趴)
return Q(category="free_create")
if tab == "uploads":
return Q(category="upload")
if tab == "finals": # final_video,或「未归类但是视频」
@@ -100,7 +110,7 @@ class AssetViewSet(TeamScopedViewSetMixin, ModelViewSet):
def summary(self, request):
"""资产库 tab 计数(人物/场景/商品图/成片/我的上传/未分类),供 tab 徽标——不必取全量。"""
base = Asset.objects.filter(team=self.get_team())
tabs = ["people", "scenes", "products", "finals", "uploads", "unclassified"]
tabs = ["people", "scenes", "products", "tryon", "kits", "creations", "finals", "uploads", "unclassified"]
return Response({t: base.filter(_tab_q(t)).count() for t in tabs})
@action(detail=False, methods=["get"])