feat: add cmhub settings panel
This commit is contained in:
@@ -25,6 +25,7 @@ from app.gui import (
|
||||
AccountLoginCheckWorker,
|
||||
AccountsTab,
|
||||
AIModelTestWorker,
|
||||
CMHubSettingsWorker,
|
||||
ApplyTab,
|
||||
ApplyWorker,
|
||||
CollectWorker,
|
||||
@@ -705,6 +706,232 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_cmhub_settings_worker_fetches_models_and_balance(self):
|
||||
worker = CMHubSettingsWorker(
|
||||
"https://cmhub.example.com",
|
||||
"sk-cmhub-secret",
|
||||
connect_timeout=7,
|
||||
include_balance=True,
|
||||
)
|
||||
models = [
|
||||
{
|
||||
"alias": "title-standard",
|
||||
"operation_type": "title",
|
||||
"pricing_status": "priced",
|
||||
}
|
||||
]
|
||||
balance = {"user": {"id": "u1"}, "points_balance": 88}
|
||||
|
||||
with mock.patch("app.gui.ai.fetch_cmhub_models", return_value=models) as fetch_models, \
|
||||
mock.patch("app.gui.ai.fetch_cmhub_balance", return_value=balance) as fetch_balance:
|
||||
result = worker.execute()
|
||||
|
||||
fetch_models.assert_called_once_with(
|
||||
"https://cmhub.example.com",
|
||||
"sk-cmhub-secret",
|
||||
connect_timeout=7,
|
||||
)
|
||||
fetch_balance.assert_called_once_with(
|
||||
"https://cmhub.example.com",
|
||||
"sk-cmhub-secret",
|
||||
connect_timeout=7,
|
||||
)
|
||||
self.assertTrue(result["ok"])
|
||||
self.assertEqual(models, result["models"])
|
||||
self.assertEqual(88, result["points_balance"])
|
||||
|
||||
def test_cmhub_settings_worker_redacts_key_on_failure(self):
|
||||
worker = CMHubSettingsWorker(
|
||||
"https://cmhub.example.com",
|
||||
"sk-cmhub-secret",
|
||||
connect_timeout=7,
|
||||
include_balance=False,
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
"app.gui.ai.fetch_cmhub_models",
|
||||
side_effect=RuntimeError("bad key sk-cmhub-secret"),
|
||||
):
|
||||
with self.assertRaises(RuntimeError) as raised:
|
||||
worker.execute()
|
||||
|
||||
self.assertIn("bad key", str(raised.exception))
|
||||
self.assertNotIn("sk-cmhub-secret", str(raised.exception))
|
||||
|
||||
def test_settings_tab_cmhub_backend_panel_saves_config_and_key(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self.make_config(temp_dir)
|
||||
cfg["ai"] = appconfig.default_config()["ai"]
|
||||
cfg["ai"]["backend"] = "cmhub"
|
||||
cfg["ai"]["cmhub"] = {
|
||||
"base_url": "https://cmhub.old",
|
||||
"title_alias": "title-old",
|
||||
"image_alias": "image-old",
|
||||
"connect_timeout": 9,
|
||||
"check_balance_before_batch": False,
|
||||
}
|
||||
cfg["cmhub_config_path"] = os.path.join(temp_dir, "config", "cmhub.json")
|
||||
appconfig.save_cmhub_config(
|
||||
{"api_key": "sk-old-secret"},
|
||||
path=cfg["cmhub_config_path"],
|
||||
)
|
||||
appconfig.save_ai_models_config(
|
||||
{
|
||||
"models": [
|
||||
{
|
||||
"name": "Text A",
|
||||
"category": "text",
|
||||
"enabled": True,
|
||||
"url": "",
|
||||
"model": "",
|
||||
"api_key": "",
|
||||
"api_type": "chat",
|
||||
"connect_timeout_seconds": 30,
|
||||
"timeout_seconds": 0,
|
||||
"extra_body": {},
|
||||
},
|
||||
{
|
||||
"name": "Image A",
|
||||
"category": "image",
|
||||
"enabled": True,
|
||||
"url": "",
|
||||
"model": "",
|
||||
"api_key": "",
|
||||
"api_type": "auto",
|
||||
"connect_timeout_seconds": 30,
|
||||
"timeout_seconds": 0,
|
||||
"extra_body": {},
|
||||
},
|
||||
]
|
||||
},
|
||||
path=cfg["ai_models_path"],
|
||||
)
|
||||
tab = SettingsTab(
|
||||
config=cfg,
|
||||
config_path=cfg["config_path"],
|
||||
ai_models_path=cfg["ai_models_path"],
|
||||
)
|
||||
self.addCleanup(tab.close)
|
||||
|
||||
self.assertEqual("cmhub", tab.backend_combo.currentData())
|
||||
self.assertTrue(tab.model_picker_panel.isHidden())
|
||||
self.assertFalse(tab.cmhub_panel.isHidden())
|
||||
self.assertEqual("https://cmhub.old", tab.cmhub_base_url_edit.text())
|
||||
self.assertEqual("sk-old-secret", tab.cmhub_api_key_edit.text())
|
||||
self.assertEqual(QLineEdit.Password, tab.cmhub_api_key_edit.echoMode())
|
||||
self.assertEqual("title-old", tab.cmhub_title_alias_combo.currentData())
|
||||
self.assertEqual("image-old", tab.cmhub_image_alias_combo.currentData())
|
||||
|
||||
tab.cmhub_base_url_edit.setText("https://cmhub.example.com")
|
||||
tab.cmhub_api_key_edit.setText("sk-new-secret")
|
||||
tab.cmhub_connect_timeout_spin.setValue(12)
|
||||
tab.cmhub_check_balance_checkbox.setChecked(True)
|
||||
tab._populate_cmhub_alias_combos(
|
||||
[
|
||||
{
|
||||
"alias": "title-standard",
|
||||
"operation_type": "title",
|
||||
"requires_image": False,
|
||||
"pricing_status": "priced",
|
||||
"prices": [{"resolution": "1K", "points_cost": 1}],
|
||||
},
|
||||
{
|
||||
"alias": "image-standard",
|
||||
"operation_type": "image",
|
||||
"requires_image": True,
|
||||
"pricing_status": "priced",
|
||||
"prices": [{"resolution": "1K", "points_cost": 5}],
|
||||
},
|
||||
],
|
||||
title_selected="title-standard",
|
||||
image_selected="image-standard",
|
||||
)
|
||||
|
||||
with mock.patch("app.gui.QMessageBox.warning") as warning, \
|
||||
mock.patch("app.gui.QMessageBox.information") as info:
|
||||
tab.save_app_settings()
|
||||
|
||||
warning.assert_called_once()
|
||||
self.assertIn("config/cmhub.json", warning.call_args[0][2])
|
||||
info.assert_called_once_with(tab, "保存设置", "设置已保存")
|
||||
saved = appconfig.load_config(cfg["config_path"])
|
||||
self.assertEqual("cmhub", saved["ai"]["backend"])
|
||||
self.assertEqual("https://cmhub.example.com", saved["ai"]["cmhub"]["base_url"])
|
||||
self.assertEqual("title-standard", saved["ai"]["cmhub"]["title_alias"])
|
||||
self.assertEqual("image-standard", saved["ai"]["cmhub"]["image_alias"])
|
||||
self.assertEqual(12, saved["ai"]["cmhub"]["connect_timeout"])
|
||||
self.assertTrue(saved["ai"]["cmhub"]["check_balance_before_batch"])
|
||||
self.assertEqual(
|
||||
"sk-new-secret",
|
||||
appconfig.get_cmhub_api_key(path=cfg["cmhub_config_path"]),
|
||||
)
|
||||
self.assertTrue(os.path.exists(cfg["ai_models_path"]))
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_settings_tab_cmhub_alias_refresh_filters_unpriced_and_keeps_saved(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self.make_config(temp_dir)
|
||||
cfg["ai"] = appconfig.default_config()["ai"]
|
||||
cfg["ai"]["backend"] = "cmhub"
|
||||
cfg["ai"]["cmhub"] = {
|
||||
"base_url": "https://cmhub.example.com",
|
||||
"title_alias": "title-saved",
|
||||
"image_alias": "image-saved",
|
||||
"connect_timeout": 10,
|
||||
"check_balance_before_batch": False,
|
||||
}
|
||||
cfg["cmhub_config_path"] = os.path.join(temp_dir, "config", "cmhub.json")
|
||||
appconfig.save_cmhub_config({"api_key": "sk-cmhub-secret"}, path=cfg["cmhub_config_path"])
|
||||
tab = SettingsTab(config=cfg, config_path=cfg["config_path"], ai_models_path=cfg["ai_models_path"])
|
||||
self.addCleanup(tab.close)
|
||||
|
||||
tab._on_cmhub_finished(
|
||||
{
|
||||
"ok": True,
|
||||
"points_balance": 55,
|
||||
"models": [
|
||||
{
|
||||
"alias": "title-priced",
|
||||
"operation_type": "title",
|
||||
"requires_image": False,
|
||||
"pricing_status": "priced",
|
||||
"prices": [{"resolution": "512", "points_cost": 1}],
|
||||
},
|
||||
{
|
||||
"alias": "title-free",
|
||||
"operation_type": "title",
|
||||
"pricing_status": "unpriced",
|
||||
"prices": [],
|
||||
},
|
||||
{
|
||||
"alias": "image-priced",
|
||||
"operation_type": "image",
|
||||
"requires_image": True,
|
||||
"pricing_status": "priced",
|
||||
"prices": [{"resolution": "1K", "points_cost": 5}],
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
title_aliases = [
|
||||
tab.cmhub_title_alias_combo.itemData(index)
|
||||
for index in range(tab.cmhub_title_alias_combo.count())
|
||||
]
|
||||
image_labels = [
|
||||
tab.cmhub_image_alias_combo.itemText(index)
|
||||
for index in range(tab.cmhub_image_alias_combo.count())
|
||||
]
|
||||
self.assertIn("title-priced", title_aliases)
|
||||
self.assertIn("title-saved", title_aliases)
|
||||
self.assertNotIn("title-free", title_aliases)
|
||||
self.assertIn("512:1点", tab.cmhub_title_alias_combo.itemText(0))
|
||||
self.assertTrue(any("需参考图" in label for label in image_labels))
|
||||
self.assertIn("余额 55", tab.cmhub_result_label.text())
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_generate_tab_has_prompt_editors_and_task_table(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
title_prompt_path = os.path.join(temp_dir, "title_prompt.txt")
|
||||
|
||||
Reference in New Issue
Block a user