from __future__ import annotations from typing import Annotated from fastapi import APIRouter, Depends, Header, HTTPException, status from sqlalchemy.orm import Session from app.api.deps import get_db from app.core.config import get_settings as get_runtime_settings from app.schemas.common import ErrorResponse from app.schemas.settings import ( ModelConnectivityTestRead, ModelConnectivityTestRequest, RuntimeModelConfigRead, SettingsRead, SettingsWrite, ) from app.services.model_connectivity import probe_model_connectivity from app.services.settings import SettingsService router = APIRouter(prefix="/settings") DbSession = Annotated[Session, Depends(get_db)] def require_hermes_agent_token( authorization: Annotated[ str | None, Header(description="Hermes 读取运行时模型配置时使用的 Bearer Token。"), ] = None, ) -> None: configured_token = str(get_runtime_settings().hermes_agent_shared_token or "").strip() if not configured_token: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Hermes 集成令牌未配置。", ) normalized = str(authorization or "").strip() expected = f"Bearer {configured_token}" if normalized != expected: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Hermes 集成鉴权失败。", ) @router.get( "", response_model=SettingsRead, summary="读取系统设置", description="返回公司、管理员、模型、日志、邮件和 ONLYOFFICE 的设置快照。", ) def get_settings(db: DbSession) -> SettingsRead: return SettingsService(db).get_settings_snapshot() @router.put( "", response_model=SettingsRead, summary="保存系统设置", description="保存系统设置,并同步运行时模型配置与 Hermes 使用的模型路由。", responses={ status.HTTP_400_BAD_REQUEST: { "model": ErrorResponse, "description": "设置字段校验失败。", } }, ) def update_settings(payload: SettingsWrite, db: DbSession) -> SettingsRead: try: return SettingsService(db).save_settings_snapshot(payload) except ValueError as exc: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc @router.post( "/model-connectivity", response_model=ModelConnectivityTestRead, summary="测试模型连通性", description="验证指定模型服务端点是否可用;当未传 API Key 且提供 slot 时会尝试复用已保存密钥。", ) def test_model_connectivity( payload: ModelConnectivityTestRequest, db: DbSession, ) -> ModelConnectivityTestRead: resolved_payload = payload if not payload.api_key and payload.slot: stored_api_key = SettingsService(db).load_saved_model_api_key(payload.slot) if stored_api_key: resolved_payload = payload.model_copy(update={"api_key": stored_api_key}) return probe_model_connectivity(resolved_payload) @router.get( "/runtime-models/{slot}", response_model=RuntimeModelConfigRead, dependencies=[Depends(require_hermes_agent_token)], summary="读取 Hermes 运行时模型配置", description="供 Hermes 进程读取主模型、备用模型、VLM 或 Embedding 模型的运行时配置。", responses={ status.HTTP_401_UNAUTHORIZED: { "model": ErrorResponse, "description": "Hermes 令牌校验失败。", }, status.HTTP_404_NOT_FOUND: { "model": ErrorResponse, "description": "指定模型槽位不存在。", }, status.HTTP_503_SERVICE_UNAVAILABLE: { "model": ErrorResponse, "description": "Hermes 集成令牌尚未配置。", }, }, ) def get_runtime_model_config( slot: str, db: DbSession, ) -> RuntimeModelConfigRead: try: payload = SettingsService(db).get_runtime_model_config(slot) except ValueError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc return RuntimeModelConfigRead(**payload)