feat(ai): generate covers without new titles
This commit is contained in:
@@ -129,6 +129,32 @@ class AITests(TempDirMixin, unittest.TestCase):
|
||||
)
|
||||
return batch_id, db.list_tasks(batch_id=batch_id, path=cfg["db_path"])
|
||||
|
||||
def test_cover_title_context_and_cover_only_generation_needs(self):
|
||||
task = SimpleNamespace(
|
||||
stage="collected",
|
||||
status="success",
|
||||
old_title="旧标题",
|
||||
new_title=None,
|
||||
new_cover_path=None,
|
||||
apply_attempts=0,
|
||||
)
|
||||
|
||||
self.assertEqual("旧标题", ai.cover_title_context(task))
|
||||
self.assertEqual(
|
||||
{"title": False, "cover": True},
|
||||
ai.generation_needs(task, generate_mode="cover"),
|
||||
)
|
||||
task.new_title = "新标题"
|
||||
self.assertEqual("新标题", ai.cover_title_context(task))
|
||||
task.new_title = None
|
||||
task.old_title = ""
|
||||
self.assertEqual("", ai.cover_title_context(task))
|
||||
self.assertFalse(ai.is_generatable_task(task, generate_mode="cover"))
|
||||
self.assertEqual(
|
||||
{"title": True, "cover": True},
|
||||
ai.generation_needs(task, generate_mode="title_cover"),
|
||||
)
|
||||
|
||||
def _write_old_cover_files(self, tasks):
|
||||
from PIL import Image
|
||||
|
||||
@@ -1644,6 +1670,68 @@ class AITests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_generate_batch_cover_only_uses_old_title_and_keeps_new_title_empty(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self._config()
|
||||
cfg["db_path"] = os.path.join(temp_dir, "cmshopee.db")
|
||||
cfg["image_dir"] = os.path.join(temp_dir, "images")
|
||||
cfg["ai"]["generate_mode"] = "cover"
|
||||
cfg["ai"]["generate_cover"] = True
|
||||
batch_id, tasks = self._collected_tasks(temp_dir, cfg, ["旧标题"])
|
||||
cover_prompts = []
|
||||
events = []
|
||||
|
||||
def fake_cover(cover_prompt, old_cover_path, out_path, **kwargs):
|
||||
cover_prompts.append(cover_prompt)
|
||||
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||
with open(out_path, "wb") as fh:
|
||||
fh.write(b"jpeg")
|
||||
return out_path
|
||||
|
||||
with mock.patch("app.ai.gen_title") as gen_title, \
|
||||
mock.patch("app.ai.gen_cover", side_effect=fake_cover) as gen_cover:
|
||||
summary = ai.generate_batch(
|
||||
tasks,
|
||||
{"title": "标题提示", "cover": "封面 {新标题} / {旧标题}"},
|
||||
ai_cfg={
|
||||
"config": cfg,
|
||||
"db_path": cfg["db_path"],
|
||||
"on_event": events.append,
|
||||
},
|
||||
)
|
||||
|
||||
self.assertTrue(summary["ok"])
|
||||
self.assertEqual(0, summary["title_total"])
|
||||
self.assertEqual(1, summary["cover_total"])
|
||||
gen_title.assert_not_called()
|
||||
gen_cover.assert_called_once()
|
||||
self.assertEqual(["封面 旧标题 / 旧标题"], cover_prompts)
|
||||
updated = db.get_task(tasks[0].id, path=cfg["db_path"])
|
||||
self.assertIsNone(updated.new_title)
|
||||
self.assertTrue(os.path.exists(updated.new_cover_path))
|
||||
self.assertTrue(
|
||||
any("使用旧标题作为封面参考" in str(event.get("detail") or "") for event in events)
|
||||
)
|
||||
|
||||
cfg["ai"]["generate_mode"] = "title"
|
||||
cfg["ai"]["generate_cover"] = False
|
||||
with mock.patch("app.ai.gen_title", return_value="后补新标题") as gen_title, \
|
||||
mock.patch("app.ai.gen_cover") as gen_cover:
|
||||
title_summary = ai.generate_batch(
|
||||
[updated],
|
||||
{"title": "标题提示", "cover": "封面"},
|
||||
ai_cfg={"config": cfg, "db_path": cfg["db_path"]},
|
||||
)
|
||||
|
||||
self.assertTrue(title_summary["ok"])
|
||||
gen_title.assert_called_once()
|
||||
gen_cover.assert_not_called()
|
||||
completed = db.get_task(tasks[0].id, path=cfg["db_path"])
|
||||
self.assertEqual("后补新标题", completed.new_title)
|
||||
self.assertEqual(updated.new_cover_path, completed.new_cover_path)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_generate_batch_fills_reset_cover_after_committed_history(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self._config()
|
||||
|
||||
@@ -217,6 +217,49 @@ class DbTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_set_generated_cover_preserves_title_and_records_ai_success(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
db.init_db(db_path)
|
||||
batch_id = db.create_batch(["input.xlsx"], path=db_path)
|
||||
db.insert_tasks(
|
||||
batch_id,
|
||||
[
|
||||
{
|
||||
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
|
||||
"source_sheet": "Sheet1",
|
||||
"source_row": row,
|
||||
"account_name": "shop",
|
||||
"alias": "alias",
|
||||
"item_id": str(51100639510 + row),
|
||||
}
|
||||
for row in (2, 3)
|
||||
],
|
||||
path=db_path,
|
||||
)
|
||||
tasks = db.list_tasks(batch_id=batch_id, path=db_path)
|
||||
for task in tasks:
|
||||
db.set_collected(task.id, "旧标题", "old.jpg", path=db_path)
|
||||
db.set_generated(tasks[1].id, "已有新标题", None, path=db_path)
|
||||
before_with_title = db.get_task(tasks[1].id, path=db_path)
|
||||
|
||||
db.set_generated_cover(tasks[0].id, "cover-a.jpg", path=db_path)
|
||||
db.set_generated_cover(tasks[1].id, "cover-b.jpg", path=db_path)
|
||||
|
||||
without_title = db.get_task(tasks[0].id, path=db_path)
|
||||
with_title = db.get_task(tasks[1].id, path=db_path)
|
||||
self.assertIsNone(without_title.new_title)
|
||||
self.assertEqual("cover-a.jpg", without_title.new_cover_path)
|
||||
self.assertEqual("generated", without_title.stage)
|
||||
self.assertEqual("success", without_title.status)
|
||||
self.assertEqual(1, without_title.generate_attempts)
|
||||
self.assertIsNotNone(without_title.generated_at)
|
||||
self.assertEqual("已有新标题", with_title.new_title)
|
||||
self.assertEqual("cover-b.jpg", with_title.new_cover_path)
|
||||
self.assertEqual(before_with_title.generate_attempts + 1, with_title.generate_attempts)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_image_task_helpers_and_reset_lifecycle(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
db_path = os.path.join(temp_dir, "cmshopee.db")
|
||||
|
||||
+99
-4
@@ -3272,6 +3272,21 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
apply_attempts=0,
|
||||
committed=0,
|
||||
),
|
||||
SimpleNamespace(
|
||||
alias="papa",
|
||||
account_name="papa 店铺",
|
||||
item_id="1006",
|
||||
old_title="旧标题6",
|
||||
new_title="",
|
||||
new_cover_path="cover-only.jpg",
|
||||
stage="generated",
|
||||
status="success",
|
||||
last_error="",
|
||||
collect_attempts=1,
|
||||
generate_attempts=1,
|
||||
apply_attempts=0,
|
||||
committed=0,
|
||||
),
|
||||
]
|
||||
model = gui.GenerateTaskTableModel()
|
||||
model.set_tasks(tasks, [account])
|
||||
@@ -3290,6 +3305,13 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
self.assertEqual("待生成", model.index(3, 5).data())
|
||||
self.assertEqual("已生成", model.index(4, 5).data())
|
||||
|
||||
model.set_generate_mode("cover")
|
||||
self.assertEqual("待生成", model.index(1, 5).data())
|
||||
self.assertEqual("待生成", model.index(3, 5).data())
|
||||
self.assertEqual("已生成", model.index(4, 5).data())
|
||||
self.assertEqual("待生成", model.index(5, 4).data())
|
||||
self.assertEqual("已生成", model.index(5, 5).data())
|
||||
|
||||
tab = GenerateTab(config=self.make_config(temp_dir))
|
||||
self.addCleanup(tab.close)
|
||||
tab.task_table.resize(600, 240)
|
||||
@@ -3920,6 +3942,47 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_generate_tab_cover_preview_uses_old_title_when_new_title_is_missing(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self.make_config(temp_dir)
|
||||
cfg["ai"] = appconfig.ai_config(cfg)
|
||||
cfg["ai"]["generate_mode"] = "cover"
|
||||
cfg["ai"]["generate_cover"] = True
|
||||
accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
|
||||
batch_id = db.create_batch(["input.xlsx"], path=cfg["db_path"])
|
||||
db.insert_tasks(
|
||||
batch_id,
|
||||
[
|
||||
{
|
||||
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
|
||||
"source_sheet": "商品",
|
||||
"source_row": 2,
|
||||
"account_name": "Excel主店",
|
||||
"alias": "alias-a",
|
||||
"item_id": "51100639510",
|
||||
}
|
||||
],
|
||||
path=cfg["db_path"],
|
||||
)
|
||||
task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
|
||||
db.set_collected(task.id, "旧标题", "old.jpg", path=cfg["db_path"])
|
||||
tab = GenerateTab(
|
||||
config=cfg,
|
||||
title_prompt_path=os.path.join(temp_dir, "title_prompt.txt"),
|
||||
cover_prompts_dir=os.path.join(temp_dir, "prompts", "cover"),
|
||||
title_templates_dir=os.path.join(temp_dir, "prompts", "title"),
|
||||
)
|
||||
self.addCleanup(tab.close)
|
||||
tab.cover_prompt_edit.setPlainText("新:{新标题};旧:{旧标题}")
|
||||
tab.task_table.selectRow(0)
|
||||
|
||||
with mock.patch("app.gui.QMessageBox.information") as information:
|
||||
tab.preview_cover_prompt()
|
||||
|
||||
self.assertEqual("新:旧标题;旧:旧标题", information.call_args[0][2])
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_cover_gallery_lists_candidates_and_selects_current_cover(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self.make_config(temp_dir)
|
||||
@@ -5403,7 +5466,6 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
)
|
||||
task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
|
||||
db.set_collected(task.id, "旧标题", "old.jpg", path=cfg["db_path"])
|
||||
db.set_generated(task.id, "手动标题", None, path=cfg["db_path"])
|
||||
tasks = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])
|
||||
logs = []
|
||||
progress = []
|
||||
@@ -5436,14 +5498,14 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
gen_title.assert_not_called()
|
||||
gen_cover.assert_called_once()
|
||||
updated = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
|
||||
self.assertEqual("手动标题", updated.new_title)
|
||||
self.assertIsNone(updated.new_title)
|
||||
self.assertTrue(os.path.exists(updated.new_cover_path))
|
||||
self.assertEqual(0, progress[-1]["title_total"])
|
||||
self.assertEqual(1, progress[-1]["cover_total"])
|
||||
joined_logs = "\n".join(logs)
|
||||
self.assertIn("本轮生成内容:只生成封面", joined_logs)
|
||||
self.assertIn("标题0,图片1", joined_logs)
|
||||
self.assertIn("已有标题,跳过生文", joined_logs)
|
||||
self.assertIn("没有新标题,本轮使用旧标题作为封面参考", joined_logs)
|
||||
self.assertIn("[完成] AI 生成完成:标题0/0,图片1/1,失败0", joined_logs)
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
@@ -5554,9 +5616,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
)
|
||||
task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
|
||||
db.set_collected(task.id, "旧标题", "old.jpg", path=cfg["db_path"])
|
||||
db.set_generated(task.id, "手动标题", None, path=cfg["db_path"])
|
||||
tab = GenerateTab(config=cfg)
|
||||
self.addCleanup(tab.close)
|
||||
tab.status_filter.setCurrentIndex(tab.status_filter.findData("to_generate"))
|
||||
self.assertEqual(1, tab.model.rowCount())
|
||||
|
||||
class FakeSignal:
|
||||
def __init__(self):
|
||||
@@ -6137,6 +6200,38 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_apply_tab_allows_cover_only_result_but_blocks_modes_that_need_title(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self.make_config(temp_dir)
|
||||
accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
|
||||
batch_id = db.create_batch(["input.xlsx"], path=cfg["db_path"])
|
||||
db.insert_tasks(
|
||||
batch_id,
|
||||
[
|
||||
{
|
||||
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
|
||||
"source_sheet": "商品",
|
||||
"source_row": 2,
|
||||
"account_name": "Excel主店",
|
||||
"alias": "alias-a",
|
||||
"item_id": "51100639510",
|
||||
}
|
||||
],
|
||||
path=cfg["db_path"],
|
||||
)
|
||||
task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0]
|
||||
db.set_collected(task.id, "旧标题", "old.jpg", path=cfg["db_path"])
|
||||
db.set_generated_cover(task.id, "new-cover.jpg", path=cfg["db_path"])
|
||||
generated = db.get_task(task.id, path=cfg["db_path"])
|
||||
tab = ApplyTab(config=cfg)
|
||||
self.addCleanup(tab.close)
|
||||
|
||||
self.assertIsNone(tab._update_content_error([generated], "cover"))
|
||||
self.assertIn("缺少新标题", tab._update_content_error([generated], "title"))
|
||||
self.assertIn("缺少新标题", tab._update_content_error([generated], "title_cover"))
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_apply_worker_applies_success_failure_and_unmatched_serially(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self.make_config(temp_dir)
|
||||
|
||||
Reference in New Issue
Block a user