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:
co-authored by
Claude Opus 4.8
parent
44e90233ff
commit
c8f3f91d38
@@ -212,6 +212,69 @@ class StandaloneImageReferenceTests(TestCase):
|
||||
prov.image_edit.assert_not_called()
|
||||
|
||||
|
||||
class StandaloneCategoryTests(TestCase):
|
||||
"""图片趴三类归类(期2):模特上身图→model_tryon / 平台套图→platform_kit / 自由创作→free_create;
|
||||
生成演员(model 无 product)仍→person(视频角色)。直接跑 worker 函数避开异步 .delay。"""
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(username="catowner", password="pass")
|
||||
self.team = Team.objects.create(name="CT", owner=self.user)
|
||||
CreditAccount.objects.create(team=self.team, balance="100.0000")
|
||||
self.product = Product.objects.create(team=self.team, created_by=self.user, title="测试商品")
|
||||
cover = Asset.objects.create(
|
||||
team=self.team, created_by=self.user, name="主图", asset_type=Asset.Type.IMAGE,
|
||||
source=Asset.Source.UPLOAD, category=Asset.Category.PRODUCT_IMAGE,
|
||||
)
|
||||
AssetFile.objects.create(asset=cover, object_key="c.png", bucket="b", content_type="image/png", preview_url="http://x/cover.png", is_primary=True)
|
||||
self.product.cover_asset = cover
|
||||
self.product.save(update_fields=["cover_asset"])
|
||||
|
||||
def _patch_provider(self):
|
||||
provider = patch("apps.ai.services.get_image_provider").start()
|
||||
prov = provider.return_value
|
||||
prov.image_edit.return_value = {"data": [{"url": "http://x/out.png"}]}
|
||||
prov.image_generation.return_value = {"data": [{"url": "http://x/out.png"}]}
|
||||
prov.extract_first_media_url.return_value = "http://x/out.png"
|
||||
media = patch("apps.ai.services.VolcanoArkProvider.media_to_bytes").start()
|
||||
media.return_value = (BytesIO(b"img"), "image/png")
|
||||
store = patch("apps.ai.services.TosStorage").start()
|
||||
stored = store.return_value.upload_fileobj.return_value
|
||||
stored.object_key, stored.bucket, stored.content_type, stored.size_bytes = "o.png", "b", "image/png", 3
|
||||
self.addCleanup(patch.stopall)
|
||||
return prov
|
||||
|
||||
def _run(self, mode, *, product_id=None, model_id=None):
|
||||
from apps.ai.services import run_standalone_image_task
|
||||
|
||||
self._patch_provider()
|
||||
tasks = enqueue_standalone_images(
|
||||
team=self.team, user=self.user, prompt="x", mode=mode, count=1,
|
||||
product_id=product_id, model_id=model_id, model_entity_id="ent-1", ratio="4:5",
|
||||
)
|
||||
run_standalone_image_task(task_id=str(tasks[0].id))
|
||||
return Asset.objects.filter(origin_task=tasks[0]).first()
|
||||
|
||||
def test_model_tryon_category(self):
|
||||
a = self._run("model", product_id=str(self.product.id))
|
||||
self.assertEqual(a.category, Asset.Category.MODEL_TRYON)
|
||||
self.assertEqual(a.metadata.get("mode"), "model")
|
||||
self.assertTrue(a.metadata.get("batch_id")) # 成组
|
||||
self.assertEqual(a.metadata.get("model_entity_id"), "ent-1") # 溯源模特库
|
||||
|
||||
def test_platform_kit_category(self):
|
||||
a = self._run("cover", product_id=str(self.product.id))
|
||||
self.assertEqual(a.category, Asset.Category.PLATFORM_KIT)
|
||||
|
||||
def test_free_create_category(self):
|
||||
a = self._run("image")
|
||||
self.assertEqual(a.category, Asset.Category.FREE_CREATE)
|
||||
|
||||
def test_generate_actor_stays_person(self):
|
||||
# 生成演员:mode=model 但无 product → 视频角色,仍 person(送审范围内)
|
||||
a = self._run("model")
|
||||
self.assertEqual(a.category, Asset.Category.PERSON)
|
||||
|
||||
|
||||
class _FakeStreamResp:
|
||||
"""模拟 requests 流式响应:支持 with、raise_for_status、可写 encoding、iter_lines。"""
|
||||
status_code = 200
|
||||
|
||||
Reference in New Issue
Block a user