|
# retoor <retoor@molodetz.nl>
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Request
|
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
from pydantic import ValidationError
|
|
|
|
from devplacepy.seo import base_seo_context, site_url, website_schema
|
|
from devplacepy.services.audit import record as audit
|
|
from devplacepy.services.manager import service_manager
|
|
from devplacepy.services.openai_gateway import routing
|
|
from devplacepy.templating import templates
|
|
from devplacepy.utils import require_admin
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
|
|
def _default_provider_summary() -> dict:
|
|
svc = service_manager.get_service("openai")
|
|
cfg = svc.get_config() if svc is not None else {}
|
|
return {
|
|
"base_url": cfg.get("gateway_upstream_url", ""),
|
|
"model": cfg.get("gateway_model", ""),
|
|
"embed_url": cfg.get("gateway_embed_url", ""),
|
|
"embed_model": cfg.get("gateway_embed_model", ""),
|
|
"image_url": cfg.get("gateway_image_url", ""),
|
|
"image_model": cfg.get("gateway_image_model", ""),
|
|
"vision_url": cfg.get("gateway_vision_url", ""),
|
|
"vision_model": cfg.get("gateway_vision_model", ""),
|
|
}
|
|
|
|
|
|
def _validation_error(exc: ValidationError) -> JSONResponse:
|
|
first = exc.errors()[0]
|
|
message = first.get("msg", "Invalid input")
|
|
return JSONResponse({"ok": False, "error": message}, status_code=400)
|
|
|
|
|
|
async def _payload(request: Request) -> dict:
|
|
content_type = request.headers.get("content-type", "")
|
|
if "application/json" in content_type:
|
|
try:
|
|
data = await request.json()
|
|
except Exception:
|
|
return {}
|
|
return data if isinstance(data, dict) else {}
|
|
form = await request.form()
|
|
return {key: value for key, value in form.items()}
|
|
|
|
|
|
@router.get("/gateway", response_class=HTMLResponse)
|
|
async def gateway_config_page(request: Request):
|
|
admin = require_admin(request)
|
|
base = site_url(request)
|
|
seo_ctx = base_seo_context(
|
|
request,
|
|
title="Gateway routing - Admin",
|
|
description="Manage OpenAI gateway providers and per-model routing.",
|
|
robots="noindex,nofollow",
|
|
breadcrumbs=[
|
|
{"name": "Home", "url": "/feed"},
|
|
{"name": "Admin", "url": "/admin"},
|
|
{"name": "Gateway", "url": "/admin/gateway"},
|
|
],
|
|
schemas=[website_schema(base)],
|
|
)
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"admin_gateway.html",
|
|
{
|
|
**seo_ctx,
|
|
"request": request,
|
|
"user": admin,
|
|
"admin_section": "gateway",
|
|
"default_provider": _default_provider_summary(),
|
|
},
|
|
)
|
|
|
|
|
|
@router.get("/gateway/providers")
|
|
async def list_providers(request: Request):
|
|
require_admin(request)
|
|
return JSONResponse(
|
|
{
|
|
"providers": routing.provider_store.list(),
|
|
"default": _default_provider_summary(),
|
|
"count": routing.provider_store.count(),
|
|
}
|
|
)
|
|
|
|
|
|
@router.post("/gateway/providers")
|
|
async def save_provider(request: Request):
|
|
admin = require_admin(request)
|
|
body = await _payload(request)
|
|
try:
|
|
payload = routing.ProviderIn(**body)
|
|
except ValidationError as exc:
|
|
return _validation_error(exc)
|
|
saved = routing.provider_store.set(payload)
|
|
audit.record(
|
|
request,
|
|
"gateway.provider.update",
|
|
user=admin,
|
|
target_type="gateway_provider",
|
|
target_uid=payload.name,
|
|
target_label=payload.name,
|
|
summary=f"admin {admin['username']} saved gateway provider {payload.name}",
|
|
)
|
|
return JSONResponse({"ok": True, "provider": saved})
|
|
|
|
|
|
@router.delete("/gateway/providers/{name}")
|
|
async def delete_provider(request: Request, name: str):
|
|
admin = require_admin(request)
|
|
existed = routing.provider_store.remove(name)
|
|
if not existed:
|
|
return JSONResponse({"ok": False, "error": "Provider not found"}, status_code=404)
|
|
audit.record(
|
|
request,
|
|
"gateway.provider.delete",
|
|
user=admin,
|
|
target_type="gateway_provider",
|
|
target_uid=name,
|
|
target_label=name,
|
|
summary=f"admin {admin['username']} deleted gateway provider {name}",
|
|
)
|
|
return JSONResponse({"ok": True})
|
|
|
|
|
|
@router.get("/gateway/models")
|
|
async def list_models(request: Request):
|
|
require_admin(request)
|
|
return JSONResponse(
|
|
{
|
|
"models": routing.model_store.list(),
|
|
"providers": routing.provider_store.names(),
|
|
"count": routing.model_store.count(),
|
|
}
|
|
)
|
|
|
|
|
|
@router.post("/gateway/models")
|
|
async def save_model(request: Request):
|
|
admin = require_admin(request)
|
|
body = await _payload(request)
|
|
try:
|
|
payload = routing.ModelRouteIn(**body)
|
|
except ValidationError as exc:
|
|
return _validation_error(exc)
|
|
saved = routing.model_store.set(payload)
|
|
audit.record(
|
|
request,
|
|
"gateway.model.update",
|
|
user=admin,
|
|
target_type="gateway_model",
|
|
target_uid=payload.source_model,
|
|
target_label=f"{payload.source_model} -> {payload.target_model}",
|
|
summary=(
|
|
f"admin {admin['username']} saved gateway model route "
|
|
f"{payload.source_model} -> {payload.target_model}"
|
|
),
|
|
)
|
|
return JSONResponse({"ok": True, "model": saved})
|
|
|
|
|
|
@router.delete("/gateway/models/{source_model}")
|
|
async def delete_model(request: Request, source_model: str):
|
|
admin = require_admin(request)
|
|
existed = routing.model_store.remove(source_model)
|
|
if not existed:
|
|
return JSONResponse({"ok": False, "error": "Model route not found"}, status_code=404)
|
|
audit.record(
|
|
request,
|
|
"gateway.model.delete",
|
|
user=admin,
|
|
target_type="gateway_model",
|
|
target_uid=source_model,
|
|
target_label=source_model,
|
|
summary=f"admin {admin['username']} deleted gateway model route {source_model}",
|
|
)
|
|
return JSONResponse({"ok": True})
|