jira-ai-fixer/app/api/settings.py

147 lines
5.3 KiB
Python

"""Organization settings endpoints."""
from typing import Dict, Any, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from pydantic import BaseModel
import httpx
from app.core.database import get_db
from app.models.organization import Organization, OrganizationMember
from app.api.deps import get_current_user, require_role
router = APIRouter()
class AIConfig(BaseModel):
provider: str = "openrouter"
apiKey: str = ""
model: str = "meta-llama/llama-3.3-70b-instruct"
autoAnalyze: bool = True
autoCreatePR: bool = True
confidenceThreshold: int = 70
class SettingsUpdate(BaseModel):
ai_config: Optional[AIConfig] = None
class TestLLMRequest(BaseModel):
provider: str
api_key: str
model: str
# In-memory storage for now (should be moved to database)
ORG_SETTINGS: Dict[int, Dict[str, Any]] = {}
@router.get("/{org_id}/settings")
async def get_settings(
org_id: int,
member: OrganizationMember = Depends(require_role("viewer")),
db: AsyncSession = Depends(get_db)
):
"""Get organization settings."""
settings = ORG_SETTINGS.get(org_id, {})
# Mask API key for security
if settings.get("ai_config", {}).get("apiKey"):
settings["ai_config"]["apiKey"] = "***configured***"
return settings
@router.put("/{org_id}/settings")
async def update_settings(
org_id: int,
settings: SettingsUpdate,
member: OrganizationMember = Depends(require_role("admin")),
db: AsyncSession = Depends(get_db)
):
"""Update organization settings."""
if org_id not in ORG_SETTINGS:
ORG_SETTINGS[org_id] = {}
if settings.ai_config:
ORG_SETTINGS[org_id]["ai_config"] = settings.ai_config.dict()
return {"message": "Settings updated", "settings": ORG_SETTINGS[org_id]}
@router.post("/{org_id}/test-llm")
async def test_llm_connection(
org_id: int,
request: TestLLMRequest,
member: OrganizationMember = Depends(require_role("admin")),
db: AsyncSession = Depends(get_db)
):
"""Test LLM API connection."""
# Build request based on provider
if request.provider == "openrouter":
url = "https://openrouter.ai/api/v1/chat/completions"
headers = {
"Authorization": f"Bearer {request.api_key}",
"Content-Type": "application/json",
"HTTP-Referer": "https://jira-fixer.startdata.com.br",
"X-Title": "JIRA AI Fixer"
}
payload = {
"model": request.model,
"messages": [{"role": "user", "content": "Say 'OK' if you can read this."}],
"max_tokens": 10
}
elif request.provider == "anthropic":
url = "https://api.anthropic.com/v1/messages"
headers = {
"x-api-key": request.api_key,
"Content-Type": "application/json",
"anthropic-version": "2023-06-01"
}
payload = {
"model": request.model,
"max_tokens": 10,
"messages": [{"role": "user", "content": "Say 'OK' if you can read this."}]
}
elif request.provider == "openai":
url = "https://api.openai.com/v1/chat/completions"
headers = {
"Authorization": f"Bearer {request.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": request.model,
"messages": [{"role": "user", "content": "Say 'OK' if you can read this."}],
"max_tokens": 10
}
elif request.provider == "groq":
url = "https://api.groq.com/openai/v1/chat/completions"
headers = {
"Authorization": f"Bearer {request.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": request.model,
"messages": [{"role": "user", "content": "Say 'OK' if you can read this."}],
"max_tokens": 10
}
elif request.provider == "google":
url = f"https://generativelanguage.googleapis.com/v1beta/models/{request.model}:generateContent?key={request.api_key}"
headers = {"Content-Type": "application/json"}
payload = {
"contents": [{"parts": [{"text": "Say 'OK' if you can read this."}]}],
"generationConfig": {"maxOutputTokens": 10}
}
else:
raise HTTPException(status_code=400, detail=f"Unsupported provider: {request.provider}")
try:
async with httpx.AsyncClient() as client:
response = await client.post(url, headers=headers, json=payload, timeout=15.0)
if response.status_code == 200:
return {"success": True, "message": "Connection successful"}
elif response.status_code == 401:
raise HTTPException(status_code=400, detail="Invalid API key")
elif response.status_code == 403:
raise HTTPException(status_code=400, detail="API key lacks permissions")
else:
error_detail = response.json().get("error", {}).get("message", response.text)
raise HTTPException(status_code=400, detail=f"API error: {error_detail}")
except httpx.TimeoutException:
raise HTTPException(status_code=400, detail="Connection timeout")
except httpx.ConnectError:
raise HTTPException(status_code=400, detail="Could not connect to API")