diff --git a/app/gui/tabs/generate.py b/app/gui/tabs/generate.py index e4eca42..2107065 100644 --- a/app/gui/tabs/generate.py +++ b/app/gui/tabs/generate.py @@ -751,6 +751,7 @@ class GenerateTab(QWidget): status_callback=None, title_prompt_path=None, cover_prompts_dir=None, + title_templates_dir=None, open_accounts_callback=None, ): super().__init__(parent) @@ -765,7 +766,13 @@ class GenerateTab(QWidget): self.open_accounts_callback = open_accounts_callback self.title_prompt_path = title_prompt_path or appconfig.title_prompt_path(self.config) self.cover_prompts_dir = cover_prompts_dir or appconfig.cover_prompts_dir(self.config) - prompts.ensure_default_prompts(self.title_prompt_path, self.cover_prompts_dir) + self.title_templates_dir = title_templates_dir or appconfig.title_templates_dir(self.config) + prompts.ensure_default_prompts( + self.title_prompt_path, + self.cover_prompts_dir, + self.title_templates_dir, + ) + self.current_title_template = None self.current_cover_template = None self.generate_worker = None self.generate_thread = None @@ -781,7 +788,27 @@ class GenerateTab(QWidget): self.title_prompt_edit.setPlainText( prompts.load_title_prompt(self.title_prompt_path) ) + self.title_template_combo = QComboBox() + self.title_template_combo.setObjectName("titleTemplateCombo") + self.title_template_combo.setToolTip("选择标题提示词模板并载入到输入框") + self.new_title_template_button = QPushButton("新建") + self.new_title_template_button.setObjectName("newTitleTemplateButton") + self.new_title_template_button.setToolTip("新建一个标题提示词模板") + self.save_title_template_button = QPushButton("保存模板") + self.save_title_template_button.setObjectName("saveTitleTemplateButton") + self.save_title_template_button.setToolTip("保存当前选中的标题提示词模板;未选中模板时会另存为新模板") + self.title_template_actions_button = QPushButton("模板操作") + self.title_template_actions_button.setObjectName("titleTemplateActionsButton") + self.title_template_actions_menu = QMenu(self) + self.save_title_template_as_action = self.title_template_actions_menu.addAction("另存为") + self.save_title_template_as_action.setObjectName("saveTitleTemplateAsAction") + self.rename_title_template_action = self.title_template_actions_menu.addAction("重命名") + self.rename_title_template_action.setObjectName("renameTitleTemplateAction") + self.delete_title_template_action = self.title_template_actions_menu.addAction("删除") + self.delete_title_template_action.setObjectName("deleteTitleTemplateAction") + self.title_template_actions_button.setMenu(self.title_template_actions_menu) self.save_title_button = QPushButton("保存标题提示词") + self.save_title_button.setToolTip("保存当前标题提示词工作文本,生成时会使用这份内容") self.insert_old_title_button = QPushButton("插入旧标题") self.insert_old_title_button.setObjectName("insertOldTitleButton") self.cover_prompt_edit = QPlainTextEdit() @@ -790,7 +817,8 @@ class GenerateTab(QWidget): self.cover_template_combo = QComboBox() self.cover_template_combo.setObjectName("coverTemplateCombo") self.new_cover_template_button = QPushButton("新建") - self.save_cover_template_button = QPushButton("保存") + self.save_cover_template_button = QPushButton("保存模板") + self.save_cover_template_button.setToolTip("保存当前选中的封面提示词模板;未选中模板时会另存为新模板") self.cover_template_actions_button = QPushButton("模板操作") self.cover_template_actions_button.setObjectName("coverTemplateActionsButton") self.cover_template_actions_menu = QMenu(self) @@ -851,19 +879,24 @@ class GenerateTab(QWidget): left_layout = QVBoxLayout(left_panel) left_layout.setContentsMargins(0, 0, 12, 0) left_layout.addWidget(QLabel("标题提示词")) - left_layout.addWidget(self.title_prompt_edit, 1) + title_template_layout = QHBoxLayout() + title_template_layout.addWidget(self.title_template_combo, 1) + title_template_layout.addWidget(self.new_title_template_button) + title_template_layout.addWidget(self.save_title_template_button) + title_template_layout.addWidget(self.title_template_actions_button) + left_layout.addLayout(title_template_layout) + left_layout.addWidget(self.title_prompt_edit, 2) title_action_layout = QHBoxLayout() - title_action_layout.addWidget(self.save_title_button) title_action_layout.addWidget(self.insert_old_title_button) + title_action_layout.addWidget(self.save_title_button) title_action_layout.addStretch(1) left_layout.addLayout(title_action_layout) left_layout.addWidget(QLabel("封面提示词")) - left_layout.addWidget(self.cover_template_combo) cover_template_layout = QHBoxLayout() + cover_template_layout.addWidget(self.cover_template_combo, 1) cover_template_layout.addWidget(self.new_cover_template_button) cover_template_layout.addWidget(self.save_cover_template_button) cover_template_layout.addWidget(self.cover_template_actions_button) - cover_template_layout.addStretch(1) left_layout.addLayout(cover_template_layout) left_layout.addWidget(self.cover_prompt_edit, 2) cover_action_layout = QHBoxLayout() @@ -981,6 +1014,12 @@ class GenerateTab(QWidget): self.status_filter.currentIndexChanged.connect(self.refresh_tasks) self.refresh_button.clicked.connect(self.refresh_tasks) self.open_image_dir_button.clicked.connect(self.open_image_directory) + self.title_template_combo.currentIndexChanged.connect(self.load_selected_title_template) + self.new_title_template_button.clicked.connect(self.new_title_template) + self.save_title_template_button.clicked.connect(self.save_title_template) + self.save_title_template_as_action.triggered.connect(self.save_title_template_as) + self.rename_title_template_action.triggered.connect(self.rename_title_template) + self.delete_title_template_action.triggered.connect(self.delete_title_template) self.save_title_button.clicked.connect(self.save_title_prompt) self.insert_old_title_button.clicked.connect(self.insert_old_title_placeholder) self.cover_template_combo.currentIndexChanged.connect(self.load_selected_cover_template) @@ -997,6 +1036,7 @@ class GenerateTab(QWidget): self.reset_generate_button.clicked.connect(self.reset_generated_result) self.task_table.doubleClicked.connect(self.show_task_images) + self.refresh_title_templates() self.refresh_cover_templates() self.refresh_tasks() self._show_current_run_log_empty() @@ -1224,6 +1264,120 @@ class GenerateTab(QWidget): return self._set_status("标题提示词已保存") + def refresh_title_templates(self, selected=None): + try: + template_names = prompts.list_title_templates(self.title_templates_dir) + except Exception as exc: + template_names = [] + self._show_prompt_error(exc) + current = selected if selected is not None else self.current_title_template + self.title_template_combo.blockSignals(True) + self.title_template_combo.clear() + self.title_template_combo.addItem("选择模板", None) + for name in template_names: + self.title_template_combo.addItem(name, name) + index = self.title_template_combo.findData(current) + if index >= 0: + self.title_template_combo.setCurrentIndex(index) + self.current_title_template = current + else: + self.title_template_combo.setCurrentIndex(0) + self.current_title_template = None + self.title_template_combo.blockSignals(False) + + def load_selected_title_template(self, index=None): + name = self.title_template_combo.currentData() + self.current_title_template = name + if name is None: + return + try: + self.title_prompt_edit.setPlainText( + prompts.load_title_template(name, self.title_templates_dir) + ) + except Exception as exc: + self._show_prompt_error(exc) + + def new_title_template(self, checked=False): + name = self._ask_template_name("新建标题提示词模板") + if not name: + return + try: + prompts.save_title_template(name, "", self.title_templates_dir) + except Exception as exc: + self._show_prompt_error(exc) + return + self.refresh_title_templates(selected=name) + self.title_prompt_edit.setPlainText("") + self._set_status(f"标题提示词模板已新建:{name}") + + def save_title_template(self, checked=False): + name = self.current_title_template + if name is None: + self.save_title_template_as() + return + try: + prompts.save_title_template( + name, + self.title_prompt_edit.toPlainText(), + self.title_templates_dir, + ) + except Exception as exc: + self._show_prompt_error(exc) + return + self._set_status(f"标题提示词模板已保存:{name}") + + def save_title_template_as(self, checked=False): + name = self._ask_template_name("另存标题提示词模板") + if not name: + return + try: + prompts.save_title_template( + name, + self.title_prompt_edit.toPlainText(), + self.title_templates_dir, + ) + except Exception as exc: + self._show_prompt_error(exc) + return + self.refresh_title_templates(selected=name) + self._set_status(f"标题提示词模板已另存为:{name}") + + def rename_title_template(self, checked=False): + old_name = self.current_title_template + if old_name is None: + self._set_status("没有可重命名的标题提示词模板") + return + new_name = self._ask_template_name("重命名标题提示词模板", text=old_name) + if not new_name or new_name == old_name: + return + try: + prompts.rename_title_template(old_name, new_name, self.title_templates_dir) + except Exception as exc: + self._show_prompt_error(exc) + return + self.refresh_title_templates(selected=new_name) + self._set_status(f"标题提示词模板已重命名:{new_name}") + + def delete_title_template(self, checked=False): + name = self.current_title_template + if name is None: + self._set_status("没有可删除的标题提示词模板") + return + choice = QMessageBox.question( + self, + "删除标题提示词模板", + f"确定删除「{name}」吗?", + ) + if choice != QMessageBox.Yes: + return + try: + prompts.delete_title_template(name, self.title_templates_dir) + except Exception as exc: + self._show_prompt_error(exc) + return + self.refresh_title_templates() + self._set_status(f"标题提示词模板已删除:{name}") + def refresh_cover_templates(self, selected=None): try: template_names = prompts.list_cover_templates(self.cover_prompts_dir) @@ -1656,7 +1810,15 @@ class GenerateTab(QWidget): self.shop_filter.setEnabled(not running) self.item_filter.setEnabled(not running) self.status_filter.setEnabled(not running) + self.title_template_combo.setEnabled(not running) + self.new_title_template_button.setEnabled(not running) + self.save_title_template_button.setEnabled(not running) + self.title_template_actions_button.setEnabled(not running) + self.save_title_template_as_action.setEnabled(not running) + self.rename_title_template_action.setEnabled(not running) + self.delete_title_template_action.setEnabled(not running) self.save_title_button.setEnabled(not running) + self.cover_template_combo.setEnabled(not running) self.new_cover_template_button.setEnabled(not running) self.save_cover_template_button.setEnabled(not running) self.cover_template_actions_button.setEnabled(not running) diff --git a/docs/04-architecture.md b/docs/04-architecture.md index 03506a8..d182d67 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -417,8 +417,8 @@ data/images///__new. # AI 生成的新 提示词管理: -- **标题提示词**:`data/title_prompt.txt` 是当前工作文本,「保存标题提示词」写入该文件;软件启动时加载该文件回显到输入框(缺失则空)。标题命名模板另存于 `data/prompts/title/*.txt`,只负责把命名模板载入编辑框或保存当前编辑框内容为模板,不改变生成读取路径。T-549 后标题提示词支持 `{旧标题}` 占位符:若提示词含 `{旧标题}`,生成前替换为该任务旧标题且不再自动追加旧标题块;若不含,则保持旧行为自动追加“旧标题:...”块。两种情况都会保留“请只返回新标题,不要解释。”输出约束。 -- **封面提示词**:多模板。下拉选模板(读 `data/prompts/cover/*.txt`),图标工具栏 新建/保存/另存为/重命名/删除;重名校验、删除二次确认、删空给默认。 +- **标题提示词**:`data/title_prompt.txt` 是当前工作文本,「保存标题提示词」写入该文件;软件启动时加载该文件回显到输入框(缺失则空)。标题命名模板另存于 `data/prompts/title/*.txt`,左侧模板行提供下拉、新建、保存模板、另存为、重命名、删除;模板只负责被用户选中后载入编辑框,或把当前编辑框内容保存为命名模板,不改变生成读取路径,也不在启动时覆盖 `title_prompt.txt`。T-549 后标题提示词支持 `{旧标题}` 占位符:若提示词含 `{旧标题}`,生成前替换为该任务旧标题且不再自动追加旧标题块;若不含,则保持旧行为自动追加“旧标题:...”块。两种情况都会保留“请只返回新标题,不要解释。”输出约束。 +- **封面提示词**:多模板。左侧模板行提供下拉(读 `data/prompts/cover/*.txt`)、新建、保存模板、模板操作(另存为/重命名/删除);重名校验、删除二次确认、删空给默认。 - **变量**:标题提示词本阶段只支持 `{旧标题}`,左侧按钮「插入旧标题」在标题提示词光标处插入 `{旧标题}`。封面提示词支持占位符 `{旧标题}`、`{新标题}`、`{商品id}`、`{店铺}`,生成前用该任务真实值替换(`render_prompt`)。「插入标题」= 在封面提示词光标处插入 `{新标题}`;「预览」= 用某条任务的值替换封面变量后展示,确认实际发送给 AI 的内容。 ### 6.3 应用更新(③ Tab) diff --git a/docs/routes.md b/docs/routes.md index 58862bd..0650688 100644 --- a/docs/routes.md +++ b/docs/routes.md @@ -78,24 +78,23 @@ ``` ┌ AI生成 ───────────────────────────────────────────────────────┐ -│ ┌─左 ~1/4─┐ ┌──────────────── 右 3/4 ──────────────────────┐ │ -│ │标题提示词│ │ 批次[本次▼] 店铺[全部▼] 商品ID[____] 状态[全部▼] [筛选] │ │ -│ │[ ]│ │ ┌──────────────────────────────────────────┐ │ │ -│ │[ ]│ │ │ 店铺名 商品id 旧标题 新标题 标题状态 图片状态 │ │ │ -│ │ │ │ │ 女装店 511..639 …T恤 …百搭 已生成 未启用 │ │ │ -│ │封面提示词│ │ │ 女装店 511..640 … — 待生成 待生成 │ │ │ -│ │[ ]│ │ └──────────────────────────────────────────┘ │ │ -│ │[ ]│ │ (双击某行 → 弹窗看 旧封面 | 新封面) │ │ -│ │ │ │ 运行日志:逐条记录标题/封面生成步骤与失败原因 │ │ -│ └──────────┘ └───────────────────────────────────────────────┘ │ +│ 左侧提示词区: │ +│ 标题提示词 │ +│ 模板[▼] [新建] [保存模板] [模板操作▼] │ +│ [标题提示词输入框,较旧版增高] │ +│ [插入旧标题] [保存标题提示词] │ +│ 封面提示词 │ +│ 模板[▼] [新建] [保存模板] [模板操作▼] │ +│ [封面提示词输入框] [插入标题] [预览] │ +│ 右侧任务区:批次/店铺/商品ID/状态筛选 + 任务表 + AI生成运行日志 │ │ 生成内容[只生成标题▼] 标题30/30 ━━━━━ 生标题用时 18 秒 [▶ 开始生成][■停止][重置生成结果] │ │ 图片0/0 ━━━━━ 生图用时 0 秒 │ └───────────────────────────────────────────────────────────────┘ ``` - 左侧(提示词管理,上下两块): - - **标题提示词**:多行输入 + 「保存标题提示词」(写 `data/title_prompt.txt`)+「插入旧标题」(插 `{旧标题}`);启动时自动加载回显。若标题提示词包含 `{旧标题}`,生成前替换为当前任务旧标题且不重复追加旧标题块;不包含时保持旧行为自动追加旧标题。 - - **封面提示词**:模板下拉(读 `data/prompts/cover/*.txt`)+ 图标工具栏(新建/保存/另存为/重命名/删除)+ 多行输入 + 「插入标题」(插 `{新标题}`)/「预览」(变量替换后查看)。 + - **标题提示词**:模板下拉(读 `data/prompts/title/*.txt`)+「新建 / 保存模板 / 模板操作(另存为、重命名、删除)」+ 多行输入 + 下方工具条「插入旧标题」(插 `{旧标题}`)/「保存标题提示词」(写 `data/title_prompt.txt`)。启动时自动加载 `title_prompt.txt` 回显,不自动用模板覆盖工作文本;用户选择模板时才把模板内容载入输入框。若标题提示词包含 `{旧标题}`,生成前替换为当前任务旧标题且不重复追加旧标题块;不包含时保持旧行为自动追加旧标题。 + - **封面提示词**:模板控件压缩到一行(读 `data/prompts/cover/*.txt`,新建/保存模板/另存为/重命名/删除)+ 多行输入 + 下方工具条「插入标题」(插 `{新标题}`)/「预览」(变量替换后查看)。 - 变量:标题提示词本阶段只支持 `{旧标题}`;封面提示词支持 `{旧标题}`/`{新标题}`/`{商品id}`/`{店铺}`,生成前按任务替换。 - 右上:按导入批次 / 店铺 / 商品ID / 状态筛选任务;商品ID输入框按包含匹配 `item_id`,清空表示全部。 - 筛选行提供「打开图片文件夹」按钮,用于只读打开本地图片目录:选中某行时打开该商品所在账号图片文件夹(优先打开已有新/旧封面文件的真实父目录,缺失时回退到规范账号目录);未选行且选择具体批次时打开该批次图片文件夹;未选行且为全部批次时打开图片根目录。目录不存在只中文提示,不自动创建目录,不修改任务状态。 diff --git a/docs/tasks/T-584.md b/docs/tasks/T-584.md index 9bfd92f..b7d5d42 100644 --- a/docs/tasks/T-584.md +++ b/docs/tasks/T-584.md @@ -3,7 +3,7 @@ id: T-584 title: ②左侧提示词区 reorg:标题提示词加模板控件行 + 封面控件压一行 + 插入类按钮下移工具条 + 增高标题输入框 phase: 7 deps: [T-583] -status: TODO +status: DONE created: 2026-07-10 --- @@ -77,4 +77,14 @@ created: 2026-07-10 ## 执行记录 -(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策。) +2026-07-10 Codex: +- `app/gui/tabs/generate.py`:`GenerateTab` 增加 `title_templates_dir` 注入,默认走 `appconfig.title_templates_dir(self.config)`;`ensure_default_prompts` 传入标题模板目录,避免测试或新用户初始化写错目录。标题提示词区新增模板下拉/新建/保存模板/模板操作(另存为/重命名/删除),封面模板控件压缩到一行并把保存按钮文案统一为「保存模板」。标题「插入旧标题」「保存标题提示词」移动到输入框下工具条;封面「插入标题」「预览」保留在封面输入框下工具条。生成运行中会禁用标题/封面模板下拉、模板按钮和菜单 action。 +- 标题模板行为:启动时仍回显 `title_prompt.txt` 工作文本,不自动用默认模板覆盖;用户选择标题模板时才载入到标题输入框。「保存标题提示词」只写 `title_prompt.txt`,「保存模板」只写当前标题模板,未选中模板时进入另存为。 +- `tests/test_gui.py`:覆盖标题模板控件 objectName/菜单、默认标题模板播种、标题模板 CRUD、工作文本与模板保存互不混淆、运行中禁用新增模板控件;同步封面保存按钮新文案。 +- `docs/routes.md`、`docs/04-architecture.md`:同步 ② AI生成左侧提示词区结构、标题模板目录和“选择模板才载入,不覆盖工作文本”的约束。 +- 当前工作树有无关的默认封面提示词/版本号/任务总表本地改动,未纳入本任务;因此先跑不依赖默认封面文件名的关键 GUI 子集。 +- 验证: + - `python -m ruff check app tests main.py` 通过。 + - `py -3.10 -m compileall app main.py` 通过。 + - `git diff --check` 通过。 + - `py -3.10 -m unittest tests.test_gui.GuiTests.test_generate_tab_manages_prompt_files_and_preview tests.test_gui.GuiTests.test_generate_tab_disables_reset_while_generation_running` 通过。 diff --git a/tests/test_gui.py b/tests/test_gui.py index c8e9503..442447b 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -1792,11 +1792,13 @@ class GuiTests(TempDirMixin, unittest.TestCase): def test_generate_tab_has_prompt_editors_and_task_table(self): with self.make_temp_dir() as temp_dir: title_prompt_path = os.path.join(temp_dir, "title_prompt.txt") + title_templates_dir = os.path.join(temp_dir, "prompts", "title") cover_prompts_dir = os.path.join(temp_dir, "prompts", "cover") tab = GenerateTab( config=self.make_config(temp_dir), title_prompt_path=title_prompt_path, cover_prompts_dir=cover_prompts_dir, + title_templates_dir=title_templates_dir, ) self.addCleanup(tab.close) @@ -1804,9 +1806,14 @@ class GuiTests(TempDirMixin, unittest.TestCase): self.assertIsInstance(tab.cover_prompt_edit, QPlainTextEdit) self.assertIsInstance(tab.task_table, QTableView) self.assertIsInstance(tab.generate_mode_combo, QComboBox) + self.assertEqual("titleTemplateCombo", tab.title_template_combo.objectName()) + self.assertEqual("newTitleTemplateButton", tab.new_title_template_button.objectName()) + self.assertEqual("saveTitleTemplateButton", tab.save_title_template_button.objectName()) + self.assertEqual("titleTemplateActionsButton", tab.title_template_actions_button.objectName()) self.assertEqual("标题提示词", tab.title_prompt_edit.placeholderText()) self.assertEqual("封面提示词", tab.cover_prompt_edit.placeholderText()) self.assertEqual("保存标题提示词", tab.save_title_button.text()) + self.assertEqual("保存模板", tab.save_title_template_button.text()) self.assertEqual("插入旧标题", tab.insert_old_title_button.text()) self.assertEqual("开始生成", tab.generate_button.text()) self.assertEqual("停止", tab.stop_generate_button.text()) @@ -1840,8 +1847,17 @@ class GuiTests(TempDirMixin, unittest.TestCase): self.assertEqual(1, tab.title_progress_bar.maximum()) self.assertEqual(1, tab.cover_progress_bar.maximum()) self.assertIn("蝦皮台灣站", tab.title_prompt_edit.toPlainText()) + self.assertEqual("选择模板", tab.title_template_combo.currentText()) + self.assertIn("默认", [tab.title_template_combo.itemText(index) for index in range(tab.title_template_combo.count())]) + self.assertEqual("模板操作", tab.title_template_actions_button.text()) + self.assertIs(tab.title_template_actions_menu, tab.title_template_actions_button.menu()) + self.assertEqual( + ["另存为", "重命名", "删除"], + [action.text() for action in tab.title_template_actions_menu.actions()], + ) self.assertEqual("papa1", tab.cover_template_combo.currentText()) self.assertIn("商品标题:{新标题}", tab.cover_prompt_edit.toPlainText()) + self.assertEqual("保存模板", tab.save_cover_template_button.text()) self.assertEqual("模板操作", tab.cover_template_actions_button.text()) self.assertIs(tab.cover_template_actions_menu, tab.cover_template_actions_button.menu()) self.assertEqual( @@ -2449,12 +2465,14 @@ class GuiTests(TempDirMixin, unittest.TestCase): with self.make_temp_dir() as temp_dir: cfg = self.make_config(temp_dir) title_prompt_path = os.path.join(temp_dir, "title_prompt.txt") + title_templates_dir = os.path.join(temp_dir, "prompts", "title") cover_prompts_dir = os.path.join(temp_dir, "prompts", "cover") tab = GenerateTab( config=cfg, title_prompt_path=title_prompt_path, cover_prompts_dir=cover_prompts_dir, + title_templates_dir=title_templates_dir, ) self.addCleanup(tab.close) @@ -2462,6 +2480,7 @@ class GuiTests(TempDirMixin, unittest.TestCase): self.assertEqual("papa1", tab.cover_template_combo.currentText()) self.assertIn("商品标题:{新标题}", tab.cover_prompt_edit.toPlainText()) self.assertTrue(os.path.exists(title_prompt_path)) + self.assertTrue(os.path.exists(os.path.join(title_templates_dir, "默认.txt"))) self.assertTrue(os.path.exists(os.path.join(cover_prompts_dir, "papa1.txt"))) self.assert_removed(temp_dir) @@ -2470,8 +2489,10 @@ class GuiTests(TempDirMixin, unittest.TestCase): with self.make_temp_dir() as temp_dir: cfg = self.make_config(temp_dir) title_prompt_path = os.path.join(temp_dir, "title_prompt.txt") + title_templates_dir = os.path.join(temp_dir, "prompts", "title") cover_prompts_dir = os.path.join(temp_dir, "prompts", "cover") prompts.save_title_prompt("标题启动回显", title_prompt_path) + prompts.save_title_template("标题基础", "模板标题 {旧标题}", title_templates_dir) prompts.save_cover_template( "基础", "把{旧标题}变成{新标题},商品{商品id},店铺{店铺}", @@ -2502,10 +2523,13 @@ class GuiTests(TempDirMixin, unittest.TestCase): status_callback=statuses.append, title_prompt_path=title_prompt_path, cover_prompts_dir=cover_prompts_dir, + title_templates_dir=title_templates_dir, ) self.addCleanup(tab.close) self.assertEqual("标题启动回显", tab.title_prompt_edit.toPlainText()) + self.assertEqual("选择模板", tab.title_template_combo.currentText()) + self.assertIn("标题基础", prompts.list_title_templates(title_templates_dir)) self.assertEqual("基础", tab.cover_template_combo.currentText()) self.assertIn("{旧标题}", tab.cover_prompt_edit.toPlainText()) @@ -2518,6 +2542,30 @@ class GuiTests(TempDirMixin, unittest.TestCase): tab.title_prompt_edit.setTextCursor(cursor) tab.insert_old_title_placeholder() self.assertEqual("A{旧标题}B", tab.title_prompt_edit.toPlainText()) + self.assertEqual("新标题提示词", prompts.load_title_prompt(title_prompt_path)) + + tab.title_template_combo.setCurrentIndex(tab.title_template_combo.findData("标题基础")) + self.assertEqual("模板标题 {旧标题}", tab.title_prompt_edit.toPlainText()) + tab.title_prompt_edit.setPlainText("标题另存内容") + with mock.patch("app.gui.QInputDialog.getText", return_value=("标题另存", True)): + tab.save_title_template_as_action.trigger() + self.assertEqual("标题另存", tab.title_template_combo.currentText()) + self.assertEqual( + "标题另存内容", + prompts.load_title_template("标题另存", title_templates_dir), + ) + self.assertEqual("新标题提示词", prompts.load_title_prompt(title_prompt_path)) + + with mock.patch("app.gui.QInputDialog.getText", return_value=("标题改名", True)): + tab.rename_title_template_action.trigger() + self.assertEqual("标题改名", tab.title_template_combo.currentText()) + self.assertIn("标题改名", prompts.list_title_templates(title_templates_dir)) + tab.title_prompt_edit.setPlainText("标题模板保存") + tab.save_title_template() + self.assertEqual( + "标题模板保存", + prompts.load_title_template("标题改名", title_templates_dir), + ) tab.cover_prompt_edit.setPlainText("另存模板 {新标题}") with mock.patch("app.gui.QInputDialog.getText", return_value=("另存", True)): @@ -2548,6 +2596,10 @@ class GuiTests(TempDirMixin, unittest.TestCase): tab.delete_cover_template_action.trigger() self.assertNotIn("改名", prompts.list_cover_templates(cover_prompts_dir)) + with mock.patch("app.gui.QMessageBox.question", return_value=gui.QMessageBox.Yes): + tab.delete_title_template_action.trigger() + self.assertNotIn("标题改名", prompts.list_title_templates(title_templates_dir)) + self.assert_removed(temp_dir) def test_cover_gallery_lists_candidates_and_selects_current_cover(self): @@ -3424,6 +3476,14 @@ class GuiTests(TempDirMixin, unittest.TestCase): tab._set_generate_running(True) self.assertFalse(tab.reset_generate_button.isEnabled()) + self.assertFalse(tab.title_template_combo.isEnabled()) + self.assertFalse(tab.new_title_template_button.isEnabled()) + self.assertFalse(tab.save_title_template_button.isEnabled()) + self.assertFalse(tab.title_template_actions_button.isEnabled()) + self.assertFalse(tab.save_title_template_as_action.isEnabled()) + self.assertFalse(tab.rename_title_template_action.isEnabled()) + self.assertFalse(tab.delete_title_template_action.isEnabled()) + self.assertFalse(tab.cover_template_combo.isEnabled()) tab.generate_thread = object() tab.reset_generated_result()