import { useEffect, useState } from "react" import { Box, Text, useWindowSize } from "ink" import { request, type Connection } from "../daemon.js" import { profiles, type Profiles, type Settings } from "../profiles.js" import { Picker } from "./picker.js" import { TextInput } from "./text-input.js" type ModelPickerProps = { connection: Connection; provider: string; current: string; onSelect: (model: string, provider: string) => Promise; onCancel: () => void } export function ModelPicker({ connection, provider, current, onSelect, onCancel }: ModelPickerProps) { const [saved, setSaved] = useState() const [name, setName] = useState(provider) const [choosingProvider, setChoosingProvider] = useState(false) const [error, setError] = useState("") const sessionProvider = provider || saved?.active useEffect(() => { let active = true void profiles().then(saved => { if (active) { setSaved(saved); setName(provider || saved.active || "") } }).catch(() => { if (active) setError("could not read providers; check /login") }) return () => { active = false } }, [provider]) return albedo /model · {name || "session provider"} changes this idle session and the default for new sessions {!saved ? <> {error || "loading providers…"} {}} onKey={(input, key) => { if (key.escape || (key.ctrl && ["c", "d"].includes(input))) { onCancel(); return true } return false }} /> : choosingProvider ? ({ id, label: id, detail: `${settings.protocol}${id === sessionProvider ? " · current" : ""}` })) } onSelect={name => { setName(name); setChoosingProvider(false) }} onCancel={() => setChoosingProvider(false)} /> : setChoosingProvider(true)} />} } function ProviderModels({ connection, provider, settings, current, onSelect, onCancel, onProvider }: Omit & { settings?: Settings; current?: string; onProvider: () => void }) { const initial = current ?? settings?.model ?? "" const [catalog, setCatalog] = useState<{ names: string[]; note?: string }>() const [manual, setManual] = useState(false) const [value, setValue] = useState(initial) const [saving, setSaving] = useState(false) const [error, setError] = useState("") const { columns } = useWindowSize() useEffect(() => { const controller = new AbortController() const provider = settings?.extension ?? "openai" const endpoint = settings?.baseUrl ?? "" void (settings ? request(connection, `/models/${encodeURIComponent(provider)}?endpoint=${encodeURIComponent(endpoint)}`) : Promise.reject(new Error("session provider missing")) ).then(names => { if (!controller.signal.aborted) setCatalog({ names, ...(!names.length ? { note: "no models listed; enter a model id manually" } : {}) }) }).catch(() => { if (!controller.signal.aborted) setCatalog({ names: [], note: "could not list models for this provider; enter a model id manually or check /login" }) }) return () => controller.abort() }, [connection, settings]) const select = async (value: string): Promise => { const model = value.trim() if (!model || model.length > 512 || /[\x00-\x1f\x7f]/.test(model)) { setError("enter a model id of 1–512 characters"); return } setError(""); setSaving(true) try { await onSelect(model, provider) } catch (error) { setError(error instanceof Error ? error.message : "could not change model"); setSaving(false) } } const cancelKey = (input: string, key: { escape: boolean; ctrl: boolean }): boolean => { if (key.escape || (key.ctrl && ["c", "d"].includes(input))) { onCancel(); return true } return false } return {error && {error}} {saving ? switching model… : manual ? <> model id: { void select(value) }} onKey={cancelKey} width={Math.max(1, columns - 10)} /> enter choose · esc cancel : <> {!catalog && loading models…} {catalog?.note && {catalog.note}} ({ id: `model:${model}`, label: model, ...(model === current ? { detail: "current" } : {}) })), { id: "manual", label: "enter model id manually" }, ]} onSelect={id => { if (id === "provider") onProvider(); else if (id === "manual") setManual(true); else void select(id.slice(6)) }} onCancel={onCancel} /> } }