- Fix admin sidebar: remove duplicate Teams, add Storage nav item - Analytics: client-scoped queries, super_admin sees all (including NULL client_id) - Storage management: list/download/delete presentations with file metadata - Settings page with brand config router - AI usage tracking: new AIUsageModel, ai_usage_service, analytics endpoint - Master deck → template bridge: _register_as_template creates TemplateModel + PresentationLayoutCodeModel so parsed layouts appear in template picker - Multi-provider LLM vision in parser: Anthropic/Google/OpenAI with asyncio.to_thread - Fix PPTX upload 400: accept by .pptx extension (browser sends octet-stream) - Fix reparse FK violation: presentation_id=None for parse_master_deck jobs - Worker job_timeout increased to 1800s for LLM-heavy master deck parsing - PYTHONUNBUFFERED=1 in docker-compose worker for real-time log output - Auth: clientId in /me response, dev-login cookie improvements - Frontend: auth slice clientId, master-deck thumbnails, storage page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
128 lines
4.5 KiB
Python
128 lines
4.5 KiB
Python
"""Admin router for system settings — LLM and image provider configuration."""
|
|
import os
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from models.sql.user import UserModel
|
|
from services.database import get_async_session
|
|
from utils.auth_dependencies import require_client_admin
|
|
|
|
SETTINGS_ROUTER = APIRouter(tags=["Admin - Settings"])
|
|
|
|
|
|
class SystemSettings(BaseModel):
|
|
llm_provider: Optional[str] = None
|
|
llm_model: Optional[str] = None
|
|
image_provider: Optional[str] = None
|
|
anthropic_api_key_set: bool = False
|
|
openai_api_key_set: bool = False
|
|
google_api_key_set: bool = False
|
|
|
|
|
|
class SettingsUpdate(BaseModel):
|
|
llm_provider: Optional[str] = None
|
|
llm_model: Optional[str] = None
|
|
image_provider: Optional[str] = None
|
|
anthropic_api_key: Optional[str] = None
|
|
openai_api_key: Optional[str] = None
|
|
google_api_key: Optional[str] = None
|
|
|
|
|
|
LLM_PROVIDERS = ["anthropic", "openai", "google", "ollama", "custom"]
|
|
IMAGE_PROVIDERS = ["google", "dall-e-3", "gpt-image-1.5", "pexels", "pixabay", "comfyui"]
|
|
|
|
|
|
@SETTINGS_ROUTER.get("/settings")
|
|
async def get_settings(
|
|
admin: UserModel = Depends(require_client_admin),
|
|
_session: AsyncSession = Depends(get_async_session),
|
|
):
|
|
"""Return current system settings (env-based config)."""
|
|
if admin.role != "super_admin":
|
|
raise HTTPException(status_code=403, detail="Super admin only")
|
|
|
|
return {
|
|
"llm_provider": os.getenv("LLM", "anthropic"),
|
|
"llm_model": _get_current_model(),
|
|
"image_provider": os.getenv("IMAGE_PROVIDER", "google"),
|
|
"anthropic_api_key_set": bool(os.getenv("ANTHROPIC_API_KEY")),
|
|
"openai_api_key_set": bool(os.getenv("OPENAI_API_KEY")),
|
|
"google_api_key_set": bool(os.getenv("GOOGLE_API_KEY")),
|
|
"available_llm_providers": LLM_PROVIDERS,
|
|
"available_image_providers": IMAGE_PROVIDERS,
|
|
}
|
|
|
|
|
|
@SETTINGS_ROUTER.put("/settings")
|
|
async def update_settings(
|
|
body: SettingsUpdate,
|
|
admin: UserModel = Depends(require_client_admin),
|
|
_session: AsyncSession = Depends(get_async_session),
|
|
):
|
|
"""Update system settings (runtime env vars). Changes don't persist across restarts."""
|
|
if admin.role != "super_admin":
|
|
raise HTTPException(status_code=403, detail="Super admin only")
|
|
|
|
changed = []
|
|
|
|
if body.llm_provider is not None:
|
|
if body.llm_provider not in LLM_PROVIDERS:
|
|
raise HTTPException(status_code=400, detail=f"Invalid LLM provider: {body.llm_provider}")
|
|
os.environ["LLM"] = body.llm_provider
|
|
changed.append("llm_provider")
|
|
|
|
if body.llm_model is not None:
|
|
provider = body.llm_provider or os.getenv("LLM", "anthropic")
|
|
model_env_map = {
|
|
"anthropic": "ANTHROPIC_MODEL",
|
|
"openai": "OPENAI_MODEL",
|
|
"google": "GOOGLE_MODEL",
|
|
"ollama": "OLLAMA_MODEL",
|
|
"custom": "CUSTOM_MODEL",
|
|
}
|
|
env_key = model_env_map.get(provider)
|
|
if env_key:
|
|
os.environ[env_key] = body.llm_model
|
|
changed.append("llm_model")
|
|
|
|
if body.image_provider is not None:
|
|
if body.image_provider not in IMAGE_PROVIDERS:
|
|
raise HTTPException(status_code=400, detail=f"Invalid image provider: {body.image_provider}")
|
|
os.environ["IMAGE_PROVIDER"] = body.image_provider
|
|
changed.append("image_provider")
|
|
|
|
if body.anthropic_api_key is not None:
|
|
os.environ["ANTHROPIC_API_KEY"] = body.anthropic_api_key
|
|
changed.append("anthropic_api_key")
|
|
|
|
if body.openai_api_key is not None:
|
|
os.environ["OPENAI_API_KEY"] = body.openai_api_key
|
|
changed.append("openai_api_key")
|
|
|
|
if body.google_api_key is not None:
|
|
os.environ["GOOGLE_API_KEY"] = body.google_api_key
|
|
changed.append("google_api_key")
|
|
|
|
return {"ok": True, "changed": changed}
|
|
|
|
|
|
def _get_current_model() -> str:
|
|
provider = os.getenv("LLM", "anthropic")
|
|
model_env_map = {
|
|
"anthropic": "ANTHROPIC_MODEL",
|
|
"openai": "OPENAI_MODEL",
|
|
"google": "GOOGLE_MODEL",
|
|
"ollama": "OLLAMA_MODEL",
|
|
"custom": "CUSTOM_MODEL",
|
|
}
|
|
from constants.llm import DEFAULT_ANTHROPIC_MODEL, DEFAULT_OPENAI_MODEL, DEFAULT_GOOGLE_MODEL
|
|
defaults = {
|
|
"anthropic": DEFAULT_ANTHROPIC_MODEL,
|
|
"openai": DEFAULT_OPENAI_MODEL,
|
|
"google": DEFAULT_GOOGLE_MODEL,
|
|
}
|
|
env_key = model_env_map.get(provider, "ANTHROPIC_MODEL")
|
|
return os.getenv(env_key, defaults.get(provider, ""))
|