fix: filter title model from outfit picker
This commit is contained in:
@@ -698,15 +698,26 @@ class AiOutfitPanel(QWidget):
|
||||
self._last_model = self._model_combo.currentText() if self._models else ""
|
||||
|
||||
def _fill_model_combo(self, combo, selected_name):
|
||||
"""Fill a model dropdown from self._models (shared by 图片/标题 model, §17.3)."""
|
||||
"""Fill the image model dropdown, excluding the configured title model."""
|
||||
combo.clear()
|
||||
if not self._models:
|
||||
combo.addItem("(未配置模型,请在 ai_models.json 添加)")
|
||||
combo.setEnabled(False)
|
||||
return
|
||||
|
||||
title_model_name = (self._title_model_name or "").strip()
|
||||
image_models = [
|
||||
m for m in self._models
|
||||
if (m.get("name") or m.get("model")) != title_model_name
|
||||
]
|
||||
if not image_models:
|
||||
combo.addItem("(未配置可用图片模型,请在 ai_models.json 添加)")
|
||||
combo.setEnabled(False)
|
||||
return
|
||||
|
||||
combo.setEnabled(True)
|
||||
for m in self._models:
|
||||
combo.addItem(m.get("name") or m.get("model") or "(未命名)")
|
||||
for m in image_models:
|
||||
combo.addItem(m.get("name") or m.get("model") or "(未命名)", m)
|
||||
self._set_combo(combo, selected_name)
|
||||
|
||||
def _set_combo(self, combo, value):
|
||||
@@ -718,7 +729,8 @@ class AiOutfitPanel(QWidget):
|
||||
self.config_changed.emit({
|
||||
"outfit_excel": self._excel_edit.text(),
|
||||
"outfit_output_dir": self._output_edit.text(),
|
||||
"outfit_model": self._model_combo.currentText() if self._models else "",
|
||||
"outfit_model": self._model_combo.currentText()
|
||||
if self._model_combo.currentData() else "",
|
||||
"outfit_concurrency": self._concurrency.value(),
|
||||
"outfit_request_interval": self._interval.value(),
|
||||
"outfit_task_cooldown": self._cooldown.value(),
|
||||
@@ -1073,9 +1085,9 @@ class AiOutfitPanel(QWidget):
|
||||
return
|
||||
self._last_model = name
|
||||
api_type = "auto"
|
||||
idx = self._model_combo.currentIndex()
|
||||
if self._models and 0 <= idx < len(self._models):
|
||||
api_type = self._models[idx].get("api_type", "auto") or "auto"
|
||||
data = self._model_combo.currentData()
|
||||
if isinstance(data, dict):
|
||||
api_type = data.get("api_type", "auto") or "auto"
|
||||
QMessageBox.information(
|
||||
self, "AI 模型已切换",
|
||||
"已切换模型到 {}。\n\n"
|
||||
@@ -1158,12 +1170,12 @@ class AiOutfitPanel(QWidget):
|
||||
self._stop_btn.setText("停止中…")
|
||||
|
||||
def _selected_model_config(self):
|
||||
if not self._models:
|
||||
data = self._model_combo.currentData()
|
||||
if not isinstance(data, dict):
|
||||
QMessageBox.warning(
|
||||
self, "未配置模型",
|
||||
"尚未配置 AI 模型。请在 ~/.cmbot/config/ai_models.json 添加后重试。")
|
||||
"尚未配置可用图片 AI 模型。请在 ~/.cmbot/config/ai_models.json 添加后重试。")
|
||||
return None
|
||||
data = self._models[self._model_combo.currentIndex()]
|
||||
from services.ai_image_service import AiModelConfig, api_config_errors
|
||||
errors = api_config_errors(data)
|
||||
if errors:
|
||||
@@ -1176,7 +1188,7 @@ class AiOutfitPanel(QWidget):
|
||||
self._stop_btn.setEnabled(running)
|
||||
self._stop_btn.setText("停止生成") # reset 「停止中…」 (§19.24)
|
||||
self._excel_edit.setEnabled(not running)
|
||||
self._model_combo.setEnabled(not running and bool(self._models))
|
||||
self._model_combo.setEnabled(not running and bool(self._model_combo.currentData()))
|
||||
self._title_btn.setEnabled(not running) # mutually exclusive with 生成标题
|
||||
|
||||
# -- worker callbacks (UI thread) -----------------------------------
|
||||
|
||||
@@ -1537,7 +1537,7 @@
|
||||
任务:
|
||||
|
||||
- [x] 文档更新:`docs/11-ai-outfit.md` 已明确右栏图片 `AI 模型` 下拉填充时跳过 `name == app_config.title_model` 的模型;不新增 `usage` 字段
|
||||
- [ ] `ai_outfit_panel.py`:`_fill_model_combo()` 按 `self._title_model_name` 过滤图片模型下拉;标题模型仍保留在 `self._models` 中供 `_resolve_title_model_config()` 使用
|
||||
- [ ] 行为:如果过滤后没有可选图片模型,显示「未配置可用图片模型」类占位并保持开始生成前校验提示
|
||||
- [ ] 测试:标题模型不出现在图片 AI 模型下拉;图片模型仍显示;标题生成仍能按 `title_model` 找到同名模型;只有标题模型时下拉为空/占位
|
||||
- [ ] 验证:`test_ai_outfit_panel.py`、全套测试、离屏启动 AI 穿搭页
|
||||
- [x] `ai_outfit_panel.py`:`_fill_model_combo()` 按 `self._title_model_name` 过滤图片模型下拉;标题模型仍保留在 `self._models` 中供 `_resolve_title_model_config()` 使用
|
||||
- [x] 行为:如果过滤后没有可选图片模型,显示「未配置可用图片模型」类占位并保持开始生成前校验提示
|
||||
- [x] 测试:标题模型不出现在图片 AI 模型下拉;图片模型仍显示;标题生成仍能按 `title_model` 找到同名模型;只有标题模型时下拉为空/占位
|
||||
- [x] 验证:`py_compile`、`test_ai_outfit_panel.py`、全套 `python -m unittest discover -s tests`、离屏启动 AI 穿搭页通过
|
||||
|
||||
@@ -174,6 +174,81 @@ class TestAiOutfitPanelDefaults(unittest.TestCase):
|
||||
self.assertIsInstance(config, AiModelConfig)
|
||||
self.assertEqual(config.model, "gpt-5.5")
|
||||
|
||||
def test_image_model_combo_filters_configured_title_model(self):
|
||||
"""§19.29: 图片 AI 模型下拉不显示 app_config.title_model 同名模型。"""
|
||||
panel = self._panel()
|
||||
panel._models = [
|
||||
{"name": "GPT-5.5 文本", "url": "https://r/v1/chat/completions",
|
||||
"model": "gpt-5.5", "api_key": "sk-x", "api_type": "chat"},
|
||||
{"name": "GPT Image 2", "url": "https://r/v1/images/edits",
|
||||
"model": "gpt-image", "api_key": "sk-y", "api_type": "images_edits"},
|
||||
{"name": "Nano Banana 2", "url": "https://r/v1/chat/completions",
|
||||
"model": "nano", "api_key": "sk-z", "api_type": "auto"},
|
||||
]
|
||||
panel._title_model_name = "GPT-5.5 文本"
|
||||
|
||||
panel._fill_model_combo(panel._model_combo, "GPT-5.5 文本")
|
||||
|
||||
names = [panel._model_combo.itemText(i) for i in range(panel._model_combo.count())]
|
||||
self.assertNotIn("GPT-5.5 文本", names)
|
||||
self.assertEqual(names, ["GPT Image 2", "Nano Banana 2"])
|
||||
self.assertEqual(panel._model_combo.currentText(), "GPT Image 2")
|
||||
self.assertTrue(panel._model_combo.isEnabled())
|
||||
self.assertEqual(panel._model_combo.currentData()["name"], "GPT Image 2")
|
||||
|
||||
def test_selected_model_config_uses_filtered_combo_data(self):
|
||||
"""Filtered combo indices no longer match self._models; use item data."""
|
||||
panel = self._panel()
|
||||
panel._models = [
|
||||
{"name": "GPT-5.5 文本", "url": "https://r/v1/chat/completions",
|
||||
"model": "gpt-5.5", "api_key": "sk-x", "api_type": "chat"},
|
||||
{"name": "GPT Image 2", "url": "https://r/v1/images/edits",
|
||||
"model": "gpt-image", "api_key": "sk-y", "api_type": "images_edits"},
|
||||
{"name": "Nano Banana 2", "url": "https://r/v1/chat/completions",
|
||||
"model": "nano", "api_key": "sk-z", "api_type": "auto"},
|
||||
]
|
||||
panel._title_model_name = "GPT-5.5 文本"
|
||||
|
||||
panel._fill_model_combo(panel._model_combo, "Nano Banana 2")
|
||||
config = panel._selected_model_config()
|
||||
|
||||
self.assertEqual(config.model, "nano")
|
||||
|
||||
def test_image_model_combo_keeps_title_model_available_for_title_generation(self):
|
||||
"""Filtering the image dropdown must not remove the title model from self._models."""
|
||||
from services.ai_image_service import AiModelConfig
|
||||
|
||||
panel = self._panel()
|
||||
panel._models = [
|
||||
{"name": "GPT-5.5 文本", "url": "https://r/v1/chat/completions",
|
||||
"model": "gpt-5.5", "api_key": "sk-x", "api_type": "chat"},
|
||||
{"name": "GPT Image 2", "url": "https://r/v1/images/edits",
|
||||
"model": "gpt-image", "api_key": "sk-y", "api_type": "images_edits"},
|
||||
]
|
||||
panel._title_model_name = "GPT-5.5 文本"
|
||||
|
||||
panel._fill_model_combo(panel._model_combo, "GPT Image 2")
|
||||
config, error = panel._find_title_model()
|
||||
|
||||
self.assertIsNone(error)
|
||||
self.assertIsInstance(config, AiModelConfig)
|
||||
self.assertEqual(config.model, "gpt-5.5")
|
||||
|
||||
def test_image_model_combo_only_title_model_shows_placeholder(self):
|
||||
panel = self._panel()
|
||||
panel._models = [
|
||||
{"name": "GPT-5.5 文本", "url": "https://r/v1/chat/completions",
|
||||
"model": "gpt-5.5", "api_key": "sk-x", "api_type": "chat"},
|
||||
]
|
||||
panel._title_model_name = "GPT-5.5 文本"
|
||||
|
||||
panel._fill_model_combo(panel._model_combo, "")
|
||||
|
||||
self.assertEqual(panel._model_combo.count(), 1)
|
||||
self.assertIn("未配置可用图片模型", panel._model_combo.currentText())
|
||||
self.assertFalse(panel._model_combo.isEnabled())
|
||||
self.assertIsNone(panel._model_combo.currentData())
|
||||
|
||||
def test_find_title_model_missing_name_errors(self):
|
||||
panel = self._panel()
|
||||
panel._models = [{"name": "别的模型", "url": "https://r/v1/chat/completions",
|
||||
|
||||
Reference in New Issue
Block a user