147 lines
5.3 KiB
Python
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")
|