feat(core): 基础资产页按流程文档补完 step4(人物/场景详情+演员库+三视图绑定立绘)
人物/场景: - 卡片可点开详情弹窗:立绘大图(hover 右下角 下载/查看大图)+ 立绘版本切换 + 提示词重跑(覆盖卡片立绘,归入同角色)+ 应用到当前项目;场景详情不显示三视图 - 进入基础资产自动生成脚本提取的待生成人物/场景(带 loading),每项目一次 - 「替换」→ 演员库 replace 回填;人物区头部「演员库」入口 三视图:与「某一版立绘」1:1 绑定(metadata.triview_of=立绘组id) - 生成/重跑立绘 → 链式据该立绘 image_edit 出配套三视图(锁角色一致性) - 详情切换立绘版本 → 三视图跟着切;可据当前立绘再出多版,下方版本小图可切 - 后端 generate_person_triview + 端点 generate-triview,复用 model_library 三视图 SOP 商品三视图弹窗:记录版本、切换查看、重跑生成、采用此版本 演员库(新建 components/actor-library.tsx 覆盖层): - 平台预设演员 / 我的演员两 tab + 添加演员工作台(AI 生成 / 本地上传保存) - 替换基础资产卡:新增后端 attach-base-asset(把现有资产挂为候选并采用) 合并 dev 进来的火山真人审核绿/红盾到新的实体卡。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -625,6 +625,58 @@ def generate_base_asset(*, project, user, kind: str, prompt: str, label: str = "
|
||||
raise
|
||||
|
||||
|
||||
def generate_person_triview(*, project, user, portrait_group) -> "BaseAssetGroup":
|
||||
"""流程步骤4 · 据「某一版立绘」生成它配套的三视图(image_edit 以立绘为参考,锁角色一致性)。
|
||||
三视图与立绘 1:1 绑定:新立绘 → 新三视图。落一个 metadata.triview_of=<立绘组id> 的 person 组。"""
|
||||
from apps.ai.model_library import THREE_VIEW_PROMPT
|
||||
|
||||
if portrait_group.kind != BaseAssetGroup.Kind.PERSON:
|
||||
raise ValueError("三视图仅用于人物立绘")
|
||||
portrait_asset = portrait_group.adopted_asset
|
||||
if portrait_asset is None:
|
||||
raise ValueError("该立绘尚未生成,无法据它生成三视图")
|
||||
model_config = get_default_model(ModelConfig.Capability.IMAGE)
|
||||
if model_config is None:
|
||||
raise ValueError("no active image model configured")
|
||||
provider = get_image_provider(model_config)
|
||||
if not hasattr(provider, "image_edit"):
|
||||
raise ValueError(f"当前图像模型 {model_config.provider.name}:{model_config.name} 不支持参考图三视图(image_edit)")
|
||||
ref_url = _asset_preview_url(portrait_asset)
|
||||
payload = {"model": model_config.name, "prompt": THREE_VIEW_PROMPT, "kind": "person", "triview_of": str(portrait_group.id)}
|
||||
task = create_ai_task(project=project, user=user, task_type=AITask.Type.PERSON_IMAGE, model_config=model_config, request_payload=payload)
|
||||
reservation = task.credit_reservation
|
||||
try:
|
||||
response = provider.image_edit(model=model_config.name, prompt=THREE_VIEW_PROMPT, images=[ref_url], size="1536x1024")
|
||||
media = provider.extract_first_media_url(response)
|
||||
with transaction.atomic():
|
||||
task.status = AITask.Status.SUCCEEDED
|
||||
task.response_payload = response
|
||||
task.actual_cost = task.estimated_cost
|
||||
task.completed_at = timezone.now()
|
||||
task.save(update_fields=["status", "response_payload", "actual_cost", "completed_at", "updated_at"])
|
||||
charge_reserved_credit(reservation=reservation, actual_amount=task.actual_cost)
|
||||
asset = _store_generated_media(
|
||||
team=project.team, user=user, project=project, task=task, media=media,
|
||||
name=f"{project.name}-三视图", category=Asset.Category.PERSON, asset_type=Asset.Type.IMAGE,
|
||||
)
|
||||
# label 带立绘组 id:前端据此把三视图归到对应立绘版本(切立绘 → 切三视图)
|
||||
group = BaseAssetGroup.objects.create(
|
||||
project=project, kind=BaseAssetGroup.Kind.PERSON, task=task, prompt=THREE_VIEW_PROMPT,
|
||||
metadata={"label": f"{portrait_group.id} ·三视图", "triview_of": str(portrait_group.id)},
|
||||
)
|
||||
group.candidate_assets.add(asset)
|
||||
group.adopted_asset = asset
|
||||
group.save(update_fields=["adopted_asset", "updated_at"])
|
||||
return group
|
||||
except Exception as exc:
|
||||
task.status = AITask.Status.FAILED
|
||||
task.error_message = str(exc)
|
||||
task.completed_at = timezone.now()
|
||||
task.save(update_fields=["status", "error_message", "completed_at", "updated_at"])
|
||||
release_credit(reservation=reservation, reason=str(exc))
|
||||
raise
|
||||
|
||||
|
||||
def _scene_context(project) -> str:
|
||||
"""从商品 + 已采用基础资产提炼一句「风格锚点」,贯穿故事板 / 视频,保证各镜内容一致。"""
|
||||
product = project.product
|
||||
|
||||
Reference in New Issue
Block a user