"""模特库生成:用 gpt-image-2 预生成电商真人模特。 流程(与用户定的 SOP 一致): 1) 先出 9:16 竖屏氛围正面图(image_generation); 2) 以正面图为参考,出 16:9 白底三视图(image_edit,提示词锁角色一致性)。 两张都落 TOS,存为 person 类 Asset(metadata.kind="model"),进模特库/资产库。 可被管理命令(seed_demo_models)或后端业务复用。模型走默认 image 模型(当前 = tokenssr:gpt-image-2)。 """ from __future__ import annotations import uuid from io import BytesIO from apps.ai.models import ModelConfig from apps.ai.services import _asset_preview_url, build_person_frontal_prompt, build_provider, get_default_model from apps.assets.models import Asset, AssetFile from apps.assets.storage import TosStorage THREE_VIEW_PROMPT = ( "参考图1角色,生成角色三视图,从左往右依次为:胸像特写,全身正面,全身侧面,全身背面,白色背景" ) def _store_model_asset(*, team, user, media: str, name: str, brief: str, view: str) -> Asset: """把生成的图片(url/base64)落 TOS,存为 person 类模特资产(非项目维度)。""" from apps.ai.providers import VolcanoArkProvider # media_to_bytes 复用 fileobj, content_type = VolcanoArkProvider.media_to_bytes(media) suffix = ".png" if "jpeg" in (content_type or ""): suffix = ".jpg" elif "webp" in (content_type or ""): suffix = ".webp" asset_id = uuid.uuid4() object_key = f"teams/{team.id}/models/{asset_id}{suffix}" raw = fileobj.getvalue() stored = TosStorage().upload_fileobj(fileobj=BytesIO(raw), object_key=object_key, content_type=content_type or "image/png") asset = Asset.objects.create( id=asset_id, team=team, created_by=user, name=name, asset_type=Asset.Type.IMAGE, source=Asset.Source.AI_GENERATED, category=Asset.Category.PERSON, metadata={"kind": "model", "brief": brief, "view": view}, ) AssetFile.objects.create( asset=asset, object_key=stored.object_key, bucket=stored.bucket, content_type=stored.content_type, size_bytes=stored.size_bytes, is_primary=True, ) return asset def generate_model(*, team, user, brief: str, name: str | None = None) -> dict: """生成一个电商模特(9:16 氛围正面图 + 16:9 白底三视图)。返回 {"frontal":Asset,"three_view":Asset}。""" model_config = get_default_model(ModelConfig.Capability.IMAGE) if model_config is None: raise ValueError("没有可用的图像模型(image capability)") provider = build_provider(model_config) if not hasattr(provider, "image_edit"): raise ValueError(f"当前图像模型 {model_config.provider.name}:{model_config.name} 不支持参考图三视图(image_edit)") label = name or brief[:16] # 1) 9:16 氛围正面图(画幅由 size 控制;提示词用统一的人物正面模板:全身 / 纯色背景) frontal_prompt = build_person_frontal_prompt(brief) resp = provider.image_generation(model=model_config.name, prompt=frontal_prompt, size="1024x1536") frontal = _store_model_asset( team=team, user=user, media=provider.extract_first_media_url(resp), name=f"{label}·正面氛围", brief=brief, view="frontal", ) # 2) 16:9 白底三视图(以正面图为参考,锁角色一致性) frontal_url = _asset_preview_url(frontal) resp2 = provider.image_edit(model=model_config.name, prompt=THREE_VIEW_PROMPT, images=[frontal_url], size="1536x1024") three_view = _store_model_asset( team=team, user=user, media=provider.extract_first_media_url(resp2), name=f"{label}·三视图", brief=brief, view="three_view", ) return {"frontal": frontal, "three_view": three_view} # 演示用默认模特画像(电商常用人设) DEFAULT_MODEL_BRIEFS = [ "26岁都市白领女性,知性温柔,黑色及肩直发,米色针织衫", "30岁阳光运动男性,短发,健康肤色,浅灰色休闲卫衣", "22岁元气学生女生,马尾,清透妆,浅蓝色衬衫", ]