Files
ozon-seller-kit/server/schemas/suite.py
T
2026-08-27 22:23:01 +08:00

161 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""套图规划 / 一键生成 / 单张 AI 生图 契约(studio/src/services/suite.ts 同源,见 docs/v2.1/api.md)。"""
from __future__ import annotations
from typing import Literal
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 生图模型白名单(gpt-image 系列 + Google nano-banana 系列,同一中转)
RIGHTAPI_MODELS = [
"gpt-image-2",
"gpt-image-2-vip",
"nano-banana",
"nano-banana-2",
"nano-banana-2-lite",
"nano-banana-pro",
]
# 模型 → provider 推断表:前端只传模型名,服务端据此路由(模型名优先于 provider 字段)
MODEL_PROVIDERS: dict[str, str] = {
**{m: "tongyi" for m in TONGYI_MODELS},
**{m: "rightapi" for m in RIGHTAPI_MODELS},
}
# 豆包 Seedream 走火山方舟;模型由 ARK_IMAGE_MODEL 指定(不在下拉白名单内也允许直连豆包)
DOUBAO_MODELS = ["doubao-seedream-4-5-251128"]
MODEL_PROVIDERS.update({m: "doubao" for m in DOUBAO_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
# 目标平台 → 文案语言 + 图片比例(平台决定规格,不再单独选语言)
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 TextMaterial(BaseModel):
kind: str = Field(..., description="title | params | selling_point | desc | price | brand | sales | shop")
content: str = ""
pairs: list[dict] | None = None # [{key, value}]
class WatermarkOptions(BaseModel):
"""生成图水印:AI 出图后由服务端后处理合成(与生图模型无关)。右下角,默认文字 xiongmaoyx。"""
enabled: bool = Field(default=False, description="是否开启水印")
type: Literal["image", "text"] = Field(default="image", description="图片水印 | 文字水印")
text: str = Field(default="xiongmaoyx", description="文字水印内容")
opacity: int = Field(default=30, ge=1, le=100, description="不透明度(%")
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 SuitePlanRequest(BaseModel):
"""POST /api/suite/planDeepSeek 根据商品信息生成出图方案。"""
product_id: str | None = Field(default=None, description="便于日志与上下文定位,可空")
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="分组图片数量统计 {main: n, ...}")
platform: str = Field(default="ozon")
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 GenerateImageItem(BaseModel):
url: str = Field(..., description="勾选的参考底图 URLstored_url 或源站原图)")
group_key: str = Field(default="main", description="main | sku | detail | upload")
variant_name: str | None = Field(default=None, description="SKU 规格名(方案绑定用)")
class SuiteGenerateRequest(BaseModel):
"""POST /api/suite/generate:提交套图任务,返回 suite_id 后前端轮询。"""
product_id: str | None = Field(default=None, description="用于生成图回写素材(generated 组),可空")
texts: list[TextMaterial] = Field(default_factory=list)
images: list[GenerateImageItem] = Field(default_factory=list, description="勾选的参考底图")
style_set: int = Field(default=1, ge=1, le=5)
style_prompt: str | None = Field(default=None, description="用户改写的风格提示词(覆盖 style_set 模板)")
requirements: str | None = Field(default=None, description="生图要求(最高优先级,强制约束,覆盖其他设定)")
plan: list[PlanItem] | None = Field(default=None, description="出图方案(优先于 types")
types: list[str] = Field(default_factory=list, description="旧参数:无方案时按类型生成")
platform: str = Field(default="ozon")
model: str | None = Field(default=None, description="生图模型名(按 MODEL_PROVIDERS 路由 provider")
watermark: WatermarkOptions | None = Field(default=None, description="生成图水印(服务端后处理合成);关闭时不传")
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
status: str
style_set: int
platform: str
lang: str
ratio: str
provider: str
model: str | None = None
total: int = 0 # 计划总张数(进度分母;images 是逐张追加,过程中 length < total
images: list[SuiteImageOut]
error: str | None = None
class ImageEditSingleRequest(BaseModel):
"""POST /api/suite/image-edit:单张 AI 生图(试算页每张图的「AI 生图」入口)。"""
product_id: str | None = Field(default=None, description="append=true 时回写到该商品的 generated 组")
image_url: str = Field(..., description="原图(stored_url 或 source_url,远程由服务端带 Referer 代下)")
prompt: str = Field(..., min_length=1, description="用户要求(必填,进生图指令)")
model: str = Field(default="nano-banana-2")
append: bool = Field(default=True, description="结果是否追加为商品素材(generated 组)")
class ImageEditSingleResponse(BaseModel):
url: str
asset_id: str | None = None