"""dashboard/model_picker.py — sidebar provider/model/API-key picker. Renders a compact popover in the sidebar showing the active provider+model, and lets the visitor paste their own API key for this browser session only. Returns a frozen ``RunConfig`` (or None when no key is available) that the caller threads through to generation / chat / translation — never re-read from session_state after the click that consumes it. """ from __future__ import annotations import streamlit as st from agent.llm import ( MODEL_CATALOG, PROVIDER_LABELS, RunConfig, console_url, key_looks_valid, resolve_api_key, ) from dashboard.i18n import t def render() -> tuple[RunConfig | None, str]: """Render the Model popover. Returns (config, key_source). config is None when no key is available anywhere (env or pasted) — callers must block the action (Generate / chat) in that case. """ st.session_state.setdefault("llm_provider", "anthropic") provider = st.session_state["llm_provider"] default_model = MODEL_CATALOG[provider][0][0] model_key = f"llm_model_{provider}" st.session_state.setdefault(model_key, default_model) active_model = st.session_state[model_key] active_label = next( (label for mid, label in MODEL_CATALOG[provider] if mid == active_model), active_model, ) short_label = active_label.split(" — ")[0] with st.popover(f"⚙ {PROVIDER_LABELS[provider]} · {short_label}", use_container_width=True): st.caption(t("model_settings_title")) selected_provider = st.radio( t("model_provider_label"), options=list(MODEL_CATALOG.keys()), format_func=lambda p: PROVIDER_LABELS.get(p, p), key="llm_provider", horizontal=True, ) model_options = MODEL_CATALOG[selected_provider] model_ids = [mid for mid, _label in model_options] model_key = f"llm_model_{selected_provider}" st.session_state.setdefault(model_key, model_options[0][0]) selected_model = st.selectbox( t("model_select_label"), options=model_ids, format_func=lambda mid: next((lbl for m, lbl in model_options if m == mid), mid), key=model_key, ) key_state_key = f"llm_api_key_{selected_provider}" user_key = st.text_input( t("model_key_label"), type="password", key=key_state_key, help=t("model_key_help"), ) if user_key and not key_looks_valid(selected_provider, user_key): st.caption( "⚠ " + t("model_key_prefix_warning").format( provider=PROVIDER_LABELS.get(selected_provider, selected_provider) ) ) key, source = resolve_api_key(selected_provider, user_key) if source == "env": st.caption(t("model_status_env")) elif source == "user": st.caption(t("model_status_user")) else: st.warning( t("model_status_missing").format(url=console_url(selected_provider)), icon="⚠️", ) if key is None: return None, source return RunConfig(provider=selected_provider, model=selected_model, api_key=user_key or None), source