diff --git a/cortex/routers/local_llm.py b/cortex/routers/local_llm.py index 36d0962..3d67125 100644 --- a/cortex/routers/local_llm.py +++ b/cortex/routers/local_llm.py @@ -25,11 +25,31 @@ import jwt from fastapi import APIRouter, Form, Request from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse -from auth_utils import COOKIE_NAME, decode_token +from auth_utils import COOKIE_NAME, decode_token, _read_auth from config import settings as app_settings +from persona import list_user_personas import model_registry as reg from tools import TOOL_CATEGORIES +_LAST_PERSONA_COOKIE = "cx_last_persona" + + +def _preferred_persona(request: Request, username: str) -> str: + names = list_user_personas(username) + if not names: + return "" + cookie_val = request.cookies.get(_LAST_PERSONA_COOKIE, "") + if cookie_val in names: + return cookie_val + return names[0] + + +def _integrations_nav(username: str) -> str: + role = _read_auth(username).get("role", "user") + if role == "admin": + return 'Integrations' + return "" + logger = logging.getLogger(__name__) router = APIRouter() @@ -50,7 +70,7 @@ def _get_user(request: Request) -> str | None: # ── Page renderer ───────────────────────────────────────────────────────────── -def _render(username: str, success: str = "", error: str = "") -> str: +def _render(username: str, request: Request | None = None, success: str = "", error: str = "") -> str: registry = reg.get_registry(username) hosts = registry.get("hosts", []) models = registry.get("models", []) @@ -431,6 +451,12 @@ def _render(username: str, success: str = "", error: str = "") -> str: } for key, val in replacements.items(): html = html.replace(key, val) + + back_persona = _preferred_persona(request, username) if request else "" + html = html.replace("{{ back_href }}", f"/{username}/{back_persona}" if back_persona else "/") + html = html.replace("{{ help_href }}", f"/help?persona={back_persona}" if back_persona else "/help") + html = html.replace("{{ integrations_nav }}", _integrations_nav(username)) + if success: html = html.replace("", f'
{success}
') if error: @@ -445,7 +471,7 @@ async def models_page_canonical(request: Request): username = _get_user(request) if not username: return RedirectResponse("/login", status_code=302) - return HTMLResponse(_render(username)) + return HTMLResponse(_render(username, request)) @router.get("/settings/local", include_in_schema=False) @@ -464,9 +490,9 @@ async def save_google_account( if not username: return RedirectResponse("/login", status_code=302) if not api_key.strip() and not account_id.strip(): - return HTMLResponse(_render(username, error="API key is required.")) + return HTMLResponse(_render(username, request, error="API key is required.")) reg.save_google_account(username, account_id or None, label, api_key) - return HTMLResponse(_render(username, success="Google account saved.")) + return HTMLResponse(_render(username, request, success="Google account saved.")) @router.post("/settings/local/google-account/{account_id}/remove", include_in_schema=False) @@ -475,7 +501,7 @@ async def remove_google_account(request: Request, account_id: str): if not username: return RedirectResponse("/login", status_code=302) reg.remove_google_account(username, account_id) - return HTMLResponse(_render(username, success="Google account removed.")) + return HTMLResponse(_render(username, request, success="Google account removed.")) @router.post("/settings/local/anthropic-key", include_in_schema=False) @@ -489,9 +515,9 @@ async def save_anthropic_api_key( if not username: return RedirectResponse("/login", status_code=302) if not api_key.strip() and not key_id.strip(): - return HTMLResponse(_render(username, error="API key is required.")) + return HTMLResponse(_render(username, request, error="API key is required.")) reg.save_anthropic_api_key(username, key_id or None, label, api_key) - return HTMLResponse(_render(username, success="Anthropic API key saved.")) + return HTMLResponse(_render(username, request, success="Anthropic API key saved.")) @router.post("/settings/local/anthropic-key/{key_id}/remove", include_in_schema=False) @@ -500,7 +526,7 @@ async def remove_anthropic_api_key(request: Request, key_id: str): if not username: return RedirectResponse("/login", status_code=302) reg.remove_anthropic_api_key(username, key_id) - return HTMLResponse(_render(username, success="Anthropic API key removed.")) + return HTMLResponse(_render(username, request, success="Anthropic API key removed.")) @router.post("/settings/local/host", include_in_schema=False) @@ -517,9 +543,9 @@ async def save_host( if not username: return RedirectResponse("/login", status_code=302) if not api_url.strip(): - return HTMLResponse(_render(username, error="API URL is required.")) + return HTMLResponse(_render(username, request, error="API URL is required.")) reg.save_host(username, host_id or None, label, api_url, api_key, host_type, max_concurrent) - return HTMLResponse(_render(username, success="Host saved.")) + return HTMLResponse(_render(username, request, success="Host saved.")) @router.post("/settings/local/host/{host_id}/remove", include_in_schema=False) @@ -528,7 +554,7 @@ async def remove_host(request: Request, host_id: str): if not username: return RedirectResponse("/login", status_code=302) reg.remove_host(username, host_id) - return HTMLResponse(_render(username, success="Host removed.")) + return HTMLResponse(_render(username, request, success="Host removed.")) @router.post("/settings/local/models/add", include_in_schema=False) @@ -560,9 +586,9 @@ async def add_model( if provider == "local": if not model_name.strip(): - return HTMLResponse(_render(username, error="Model name is required.")) + return HTMLResponse(_render(username, request, error="Model name is required.")) if not host_id.strip(): - return HTMLResponse(_render(username, error="Select a host.")) + return HTMLResponse(_render(username, request, error="Select a host.")) reg.save_model(username, None, host_id, label, model_name, context_k, tag_list, max_rounds=max_rounds_, tools=tools_bool, reasoning_budget_tokens=reasoning_budget_) @@ -570,9 +596,9 @@ async def add_model( elif provider in ("google", "anthropic"): if not cloud_model_name.strip(): - return HTMLResponse(_render(username, error="Select a model from the catalog.")) + return HTMLResponse(_render(username, request, error="Select a model from the catalog.")) if provider == "google" and not account_id.strip(): - return HTMLResponse(_render(username, error="Select a Google account.")) + return HTMLResponse(_render(username, request, error="Select a Google account.")) reg.save_cloud_model( username, None, provider, cloud_model_name, label, account_id=account_id or None, @@ -582,10 +608,10 @@ async def add_model( ) display = label or cloud_model_name else: - return HTMLResponse(_render(username, error=f"Unknown provider: {provider}")) + return HTMLResponse(_render(username, request, error=f"Unknown provider: {provider}")) logger.info("model added: %s / %s (%s)", username, display, provider) - return HTMLResponse(_render(username, success=f'Model "{display}" added.')) + return HTMLResponse(_render(username, request, success=f'Model "{display}" added.')) @router.post("/settings/local/models/{model_id}/edit", include_in_schema=False) @@ -608,14 +634,14 @@ async def edit_model( if not username: return RedirectResponse("/login", status_code=302) if not model_name.strip(): - return HTMLResponse(_render(username, error="Model name is required.")) + return HTMLResponse(_render(username, request, error="Model name is required.")) tag_list = [t.strip() for t in tags.split(",") if t.strip()] max_rounds_ = max_rounds or None tools_bool = tools != 0 reasoning_budget_ = reasoning_budget_tokens or None if mtype == "local_openai": if not host_id.strip(): - return HTMLResponse(_render(username, error="Select a host for this model.")) + return HTMLResponse(_render(username, request, error="Select a host for this model.")) reg.save_model(username, model_id, host_id, label, model_name, context_k, tag_list, max_rounds=max_rounds_, tools=tools_bool, reasoning_budget_tokens=reasoning_budget_) @@ -628,10 +654,10 @@ async def edit_model( credential_id=credential_id or "cli", context_k=context_k, tags=tag_list, max_rounds=max_rounds_, tools=tools_bool) else: - return HTMLResponse(_render(username, error=f"Unknown model type: {mtype}")) + return HTMLResponse(_render(username, request, error=f"Unknown model type: {mtype}")) display = label.strip() or model_name.strip() logger.info("model edited: %s / %s (%s)", username, display, mtype) - return HTMLResponse(_render(username, success=f'Model "{display}" updated.')) + return HTMLResponse(_render(username, request, success=f'Model "{display}" updated.')) @router.post("/settings/local/models/{model_id}/remove", include_in_schema=False) @@ -640,7 +666,7 @@ async def remove_model(request: Request, model_id: str): if not username: return RedirectResponse("/login", status_code=302) reg.remove_model(username, model_id) - return HTMLResponse(_render(username, success="Model removed.")) + return HTMLResponse(_render(username, request, success="Model removed.")) @router.post("/api/models/role") diff --git a/cortex/static/local_llm.html b/cortex/static/local_llm.html index 5b74d30..ff9774b 100644 --- a/cortex/static/local_llm.html +++ b/cortex/static/local_llm.html @@ -7,52 +7,10 @@ + -Configure providers, hosts, and model assignments.
-Configure providers, hosts, and model assignments.