feat(video): 期3 视频趴 — 人物→角色 + 引用模特库/自动入库 + 分镜合规归类 + 资产库素材

后端:
- B 路:视频流程新生成角色立绘+三视图成套 → 自动入模特库(run_triview_task,幂等 additive)
- 合规闭合:三视图 person→tri_view、分镜图 scene→storyboard,两者均加 on_commit 送审(此前只有角色立绘送审,三视图/分镜图漏审 = 合规缺口)
- 资产库 _tab_q:成片 finals→素材 materials(所有视频排除最终成片;最终成片隐藏不列)
- 单测 TriviewAutoEnrollTests:自动入库 + 幂等 + 三视图落审核范围

前端:
- 视频项目资产趴「人物」→「角色」(KIND_LABEL,区块名/新增/空态/详情弹窗一处全改;向导人设 persona 不动)
- A 路:演员库收编模特库 —— 顶部「模特库」tab 取自模特库实体(含官方跨团队模特),选一个即用其形象图作角色参考
- 资产库 library:人物→角色、成片 tab→素材、分类标签补全(模特上身图/平台套图/自由创作/视频素材/最终成片)
- vite.config:端口与后端代理目标可用 VITE_PORT/VITE_API_TARGET 覆盖(默认不变),避免多实例撞车

验收:后端单测全绿(基线既有 7 失败零新增);tsc+build 全绿;无头 0 console error
(资产库角色/素材改名、最终成片隐藏;pipeline 资产趴显角色;演员库模特库 tab 含 5 模特)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-21 19:27:23 +08:00
co-authored by Claude Opus 4.8
parent c8f3f91d38
commit c6e9c7e77b
8 changed files with 192 additions and 27 deletions
+58
View File
@@ -275,6 +275,64 @@ class StandaloneCategoryTests(TestCase):
self.assertEqual(a.category, Asset.Category.PERSON)
class TriviewAutoEnrollTests(TestCase):
"""B 路(期3):视频流程新生成的角色立绘 + 三视图成套 → 自动入模特库;幂等不重复建。"""
def setUp(self):
from apps.projects.models import Project
self.user = User.objects.create_user(username="trio", password="p")
self.team = Team.objects.create(name="TR", 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="P")
self.project = Project.objects.create(team=self.team, name="项目甲", product=self.product, created_by=self.user)
self.portrait = Asset.objects.create(
team=self.team, created_by=self.user, name="项目甲-person", asset_type=Asset.Type.IMAGE,
source=Asset.Source.AI_GENERATED, category=Asset.Category.PERSON,
)
AssetFile.objects.create(asset=self.portrait, object_key="p.png", bucket="b", content_type="image/png", preview_url="http://x/p.png", is_primary=True)
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/tri.png"}]}
prov.extract_first_media_url.return_value = "http://x/tri.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)
def _gen_triview(self):
from apps.ai.services import generate_person_triview, run_triview_task
task = generate_person_triview(project=self.project, user=self.user, portrait_asset=self.portrait)
run_triview_task(task_id=str(task.id))
def test_triview_auto_enrolls_model(self):
from apps.assets.models import Model
self._patch_provider()
self._gen_triview()
m = Model.objects.filter(portrait_asset=self.portrait).first()
self.assertIsNotNone(m)
self.assertIsNotNone(m.triview_asset) # 成套
self.assertTrue(m.metadata.get("auto_enrolled"))
self.assertEqual(m.source, Model.Source.AI)
# 合规(期3):三视图归 tri_view → 落在送审范围内
self.assertEqual(m.triview_asset.category, Asset.Category.TRI_VIEW)
self.assertIn(m.triview_asset.category, Asset.REVIEW_CATEGORIES)
def test_auto_enroll_is_idempotent(self):
from apps.assets.models import Model
self._patch_provider()
self._gen_triview()
self._gen_triview() # 同一立绘再出一版三视图
self.assertEqual(Model.objects.filter(portrait_asset=self.portrait).count(), 1)
class _FakeStreamResp:
"""模拟 requests 流式响应:支持 with、raise_for_status、可写 encoding、iter_lines。"""
status_code = 200