162 lines
6.1 KiB
Python
162 lines
6.1 KiB
Python
"""Pure product-suite configuration and generation planning helpers."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from collections import OrderedDict
|
||
|
||
|
||
FIXED_CATEGORIES = ("白底图", "场景图", "卖点图")
|
||
DEFAULT_CATEGORY_COUNTS = OrderedDict(
|
||
(("白底图", 1), ("场景图", 2), ("卖点图", 2))
|
||
)
|
||
PLATFORMS = ("Shopee", "Lazada", "TikTok Shop", "Amazon")
|
||
COUNTRIES = ("中国台湾", "新加坡", "马来西亚", "菲律宾", "泰国", "越南")
|
||
LANGUAGES = ("繁体中文", "简体中文", "英文", "泰文", "越南文")
|
||
RATIOS = ("1:1", "3:4", "4:3", "16:9", "9:16")
|
||
LAST_SETTING_KEYS = ("platform", "country", "language", "ratio")
|
||
MAX_CATEGORY_NAME_LENGTH = 10
|
||
MAX_GENERATION_COUNT_WITHOUT_CONFIRM = 16
|
||
|
||
|
||
def default_suite_settings():
|
||
return {
|
||
"platform": "Shopee",
|
||
"country": "中国台湾",
|
||
"language": "繁体中文",
|
||
"ratio": "1:1",
|
||
"per_image_primary": False,
|
||
"categories": dict(DEFAULT_CATEGORY_COUNTS),
|
||
"custom_category_order": [],
|
||
}
|
||
|
||
|
||
def normalize_suite_settings(value=None):
|
||
raw = dict(value or {}) if isinstance(value, dict) else {}
|
||
normalized = default_suite_settings()
|
||
normalized["platform"] = _choice(raw.get("platform"), PLATFORMS, "Shopee")
|
||
normalized["country"] = _choice(raw.get("country"), COUNTRIES, "中国台湾")
|
||
normalized["language"] = _choice(raw.get("language"), LANGUAGES, "繁体中文")
|
||
normalized["ratio"] = _choice(raw.get("ratio"), RATIOS, "1:1")
|
||
normalized["per_image_primary"] = bool(raw.get("per_image_primary", False))
|
||
|
||
raw_categories = raw.get("categories") if isinstance(raw.get("categories"), dict) else {}
|
||
categories = OrderedDict()
|
||
for name, default_count in DEFAULT_CATEGORY_COUNTS.items():
|
||
categories[name] = _count(raw_categories.get(name, default_count))
|
||
requested_order = raw.get("custom_category_order")
|
||
requested_order = requested_order if isinstance(requested_order, list) else []
|
||
seen = set(FIXED_CATEGORIES)
|
||
custom_order = []
|
||
for candidate in list(requested_order) + list(raw_categories):
|
||
name = str(candidate or "")
|
||
if name in seen or suite_name_error(name, categories):
|
||
continue
|
||
seen.add(name)
|
||
custom_order.append(name)
|
||
categories[name] = _count(raw_categories.get(name, 1))
|
||
normalized["categories"] = dict(categories)
|
||
normalized["custom_category_order"] = custom_order
|
||
return normalized
|
||
|
||
|
||
def last_suite_settings(value=None):
|
||
normalized = normalize_suite_settings(value)
|
||
return {key: normalized[key] for key in LAST_SETTING_KEYS}
|
||
|
||
|
||
def suite_name_error(name, existing=None, old_name=""):
|
||
value = str(name or "")
|
||
if not value.strip():
|
||
return "分类名称不能为空"
|
||
if value != value.strip() or any(character.isspace() for character in value):
|
||
return "分类名称不能包含空格"
|
||
if len(value) > MAX_CATEGORY_NAME_LENGTH:
|
||
return "分类名称不能超过10个字"
|
||
names = set(existing or ())
|
||
if value in names and value != str(old_name or ""):
|
||
return "分类名称已存在"
|
||
return ""
|
||
|
||
|
||
def category_order(settings):
|
||
normalized = normalize_suite_settings(settings)
|
||
categories = normalized["categories"]
|
||
custom = [
|
||
name
|
||
for name in normalized["custom_category_order"]
|
||
if name in categories and name not in FIXED_CATEGORIES
|
||
]
|
||
return list(FIXED_CATEGORIES) + custom
|
||
|
||
|
||
def suite_total_count(settings, image_count):
|
||
normalized = normalize_suite_settings(settings)
|
||
categories = normalized["categories"]
|
||
base = sum(_count(categories.get(name, 0)) for name in category_order(normalized))
|
||
if not normalized["per_image_primary"]:
|
||
return base
|
||
white_count = _count(categories.get("白底图", 0))
|
||
other_count = max(0, base - white_count)
|
||
return white_count + other_count * max(1, int(image_count or 0))
|
||
|
||
|
||
def build_suite_prompt(base_prompt, settings, category, item_id, source_index=1):
|
||
normalized = normalize_suite_settings(settings)
|
||
context = [
|
||
"生成一张电商商品套图。",
|
||
"平台:%s" % normalized["platform"],
|
||
"国家地区:%s" % normalized["country"],
|
||
"输出语言:%s" % normalized["language"],
|
||
"图片比例:%s" % normalized["ratio"],
|
||
"套图分类:%s" % str(category),
|
||
"商品ID:%s" % str(item_id or ""),
|
||
"当前主参考图序号:%d" % max(1, int(source_index or 1)),
|
||
"商品卖点与要求:%s" % str(base_prompt or "").strip(),
|
||
"保持商品主体、款式、颜色和关键细节准确,不添加无依据的功能或参数。",
|
||
]
|
||
return "\n".join(context)
|
||
|
||
|
||
def build_job_specs(source_assets, base_prompt, settings, item_id):
|
||
assets = list(source_assets or [])
|
||
if not assets:
|
||
return []
|
||
normalized = normalize_suite_settings(settings)
|
||
specs = []
|
||
for category in category_order(normalized):
|
||
count = _count(normalized["categories"].get(category, 0))
|
||
if count <= 0:
|
||
continue
|
||
targets = assets if normalized["per_image_primary"] and category != "白底图" else assets[:1]
|
||
for source_index, asset in enumerate(targets, 1):
|
||
for category_index in range(1, count + 1):
|
||
specs.append(
|
||
{
|
||
"source_asset_id": int(getattr(asset, "id", asset)),
|
||
"job_type": str(category),
|
||
"category": str(category),
|
||
"category_index": category_index,
|
||
"source_index": source_index,
|
||
"prompt": build_suite_prompt(
|
||
base_prompt,
|
||
normalized,
|
||
category,
|
||
item_id,
|
||
source_index=source_index,
|
||
),
|
||
}
|
||
)
|
||
return specs
|
||
|
||
|
||
def _count(value):
|
||
try:
|
||
return max(0, int(value or 0))
|
||
except (TypeError, ValueError):
|
||
return 0
|
||
|
||
|
||
def _choice(value, choices, default):
|
||
text = str(value or "").strip()
|
||
return text if text in choices else default
|