diff --git a/.env.example b/.env.example index c0e0c85..18eb164 100644 --- a/.env.example +++ b/.env.example @@ -2,32 +2,15 @@ # Copy this file to .env and fill in your values. # Never commit .env to version control. -# AI provider selection — keys are configured via Settings → Credentials (stored encrypted in DB) -# Set DEFAULT_PROVIDER to the provider you'll use as the default -DEFAULT_PROVIDER=openrouter # anthropic | openrouter | openai - -# Override the model (leave empty to use the provider's default) -# DEFAULT_MODEL=claude-sonnet-4-6 - -# Available models shown in the chat model selector (comma-separated) -# AVAILABLE_MODELS=claude-sonnet-4-6,claude-opus-4-6,claude-haiku-4-5-20251001 - -# Default model pre-selected in chat UI (defaults to first in AVAILABLE_MODELS) -# DEFAULT_CHAT_MODEL=claude-sonnet-4-6 - # Master password for the encrypted credential store (required) # Choose a strong passphrase — all credentials are encrypted with this. DB_MASTER_PASSWORD=change-me-to-a-strong-passphrase -# Server +# Server port PORT=8080 -# Agent limits -MAX_TOOL_CALLS=20 -MAX_AUTONOMOUS_RUNS_PER_HOUR=10 - -# Timezone for display (stored internally as UTC) -TIMEZONE=Europe/Oslo +# Default model pre-selected in chat UI (leave empty to use the first available model) +# DEFAULT_CHAT_MODEL=claude-sonnet-4-6 # Main app database — PostgreSQL (shared postgres service) AIDE_DB_URL=postgresql://aide:change-me@postgres:5432/aide diff --git a/server/config.py b/server/config.py index 12dfbe8..7ecb769 100644 --- a/server/config.py +++ b/server/config.py @@ -97,16 +97,12 @@ def _load() -> Settings: max_runs = int(_optional("MAX_AUTONOMOUS_RUNS_PER_HOUR", "10")) timezone = _optional("TIMEZONE", "Europe/Oslo") - def _normalize_model(m: str) -> str: - """Prepend default_provider if model has no provider prefix.""" - parts = m.split(":", 1) - if len(parts) == 2 and parts[0] in _known_providers: - return m - return f"{default_provider}:{m}" - - available_models: list[str] = [] # unused; kept for backward compat + available_models: list[str] = [] default_chat_model_raw = _optional("DEFAULT_CHAT_MODEL", "") - default_chat_model = _normalize_model(default_chat_model_raw) if default_chat_model_raw else "" + if default_chat_model_raw and ":" not in default_chat_model_raw: + default_chat_model = f"{default_provider}:{default_chat_model_raw}" + else: + default_chat_model = default_chat_model_raw aide_db_url = _require("AIDE_DB_URL") diff --git a/server/providers/registry.py b/server/providers/registry.py index 63ff54f..32d9bef 100644 --- a/server/providers/registry.py +++ b/server/providers/registry.py @@ -24,10 +24,19 @@ async def _resolve_key(provider: str, user_id: str | None = None) -> str: return await credential_store.get(f"system:{provider}_api_key") or "" +async def _get_default_provider() -> str: + """Return the default provider name: credential_store → settings → 'anthropic'.""" + from ..database import credential_store + from ..config import settings + val = await credential_store.get("system:default_provider") + if val and val in {"anthropic", "openrouter", "openai"}: + return val + return settings.default_provider + + async def get_provider(user_id: str | None = None) -> AIProvider: """Return the default provider, with keys resolved for the given user.""" - from ..config import settings - return await get_provider_for_name(settings.default_provider, user_id=user_id) + return await get_provider_for_name(await _get_default_provider(), user_id=user_id) async def get_provider_for_name(name: str, user_id: str | None = None) -> AIProvider: @@ -64,15 +73,13 @@ async def get_provider_for_model(model_str: str, user_id: str | None = None) -> "openrouter:openai/gpt-4o" → (OpenRouterProvider, "openai/gpt-4o") "claude-sonnet-4-6" → (default_provider, "claude-sonnet-4-6") """ - from ..config import settings - _known = {"anthropic", "openrouter", "openai"} if ":" in model_str: prefix, bare = model_str.split(":", 1) if prefix in _known: return await get_provider_for_name(prefix, user_id=user_id), bare # No recognised prefix — use default provider, full string as model ID - return await get_provider_for_name(settings.default_provider, user_id=user_id), model_str + return await get_provider_for_name(await _get_default_provider(), user_id=user_id), model_str async def get_available_providers(user_id: str | None = None) -> list[str]: diff --git a/server/web/routes.py b/server/web/routes.py index 197bbc6..b49b4ec 100644 --- a/server/web/routes.py +++ b/server/web/routes.py @@ -380,6 +380,33 @@ async def get_queue_status(request: Request): return agent_runner.queue_status +@router.get("/settings/provider") +async def get_default_provider(request: Request): + _require_admin(request) + from ..providers.registry import get_available_providers + from ..config import settings as _settings + val = await credential_store.get("system:default_provider") + available = await get_available_providers() + current = val or _settings.default_provider + # If the saved provider no longer has a key, fall back to the first available + if current not in available and available: + current = available[0] + return {"default_provider": current, "available_providers": available} + + +class ProviderIn(BaseModel): + default_provider: str + + +@router.post("/settings/provider") +async def set_default_provider(request: Request, body: ProviderIn): + _require_admin(request) + if body.default_provider not in {"anthropic", "openrouter", "openai"}: + raise HTTPException(status_code=400, detail="Invalid provider. Use: anthropic, openrouter, openai") + await credential_store.set("system:default_provider", body.default_provider, "Default AI provider") + return {"default_provider": body.default_provider} + + @router.get("/settings/default-models") async def get_default_models(request: Request): _require_admin(request) diff --git a/server/web/static/app.js b/server/web/static/app.js index 65c01c1..e3f629f 100644 --- a/server/web/static/app.js +++ b/server/web/static/app.js @@ -1202,8 +1202,6 @@ function initAudit() { const el = document.getElementById(id); if (el) el.value = ""; }); - const cb = document.getElementById("filter-confirmed"); - if (cb) cb.checked = false; const banner = document.getElementById("audit-task-filter-banner"); if (banner) { banner.textContent = `Showing entries for run ${_auditTaskId.slice(0, 8)}…`; @@ -1218,7 +1216,6 @@ function initAudit() { const el = document.getElementById(id); if (el) el.value = ""; }); - document.getElementById("filter-confirmed").checked = false; _auditTaskId = ""; const banner = document.getElementById("audit-task-filter-banner"); if (banner) banner.style.display = "none"; @@ -1235,8 +1232,6 @@ async function loadAudit(page) { const end = document.getElementById("filter-end")?.value; const tool = document.getElementById("filter-tool")?.value; const session = document.getElementById("filter-session")?.value; - const confirmed = document.getElementById("filter-confirmed")?.checked; - const startISO = parseEuDate(start); const endISO = parseEuDate(end, true); if (startISO) params.set("start", startISO); @@ -1244,7 +1239,6 @@ async function loadAudit(page) { if (tool) params.set("tool_name", tool); if (session) params.set("session_id", session); if (_auditTaskId) params.set("task_id", _auditTaskId); - if (confirmed) params.set("confirmed_only", "true"); params.set("page", auditPage); params.set("per_page", 50); @@ -1265,7 +1259,6 @@ function renderAuditTable(data) {
${esc(e.tool_name)}${esc((e.session_id || "").slice(0,8))}