feat(ai-outfit): C 列支持图片目录 → 多图扇出到同名子目录 (§19.12)
Excel C 列除单张图片外也可填一个目录(d:/images/a/):对目录内每张图各 生成一张穿搭图(N→N),全部存到 输出目录/<目录叶子名>/,文件名沿用源图名; Excel 仍整行一个状态:D=子目录、E=全成功才「完成」、F=失败张数/原因。 - core/ai_outfit.py: list_directory_images(顶层、扩展名过滤、排序、忽略子目录)、 make_outfit_subdir_path(不加 _n 后缀)、generate_outfit_image 目录分支 (新参 request_interval/image_log;逐张跳过已存在→幂等重试、本地节流、发日志、 聚合成单个 OutfitResult) - core/models.py: OutfitResult 加 output_paths(目录行各 jpg,供缩略图) - ai_outfit_panel.py: gen 闭包传 request_interval/image_log;缩略图逐张; 明细「结果」列显示「子目录(N 张)」;_basename 处理目录末尾分隔符 - tests/test_ai_outfit.py: 扇出/幂等跳过/空目录/缺目录/部分失败/命名过滤等用例 - docs/11 §4.1+§9.1、tasks.md §19.12 离屏冒烟:3 图目录 + mock API → output/<dir>/ 3 jpg、D=子目录、E=完成、缩略图 3 张; 全套 12 测试文件在 Python 3.7 全绿。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -106,9 +106,13 @@ class _OutfitWorker(QObject):
|
||||
return
|
||||
|
||||
def gen(task):
|
||||
# request_interval/image_log pace and narrate directory rows that
|
||||
# fan out into many images (docs/11 §9.1); single-file rows ignore them.
|
||||
return generate_outfit_image(
|
||||
task, self._prompt, self._output_dir, self._model_config,
|
||||
quality=self._quality, resolution=self._resolution,
|
||||
request_interval=self._options.request_interval,
|
||||
image_log=self.log.emit,
|
||||
)
|
||||
|
||||
def on_progress(completed, total, result):
|
||||
@@ -870,7 +874,11 @@ class AiOutfitPanel(QWidget):
|
||||
if row is not None:
|
||||
if result.success:
|
||||
self._set_cell(row, 4, "完成")
|
||||
self._set_cell(row, 5, result.output_path)
|
||||
if result.output_paths: # 目录行:子目录 + 张数
|
||||
self._set_cell(row, 5, "{}({} 张)".format(
|
||||
result.output_path, len(result.output_paths)))
|
||||
else:
|
||||
self._set_cell(row, 5, result.output_path)
|
||||
else:
|
||||
self._set_cell(row, 4, "失败")
|
||||
self._set_cell(row, 5, result.error)
|
||||
@@ -936,12 +944,17 @@ class AiOutfitPanel(QWidget):
|
||||
# -- helpers --------------------------------------------------------
|
||||
|
||||
def _add_result_thumb(self, result):
|
||||
pix = QPixmap(result.output_path)
|
||||
item = QListWidgetItem(result.task.product_id)
|
||||
if not pix.isNull():
|
||||
item.setIcon(QIcon(pix))
|
||||
item.setData(Qt.UserRole, result.output_path)
|
||||
self._results.insertItem(0, item)
|
||||
# Directory rows produce several images (output_paths); single-file rows
|
||||
# one (output_path). Add a thumbnail for each (docs/11 §9.1).
|
||||
for path in (result.output_paths or [result.output_path]):
|
||||
if not path:
|
||||
continue
|
||||
pix = QPixmap(path)
|
||||
item = QListWidgetItem(result.task.product_id)
|
||||
if not pix.isNull():
|
||||
item.setIcon(QIcon(pix))
|
||||
item.setData(Qt.UserRole, path)
|
||||
self._results.insertItem(0, item)
|
||||
|
||||
def _open_result(self, item):
|
||||
path = item.data(Qt.UserRole)
|
||||
@@ -991,7 +1004,8 @@ class AiOutfitPanel(QWidget):
|
||||
|
||||
def _basename(path):
|
||||
import os
|
||||
return os.path.basename(str(path))
|
||||
# normpath so a directory path "d:/images/a/" shows its leaf "a" (docs/11 §4.1).
|
||||
return os.path.basename(os.path.normpath(str(path)))
|
||||
|
||||
|
||||
def _csv(value):
|
||||
|
||||
Reference in New Issue
Block a user