T-584 AI生成提示词区重排
This commit is contained in:
+168
-6
@@ -751,6 +751,7 @@ class GenerateTab(QWidget):
|
|||||||
status_callback=None,
|
status_callback=None,
|
||||||
title_prompt_path=None,
|
title_prompt_path=None,
|
||||||
cover_prompts_dir=None,
|
cover_prompts_dir=None,
|
||||||
|
title_templates_dir=None,
|
||||||
open_accounts_callback=None,
|
open_accounts_callback=None,
|
||||||
):
|
):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@@ -765,7 +766,13 @@ class GenerateTab(QWidget):
|
|||||||
self.open_accounts_callback = open_accounts_callback
|
self.open_accounts_callback = open_accounts_callback
|
||||||
self.title_prompt_path = title_prompt_path or appconfig.title_prompt_path(self.config)
|
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)
|
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.current_cover_template = None
|
||||||
self.generate_worker = None
|
self.generate_worker = None
|
||||||
self.generate_thread = None
|
self.generate_thread = None
|
||||||
@@ -781,7 +788,27 @@ class GenerateTab(QWidget):
|
|||||||
self.title_prompt_edit.setPlainText(
|
self.title_prompt_edit.setPlainText(
|
||||||
prompts.load_title_prompt(self.title_prompt_path)
|
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 = QPushButton("保存标题提示词")
|
||||||
|
self.save_title_button.setToolTip("保存当前标题提示词工作文本,生成时会使用这份内容")
|
||||||
self.insert_old_title_button = QPushButton("插入旧标题")
|
self.insert_old_title_button = QPushButton("插入旧标题")
|
||||||
self.insert_old_title_button.setObjectName("insertOldTitleButton")
|
self.insert_old_title_button.setObjectName("insertOldTitleButton")
|
||||||
self.cover_prompt_edit = QPlainTextEdit()
|
self.cover_prompt_edit = QPlainTextEdit()
|
||||||
@@ -790,7 +817,8 @@ class GenerateTab(QWidget):
|
|||||||
self.cover_template_combo = QComboBox()
|
self.cover_template_combo = QComboBox()
|
||||||
self.cover_template_combo.setObjectName("coverTemplateCombo")
|
self.cover_template_combo.setObjectName("coverTemplateCombo")
|
||||||
self.new_cover_template_button = QPushButton("新建")
|
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 = QPushButton("模板操作")
|
||||||
self.cover_template_actions_button.setObjectName("coverTemplateActionsButton")
|
self.cover_template_actions_button.setObjectName("coverTemplateActionsButton")
|
||||||
self.cover_template_actions_menu = QMenu(self)
|
self.cover_template_actions_menu = QMenu(self)
|
||||||
@@ -851,19 +879,24 @@ class GenerateTab(QWidget):
|
|||||||
left_layout = QVBoxLayout(left_panel)
|
left_layout = QVBoxLayout(left_panel)
|
||||||
left_layout.setContentsMargins(0, 0, 12, 0)
|
left_layout.setContentsMargins(0, 0, 12, 0)
|
||||||
left_layout.addWidget(QLabel("标题提示词"))
|
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 = QHBoxLayout()
|
||||||
title_action_layout.addWidget(self.save_title_button)
|
|
||||||
title_action_layout.addWidget(self.insert_old_title_button)
|
title_action_layout.addWidget(self.insert_old_title_button)
|
||||||
|
title_action_layout.addWidget(self.save_title_button)
|
||||||
title_action_layout.addStretch(1)
|
title_action_layout.addStretch(1)
|
||||||
left_layout.addLayout(title_action_layout)
|
left_layout.addLayout(title_action_layout)
|
||||||
left_layout.addWidget(QLabel("封面提示词"))
|
left_layout.addWidget(QLabel("封面提示词"))
|
||||||
left_layout.addWidget(self.cover_template_combo)
|
|
||||||
cover_template_layout = QHBoxLayout()
|
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.new_cover_template_button)
|
||||||
cover_template_layout.addWidget(self.save_cover_template_button)
|
cover_template_layout.addWidget(self.save_cover_template_button)
|
||||||
cover_template_layout.addWidget(self.cover_template_actions_button)
|
cover_template_layout.addWidget(self.cover_template_actions_button)
|
||||||
cover_template_layout.addStretch(1)
|
|
||||||
left_layout.addLayout(cover_template_layout)
|
left_layout.addLayout(cover_template_layout)
|
||||||
left_layout.addWidget(self.cover_prompt_edit, 2)
|
left_layout.addWidget(self.cover_prompt_edit, 2)
|
||||||
cover_action_layout = QHBoxLayout()
|
cover_action_layout = QHBoxLayout()
|
||||||
@@ -981,6 +1014,12 @@ class GenerateTab(QWidget):
|
|||||||
self.status_filter.currentIndexChanged.connect(self.refresh_tasks)
|
self.status_filter.currentIndexChanged.connect(self.refresh_tasks)
|
||||||
self.refresh_button.clicked.connect(self.refresh_tasks)
|
self.refresh_button.clicked.connect(self.refresh_tasks)
|
||||||
self.open_image_dir_button.clicked.connect(self.open_image_directory)
|
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.save_title_button.clicked.connect(self.save_title_prompt)
|
||||||
self.insert_old_title_button.clicked.connect(self.insert_old_title_placeholder)
|
self.insert_old_title_button.clicked.connect(self.insert_old_title_placeholder)
|
||||||
self.cover_template_combo.currentIndexChanged.connect(self.load_selected_cover_template)
|
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.reset_generate_button.clicked.connect(self.reset_generated_result)
|
||||||
self.task_table.doubleClicked.connect(self.show_task_images)
|
self.task_table.doubleClicked.connect(self.show_task_images)
|
||||||
|
|
||||||
|
self.refresh_title_templates()
|
||||||
self.refresh_cover_templates()
|
self.refresh_cover_templates()
|
||||||
self.refresh_tasks()
|
self.refresh_tasks()
|
||||||
self._show_current_run_log_empty()
|
self._show_current_run_log_empty()
|
||||||
@@ -1224,6 +1264,120 @@ class GenerateTab(QWidget):
|
|||||||
return
|
return
|
||||||
self._set_status("标题提示词已保存")
|
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):
|
def refresh_cover_templates(self, selected=None):
|
||||||
try:
|
try:
|
||||||
template_names = prompts.list_cover_templates(self.cover_prompts_dir)
|
template_names = prompts.list_cover_templates(self.cover_prompts_dir)
|
||||||
@@ -1656,7 +1810,15 @@ class GenerateTab(QWidget):
|
|||||||
self.shop_filter.setEnabled(not running)
|
self.shop_filter.setEnabled(not running)
|
||||||
self.item_filter.setEnabled(not running)
|
self.item_filter.setEnabled(not running)
|
||||||
self.status_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.save_title_button.setEnabled(not running)
|
||||||
|
self.cover_template_combo.setEnabled(not running)
|
||||||
self.new_cover_template_button.setEnabled(not running)
|
self.new_cover_template_button.setEnabled(not running)
|
||||||
self.save_cover_template_button.setEnabled(not running)
|
self.save_cover_template_button.setEnabled(not running)
|
||||||
self.cover_template_actions_button.setEnabled(not running)
|
self.cover_template_actions_button.setEnabled(not running)
|
||||||
|
|||||||
@@ -417,8 +417,8 @@ data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.<ext> # AI 生成的新
|
|||||||
|
|
||||||
提示词管理:
|
提示词管理:
|
||||||
|
|
||||||
- **标题提示词**:`data/title_prompt.txt` 是当前工作文本,「保存标题提示词」写入该文件;软件启动时加载该文件回显到输入框(缺失则空)。标题命名模板另存于 `data/prompts/title/*.txt`,只负责把命名模板载入编辑框或保存当前编辑框内容为模板,不改变生成读取路径。T-549 后标题提示词支持 `{旧标题}` 占位符:若提示词含 `{旧标题}`,生成前替换为该任务旧标题且不再自动追加旧标题块;若不含,则保持旧行为自动追加“旧标题:...”块。两种情况都会保留“请只返回新标题,不要解释。”输出约束。
|
- **标题提示词**:`data/title_prompt.txt` 是当前工作文本,「保存标题提示词」写入该文件;软件启动时加载该文件回显到输入框(缺失则空)。标题命名模板另存于 `data/prompts/title/*.txt`,左侧模板行提供下拉、新建、保存模板、另存为、重命名、删除;模板只负责被用户选中后载入编辑框,或把当前编辑框内容保存为命名模板,不改变生成读取路径,也不在启动时覆盖 `title_prompt.txt`。T-549 后标题提示词支持 `{旧标题}` 占位符:若提示词含 `{旧标题}`,生成前替换为该任务旧标题且不再自动追加旧标题块;若不含,则保持旧行为自动追加“旧标题:...”块。两种情况都会保留“请只返回新标题,不要解释。”输出约束。
|
||||||
- **封面提示词**:多模板。下拉选模板(读 `data/prompts/cover/*.txt`),图标工具栏 新建/保存/另存为/重命名/删除;重名校验、删除二次确认、删空给默认。
|
- **封面提示词**:多模板。左侧模板行提供下拉(读 `data/prompts/cover/*.txt`)、新建、保存模板、模板操作(另存为/重命名/删除);重名校验、删除二次确认、删空给默认。
|
||||||
- **变量**:标题提示词本阶段只支持 `{旧标题}`,左侧按钮「插入旧标题」在标题提示词光标处插入 `{旧标题}`。封面提示词支持占位符 `{旧标题}`、`{新标题}`、`{商品id}`、`{店铺}`,生成前用该任务真实值替换(`render_prompt`)。「插入标题」= 在封面提示词光标处插入 `{新标题}`;「预览」= 用某条任务的值替换封面变量后展示,确认实际发送给 AI 的内容。
|
- **变量**:标题提示词本阶段只支持 `{旧标题}`,左侧按钮「插入旧标题」在标题提示词光标处插入 `{旧标题}`。封面提示词支持占位符 `{旧标题}`、`{新标题}`、`{商品id}`、`{店铺}`,生成前用该任务真实值替换(`render_prompt`)。「插入标题」= 在封面提示词光标处插入 `{新标题}`;「预览」= 用某条任务的值替换封面变量后展示,确认实际发送给 AI 的内容。
|
||||||
|
|
||||||
### 6.3 应用更新(③ Tab)
|
### 6.3 应用更新(③ Tab)
|
||||||
|
|||||||
+11
-12
@@ -78,24 +78,23 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌ AI生成 ───────────────────────────────────────────────────────┐
|
┌ AI生成 ───────────────────────────────────────────────────────┐
|
||||||
│ ┌─左 ~1/4─┐ ┌──────────────── 右 3/4 ──────────────────────┐ │
|
│ 左侧提示词区: │
|
||||||
│ │标题提示词│ │ 批次[本次▼] 店铺[全部▼] 商品ID[____] 状态[全部▼] [筛选] │ │
|
│ 标题提示词 │
|
||||||
│ │[ ]│ │ ┌──────────────────────────────────────────┐ │ │
|
│ 模板[▼] [新建] [保存模板] [模板操作▼] │
|
||||||
│ │[ ]│ │ │ 店铺名 商品id 旧标题 新标题 标题状态 图片状态 │ │ │
|
│ [标题提示词输入框,较旧版增高] │
|
||||||
│ │ │ │ │ 女装店 511..639 …T恤 …百搭 已生成 未启用 │ │ │
|
│ [插入旧标题] [保存标题提示词] │
|
||||||
│ │封面提示词│ │ │ 女装店 511..640 … — 待生成 待生成 │ │ │
|
│ 封面提示词 │
|
||||||
│ │[ ]│ │ └──────────────────────────────────────────┘ │ │
|
│ 模板[▼] [新建] [保存模板] [模板操作▼] │
|
||||||
│ │[ ]│ │ (双击某行 → 弹窗看 旧封面 | 新封面) │ │
|
│ [封面提示词输入框] [插入标题] [预览] │
|
||||||
│ │ │ │ 运行日志:逐条记录标题/封面生成步骤与失败原因 │ │
|
│ 右侧任务区:批次/店铺/商品ID/状态筛选 + 任务表 + AI生成运行日志 │
|
||||||
│ └──────────┘ └───────────────────────────────────────────────┘ │
|
|
||||||
│ 生成内容[只生成标题▼] 标题30/30 ━━━━━ 生标题用时 18 秒 [▶ 开始生成][■停止][重置生成结果] │
|
│ 生成内容[只生成标题▼] 标题30/30 ━━━━━ 生标题用时 18 秒 [▶ 开始生成][■停止][重置生成结果] │
|
||||||
│ 图片0/0 ━━━━━ 生图用时 0 秒 │
|
│ 图片0/0 ━━━━━ 生图用时 0 秒 │
|
||||||
└───────────────────────────────────────────────────────────────┘
|
└───────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
- 左侧(提示词管理,上下两块):
|
- 左侧(提示词管理,上下两块):
|
||||||
- **标题提示词**:多行输入 + 「保存标题提示词」(写 `data/title_prompt.txt`)+「插入旧标题」(插 `{旧标题}`);启动时自动加载回显。若标题提示词包含 `{旧标题}`,生成前替换为当前任务旧标题且不重复追加旧标题块;不包含时保持旧行为自动追加旧标题。
|
- **标题提示词**:模板下拉(读 `data/prompts/title/*.txt`)+「新建 / 保存模板 / 模板操作(另存为、重命名、删除)」+ 多行输入 + 下方工具条「插入旧标题」(插 `{旧标题}`)/「保存标题提示词」(写 `data/title_prompt.txt`)。启动时自动加载 `title_prompt.txt` 回显,不自动用模板覆盖工作文本;用户选择模板时才把模板内容载入输入框。若标题提示词包含 `{旧标题}`,生成前替换为当前任务旧标题且不重复追加旧标题块;不包含时保持旧行为自动追加旧标题。
|
||||||
- **封面提示词**:模板下拉(读 `data/prompts/cover/*.txt`)+ 图标工具栏(新建/保存/另存为/重命名/删除)+ 多行输入 + 「插入标题」(插 `{新标题}`)/「预览」(变量替换后查看)。
|
- **封面提示词**:模板控件压缩到一行(读 `data/prompts/cover/*.txt`,新建/保存模板/另存为/重命名/删除)+ 多行输入 + 下方工具条「插入标题」(插 `{新标题}`)/「预览」(变量替换后查看)。
|
||||||
- 变量:标题提示词本阶段只支持 `{旧标题}`;封面提示词支持 `{旧标题}`/`{新标题}`/`{商品id}`/`{店铺}`,生成前按任务替换。
|
- 变量:标题提示词本阶段只支持 `{旧标题}`;封面提示词支持 `{旧标题}`/`{新标题}`/`{商品id}`/`{店铺}`,生成前按任务替换。
|
||||||
- 右上:按导入批次 / 店铺 / 商品ID / 状态筛选任务;商品ID输入框按包含匹配 `item_id`,清空表示全部。
|
- 右上:按导入批次 / 店铺 / 商品ID / 状态筛选任务;商品ID输入框按包含匹配 `item_id`,清空表示全部。
|
||||||
- 筛选行提供「打开图片文件夹」按钮,用于只读打开本地图片目录:选中某行时打开该商品所在账号图片文件夹(优先打开已有新/旧封面文件的真实父目录,缺失时回退到规范账号目录);未选行且选择具体批次时打开该批次图片文件夹;未选行且为全部批次时打开图片根目录。目录不存在只中文提示,不自动创建目录,不修改任务状态。
|
- 筛选行提供「打开图片文件夹」按钮,用于只读打开本地图片目录:选中某行时打开该商品所在账号图片文件夹(优先打开已有新/旧封面文件的真实父目录,缺失时回退到规范账号目录);未选行且选择具体批次时打开该批次图片文件夹;未选行且为全部批次时打开图片根目录。目录不存在只中文提示,不自动创建目录,不修改任务状态。
|
||||||
|
|||||||
+12
-2
@@ -3,7 +3,7 @@ id: T-584
|
|||||||
title: ②左侧提示词区 reorg:标题提示词加模板控件行 + 封面控件压一行 + 插入类按钮下移工具条 + 增高标题输入框
|
title: ②左侧提示词区 reorg:标题提示词加模板控件行 + 封面控件压一行 + 插入类按钮下移工具条 + 增高标题输入框
|
||||||
phase: 7
|
phase: 7
|
||||||
deps: [T-583]
|
deps: [T-583]
|
||||||
status: TODO
|
status: DONE
|
||||||
created: 2026-07-10
|
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` 通过。
|
||||||
|
|||||||
@@ -1792,11 +1792,13 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
def test_generate_tab_has_prompt_editors_and_task_table(self):
|
def test_generate_tab_has_prompt_editors_and_task_table(self):
|
||||||
with self.make_temp_dir() as temp_dir:
|
with self.make_temp_dir() as temp_dir:
|
||||||
title_prompt_path = os.path.join(temp_dir, "title_prompt.txt")
|
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")
|
cover_prompts_dir = os.path.join(temp_dir, "prompts", "cover")
|
||||||
tab = GenerateTab(
|
tab = GenerateTab(
|
||||||
config=self.make_config(temp_dir),
|
config=self.make_config(temp_dir),
|
||||||
title_prompt_path=title_prompt_path,
|
title_prompt_path=title_prompt_path,
|
||||||
cover_prompts_dir=cover_prompts_dir,
|
cover_prompts_dir=cover_prompts_dir,
|
||||||
|
title_templates_dir=title_templates_dir,
|
||||||
)
|
)
|
||||||
self.addCleanup(tab.close)
|
self.addCleanup(tab.close)
|
||||||
|
|
||||||
@@ -1804,9 +1806,14 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
self.assertIsInstance(tab.cover_prompt_edit, QPlainTextEdit)
|
self.assertIsInstance(tab.cover_prompt_edit, QPlainTextEdit)
|
||||||
self.assertIsInstance(tab.task_table, QTableView)
|
self.assertIsInstance(tab.task_table, QTableView)
|
||||||
self.assertIsInstance(tab.generate_mode_combo, QComboBox)
|
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.title_prompt_edit.placeholderText())
|
||||||
self.assertEqual("封面提示词", tab.cover_prompt_edit.placeholderText())
|
self.assertEqual("封面提示词", tab.cover_prompt_edit.placeholderText())
|
||||||
self.assertEqual("保存标题提示词", tab.save_title_button.text())
|
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.insert_old_title_button.text())
|
||||||
self.assertEqual("开始生成", tab.generate_button.text())
|
self.assertEqual("开始生成", tab.generate_button.text())
|
||||||
self.assertEqual("停止", tab.stop_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.title_progress_bar.maximum())
|
||||||
self.assertEqual(1, tab.cover_progress_bar.maximum())
|
self.assertEqual(1, tab.cover_progress_bar.maximum())
|
||||||
self.assertIn("蝦皮台灣站", tab.title_prompt_edit.toPlainText())
|
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.assertEqual("papa1", tab.cover_template_combo.currentText())
|
||||||
self.assertIn("商品标题:{新标题}", tab.cover_prompt_edit.toPlainText())
|
self.assertIn("商品标题:{新标题}", tab.cover_prompt_edit.toPlainText())
|
||||||
|
self.assertEqual("保存模板", tab.save_cover_template_button.text())
|
||||||
self.assertEqual("模板操作", tab.cover_template_actions_button.text())
|
self.assertEqual("模板操作", tab.cover_template_actions_button.text())
|
||||||
self.assertIs(tab.cover_template_actions_menu, tab.cover_template_actions_button.menu())
|
self.assertIs(tab.cover_template_actions_menu, tab.cover_template_actions_button.menu())
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -2449,12 +2465,14 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
with self.make_temp_dir() as temp_dir:
|
with self.make_temp_dir() as temp_dir:
|
||||||
cfg = self.make_config(temp_dir)
|
cfg = self.make_config(temp_dir)
|
||||||
title_prompt_path = os.path.join(temp_dir, "title_prompt.txt")
|
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")
|
cover_prompts_dir = os.path.join(temp_dir, "prompts", "cover")
|
||||||
|
|
||||||
tab = GenerateTab(
|
tab = GenerateTab(
|
||||||
config=cfg,
|
config=cfg,
|
||||||
title_prompt_path=title_prompt_path,
|
title_prompt_path=title_prompt_path,
|
||||||
cover_prompts_dir=cover_prompts_dir,
|
cover_prompts_dir=cover_prompts_dir,
|
||||||
|
title_templates_dir=title_templates_dir,
|
||||||
)
|
)
|
||||||
self.addCleanup(tab.close)
|
self.addCleanup(tab.close)
|
||||||
|
|
||||||
@@ -2462,6 +2480,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
self.assertEqual("papa1", tab.cover_template_combo.currentText())
|
self.assertEqual("papa1", tab.cover_template_combo.currentText())
|
||||||
self.assertIn("商品标题:{新标题}", tab.cover_prompt_edit.toPlainText())
|
self.assertIn("商品标题:{新标题}", tab.cover_prompt_edit.toPlainText())
|
||||||
self.assertTrue(os.path.exists(title_prompt_path))
|
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.assertTrue(os.path.exists(os.path.join(cover_prompts_dir, "papa1.txt")))
|
||||||
|
|
||||||
self.assert_removed(temp_dir)
|
self.assert_removed(temp_dir)
|
||||||
@@ -2470,8 +2489,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
with self.make_temp_dir() as temp_dir:
|
with self.make_temp_dir() as temp_dir:
|
||||||
cfg = self.make_config(temp_dir)
|
cfg = self.make_config(temp_dir)
|
||||||
title_prompt_path = os.path.join(temp_dir, "title_prompt.txt")
|
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")
|
cover_prompts_dir = os.path.join(temp_dir, "prompts", "cover")
|
||||||
prompts.save_title_prompt("标题启动回显", title_prompt_path)
|
prompts.save_title_prompt("标题启动回显", title_prompt_path)
|
||||||
|
prompts.save_title_template("标题基础", "模板标题 {旧标题}", title_templates_dir)
|
||||||
prompts.save_cover_template(
|
prompts.save_cover_template(
|
||||||
"基础",
|
"基础",
|
||||||
"把{旧标题}变成{新标题},商品{商品id},店铺{店铺}",
|
"把{旧标题}变成{新标题},商品{商品id},店铺{店铺}",
|
||||||
@@ -2502,10 +2523,13 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
status_callback=statuses.append,
|
status_callback=statuses.append,
|
||||||
title_prompt_path=title_prompt_path,
|
title_prompt_path=title_prompt_path,
|
||||||
cover_prompts_dir=cover_prompts_dir,
|
cover_prompts_dir=cover_prompts_dir,
|
||||||
|
title_templates_dir=title_templates_dir,
|
||||||
)
|
)
|
||||||
self.addCleanup(tab.close)
|
self.addCleanup(tab.close)
|
||||||
|
|
||||||
self.assertEqual("标题启动回显", tab.title_prompt_edit.toPlainText())
|
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.assertEqual("基础", tab.cover_template_combo.currentText())
|
||||||
self.assertIn("{旧标题}", tab.cover_prompt_edit.toPlainText())
|
self.assertIn("{旧标题}", tab.cover_prompt_edit.toPlainText())
|
||||||
|
|
||||||
@@ -2518,6 +2542,30 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
tab.title_prompt_edit.setTextCursor(cursor)
|
tab.title_prompt_edit.setTextCursor(cursor)
|
||||||
tab.insert_old_title_placeholder()
|
tab.insert_old_title_placeholder()
|
||||||
self.assertEqual("A{旧标题}B", tab.title_prompt_edit.toPlainText())
|
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("另存模板 {新标题}")
|
tab.cover_prompt_edit.setPlainText("另存模板 {新标题}")
|
||||||
with mock.patch("app.gui.QInputDialog.getText", return_value=("另存", True)):
|
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()
|
tab.delete_cover_template_action.trigger()
|
||||||
self.assertNotIn("改名", prompts.list_cover_templates(cover_prompts_dir))
|
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)
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
def test_cover_gallery_lists_candidates_and_selects_current_cover(self):
|
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)
|
tab._set_generate_running(True)
|
||||||
self.assertFalse(tab.reset_generate_button.isEnabled())
|
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.generate_thread = object()
|
||||||
tab.reset_generated_result()
|
tab.reset_generated_result()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user