201 lines
7.0 KiB
Python
201 lines
7.0 KiB
Python
"""Pydantic 契约(插件 ↔ 服务端)。"""
|
||
from __future__ import annotations
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
SUPPORTED_TYPES = [
|
||
"white_bg", "key_features", "selling_pt", "material",
|
||
"lifestyle", "multi_scene", "ecommerce_detail",
|
||
"size_chart", "sku_collection", "custom",
|
||
]
|
||
|
||
# 通义(DashScope)生图模型白名单:插件下拉可选的模型
|
||
TONGYI_MODELS = ["qwen-image-3.0-pro", "wan2.7-image-pro", "wan2.6-image", "wan2.6-t2i"]
|
||
|
||
# RightAPI 生图模型白名单
|
||
RIGHTAPI_MODELS = ["gpt-image-2"]
|
||
|
||
# 模型 → provider 推断表:插件只传模型名,服务端据此路由(模型名优先于 provider 字段)
|
||
MODEL_PROVIDERS: dict[str, str] = {
|
||
**{m: "tongyi" for m in TONGYI_MODELS},
|
||
**{m: "rightapi" for m in RIGHTAPI_MODELS},
|
||
}
|
||
|
||
|
||
def resolve_provider(model: str | None, requested: str | None, default: str) -> str:
|
||
"""已知模型名直接定位 provider;未知模型回退到请求指定的 provider 或默认值。"""
|
||
if model and model in MODEL_PROVIDERS:
|
||
return MODEL_PROVIDERS[model]
|
||
return requested or default
|
||
|
||
|
||
# ── 采集上传 ──
|
||
|
||
class SourceInfo(BaseModel):
|
||
platform: str = Field(..., description="ozon | 1688 | taobao")
|
||
itemId: str | None = None
|
||
url: str = ""
|
||
collectedAt: int | None = None # epoch 毫秒
|
||
|
||
|
||
class TextMaterial(BaseModel):
|
||
kind: str = Field(..., description="title | params | selling_point | desc | price | brand")
|
||
content: str = ""
|
||
pairs: list[dict] | None = None # [{key, value}]
|
||
|
||
|
||
class ImageMaterial(BaseModel):
|
||
groupKey: str = Field(..., description="main | sku | detail | video")
|
||
groupName: str = ""
|
||
variantName: str | None = None
|
||
url: str = Field(..., description="源站原图 URL")
|
||
index: int = 0
|
||
type: str = "img"
|
||
dedupeKey: str | None = None
|
||
|
||
|
||
class MaterialsRequest(BaseModel):
|
||
product_id: str | None = Field(default=None, description="传了=追加到已有商品")
|
||
source: SourceInfo
|
||
texts: list[TextMaterial] = Field(default_factory=list)
|
||
images: list[ImageMaterial] = Field(default_factory=list)
|
||
refererOrigin: str | None = None
|
||
|
||
|
||
class MaterialsResponse(BaseModel):
|
||
product_id: str
|
||
assets_queued: int
|
||
assets_skipped: int = 0
|
||
|
||
|
||
# ── 套图生成 ──
|
||
|
||
# 目标平台 → 文案语言 + 图片比例(平台决定规格,不再单独选语言)
|
||
PLATFORM_SPECS: dict[str, dict] = {
|
||
"ozon": {"lang": "ru", "ratio": "3:4", "label": "Ozon"},
|
||
"wb": {"lang": "ru", "ratio": "3:4", "label": "Wildberries"},
|
||
"cn": {"lang": "zh", "ratio": "1:1", "label": "中文(国内平台)"},
|
||
}
|
||
|
||
|
||
class SuiteCreateRequest(BaseModel):
|
||
style_set: int = Field(default=1, ge=1, le=7, description="风格模板 1-7")
|
||
types: list[str] = Field(default_factory=lambda: ["white_bg", "key_features", "lifestyle", "multi_scene"])
|
||
platform: str = Field(default="cn", description="目标平台:ozon | wb | cn")
|
||
provider: str | None = Field(default=None, description="覆盖默认 provider(doubao | tongyi)")
|
||
model: str | None = Field(default=None, description="覆盖默认生图模型(tongyi: qwen-image-3.0-pro / wan2.7-image-pro)")
|
||
requirements: str | None = Field(default=None, description="生图要求(最高优先级,强制约束)")
|
||
|
||
|
||
# ── 无状态套图生成(工具流程:请求自带采集数据)──
|
||
|
||
class GenerateImageItem(BaseModel):
|
||
url: str = Field(..., description="勾选的图片 URL(源站原图)")
|
||
group_key: str = Field(default="main", description="main | sku | detail")
|
||
variant_name: str | None = Field(default=None, description="SKU 规格名(方案绑定用)")
|
||
|
||
|
||
class PlanItem(BaseModel):
|
||
"""出图方案项:一类图 × 数量,可绑定 SKU 规格。"""
|
||
kind: str = Field(default="custom", description="图类型(SUPPORTED_TYPES 之一)")
|
||
title: str = Field(..., description="方案标题,如「主图·粉色」")
|
||
detail: str = Field(default="", description="这张图展示什么(中文)")
|
||
prompt_hint: str = Field(default="", description="构图提示(英文,进生图 prompt)")
|
||
count: int = Field(default=1, ge=0, le=5)
|
||
variant_name: str | None = Field(default=None, description="绑定的 SKU 规格名")
|
||
|
||
|
||
class GenerateRequest(BaseModel):
|
||
texts: list[TextMaterial] = Field(default_factory=list, description="采集的文本素材")
|
||
images: list[GenerateImageItem] = Field(default_factory=list, description="勾选的参考图")
|
||
style_set: int = Field(default=1, ge=1, le=7)
|
||
style_prompt: str | None = Field(default=None, description="用户改写的风格提示词(覆盖 style_set 模板)")
|
||
requirements: str | None = Field(default=None, description="生图要求(最高优先级,强制约束,覆盖其他设定)")
|
||
types: list[str] = Field(default_factory=list, description="旧参数:无方案时按类型生成")
|
||
plan: list[PlanItem] | None = Field(default=None, description="出图方案(优先于 types)")
|
||
platform: str = Field(default="cn", description="目标平台:ozon | wb | cn")
|
||
provider: str | None = Field(default=None, description="覆盖默认 provider(doubao | tongyi)")
|
||
model: str | None = Field(default=None, description="覆盖默认生图模型(tongyi: qwen-image-3.0-pro / wan2.7-image-pro)")
|
||
|
||
|
||
# ── 出图方案规划(DeepSeek)──
|
||
|
||
class PlanRequest(BaseModel):
|
||
texts: list[TextMaterial] = Field(default_factory=list)
|
||
sku_variants: list[str] = Field(default_factory=list, description="带图的 SKU 规格名")
|
||
image_stats: dict = Field(default_factory=dict, description="分组图片数量统计")
|
||
platform: str = Field(default="cn")
|
||
requirements: str | None = Field(default=None, description="生图要求(最高优先级,规划方案必须遵循)")
|
||
|
||
|
||
class PlanItemOut(BaseModel):
|
||
kind: str
|
||
title: str
|
||
detail: str = ""
|
||
prompt_hint: str = ""
|
||
count: int = 1
|
||
variant_name: str | None = None
|
||
|
||
|
||
class PlanResponse(BaseModel):
|
||
summary: str = ""
|
||
items: list[PlanItemOut]
|
||
|
||
|
||
class SuiteCreateResponse(BaseModel):
|
||
suite_id: str
|
||
|
||
|
||
class SuiteImageOut(BaseModel):
|
||
type_id: str
|
||
name: str
|
||
url: str
|
||
status: str
|
||
error: str | None = None
|
||
|
||
|
||
class SuiteOut(BaseModel):
|
||
id: str
|
||
product_id: str
|
||
status: str
|
||
style_set: int
|
||
platform: str
|
||
lang: str
|
||
ratio: str
|
||
types: list[str]
|
||
provider: str
|
||
model: str | None = None
|
||
images: list[SuiteImageOut]
|
||
error: str | None = None
|
||
|
||
|
||
# ── 商品 ──
|
||
|
||
class AssetOut(BaseModel):
|
||
id: str
|
||
group_key: str
|
||
variant_name: str | None = None
|
||
type: str
|
||
source_url: str
|
||
url: str | None = None
|
||
status: str
|
||
|
||
|
||
class ProductOut(BaseModel):
|
||
id: str
|
||
stage: str
|
||
source_platform: str | None = None
|
||
source_item_id: str | None = None
|
||
source_url: str | None = None
|
||
name: str
|
||
description: str
|
||
price: float | None = None
|
||
asset_counts: dict | None = None
|
||
assets: list[AssetOut] = Field(default_factory=list)
|
||
created_at: str | None = None
|
||
|
||
|
||
class ProductListOut(BaseModel):
|
||
total: int
|
||
items: list[ProductOut]
|