File size: 6,771 Bytes
5c03d80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f659d59
5c03d80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
"""Settings API routes for client configuration."""

from typing import Any

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field

router = APIRouter(prefix="/settings", tags=["settings"])

# In-memory storage for client settings (per-session)
# In production, this would be stored per-user in a database
_client_settings: dict[str, Any] = {
    "api_keys": {
        "openai": None,
        "anthropic": None,
        "google": None,
        "groq": None,
    },
    "selected_model": {
        "provider": "groq",
        "model": "gpt-oss-120b",
    },
    "plugins": [],
}


class APIKeyUpdate(BaseModel):
    """Request to update an API key."""

    provider: str = Field(..., description="Provider name: openai, anthropic, google, groq")
    api_key: str | None = Field(None, description="API key value, or null to clear")


class ModelSelection(BaseModel):
    """Request to select active model."""

    provider: str = Field(..., description="Provider name")
    model: str = Field(..., description="Model identifier")


class SettingsResponse(BaseModel):
    """Current settings state."""

    api_keys_configured: dict[str, bool]
    selected_model: dict[str, str]
    available_models: list[dict[str, Any]]
    plugins_installed: list[str]


# Available models configuration
AVAILABLE_MODELS = [
    {
        "provider": "groq",
        "model": "gpt-oss-120b",
        "name": "GPT-OSS 120B (Groq)",
        "description": "Fast open-source model via Groq",
        "default": True,
    },
    {
        "provider": "google",
        "model": "gemini-2.5-flash",
        "name": "Gemini Flash 2.5",
        "description": "Google's fast Gemini model",
        "default": False,
    },
    {
        "provider": "google",
        "model": "gemini-2.5-pro",
        "name": "Gemini Pro 2.5",
        "description": "Google's advanced Gemini model",
        "default": False,
    },
    {
        "provider": "openai",
        "model": "gpt-4o",
        "name": "GPT-4o",
        "description": "OpenAI's flagship model",
        "default": False,
    },
    {
        "provider": "openai",
        "model": "gpt-4o-mini",
        "name": "GPT-4o Mini",
        "description": "OpenAI's efficient model",
        "default": False,
    },
    {
        "provider": "anthropic",
        "model": "claude-3-5-sonnet-20241022",
        "name": "Claude 3.5 Sonnet",
        "description": "Anthropic's Claude Sonnet",
        "default": False,
    },
    {
        "provider": "groq",
        "model": "llama-3.3-70b-versatile",
        "name": "Llama 3.3 70B",
        "description": "Meta's Llama via Groq",
        "default": False,
    },
    {
        "provider": "groq",
        "model": "mixtral-8x7b-32768",
        "name": "Mixtral 8x7B",
        "description": "Mistral's MoE model via Groq",
        "default": False,
    },
]


@router.get("")
@router.get("/", response_model=SettingsResponse)
async def get_settings() -> SettingsResponse:
    """Get current client settings."""
    from app.config import get_settings as get_env_settings

    env_settings = get_env_settings()

    # Check which API keys are configured (either env or client)
    api_keys_configured = {
        "openai": bool(env_settings.openai_api_key or _client_settings["api_keys"]["openai"]),
        "anthropic": bool(env_settings.anthropic_api_key or _client_settings["api_keys"]["anthropic"]),
        "google": bool(env_settings.google_api_key or _client_settings["api_keys"]["google"]),
        "groq": bool(env_settings.groq_api_key or _client_settings["api_keys"]["groq"]),
    }

    return SettingsResponse(
        api_keys_configured=api_keys_configured,
        selected_model=_client_settings["selected_model"],
        available_models=AVAILABLE_MODELS,
        plugins_installed=_client_settings["plugins"],
    )


@router.post("/api-key")
async def update_api_key(update: APIKeyUpdate) -> dict[str, Any]:
    """Update a client API key."""
    if update.provider not in _client_settings["api_keys"]:
        raise HTTPException(
            status_code=400,
            detail=f"Unknown provider: {update.provider}. Valid: openai, anthropic, google, groq",
        )

    _client_settings["api_keys"][update.provider] = update.api_key

    return {
        "status": "success",
        "message": f"API key for {update.provider} {'set' if update.api_key else 'cleared'}",
        "provider": update.provider,
        "configured": bool(update.api_key),
    }


@router.post("/model")
async def select_model(selection: ModelSelection) -> dict[str, Any]:
    """Select the active model."""
    # Validate model exists
    valid_model = any(
        m["provider"] == selection.provider and m["model"] == selection.model for m in AVAILABLE_MODELS
    )

    if not valid_model:
        raise HTTPException(
            status_code=400,
            detail=f"Invalid model: {selection.provider}/{selection.model}",
        )

    _client_settings["selected_model"] = {
        "provider": selection.provider,
        "model": selection.model,
    }

    return {
        "status": "success",
        "message": f"Model set to {selection.provider}/{selection.model}",
        "selected_model": _client_settings["selected_model"],
    }


@router.get("/api-key/required")
async def check_api_key_required() -> dict[str, Any]:
    """Check if user needs to provide API keys."""
    from app.config import get_settings as get_env_settings

    env_settings = get_env_settings()
    selected = _client_settings["selected_model"]

    # Check if selected provider has a key configured
    provider = selected["provider"]

    env_key = getattr(env_settings, f"{provider}_api_key", None)
    client_key = _client_settings["api_keys"].get(provider)

    has_key = bool(env_key or client_key)

    return {
        "required": not has_key,
        "provider": provider,
        "model": selected["model"],
        "message": None if has_key else f"Please provide an API key for {provider} to use {selected['model']}",
    }


def get_active_api_key(provider: str) -> str | None:
    """Get the active API key for a provider (client or env)."""
    from app.config import get_settings as get_env_settings

    # Client keys take precedence
    client_key = _client_settings["api_keys"].get(provider)
    if client_key:
        return client_key

    # Fall back to environment
    env_settings = get_env_settings()
    env_key = getattr(env_settings, f"{provider}_api_key", None)
    if env_key:
        return env_key.get_secret_value() if hasattr(env_key, "get_secret_value") else str(env_key)

    return None


def get_selected_model() -> dict[str, str]:
    """Get the currently selected model."""
    return _client_settings["selected_model"]