MyMidas/backend/app/api/v1/settings.py
megaproxy d6118bac54 Add custom API URL and model to AI settings
- Settings → AI: optional base URL and model name fields
- Defaults to Anthropic/OpenAI public APIs when left blank
- Custom URL enables Open WebUI, LM Studio, Ollama, and any OpenAI-compatible endpoint
- Parse endpoint uses custom base URL and model if configured
- Migration 0004: ai_base_url + ai_model columns on users
- OpenAI provider label updated to "OpenAI-compatible"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:01:00 +00:00

84 lines
2.4 KiB
Python

"""
User-level settings: AI provider configuration.
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import update
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.security import decrypt_field, encrypt_field
from app.dependencies import get_current_user, get_db
from app.db.models.user import User
router = APIRouter(prefix="/settings", tags=["settings"])
SUPPORTED_PROVIDERS = {"anthropic", "openai"}
class AiSettingsResponse(BaseModel):
provider: str | None
has_api_key: bool
base_url: str | None
model: str | None
class AiSettingsSave(BaseModel):
provider: str
api_key: str = ""
base_url: str = ""
model: str = ""
@router.get("/ai", response_model=AiSettingsResponse)
async def get_ai_settings(user: User = Depends(get_current_user)):
return AiSettingsResponse(
provider=user.ai_provider,
has_api_key=bool(user.ai_api_key_enc),
base_url=user.ai_base_url,
model=user.ai_model,
)
@router.put("/ai", response_model=AiSettingsResponse)
async def save_ai_settings(
body: AiSettingsSave,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
if body.provider not in SUPPORTED_PROVIDERS:
raise HTTPException(status_code=400, detail=f"Unsupported provider. Choose: {', '.join(SUPPORTED_PROVIDERS)}")
values: dict = {
"ai_provider": body.provider,
"ai_base_url": body.base_url.rstrip("/") or None,
"ai_model": body.model.strip() or None,
}
if body.api_key.strip():
values["ai_api_key_enc"] = encrypt_field(body.api_key.strip())
elif not user.ai_api_key_enc:
raise HTTPException(status_code=400, detail="api_key is required when no key is saved yet")
await db.execute(update(User).where(User.id == user.id).values(**values))
await db.commit()
return AiSettingsResponse(
provider=body.provider,
has_api_key=True,
base_url=values["ai_base_url"],
model=values["ai_model"],
)
@router.delete("/ai", status_code=204)
async def clear_ai_settings(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
await db.execute(
update(User)
.where(User.id == user.id)
.values(ai_provider=None, ai_api_key_enc=None, ai_base_url=None, ai_model=None)
)
await db.commit()