2026-06-22 17:49:59 +08:00
|
|
|
"""Smoke tests for AI outfit panel defaults (offscreen Qt)."""
|
|
|
|
|
import os
|
|
|
|
|
import shutil
|
|
|
|
|
import sys
|
|
|
|
|
import tempfile
|
2026-07-04 16:47:12 +08:00
|
|
|
import time
|
2026-06-22 17:49:59 +08:00
|
|
|
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)
|
|
|
|
|
|
2026-06-22 18:10:54 +08:00
|
|
|
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)
|
|
|
|
|
|
2026-06-23 16:58:16 +08:00
|
|
|
def test_detail_table_compacts_short_columns_and_stretches_title(self):
|
|
|
|
|
from PySide6.QtWidgets import QHeaderView
|
|
|
|
|
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
header = panel._table.horizontalHeader()
|
|
|
|
|
|
|
|
|
|
self.assertEqual(header.sectionResizeMode(0), QHeaderView.Fixed)
|
|
|
|
|
self.assertEqual(header.sectionResizeMode(2), QHeaderView.Fixed)
|
|
|
|
|
self.assertEqual(header.sectionResizeMode(4), QHeaderView.Fixed)
|
|
|
|
|
self.assertEqual(header.sectionResizeMode(1), QHeaderView.Stretch)
|
|
|
|
|
self.assertLessEqual(panel._table.columnWidth(0), 56)
|
|
|
|
|
self.assertLessEqual(panel._table.columnWidth(2), 100)
|
|
|
|
|
self.assertLessEqual(panel._table.columnWidth(4), 90)
|
2026-06-23 17:09:42 +08:00
|
|
|
# §19.26: 衣服图 / 结果·原因 再收窄(仍可手拖)
|
|
|
|
|
self.assertLessEqual(panel._table.columnWidth(3), 90)
|
|
|
|
|
self.assertLessEqual(panel._table.columnWidth(5), 130)
|
2026-06-23 16:58:16 +08:00
|
|
|
|
2026-07-04 17:59:25 +08:00
|
|
|
def test_detail_status_column_uses_semantic_colors(self):
|
|
|
|
|
from core.models import OutfitTask
|
|
|
|
|
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
task = OutfitTask(row_index=2, title="标题", product_id="SKU", garment_path="a.png")
|
|
|
|
|
panel._populate_table([task])
|
|
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
|
"待处理": "#666666",
|
|
|
|
|
"生成中": "#0078d4",
|
|
|
|
|
"完成": "#107c10",
|
|
|
|
|
"失败": "#c42b1c",
|
|
|
|
|
"跳过": "#8a8a8a",
|
|
|
|
|
"停止": "#8a8a8a",
|
|
|
|
|
}
|
|
|
|
|
for status, color in expected.items():
|
|
|
|
|
panel._set_cell(0, 4, status)
|
|
|
|
|
self.assertEqual(panel._table.item(0, 4).foreground().color().name(), color)
|
|
|
|
|
|
2026-06-23 11:44:19 +08:00
|
|
|
def test_title_group_present_no_dropdown_preview_removed(self):
|
|
|
|
|
"""标题生成组存在;无标题模型下拉(配置定名 §17.3);预览块已移除。"""
|
2026-06-23 11:04:38 +08:00
|
|
|
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)
|
2026-06-23 11:44:19 +08:00
|
|
|
self.assertNotIn("标题模型", labels) # 下拉已去掉(§17.3 配置定名)
|
2026-06-23 11:04:38 +08:00
|
|
|
self.assertNotIn("最终生成要求预览", labels)
|
2026-06-23 11:44:19 +08:00
|
|
|
self.assertFalse(hasattr(panel, "_title_model_combo"))
|
2026-06-23 11:04:38 +08:00
|
|
|
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)
|
|
|
|
|
|
2026-07-04 16:47:12 +08:00
|
|
|
def test_title_wait_label_defaults_hidden(self):
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
|
|
|
|
|
self.assertTrue(hasattr(panel, "_title_wait_label"))
|
|
|
|
|
self.assertTrue(panel._title_wait_label.isHidden())
|
|
|
|
|
self.assertEqual(panel._title_wait_label.text(), "")
|
|
|
|
|
|
|
|
|
|
def test_title_wait_timer_formats_elapsed_and_limit(self):
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
|
|
|
|
|
panel._start_title_wait_timer(600)
|
|
|
|
|
panel._title_wait_started_at = time.monotonic() - 95
|
|
|
|
|
panel._refresh_title_wait_label()
|
|
|
|
|
|
|
|
|
|
self.assertFalse(panel._title_wait_label.isHidden())
|
|
|
|
|
self.assertEqual(panel._title_wait_label.text(), "等待中 01:35 / 10:00")
|
2026-07-04 17:04:23 +08:00
|
|
|
self.assertIn("#0078d4", panel._title_wait_label.styleSheet())
|
2026-07-04 16:47:12 +08:00
|
|
|
self.assertTrue(panel._title_wait_timer.isActive())
|
|
|
|
|
panel._reset_title_wait_label()
|
2026-07-04 17:04:23 +08:00
|
|
|
self.assertEqual(panel._title_wait_label.styleSheet(), "")
|
2026-07-04 16:47:12 +08:00
|
|
|
|
|
|
|
|
def test_title_wait_limit_uses_model_timeout_override(self):
|
|
|
|
|
from services.ai_image_service import AiModelConfig
|
|
|
|
|
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
default_cfg = AiModelConfig(
|
|
|
|
|
url="https://r/v1/chat/completions", model="m", api_key="sk",
|
|
|
|
|
api_type="chat", timeout_seconds=0, connect_timeout_seconds=30)
|
|
|
|
|
override_cfg = AiModelConfig(
|
|
|
|
|
url="https://r/v1/chat/completions", model="m", api_key="sk",
|
|
|
|
|
api_type="chat", timeout_seconds=120, connect_timeout_seconds=30)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(panel._title_wait_limit_for_model(default_cfg), 600)
|
|
|
|
|
self.assertEqual(panel._title_wait_limit_for_model(override_cfg), 120)
|
|
|
|
|
|
|
|
|
|
def test_title_progress_switches_wait_label_to_writing(self):
|
|
|
|
|
from core.models import OutfitTask, TitleResult
|
|
|
|
|
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
task = OutfitTask(row_index=2, title="", product_id="SKU", garment_path="a.png")
|
|
|
|
|
panel._populate_table([task])
|
|
|
|
|
panel._start_title_wait_timer(600)
|
|
|
|
|
|
|
|
|
|
panel._on_title_progress(
|
|
|
|
|
1, 3, TitleResult(task=task, success=True, generated_title="新标题"))
|
|
|
|
|
|
|
|
|
|
self.assertFalse(panel._title_wait_timer.isActive())
|
|
|
|
|
self.assertFalse(panel._title_wait_label.isHidden())
|
|
|
|
|
self.assertEqual(panel._title_wait_label.text(), "写入中 1/3")
|
2026-07-04 17:04:23 +08:00
|
|
|
self.assertIn("#107c10", panel._title_wait_label.styleSheet())
|
|
|
|
|
panel._reset_title_wait_label()
|
|
|
|
|
|
|
|
|
|
def test_title_wait_finish_uses_success_and_error_colors(self):
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
|
|
|
|
|
panel._finish_title_wait("完成 3 条", "success")
|
|
|
|
|
self.assertFalse(panel._title_wait_label.isHidden())
|
|
|
|
|
self.assertIn("#107c10", panel._title_wait_label.styleSheet())
|
|
|
|
|
|
|
|
|
|
panel._finish_title_wait("生成失败", "error")
|
|
|
|
|
self.assertIn("#c42b1c", panel._title_wait_label.styleSheet())
|
2026-07-04 16:47:12 +08:00
|
|
|
panel._reset_title_wait_label()
|
|
|
|
|
|
2026-06-23 14:30:57 +08:00
|
|
|
def test_prompt_buttons_save_row_insert_title_and_preview(self):
|
|
|
|
|
"""§19.20: 按钮行含「保存」;编辑框下方保留「插入标题」+「预览最终提示词」。"""
|
|
|
|
|
from PySide6.QtWidgets import QPushButton
|
|
|
|
|
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
panel.apply_config(dict(DEFAULT_CONFIG))
|
|
|
|
|
|
|
|
|
|
texts = [b.text() for b in panel.findChildren(QPushButton)]
|
|
|
|
|
self.assertIn("保存", texts)
|
|
|
|
|
self.assertIn("插入标题", texts)
|
|
|
|
|
self.assertIn("预览最终提示词", texts)
|
|
|
|
|
self.assertNotIn("保存话术", texts) # 旧按钮已改名上移
|
|
|
|
|
|
|
|
|
|
def test_preview_dialog_renders_selected_row(self):
|
|
|
|
|
"""§7.3: 预览弹窗按选中数据行替换 {title} 并附加输出要求。"""
|
|
|
|
|
from openpyxl import Workbook
|
|
|
|
|
from app.widgets.ai_outfit_panel import _OutfitPreviewDialog
|
|
|
|
|
|
|
|
|
|
xlsx = self.tmp / "rows.xlsx"
|
|
|
|
|
wb = Workbook(); ws = wb.active
|
|
|
|
|
ws.append(["标题", "货号", "原始图片路径", "结果", "状态", "原因"])
|
|
|
|
|
ws.append(["纯棉短袖", "TY001", r"D:\img\a.png", "", "", ""])
|
|
|
|
|
wb.save(str(xlsx)); wb.close()
|
|
|
|
|
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
panel.apply_config(dict(DEFAULT_CONFIG))
|
|
|
|
|
panel._excel_edit.setText(str(xlsx))
|
|
|
|
|
panel._prompt_edit.setPlainText("为 {title} 生成")
|
|
|
|
|
panel._set_combo(panel._resolution, "1K")
|
|
|
|
|
|
|
|
|
|
dialog = _OutfitPreviewDialog(panel) # __init__ fills rows + refresh
|
|
|
|
|
# Row dropdown picked up the valid row; pick it.
|
|
|
|
|
self.assertEqual(dialog._combo.count(), 1)
|
|
|
|
|
dialog._combo.setCurrentIndex(0)
|
|
|
|
|
|
|
|
|
|
text = dialog._view.toPlainText()
|
|
|
|
|
self.assertTrue(text.startswith("为 纯棉短袖 生成"))
|
|
|
|
|
self.assertIn("批量生成输出要求", text)
|
|
|
|
|
self.assertIn("参考解析度:1K", text)
|
|
|
|
|
dialog.deleteLater()
|
|
|
|
|
|
2026-06-23 11:44:19 +08:00
|
|
|
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")
|
|
|
|
|
|
2026-06-24 08:58:40 +08:00
|
|
|
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())
|
|
|
|
|
|
2026-06-23 11:44:19 +08:00
|
|
|
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)
|
|
|
|
|
|
2026-06-23 14:59:04 +08:00
|
|
|
def test_find_title_model_image_model_rejected_upfront(self):
|
|
|
|
|
"""§19.21: title_model 命中图片模型(images_edits)→ 开跑前报错。"""
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
panel._models = [
|
|
|
|
|
{"name": "GPT-5.5 文本", "url": "https://r/v1/images/edits",
|
|
|
|
|
"model": "gpt-image", "api_key": "sk-x", "api_type": "images_edits"},
|
|
|
|
|
]
|
|
|
|
|
panel._title_model_name = "GPT-5.5 文本"
|
|
|
|
|
|
|
|
|
|
config, error = panel._find_title_model()
|
|
|
|
|
|
|
|
|
|
self.assertIsNone(config)
|
|
|
|
|
self.assertIn("图片模型", error)
|
|
|
|
|
|
|
|
|
|
def test_find_title_model_image_model_by_url_autodetect_rejected(self):
|
|
|
|
|
"""api_type=auto 但 URL 是 images 端点 → 仍按图片模型拦截。"""
|
|
|
|
|
panel = self._panel()
|
|
|
|
|
panel._models = [
|
|
|
|
|
{"name": "标题模型", "url": "https://r/v1/images/generations",
|
|
|
|
|
"model": "x", "api_key": "sk-x", "api_type": "auto"},
|
|
|
|
|
]
|
|
|
|
|
panel._title_model_name = "标题模型"
|
|
|
|
|
|
|
|
|
|
config, error = panel._find_title_model()
|
|
|
|
|
|
|
|
|
|
self.assertIsNone(config)
|
|
|
|
|
self.assertIn("图片模型", error)
|
|
|
|
|
|
2026-06-22 17:49:59 +08:00
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main()
|