feat(settings): support gateway source switching
This commit is contained in:
+89
-24
@@ -5,6 +5,8 @@ from __future__ import annotations
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
|
||||
from PySide6.QtCore import Signal
|
||||
|
||||
from ... import ai as ai_module
|
||||
from ... import chrome
|
||||
from ... import cmhub_models
|
||||
@@ -14,7 +16,7 @@ from ..workers import CMHubSettingsWorker as _RealCMHubSettingsWorker
|
||||
|
||||
|
||||
PLAINTEXT_CMHUB_API_KEY_WARNING = (
|
||||
"cmhub API Key 会以本地明文保存到 data/config/cmhub.json,仅供本机调用 cmhub 网关使用。"
|
||||
"默认网关 API Key 会以本地明文保存到 data/config/cmhub.json,仅供本机调用默认网关使用。"
|
||||
"该文件已 gitignore,UI 打码显示,日志/导出不记录明文。"
|
||||
)
|
||||
|
||||
@@ -29,7 +31,9 @@ def CMHubSettingsWorker(*args, **kwargs):
|
||||
class SettingsTab(QWidget):
|
||||
"""Tab 5: AI model definitions stored in data/config/ai_models.json."""
|
||||
|
||||
BACKEND_ITEMS = [("直连模型", "direct"), ("cmhub 网关", "cmhub")]
|
||||
settingsSaved = Signal(str)
|
||||
|
||||
BACKEND_ITEMS = [("默认网关", "cmhub"), ("自定义网关", "direct")]
|
||||
CATEGORY_ITEMS = [("文本", "text"), ("图像", "image")]
|
||||
API_TYPE_ITEMS = [("chat", "chat"), ("images_edits", "images_edits"), ("auto", "auto")]
|
||||
RESOLUTION_ITEMS = ["512", "1k", "2k", "4k"]
|
||||
@@ -77,13 +81,37 @@ class SettingsTab(QWidget):
|
||||
for label, value in self.BACKEND_ITEMS:
|
||||
self.backend_combo.addItem(label, value)
|
||||
self.backend_combo.setVisible(False)
|
||||
self.gateway_default_button = QPushButton("默认网关")
|
||||
self.gateway_default_button.setObjectName("gatewayDefaultButton")
|
||||
self.gateway_default_button.setCheckable(True)
|
||||
self.gateway_custom_button = QPushButton("自定义网关")
|
||||
self.gateway_custom_button.setObjectName("gatewayCustomButton")
|
||||
self.gateway_custom_button.setCheckable(True)
|
||||
self.gateway_button_group = QButtonGroup(self)
|
||||
self.gateway_button_group.setExclusive(True)
|
||||
self.gateway_button_group.addButton(self.gateway_default_button)
|
||||
self.gateway_button_group.addButton(self.gateway_custom_button)
|
||||
self.gateway_selector = QWidget()
|
||||
self.gateway_selector.setObjectName("gatewaySourceSelector")
|
||||
gateway_selector_layout = QHBoxLayout(self.gateway_selector)
|
||||
gateway_selector_layout.setContentsMargins(0, 0, 0, 0)
|
||||
gateway_selector_layout.setSpacing(0)
|
||||
gateway_selector_layout.addWidget(self.gateway_default_button)
|
||||
gateway_selector_layout.addWidget(self.gateway_custom_button)
|
||||
self.gateway_selector.setStyleSheet(
|
||||
"QPushButton { min-width: 108px; padding: 6px 12px; border: 1px solid #b8c0ca; "
|
||||
"background: #f6f8fa; color: #57606a; }"
|
||||
"QPushButton:first-child { border-top-left-radius: 4px; border-bottom-left-radius: 4px; }"
|
||||
"QPushButton:last-child { border-left: 0; border-top-right-radius: 4px; border-bottom-right-radius: 4px; }"
|
||||
"QPushButton:checked { background: #0969da; border-color: #0969da; color: white; font-weight: 600; }"
|
||||
)
|
||||
self.cmhub_base_url_edit = QLineEdit()
|
||||
self.cmhub_base_url_edit.setObjectName("cmhubBaseUrlEdit")
|
||||
self.cmhub_base_url_edit.setPlaceholderText("https://host(不要带 /api 或 /api/v1)")
|
||||
self.cmhub_api_key_edit = QLineEdit()
|
||||
self.cmhub_api_key_edit.setObjectName("cmhubApiKeyEdit")
|
||||
self.cmhub_api_key_edit.setEchoMode(QLineEdit.Password)
|
||||
self.cmhub_api_key_edit.setPlaceholderText("从 cmhub 网页端复制 API Key")
|
||||
self.cmhub_api_key_edit.setPlaceholderText("从默认网关网页端复制 API Key")
|
||||
self.cmhub_title_alias_combo = QComboBox()
|
||||
self.cmhub_title_alias_combo.setObjectName("cmhubTitleAliasCombo")
|
||||
self.cmhub_image_alias_combo = QComboBox()
|
||||
@@ -103,10 +131,10 @@ class SettingsTab(QWidget):
|
||||
self.cmhub_result_label = QLabel("")
|
||||
self.cmhub_result_label.setObjectName("cmhubResultLabel")
|
||||
self.cmhub_result_label.setWordWrap(True)
|
||||
self.cmhub_base_url_hint_label = QLabel("Base URL 只填网关根,如 https://host;不要带 /api 或 /api/v1。")
|
||||
self.cmhub_base_url_hint_label = QLabel("Base URL 只填默认网关根,如 https://host;不要带 /api 或 /api/v1。")
|
||||
self.cmhub_base_url_hint_label.setObjectName("cmhubBaseUrlHintLabel")
|
||||
self.cmhub_base_url_hint_label.setWordWrap(True)
|
||||
self.cmhub_key_hint_label = QLabel("API Key 仅在 cmhub 网页端创建时显示一次;复制到此处后会本地明文保存并打码显示。")
|
||||
self.cmhub_key_hint_label = QLabel("API Key 仅在默认网关网页端创建时显示一次;复制到此处后会本地明文保存并打码显示。")
|
||||
self.cmhub_key_hint_label.setObjectName("cmhubKeyHintLabel")
|
||||
self.cmhub_key_hint_label.setWordWrap(True)
|
||||
|
||||
@@ -235,7 +263,7 @@ class SettingsTab(QWidget):
|
||||
("状态", self.enabled_checkbox),
|
||||
("服务商名", self.name_edit),
|
||||
("类别", self.category_combo),
|
||||
("api_type", self.api_type_combo),
|
||||
("接口类型", self.api_type_combo),
|
||||
("模型ID", self.model_id_edit),
|
||||
("连接超时(秒)", self.connect_timeout_spin),
|
||||
("网址", self.url_edit, True),
|
||||
@@ -296,8 +324,15 @@ class SettingsTab(QWidget):
|
||||
model_detail_layout.addLayout(action_layout)
|
||||
model_detail_layout.addWidget(self.test_result_label)
|
||||
|
||||
self.direct_gateway_notice = QLabel("图片理解与商品套图仅支持默认网关。")
|
||||
self.direct_gateway_notice.setObjectName("directGatewayNoticeLabel")
|
||||
self.direct_gateway_notice.setStyleSheet("color: #6b7280;")
|
||||
self.direct_role_panel = QWidget()
|
||||
self.direct_role_panel.setLayout(direct_role_form)
|
||||
direct_role_layout = QVBoxLayout(self.direct_role_panel)
|
||||
direct_role_layout.setContentsMargins(0, 0, 0, 0)
|
||||
direct_role_layout.setSpacing(8)
|
||||
direct_role_layout.addLayout(direct_role_form)
|
||||
direct_role_layout.addWidget(self.direct_gateway_notice)
|
||||
|
||||
cmhub_form = self._three_column_form(
|
||||
[
|
||||
@@ -332,7 +367,7 @@ class SettingsTab(QWidget):
|
||||
self.settings_panel_layout = panel_layout
|
||||
panel_layout.setContentsMargins(13, 18, 13, 18)
|
||||
self.ai_model_section_title = self._section_title(
|
||||
"cmhub 网关",
|
||||
"生成网关",
|
||||
"settingsAiModelSectionTitle",
|
||||
)
|
||||
self.model_detail_section_title = self._section_title(
|
||||
@@ -352,6 +387,8 @@ class SettingsTab(QWidget):
|
||||
"settingsInfrastructureSectionTitle",
|
||||
)
|
||||
panel_layout.addWidget(self.ai_model_section_title)
|
||||
panel_layout.addWidget(self.gateway_selector)
|
||||
panel_layout.addSpacing(8)
|
||||
panel_layout.addWidget(self.model_picker_panel)
|
||||
panel_layout.addSpacing(14)
|
||||
panel_layout.addWidget(self.model_detail_section_title)
|
||||
@@ -394,7 +431,8 @@ class SettingsTab(QWidget):
|
||||
self.delete_model_button.clicked.connect(self.delete_model)
|
||||
self.save_model_button.clicked.connect(self.save_model)
|
||||
self.test_connection_button.clicked.connect(self.test_connection)
|
||||
self.backend_combo.currentIndexChanged.connect(self._on_backend_changed)
|
||||
self.gateway_default_button.toggled.connect(self._on_gateway_source_toggled)
|
||||
self.gateway_custom_button.toggled.connect(self._on_gateway_source_toggled)
|
||||
self.cmhub_refresh_button.clicked.connect(self.refresh_cmhub_models)
|
||||
self.cmhub_test_button.clicked.connect(self.test_cmhub_connection)
|
||||
self.resolution_combo.currentIndexChanged.connect(
|
||||
@@ -480,7 +518,6 @@ class SettingsTab(QWidget):
|
||||
self.chrome_path_edit,
|
||||
)
|
||||
combos = (
|
||||
self.backend_combo,
|
||||
self.cmhub_title_alias_combo,
|
||||
self.cmhub_image_alias_combo,
|
||||
self.cmhub_vision_alias_combo,
|
||||
@@ -516,6 +553,9 @@ class SettingsTab(QWidget):
|
||||
for widget in checkboxes:
|
||||
widget.toggled.connect(self._mark_dirty)
|
||||
|
||||
self.gateway_default_button.toggled.connect(self._mark_dirty)
|
||||
self.gateway_custom_button.toggled.connect(self._mark_dirty)
|
||||
|
||||
def _mark_dirty(self, *args):
|
||||
if self._suspend_dirty > 0:
|
||||
return
|
||||
@@ -576,13 +616,36 @@ class SettingsTab(QWidget):
|
||||
self._cmhub_auto_refresh_done = True
|
||||
self.refresh_cmhub_models()
|
||||
|
||||
def _selected_backend(self):
|
||||
return "direct" if self.gateway_custom_button.isChecked() else "cmhub"
|
||||
|
||||
def _set_selected_backend(self, backend):
|
||||
value = "direct" if str(backend or "").strip().lower() == "direct" else "cmhub"
|
||||
target = self.gateway_custom_button if value == "direct" else self.gateway_default_button
|
||||
for button in (self.gateway_default_button, self.gateway_custom_button):
|
||||
previous = button.blockSignals(True)
|
||||
button.setChecked(button is target)
|
||||
button.blockSignals(previous)
|
||||
previous = self.backend_combo.blockSignals(True)
|
||||
self._set_combo_by_data(self.backend_combo, value)
|
||||
self.backend_combo.blockSignals(previous)
|
||||
|
||||
def _on_gateway_source_toggled(self, checked):
|
||||
if checked:
|
||||
self._on_backend_changed()
|
||||
|
||||
def _on_backend_changed(self, index=None):
|
||||
backend = self._selected_backend()
|
||||
previous = self.backend_combo.blockSignals(True)
|
||||
self._set_combo_by_data(self.backend_combo, backend)
|
||||
self.backend_combo.blockSignals(previous)
|
||||
self.backend_combo.setVisible(False)
|
||||
self.model_picker_panel.setVisible(False)
|
||||
self.model_detail_section_title.setVisible(False)
|
||||
self.model_detail_panel.setVisible(False)
|
||||
self.direct_role_panel.setVisible(False)
|
||||
self.cmhub_panel.setVisible(True)
|
||||
is_direct = backend == "direct"
|
||||
self.model_picker_panel.setVisible(is_direct)
|
||||
self.model_detail_section_title.setVisible(is_direct)
|
||||
self.model_detail_panel.setVisible(is_direct)
|
||||
self.direct_role_panel.setVisible(is_direct)
|
||||
self.cmhub_panel.setVisible(not is_direct)
|
||||
self._set_cmhub_running(self.cmhub_thread is not None)
|
||||
self._update_button_state()
|
||||
|
||||
@@ -733,8 +796,10 @@ class SettingsTab(QWidget):
|
||||
self._replace_config(saved)
|
||||
self._populate_app_settings()
|
||||
self._set_dirty(False)
|
||||
self._set_status("设置已保存")
|
||||
backend_label = "自定义网关" if self._selected_backend() == "direct" else "默认网关"
|
||||
self._set_status("设置已保存,当前使用%s" % backend_label)
|
||||
QMessageBox.information(self, "保存设置", "设置已保存")
|
||||
self.settingsSaved.emit(self._selected_backend())
|
||||
return True
|
||||
|
||||
def _app_settings_values(self):
|
||||
@@ -748,7 +813,7 @@ class SettingsTab(QWidget):
|
||||
self._show_error("默认调试端口必须在调试端口范围内")
|
||||
return None
|
||||
ai_cfg = appconfig.ai_config(self.config)
|
||||
backend = "cmhub"
|
||||
backend = self._selected_backend()
|
||||
text_model = self.default_text_model_combo.currentData() or ai_cfg.get("default_text_model")
|
||||
image_model = self.default_image_model_combo.currentData() or ai_cfg.get("default_image_model")
|
||||
cmhub_cfg = self._cmhub_settings_values(backend)
|
||||
@@ -847,7 +912,7 @@ class SettingsTab(QWidget):
|
||||
return
|
||||
self._populate_role_model_combos()
|
||||
ai_cfg = appconfig.ai_config(self.config)
|
||||
self._set_combo_by_data(self.backend_combo, "cmhub")
|
||||
self._set_selected_backend(ai_cfg.get("backend", "cmhub"))
|
||||
cmhub_cfg = appconfig.cmhub_config(self.config)
|
||||
self.cmhub_base_url_edit.setText(appconfig.normalize_cmhub_base_url(cmhub_cfg.get("base_url", "")))
|
||||
self._loaded_cmhub_api_key = appconfig.get_cmhub_api_key(path=self.cmhub_config_path)
|
||||
@@ -1092,7 +1157,7 @@ class SettingsTab(QWidget):
|
||||
|
||||
def _start_cmhub_worker(self, include_balance):
|
||||
if self.cmhub_thread is not None:
|
||||
self._set_status("cmhub 检测正在进行...")
|
||||
self._set_status("默认网关检测正在进行...")
|
||||
return
|
||||
base_url = appconfig.normalize_cmhub_base_url(self.cmhub_base_url_edit.text())
|
||||
if base_url != self.cmhub_base_url_edit.text().strip():
|
||||
@@ -1104,7 +1169,7 @@ class SettingsTab(QWidget):
|
||||
if not api_key:
|
||||
missing.append("API Key")
|
||||
if missing:
|
||||
self._show_error("cmhub 配置不完整:缺少 " + "、".join(missing))
|
||||
self._show_error("默认网关配置不完整:缺少 " + "、".join(missing))
|
||||
return
|
||||
worker = CMHubSettingsWorker(
|
||||
base_url,
|
||||
@@ -1121,7 +1186,7 @@ class SettingsTab(QWidget):
|
||||
self.cmhub_worker = worker
|
||||
self.cmhub_thread = thread
|
||||
self._set_cmhub_running(True)
|
||||
message = "正在测试 cmhub 连接并查询余额..." if include_balance else "正在刷新 cmhub 别名..."
|
||||
message = "正在测试默认网关连接并查询余额..." if include_balance else "正在刷新默认网关别名..."
|
||||
self.cmhub_result_label.setText(message)
|
||||
self._set_status(message)
|
||||
thread.start()
|
||||
@@ -1183,8 +1248,8 @@ class SettingsTab(QWidget):
|
||||
def _cmhub_success_subject(self, payload):
|
||||
account_name = self._cmhub_account_display_name(payload)
|
||||
if account_name:
|
||||
return f"cmhub 账号「{account_name}」连接成功"
|
||||
return "cmhub 连接成功"
|
||||
return f"账号「{account_name}」连接默认网关成功"
|
||||
return "默认网关连接成功"
|
||||
|
||||
def _cmhub_account_display_name(self, payload):
|
||||
if not isinstance(payload, dict):
|
||||
@@ -1237,7 +1302,7 @@ class SettingsTab(QWidget):
|
||||
return ""
|
||||
|
||||
def _on_cmhub_failed(self, _task_id, error):
|
||||
message = f"cmhub 连接失败:{error}"
|
||||
message = "默认网关连接失败:%s" % str(error or "连接失败").replace("cmhub", "默认网关")
|
||||
self.cmhub_result_label.setText(message)
|
||||
self._set_status(message)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user