标题模型是「配一次就固定」的,不像分辨率/话术需每次选;左栏 ~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>
143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
"""Smoke tests for AI outfit panel defaults (offscreen Qt)."""
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
|
|
import services.file_service as fs
|
|
from services.config_service import DEFAULT_CONFIG
|
|
|
|
|
|
class TestAiOutfitPanelDefaults(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = Path(tempfile.mkdtemp())
|
|
self._env = os.environ.get("CMBOT_DATA_DIR")
|
|
os.environ["CMBOT_DATA_DIR"] = str(self.tmp / "data")
|
|
self.app_dir = self.tmp / "app"
|
|
self.app_dir.mkdir(parents=True)
|
|
self._orig_get_app_dir = fs.get_app_dir
|
|
fs.get_app_dir = lambda: self.app_dir
|
|
self.app = self._app()
|
|
|
|
def tearDown(self):
|
|
fs.get_app_dir = self._orig_get_app_dir
|
|
if self._env is None:
|
|
os.environ.pop("CMBOT_DATA_DIR", None)
|
|
else:
|
|
os.environ["CMBOT_DATA_DIR"] = self._env
|
|
shutil.rmtree(str(self.tmp), ignore_errors=True)
|
|
|
|
def _app(self):
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
app = QApplication.instance()
|
|
if app is None:
|
|
app = QApplication([])
|
|
return app
|
|
|
|
def _panel(self):
|
|
from app.widgets.ai_outfit_panel import AiOutfitPanel
|
|
|
|
return AiOutfitPanel()
|
|
|
|
def test_empty_config_defaults_to_outfit_output_dir(self):
|
|
panel = self._panel()
|
|
cfg = dict(DEFAULT_CONFIG)
|
|
cfg["outfit_output_dir"] = ""
|
|
|
|
panel.apply_config(cfg)
|
|
|
|
self.assertEqual(Path(panel._output_edit.text()), self.app_dir / "穿搭图片")
|
|
|
|
def test_saved_outfit_output_dir_takes_precedence(self):
|
|
panel = self._panel()
|
|
saved = self.tmp / "custom-output"
|
|
cfg = dict(DEFAULT_CONFIG)
|
|
cfg["outfit_output_dir"] = str(saved)
|
|
|
|
panel.apply_config(cfg)
|
|
|
|
self.assertEqual(Path(panel._output_edit.text()), saved)
|
|
|
|
def test_generation_setting_label_is_image_concurrency(self):
|
|
from PySide6.QtWidgets import QLabel
|
|
|
|
panel = self._panel()
|
|
|
|
labels = [label.text() for label in panel.findChildren(QLabel)]
|
|
|
|
self.assertIn("图片并发数", labels)
|
|
|
|
def test_title_group_present_no_dropdown_preview_removed(self):
|
|
"""标题生成组存在;无标题模型下拉(配置定名 §17.3);预览块已移除。"""
|
|
from PySide6.QtWidgets import QLabel
|
|
|
|
panel = self._panel()
|
|
panel.apply_config(dict(DEFAULT_CONFIG))
|
|
|
|
labels = [label.text() for label in panel.findChildren(QLabel)]
|
|
self.assertIn("标题生成提示词", labels)
|
|
self.assertNotIn("标题模型", labels) # 下拉已去掉(§17.3 配置定名)
|
|
self.assertNotIn("最终生成要求预览", labels)
|
|
self.assertFalse(hasattr(panel, "_title_model_combo"))
|
|
self.assertFalse(hasattr(panel, "_preview_view"))
|
|
self.assertFalse(hasattr(panel, "_sample_combo"))
|
|
self.assertTrue(hasattr(panel, "_title_btn"))
|
|
|
|
def test_title_prompt_defaults_loaded(self):
|
|
from services.config_service import DEFAULT_TITLE_PROMPT
|
|
|
|
panel = self._panel()
|
|
panel.apply_config(dict(DEFAULT_CONFIG))
|
|
|
|
self.assertEqual(panel._title_prompt_edit.toPlainText(), DEFAULT_TITLE_PROMPT)
|
|
|
|
def test_find_title_model_resolves_by_config_name(self):
|
|
"""title_model 名字命中 ai_models.json → 返回 AiModelConfig。"""
|
|
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": "图片模型", "url": "https://r/v1/images/edits",
|
|
"model": "img", "api_key": "sk-y", "api_type": "images_edits"},
|
|
]
|
|
panel._title_model_name = "GPT-5.5 文本"
|
|
|
|
config, error = panel._find_title_model()
|
|
|
|
self.assertIsNone(error)
|
|
self.assertIsInstance(config, AiModelConfig)
|
|
self.assertEqual(config.model, "gpt-5.5")
|
|
|
|
def test_find_title_model_missing_name_errors(self):
|
|
panel = self._panel()
|
|
panel._models = [{"name": "别的模型", "url": "https://r/v1/chat/completions",
|
|
"model": "m", "api_key": "sk-x", "api_type": "chat"}]
|
|
panel._title_model_name = "GPT-5.5 文本"
|
|
|
|
config, error = panel._find_title_model()
|
|
|
|
self.assertIsNone(config)
|
|
self.assertIn("GPT-5.5 文本", error)
|
|
|
|
def test_find_title_model_no_models_errors(self):
|
|
panel = self._panel()
|
|
panel._models = []
|
|
panel._title_model_name = "GPT-5.5 文本"
|
|
|
|
config, error = panel._find_title_model()
|
|
|
|
self.assertIsNone(config)
|
|
self.assertTrue(error)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|