File size: 3,351 Bytes
7880373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""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