from fastapi import APIRouter, Depends from pydantic import BaseModel from typing import Optional, Any import json import aiosqlite from database import get_db router = APIRouter(prefix="/api/settings", tags=["settings"]) EDITABLE_KEYS = [ "ai_base_url", "ai_api_key", "ai_model", "summary_model", "vision_model", "voice_model", "topic_analysis_prompt", ] # 万川 AI 平台对接配置整体作为一条 JSON 存储,独立于上面的 AI 模型配置。 # 存到后端 SQLite(app_settings 表)后,配置不再依赖前端 localStorage 的 origin, # 桌面应用(exe)即便后端端口/origin 变化也能跨次启动恢复平台地址、账号、密码与已选知识库。 WANCHUAN_KEY = "wanchuan_config" def _mask_key(value: str) -> str: if not value or len(value) <= 8: return "*" * len(value) if value else "" return value[:3] + "*" * (len(value) - 7) + value[-4:] @router.get("") async def get_settings(db: aiosqlite.Connection = Depends(get_db)): result = {} placeholders = ",".join("?" for _ in EDITABLE_KEYS) async with db.execute( f"SELECT key, value FROM app_settings WHERE key IN ({placeholders})", EDITABLE_KEYS, ) as cur: rows = await cur.fetchall() for row in rows: k, v = row["key"], row["value"] result[k] = _mask_key(v) if k == "ai_api_key" else v for k in EDITABLE_KEYS: if k not in result: result[k] = "" return result class SettingsUpdate(BaseModel): ai_base_url: Optional[str] = None ai_api_key: Optional[str] = None ai_model: Optional[str] = None summary_model: Optional[str] = None vision_model: Optional[str] = None voice_model: Optional[str] = None topic_analysis_prompt: Optional[str] = None @router.put("") async def update_settings(body: SettingsUpdate, db: aiosqlite.Connection = Depends(get_db)): updates = body.model_dump(exclude_none=True) for k, v in updates.items(): if k not in EDITABLE_KEYS: continue if k == "ai_api_key" and "*" in v: continue await db.execute( "INSERT INTO app_settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?", (k, v, v), ) await db.commit() from services.runtime_settings import invalidate_cache invalidate_cache() return {"status": "ok"} # ── 万川 AI 平台对接配置(整体 JSON 持久化) ────────────────── # 前端把 { platformUrl, username, password, selectedKbId, selectedKbInfo } 整体存这里。 # 桌面应用 origin 变化不影响该配置(不依赖 localStorage)。 @router.get("/wanchuan") async def get_wanchuan_config(db: aiosqlite.Connection = Depends(get_db)): async with db.execute( "SELECT value FROM app_settings WHERE key = ?", (WANCHUAN_KEY,) ) as cur: row = await cur.fetchone() if not row or not row["value"]: return {} try: return json.loads(row["value"]) except (ValueError, TypeError): return {} @router.put("/wanchuan") async def update_wanchuan_config(body: dict[str, Any], db: aiosqlite.Connection = Depends(get_db)): value = json.dumps(body, ensure_ascii=False) await db.execute( "INSERT INTO app_settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?", (WANCHUAN_KEY, value, value), ) await db.commit() return {"status": "ok"}