feat(webui): simplify model preset settings (#5061)

This commit is contained in:
chengyongru
2026-07-24 00:55:06 +08:00
committed by GitHub
parent d993c81f08
commit aae259c790
29 changed files with 4548 additions and 1182 deletions
+82 -1
View File
@@ -13,6 +13,7 @@ import json
import time
from collections.abc import Callable
from typing import Any
from urllib.parse import unquote
from websockets.http11 import Request as WsRequest
from websockets.http11 import Response
@@ -51,15 +52,19 @@ from nanobot.webui.settings_api import (
WebUISettingsError,
complete_oauth_provider,
create_model_configuration,
create_provider_settings,
decorate_settings_payload,
delete_model_configuration,
login_oauth_provider,
logout_oauth_provider,
migrate_model_configurations,
provider_models_payload,
settings_payload,
settings_usage_payload,
update_agent_settings,
update_api_settings,
update_image_generation_settings,
update_model_call_order,
update_model_configuration,
update_network_safety_settings,
update_provider_settings,
@@ -72,6 +77,8 @@ QueryParams = dict[str, list[str]]
_MCP_VALUES_HEADER = "X-Nanobot-MCP-Values"
_MCP_VALUES_HEADER_MAX_BYTES = 64 * 1024
_PROVIDER_VALUES_HEADER = "X-Nanobot-Provider-Values"
_PROVIDER_VALUES_HEADER_MAX_BYTES = 64 * 1024
_CHANNEL_VALUES_HEADER = "X-Nanobot-Channel-Values"
_CHANNEL_VALUES_HEADER_MAX_BYTES = 64 * 1024
_API_SERVICE_VALUES_HEADER = "X-Nanobot-API-Service-Values"
@@ -145,8 +152,16 @@ class WebUISettingsRouter:
return self._handle_settings_model_configuration_create(request)
if path == "/api/settings/model-configurations/update":
return self._handle_settings_model_configuration_update(request)
if path == "/api/settings/model-configurations/delete":
return self._handle_settings_model_configuration_delete(request)
if path == "/api/settings/model-configurations/migrate":
return self._handle_settings_model_configurations_migrate(request)
if path == "/api/settings/model-call-order/update":
return self._handle_settings_model_call_order_update(request)
if path == "/api/settings/provider/update":
return await self._handle_settings_provider_update(request)
if path == "/api/settings/provider/create":
return self._handle_settings_provider_create(request)
if path == "/api/settings/provider-models":
return await self._handle_settings_provider_models(request)
if path == "/api/settings/provider/oauth-login":
@@ -269,6 +284,36 @@ class WebUISettingsRouter:
merged[key] = [text]
return merged
def _parse_provider_settings_query(self, request: WsRequest) -> QueryParams:
query = self._query(request)
raw = request.headers.get(_PROVIDER_VALUES_HEADER)
if not raw:
return query
if len(raw.encode("utf-8")) > _PROVIDER_VALUES_HEADER_MAX_BYTES:
raise WebUISettingsError("provider settings payload is too large")
try:
payload = json.loads(raw)
except json.JSONDecodeError as exc:
try:
payload = json.loads(unquote(raw))
except json.JSONDecodeError:
raise WebUISettingsError("invalid provider settings payload") from exc
if not isinstance(payload, dict):
raise WebUISettingsError("provider settings payload must be a JSON object")
merged = {key: list(values) for key, values in query.items()}
for key, value in payload.items():
if not isinstance(key, str) or not key:
raise WebUISettingsError("provider settings payload contains an invalid key")
if isinstance(value, str):
text = value
elif value is None:
text = ""
else:
text = json.dumps(value, ensure_ascii=False, separators=(",", ":"))
merged[key] = [text]
return merged
def _handle_settings(self, request: WsRequest) -> Response:
if not self._authorized(request):
return self._unauthorized()
@@ -353,16 +398,52 @@ class WebUISettingsRouter:
return self._error_response(e.status, e.message)
return self._json_response(self._with_restart_state(payload))
def _handle_settings_model_configuration_delete(self, request: WsRequest) -> Response:
if not self._authorized(request):
return self._unauthorized()
try:
payload = delete_model_configuration(self._query(request))
except WebUISettingsError as e:
return self._error_response(e.status, e.message)
return self._json_response(self._with_restart_state(payload))
def _handle_settings_model_configurations_migrate(self, request: WsRequest) -> Response:
if not self._authorized(request):
return self._unauthorized()
try:
payload = migrate_model_configurations(self._query(request))
except WebUISettingsError as e:
return self._error_response(e.status, e.message)
return self._json_response(self._with_restart_state(payload))
def _handle_settings_model_call_order_update(self, request: WsRequest) -> Response:
if not self._authorized(request):
return self._unauthorized()
try:
payload = update_model_call_order(self._query(request))
except WebUISettingsError as e:
return self._error_response(e.status, e.message)
return self._json_response(self._with_restart_state(payload))
async def _handle_settings_provider_update(self, request: WsRequest) -> Response:
if not self._authorized(request):
return self._unauthorized()
try:
payload = update_provider_settings(self._query(request))
payload = update_provider_settings(self._parse_provider_settings_query(request))
except WebUISettingsError as e:
return self._error_response(e.status, e.message)
payload = await self._apply_image_generation_runtime_change(payload)
return self._json_response(self._with_restart_state(payload, section="image"))
def _handle_settings_provider_create(self, request: WsRequest) -> Response:
if not self._authorized(request):
return self._unauthorized()
try:
payload = create_provider_settings(self._parse_provider_settings_query(request))
except WebUISettingsError as e:
return self._error_response(e.status, e.message)
return self._json_response(self._with_restart_state(payload))
async def _handle_settings_provider_models(self, request: WsRequest) -> Response:
if not self._authorized(request):
return self._unauthorized()