refactor(ai-outfit): 标题模型改为「配置定名」、去掉下拉 (§19.19)

标题模型是「配一次就固定」的,不像分辨率/话术需每次选;左栏 ~360px 已叠两个
提示词组,去掉下拉更干净,还消掉「误选图片模型」的坑。模型名放 app_config.title_model
(默认 GPT-5.5 文本),运行时按名字查 ai_models.json —— 换模型只改配置不改代码(docs/11 §17.3/§17.6)。

- ai_outfit_panel.py:_build_title_group 去掉标题模型下拉;apply_config 改记 title_model
  名字、_emit_config 不再写(UI 不覆盖);_selected_title_model_config → 纯函数
  _find_title_model(按 name 查 + 校验,返回 (config, error))+ UI 包装
  _resolve_title_model_config;找不到/未配置/图片接口时明确报错
- config_service:DEFAULT_CONFIG.title_model 默认改为 "GPT-5.5 文本"
- docs/ai_models.sample.json:补一条 name=「GPT-5.5 文本」的 chat 文本模型样板(占位 key)
- 测试:面板断言无标题模型下拉;_find_title_model 命中/缺失/无模型三例;全套 py37 通过
- 冒烟:seeded ai_models.json + apply_config → title_model 名字解析到 gpt-5.5(chat),缺失→报错

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 11:45:36 +08:00
co-authored by Claude Opus 4.8
parent 2fa488bd21
commit c47e9dee95
6 changed files with 145 additions and 47 deletions
+32 -24
View File
@@ -234,6 +234,7 @@ class AiOutfitPanel(QWidget):
self._title_thread = None # 标题生成线程(§17)
self._title_worker = None
self._title_fail = 0 # running failure count for title stats
self._title_model_name = "" # configured 标题模型 name (§17.3 配置定名)
self._row_to_table = {} # excel row_index -> table row
self._failures = [] # list of OutfitResult (failed)
self._last_resolution = "" # for "value actually changed" check (§10.2)
@@ -373,7 +374,10 @@ class AiOutfitPanel(QWidget):
return scroll
def _build_title_group(self):
"""标题生成组(§17):提示词 + 标题模型下拉 + 保存 + 生成标题。"""
"""标题生成组(§17):提示词 + 保存 + 生成标题。
无标题模型下拉——模型由 app_config.title_model「配置定名」(§17.3/§17.6)。
"""
box = QGroupBox()
v = QVBoxLayout(box)
v.addWidget(QLabel("标题生成提示词"))
@@ -381,13 +385,6 @@ class AiOutfitPanel(QWidget):
self._title_prompt_edit.setMinimumHeight(110)
v.addWidget(self._title_prompt_edit)
model_row = QHBoxLayout()
model_row.addWidget(QLabel("标题模型"))
self._title_model_combo = QComboBox()
self._compact_combo(self._title_model_combo)
model_row.addWidget(self._title_model_combo, stretch=1)
v.addLayout(model_row)
btn_row = QHBoxLayout()
save_title_btn = QPushButton("保存")
save_title_btn.clicked.connect(self._save_title_prompt)
@@ -582,10 +579,11 @@ class AiOutfitPanel(QWidget):
# 标题生成提示词(单份,§17.3)
self._title_prompt_edit.setPlainText(load_title_prompt())
# 标题模型「配置定名」:记下名字,运行时按名查 ai_models.json(§17.3,无下拉)
self._title_model_name = str(config.get("title_model", "") or "")
self._models = load_ai_models()
self._fill_model_combo(self._model_combo, config.get("outfit_model", ""))
self._fill_model_combo(self._title_model_combo, config.get("title_model", ""))
# Snapshot current dropdown values so a later user re-select of the same
# item doesn't trigger the info popup (§10.2).
@@ -622,7 +620,7 @@ class AiOutfitPanel(QWidget):
"outfit_quality": self._quality.currentText(),
"outfit_retry_failed": self._retry_failed_chk.isChecked(),
"outfit_prompt_name": self._current_prompt_name,
"title_model": self._title_model_combo.currentText() if self._models else "",
# title_model 由 app_config 配置定名(§17.3),UI 不写、不覆盖。
})
# -- left actions ---------------------------------------------------
@@ -786,23 +784,34 @@ class AiOutfitPanel(QWidget):
if not silent:
self.statusBar_message("标题提示词已保存")
def _selected_title_model_config(self):
def _find_title_model(self):
"""Resolve the configured 标题模型 → (AiModelConfig|None, error|None). Pure.
Looks up app_config.title_model (a name) in ai_models.json by 'name'
(matching how models are labelled), so 换模型 is a config edit (§17.3).
"""
if not self._models:
QMessageBox.warning(
self, "未配置模型",
"尚未配置 AI 模型。请在 ~/.cmbot/config/ai_models.json 添加后重试。")
return None
idx = self._title_model_combo.currentIndex()
if idx < 0 or idx >= len(self._models):
QMessageBox.warning(self, "未选择标题模型", "请先选择标题模型。")
return None
return None, ("尚未配置 AI 模型。请在 ~/.cmbot/config/ai_models.json "
"添加可生成文字的模型(chat/gemini)后重试。")
name = (self._title_model_name or "").strip()
data = next((m for m in self._models
if (m.get("name") or m.get("model")) == name), None)
if data is None:
return None, ("未找到标题模型「{}」。请在 ai_models.json 添加可生成文字的"
"模型(chat/gemini),并把 app_config 的 title_model 设为它的 "
"name。".format(name or "(未配置)"))
from services.ai_image_service import AiModelConfig, api_config_errors
data = self._models[idx]
errors = api_config_errors(data)
if errors:
QMessageBox.warning(self, "模型配置有误", ";".join(errors))
return None, "标题模型「{}」配置有误:{}".format(name, ";".join(errors))
return AiModelConfig.from_dict(data), None
def _resolve_title_model_config(self):
config, error = self._find_title_model()
if error:
QMessageBox.warning(self, "标题模型不可用", error)
return None
return AiModelConfig.from_dict(data)
return config
def _start_title(self):
if self._thread is not None or self._title_thread is not None:
@@ -811,7 +820,7 @@ class AiOutfitPanel(QWidget):
if not excel:
QMessageBox.information(self, "提示", "请先选择 Excel 文件。")
return
model_config = self._selected_title_model_config()
model_config = self._resolve_title_model_config()
if model_config is None:
return
prompt = self._title_prompt_edit.toPlainText().strip()
@@ -905,7 +914,6 @@ class AiOutfitPanel(QWidget):
self._title_btn.setText("生成中…" if running else "生成标题")
self._start_btn.setEnabled(not running) # mutually exclusive with 开始生成
self._excel_edit.setEnabled(not running)
self._title_model_combo.setEnabled(not running and bool(self._models))
# -- switch info popups (user-only; §10.2) --------------------------
+1 -1
View File
@@ -27,7 +27,7 @@ DEFAULT_CONFIG = {
"outfit_quality": "均衡",
"outfit_retry_failed": False,
"outfit_prompt_name": "默认", # last-selected 话术模板 name (docs/11 §7.2)
"title_model": "", # last-selected 标题模型 name (docs/11 §17.3)
"title_model": "GPT-5.5 文本", # 标题模型名字(对应 ai_models.json 的 name,§17.3 配置定名)
}
_CONFIG_FILENAME = "app_config.json"