feat: complete T-530 cmhub URL normalization
This commit is contained in:
@@ -771,7 +771,7 @@ def _cmhub_error_from_response(data, response, api_key):
|
||||
error = data.get("error") if isinstance(data, dict) else None
|
||||
if not isinstance(error, dict):
|
||||
error = {}
|
||||
code = str(error.get("code") or _cmhub_code_for_status(status))
|
||||
code = _normalize_cmhub_error_code(error.get("code") or _cmhub_code_for_status(status), status)
|
||||
raw_message = (error.get("message") or data.get("message")) if isinstance(data, dict) else ""
|
||||
if not raw_message:
|
||||
raw_message = getattr(response, "text", "")[:500]
|
||||
@@ -786,12 +786,20 @@ def _cmhub_error_from_response(data, response, api_key):
|
||||
)
|
||||
|
||||
|
||||
def _normalize_cmhub_error_code(code, status=None):
|
||||
normalized = str(code or "unknown").strip().lower().replace("-", "_")
|
||||
if status == 404 or normalized in {"notfound", "not_found"}:
|
||||
return "not_found"
|
||||
return normalized or "unknown"
|
||||
|
||||
|
||||
def _cmhub_code_for_status(status):
|
||||
return {
|
||||
400: "bad_request",
|
||||
401: "unauthorized",
|
||||
402: "insufficient_points",
|
||||
403: "account_disabled",
|
||||
404: "not_found",
|
||||
429: "rate_limited",
|
||||
502: "upstream_error",
|
||||
}.get(status, "unknown")
|
||||
@@ -812,8 +820,11 @@ def _cmhub_user_message(code, message):
|
||||
"content_blocked": "cmhub 内容安全策略拒绝本次生成",
|
||||
"upstream_error": "cmhub 上游生成失败,请稍后重试",
|
||||
"rate_limited": "cmhub 请求过于频繁,请稍后重试",
|
||||
"not_found": "cmhub 接口不存在,请检查 Base URL 或该实例是否已部署 /api/v1/models",
|
||||
}
|
||||
default = defaults.get(str(code))
|
||||
if str(code) == "not_found":
|
||||
return default
|
||||
if default and message and str(message) not in default:
|
||||
return "%s:%s" % (default, message)
|
||||
return default or str(message or code)
|
||||
|
||||
+33
-2
@@ -9,6 +9,7 @@ import copy
|
||||
import json
|
||||
import os
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
|
||||
@@ -129,6 +130,17 @@ def _deep_merge(defaults, loaded):
|
||||
return merged
|
||||
|
||||
|
||||
def _normalize_config_values(config):
|
||||
if not isinstance(config, dict):
|
||||
return config
|
||||
ai = config.get("ai")
|
||||
if isinstance(ai, dict):
|
||||
cmhub = ai.get("cmhub")
|
||||
if isinstance(cmhub, dict):
|
||||
cmhub["base_url"] = normalize_cmhub_base_url(cmhub.get("base_url", ""))
|
||||
return config
|
||||
|
||||
|
||||
def _assert_no_secrets(config):
|
||||
def visit(value, path):
|
||||
if isinstance(value, dict):
|
||||
@@ -247,6 +259,7 @@ def save_config(config, path=CONFIG_PATH) -> dict:
|
||||
"""Persist config to JSON and return the normalized config."""
|
||||
|
||||
normalized = _deep_merge(DEFAULT_CONFIG, config)
|
||||
_normalize_config_values(normalized)
|
||||
_assert_no_secrets(normalized)
|
||||
directory = os.path.dirname(os.path.abspath(path))
|
||||
if directory:
|
||||
@@ -268,6 +281,7 @@ def load_config(path=CONFIG_PATH) -> dict:
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ConfigError(f"配置文件不是有效 JSON: {path}") from exc
|
||||
normalized = _deep_merge(DEFAULT_CONFIG, loaded)
|
||||
_normalize_config_values(normalized)
|
||||
_assert_no_secrets(normalized)
|
||||
return normalized
|
||||
|
||||
@@ -334,7 +348,7 @@ def cmhub_config(config=None) -> dict:
|
||||
if not isinstance(value, dict):
|
||||
raise ConfigError("ai.cmhub 必须是对象")
|
||||
merged = _deep_merge(DEFAULT_CONFIG["ai"]["cmhub"], value)
|
||||
merged["base_url"] = str(merged.get("base_url", "") or "").strip()
|
||||
merged["base_url"] = normalize_cmhub_base_url(merged.get("base_url", ""))
|
||||
merged["title_alias"] = str(merged.get("title_alias", "") or "").strip()
|
||||
merged["image_alias"] = str(merged.get("image_alias", "") or "").strip()
|
||||
merged["connect_timeout"] = int(merged.get("connect_timeout", 10) or 10)
|
||||
@@ -344,8 +358,25 @@ def cmhub_config(config=None) -> dict:
|
||||
return merged
|
||||
|
||||
|
||||
def normalize_cmhub_base_url(base_url) -> str:
|
||||
"""Return the cmhub gateway root URL without path/query/fragment."""
|
||||
|
||||
text = str(base_url or "").strip()
|
||||
if not text:
|
||||
return ""
|
||||
parts = urllib.parse.urlsplit(text)
|
||||
if parts.scheme and parts.netloc:
|
||||
return urllib.parse.urlunsplit((parts.scheme, parts.netloc, "", "", ""))
|
||||
if parts.netloc:
|
||||
return urllib.parse.urlunsplit((parts.scheme, parts.netloc, "", "", ""))
|
||||
without_query = text.split("?", 1)[0].split("#", 1)[0].strip().strip("/")
|
||||
if "://" not in without_query:
|
||||
return without_query.split("/", 1)[0]
|
||||
return text.rstrip("/")
|
||||
|
||||
|
||||
def cmhub_request_url(base_url, endpoint) -> str:
|
||||
base = str(base_url or "").strip().rstrip("/")
|
||||
base = normalize_cmhub_base_url(base_url)
|
||||
path = "/" + str(endpoint or "").strip().lstrip("/")
|
||||
if not base:
|
||||
return path
|
||||
|
||||
@@ -71,7 +71,7 @@ class SettingsTab(QWidget):
|
||||
self.backend_combo.setVisible(False)
|
||||
self.cmhub_base_url_edit = QLineEdit()
|
||||
self.cmhub_base_url_edit.setObjectName("cmhubBaseUrlEdit")
|
||||
self.cmhub_base_url_edit.setPlaceholderText("https://<cmhub>")
|
||||
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)
|
||||
@@ -93,6 +93,9 @@ 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.setObjectName("cmhubBaseUrlHintLabel")
|
||||
self.cmhub_base_url_hint_label.setWordWrap(True)
|
||||
self.cmhub_key_hint_label = QLabel("API Key 仅在 cmhub 网页端创建时显示一次;复制到此处后会本地明文保存并打码显示。")
|
||||
self.cmhub_key_hint_label.setObjectName("cmhubKeyHintLabel")
|
||||
self.cmhub_key_hint_label.setWordWrap(True)
|
||||
@@ -302,6 +305,7 @@ class SettingsTab(QWidget):
|
||||
cmhub_panel_layout.setContentsMargins(0, 0, 0, 0)
|
||||
cmhub_panel_layout.setSpacing(8)
|
||||
cmhub_panel_layout.addLayout(cmhub_form)
|
||||
cmhub_panel_layout.addWidget(self.cmhub_base_url_hint_label)
|
||||
cmhub_panel_layout.addWidget(self.cmhub_key_hint_label)
|
||||
cmhub_panel_layout.addLayout(cmhub_action_layout)
|
||||
cmhub_panel_layout.addWidget(self.cmhub_result_label)
|
||||
@@ -673,7 +677,7 @@ class SettingsTab(QWidget):
|
||||
def _cmhub_settings_values(self, backend):
|
||||
current = appconfig.cmhub_config(self.config)
|
||||
values = {
|
||||
"base_url": self.cmhub_base_url_edit.text().strip(),
|
||||
"base_url": appconfig.normalize_cmhub_base_url(self.cmhub_base_url_edit.text()),
|
||||
"title_alias": self.cmhub_title_alias_combo.currentData() or "",
|
||||
"image_alias": self.cmhub_image_alias_combo.currentData() or "",
|
||||
"connect_timeout": self.cmhub_connect_timeout_spin.value(),
|
||||
@@ -700,7 +704,7 @@ class SettingsTab(QWidget):
|
||||
ai_cfg = appconfig.ai_config(self.config)
|
||||
self._set_combo_by_data(self.backend_combo, "cmhub")
|
||||
cmhub_cfg = appconfig.cmhub_config(self.config)
|
||||
self.cmhub_base_url_edit.setText(cmhub_cfg.get("base_url", ""))
|
||||
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)
|
||||
self.cmhub_api_key_edit.setText(self._loaded_cmhub_api_key)
|
||||
self.cmhub_connect_timeout_spin.setValue(
|
||||
@@ -937,7 +941,9 @@ class SettingsTab(QWidget):
|
||||
if self.cmhub_thread is not None:
|
||||
self._set_status("cmhub 检测正在进行...")
|
||||
return
|
||||
base_url = self.cmhub_base_url_edit.text().strip()
|
||||
base_url = appconfig.normalize_cmhub_base_url(self.cmhub_base_url_edit.text())
|
||||
if base_url != self.cmhub_base_url_edit.text().strip():
|
||||
self.cmhub_base_url_edit.setText(base_url)
|
||||
api_key = self.cmhub_api_key_edit.text()
|
||||
missing = []
|
||||
if not base_url:
|
||||
|
||||
Reference in New Issue
Block a user