Files
cmbot/src/core/models.py
T
adminandClaude Opus 4.8 2fa488bd21 feat(ai-outfit): 左栏新增「标题生成」(看图→提示词→AI 文字标题→写回A列) (§19.18)
AI 穿搭页左栏新增独立的「生成标题」流程:用户写标题提示词,AI 看该行
衣服图(目录行取首图)生成电商标题,逐行立即写回 Excel A 列并刷新明细表;
完成后重载 Excel,紧接「开始生成」跑图即用新标题。移除原「最终生成要求预览」
腾出版面(docs/11 §17)。

- ai_text_service.py:AiTextClient 复用图像服务 HTTP 管道做文本输出;
  chat/gemini 带图视觉,images/images_edits 明确报错;extract_text 取首条标题
- ai_title.py + TitleResult:单行编排,never raises
- excel_service.write_title_result:只写 A 列、不动 D/E/F
- config_service:title_model 默认 + load/save_title_prompt + 默认标题话术
- 面板:标题生成组(提示词+标题模型下拉+保存+生成标题)置于话术组上方;
  _TitleWorker 顺序逐行+立即回填+刷新;与「开始生成」互斥;删预览相关组件
- 测试:文本解析/payload、generate_title(单文件/目录首图/失败)、
  write_title_result、面板标题组存在且无预览;全套 py37 通过

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:04:38 +08:00

220 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.
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import List, Optional
# ---------------------------------------------------------------------------
# 素材
# ---------------------------------------------------------------------------
@dataclass
class ImageAsset:
"""一张图片素材(衣服图或印花图)。"""
path: Path
selected: bool = True # 是否参与批量合成
# ---------------------------------------------------------------------------
# 印花变换状态
# ---------------------------------------------------------------------------
@dataclass
class TransformState:
"""印花在衣服原图像素坐标系中的变换参数。
x/y: 未旋转包围盒左上角坐标(原图像素)。
width/height: 缩放后尺寸(原图像素)。
rotation: 围绕印花中心旋转角度(度)。
"""
x: float = 0.0
y: float = 0.0
width: float = 0.0
height: float = 0.0
rotation: float = 0.0
keep_aspect_ratio: bool = True
def center(self):
"""返回印花中心坐标 (cx, cy)。"""
return self.x + self.width / 2.0, self.y + self.height / 2.0
# ---------------------------------------------------------------------------
# 模板
# ---------------------------------------------------------------------------
@dataclass
class Template:
"""可复用的合成参数模板,使用比例坐标适配任意尺寸衣服图。
x_ratio / y_ratio: 印花左上角相对于衣服宽高的比例。
width_ratio / height_ratio: 印花宽高相对于衣服宽高的比例。
type: "builtin" 为内置模板,"custom" 为用户自定义模板。
"""
name: str
x_ratio: float = 0.0
y_ratio: float = 0.0
width_ratio: float = 0.3
height_ratio: float = 0.3
rotation: float = 0.0
type: str = "custom" # "builtin" | "custom"
def to_transform_state(
self,
garment_width: float,
garment_height: float,
print_width: float = None,
print_height: float = None,
) -> TransformState:
"""将比例参数转换为针对指定衣服尺寸的像素坐标 TransformState。
width_ratio / height_ratio 定义衣服上的目标框。若提供印花原始尺寸
(print_width / print_height),印花按原始宽高比缩放后 contain 进目标框
并居中,避免被拉伸变形;未提供时退化为直接铺满目标框(旧行为)。
"""
box_x = self.x_ratio * garment_width
box_y = self.y_ratio * garment_height
box_w = self.width_ratio * garment_width
box_h = self.height_ratio * garment_height
if print_width and print_height and print_width > 0 and print_height > 0:
scale = min(box_w / print_width, box_h / print_height)
draw_w = print_width * scale
draw_h = print_height * scale
cx = box_x + box_w / 2.0
cy = box_y + box_h / 2.0
return TransformState(
x=cx - draw_w / 2.0,
y=cy - draw_h / 2.0,
width=draw_w,
height=draw_h,
rotation=self.rotation,
)
return TransformState(
x=box_x,
y=box_y,
width=box_w,
height=box_h,
rotation=self.rotation,
)
# ---------------------------------------------------------------------------
# 导出选项
# ---------------------------------------------------------------------------
@dataclass
class ExportOptions:
"""单张或批量导出时的输出配置。"""
output_dir: str = "" # 空字符串 = 使用程序默认 output/ 目录
output_format: str = "PNG" # "PNG" | "JPG"
quality: int = 95 # JPG 质量 1-95,PNG 时忽略
# ---------------------------------------------------------------------------
# 批量选项
# ---------------------------------------------------------------------------
class BatchMode(str, Enum):
"""批量合成的配对模式。"""
ONE_TO_ONE = "one_to_one" # 一一匹配
MANY_GARMENTS = "many_garments" # 多衣服 × 单印花
MANY_PRINTS = "many_prints" # 单衣服 × 多印花
FULL_COMBO = "full_combo" # 全组合(矩阵)
class VisibilityStatus(str, Enum):
"""衣服目标区域与印花的可见度状态。"""
NORMAL = "正常"
LOW = "偏低"
UNCLEAR = "不明显"
UNKNOWN = "无法判断"
@dataclass
class BatchOptions:
"""批量任务配置。"""
mode: BatchMode = BatchMode.FULL_COMBO
export_options: ExportOptions = field(default_factory=ExportOptions)
@dataclass
class VisibilityResult:
"""衣服目标区域与印花颜色对比分析结果。"""
status: VisibilityStatus
score: float = 0.0
rgb_distance: float = 0.0
brightness_difference: float = 0.0
error: str = ""
# ---------------------------------------------------------------------------
# AI 穿搭
# ---------------------------------------------------------------------------
@dataclass
class OutfitTask:
"""AI 穿搭单行任务。
row_index: Excel 行号(从 1 开始,便于写回 D/E/F 列)。
status: 待处理 / 生成中 / 完成 / 失败 / 跳过。
"""
row_index: int
title: str
product_id: str
garment_path: str
status: str = "待处理"
@dataclass
class OutfitResult:
"""AI 穿搭单行生成结果。
output_path: 单文件行 = 结果图路径;目录行 = 输出子目录路径(写回 Excel D 列)。
output_paths: 目录行下每张结果图的路径(供缩略图逐张展示);单文件行留空。
"""
task: OutfitTask
success: bool
output_path: str = ""
error: str = ""
attempts: int = 0
output_paths: List[str] = field(default_factory=list)
@dataclass
class TitleResult:
"""AI 标题生成单行结果(docs/11 §17)。
generated_title: 成功时为 AI 生成、清洗后的单行标题(写回 Excel A 列)。
"""
task: OutfitTask
success: bool
generated_title: str = ""
error: str = ""
attempts: int = 0
# ---------------------------------------------------------------------------
# 合成结果
# ---------------------------------------------------------------------------
@dataclass
class ComposeResult:
"""单张合成任务的结果。"""
success: bool
garment_path: Optional[Path] = None
print_path: Optional[Path] = None
output_path: Optional[Path] = None # 成功时有效
error: str = "" # 失败时的错误说明
visibility: Optional[VisibilityResult] = None
@dataclass
class BatchResult:
"""批量合成任务的汇总结果。"""
results: List[ComposeResult] = field(default_factory=list)
total: int = 0
success_count: int = 0
failure_count: int = 0