Settings: real model dropdown with cost hints + Custom escape hatch
The previous datalist-on-input approach was fragile — Safari hid the suggestions, and there was no visual cue that a dropdown existed. Replace with a proper Select populated from the catalog. Each option shows the per-1M-token rates inline so operators see cost while choosing. "Custom…" switches to free-text for models the catalog doesn't know about, with a "Catalog" button to flip back. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -224,17 +224,11 @@ export function LlmConfigurationsPanel() {
|
|||||||
</Select>
|
</Select>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Model">
|
<Field label="Model">
|
||||||
<Input
|
<ModelPicker
|
||||||
value={draft.model}
|
value={draft.model}
|
||||||
onChange={(e) => setDraft({ ...draft, model: e.target.value })}
|
models={modelsForProvider}
|
||||||
list="llm-config-models"
|
onChange={(model) => setDraft({ ...draft, model })}
|
||||||
placeholder="gpt-4o-mini"
|
|
||||||
/>
|
/>
|
||||||
<datalist id="llm-config-models">
|
|
||||||
{modelsForProvider.map((m) => (
|
|
||||||
<option key={m.model} value={m.model} />
|
|
||||||
))}
|
|
||||||
</datalist>
|
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Vault secret name (optional)">
|
<Field label="Vault secret name (optional)">
|
||||||
<Input
|
<Input
|
||||||
@@ -332,6 +326,89 @@ export function LlmConfigurationsPanel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model picker: dropdown of catalog entries for the selected provider, plus
|
||||||
|
* a "Custom…" escape hatch that switches to free-text for models the
|
||||||
|
* catalog doesn't know about. The custom branch is sticky — once the user
|
||||||
|
* types a custom name, switching providers resets to the dropdown.
|
||||||
|
*/
|
||||||
|
function ModelPicker({
|
||||||
|
value,
|
||||||
|
models,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
value: string
|
||||||
|
models: CatalogEntry[]
|
||||||
|
onChange: (model: string) => void
|
||||||
|
}) {
|
||||||
|
const known = useMemo(() => new Set(models.map((m) => m.model)), [models])
|
||||||
|
const isCustom = value !== "" && !known.has(value)
|
||||||
|
const [customMode, setCustomMode] = useState(isCustom)
|
||||||
|
|
||||||
|
// Reset to dropdown if the available models change (e.g. provider switched)
|
||||||
|
// and we're not actively in custom mode.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isCustom) setCustomMode(false)
|
||||||
|
}, [models, isCustom])
|
||||||
|
|
||||||
|
if (customMode) {
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
placeholder="custom-model-id"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setCustomMode(false)
|
||||||
|
onChange("")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Catalog
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={known.has(value) ? value : ""}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
if (v === "__custom__") {
|
||||||
|
setCustomMode(true)
|
||||||
|
onChange("")
|
||||||
|
} else {
|
||||||
|
onChange(v)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={models.length ? "Pick a model…" : "No models for this provider"} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{models.map((m) => (
|
||||||
|
<SelectItem key={m.model} value={m.model}>
|
||||||
|
<span className="flex items-center justify-between gap-3">
|
||||||
|
<span>{m.model}</span>
|
||||||
|
<span className="text-[10px] text-muted-foreground">
|
||||||
|
${m.input_cost_per_million.toFixed(2)} / ${m.output_cost_per_million.toFixed(2)} per 1M
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectItem value="__custom__">
|
||||||
|
<span className="text-muted-foreground">Custom…</span>
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function Field({ label, children }: { label: string; children: React.ReactNode }) {
|
function Field({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user