112 lines
3.6 KiB
Python
112 lines
3.6 KiB
Python
"""Settings, TTS routes, routing log, server logs, and voice design presets endpoints."""
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
|
|
from core.config import _load_settings, _save_settings, _normalize_settings, _SETTINGS_KEYS, _ensure_external_api_key
|
|
from core.routing import _load_tts_routes, _save_tts_routes
|
|
from core.constants import _log_buffer, _LOG_BUFFER_MAX, _routing_log, _ROUTING_LOG_MAX
|
|
from core.presets import _load_design_presets, _save_design_presets
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
_SECRET_SETTING_KEYS = {
|
|
"groq_api_key", "whisper_api_key", "tts_api_key", "voice_design_api_key",
|
|
"elevenlabs_api_key", "llm_api_key", "engine_api_keys", "external_api_key",
|
|
}
|
|
|
|
|
|
@router.get("/api/settings")
|
|
async def get_settings(request: Request):
|
|
s = _load_settings()
|
|
# Lazily generated on first read, not at server startup — a fresh
|
|
# install shows a real usable key in Settings immediately without a
|
|
# migration step, whether or not the gate is actually turned on yet.
|
|
s["external_api_key"] = _ensure_external_api_key()
|
|
host = request.headers.get("host", "")
|
|
origin = request.headers.get("origin", "") or request.headers.get("referer", "")
|
|
try:
|
|
from urllib.parse import urlparse
|
|
same_origin = (
|
|
request.headers.get("sec-fetch-site", "").lower() == "same-origin"
|
|
or bool(origin and host and urlparse(origin).netloc == host)
|
|
)
|
|
except Exception:
|
|
same_origin = False
|
|
if not same_origin:
|
|
s = {key: value for key, value in s.items() if key not in _SECRET_SETTING_KEYS}
|
|
return s
|
|
|
|
|
|
@router.post("/api/settings/regenerate-api-key")
|
|
async def regenerate_api_key():
|
|
import secrets
|
|
s = _load_settings()
|
|
s["external_api_key"] = secrets.token_urlsafe(32)
|
|
_save_settings(s)
|
|
return {"external_api_key": s["external_api_key"]}
|
|
|
|
|
|
@router.post("/api/settings")
|
|
async def post_settings(request: Request):
|
|
data = await request.json()
|
|
s = _load_settings()
|
|
s.update({k: v for k, v in data.items() if k in _SETTINGS_KEYS})
|
|
s = _normalize_settings(s)
|
|
_save_settings(s)
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/api/tts-routes")
|
|
async def get_tts_routes():
|
|
return {"routes": _load_tts_routes()}
|
|
|
|
|
|
@router.post("/api/tts-routes")
|
|
async def post_tts_routes(request: Request):
|
|
data = await request.json()
|
|
routes = data.get("routes", data) if isinstance(data, dict) else data
|
|
if not isinstance(routes, list):
|
|
raise HTTPException(400, "routes must be a list")
|
|
_save_tts_routes(routes)
|
|
return {"ok": True, "routes": _load_tts_routes()}
|
|
|
|
|
|
@router.get("/api/tts-routing-log")
|
|
async def get_tts_routing_log(limit: int = 60):
|
|
limit = max(1, min(int(limit or 60), _ROUTING_LOG_MAX))
|
|
return {"items": _routing_log[:limit], "max": _ROUTING_LOG_MAX}
|
|
|
|
|
|
@router.delete("/api/tts-routing-log")
|
|
async def clear_tts_routing_log():
|
|
_routing_log.clear()
|
|
return {"ok": True, "items": []}
|
|
|
|
|
|
@router.get("/api/logs")
|
|
async def get_server_logs(limit: int = 200):
|
|
limit = max(1, min(int(limit or 200), _LOG_BUFFER_MAX))
|
|
return {"items": _log_buffer[:limit], "max": _LOG_BUFFER_MAX}
|
|
|
|
|
|
@router.delete("/api/logs")
|
|
async def clear_server_logs():
|
|
_log_buffer.clear()
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/api/voice-design-presets")
|
|
async def get_voice_design_presets():
|
|
return _load_design_presets()
|
|
|
|
|
|
@router.post("/api/voice-design-presets")
|
|
async def post_voice_design_presets(request: Request):
|
|
data = await request.json()
|
|
if not isinstance(data, dict):
|
|
raise HTTPException(400, "Expected a preset object")
|
|
_save_design_presets(data)
|
|
return {"ok": True, "presets": _load_design_presets()}
|