Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
6.8 kB · 183 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184import { useEffect, useRef, useState } from "octane";import { CaretUpDown, Check, MagnifyingGlass } from "@octanejs/phosphor-icons";import { SearchField, Input as AriaInput } from "@octanejs/aria/components";import { inputVariants } from "octane-kumo/components/input";import { selectVariants } from "octane-kumo/components/select";import { CommandPalette } from "octane-kumo/components/command-palette";import { Popover } from "octane-kumo/components/popover";import { MODEL_CATALOG, MODEL_PROVIDERS, modelLabel, type ModelProvider, type ModelConfiguration,} from "../../shared/model-providers";
const modelChoices = Object.entries(MODEL_CATALOG).flatMap( ([provider, models]) => models.map((model) => ({ value: `${provider}:${model}`, label: modelLabel(provider as ModelProvider, model), configuration: { provider, model } as ModelConfiguration, })),);
function choiceLabel(value: string) { return value === "default" ? "Installation default" : (modelChoices.find((choice) => choice.value === value)?.label ?? value);}
export function ChatModelPicker({ value, disabled, providerReady, onChange,}: { value: ModelConfiguration | null; disabled: boolean; providerReady: (provider: ModelProvider) => boolean; onChange: (value: ModelConfiguration | null) => void;}) { const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const selected = value ? `${value.provider}:${value.model}` : "default"; const select = (configuration: ModelConfiguration | null) => { onChange(configuration); setOpen(false); }; return ( <Popover open={open} onOpenChange={(next) => { setOpen(next); setQuery(""); }} > <Popover.Trigger disabled={disabled} role="combobox" aria-label="Model" aria-haspopup="listbox" className={`${selectVariants({ size: "base" })} chat-model-trigger`} > <span className="chat-model-trigger-label"> {value ? value.model.split("/").at(-1) : "Installation default"} </span> <CaretUpDown size={16} aria-hidden="true" /> </Popover.Trigger> <Popover.Content side="top" align="start" className="chat-model-dropdown"> <CommandPalette.Panel items={["default", ...modelChoices.map((choice) => choice.value)]} value={query} onValueChange={setQuery} itemToStringValue={choiceLabel} filter={(value, query) => choiceLabel(value) .toLowerCase() .includes(query.trim().toLowerCase()) } > <ModelSearchInput onClose={() => setOpen(false)} /> <CommandPalette.List className="chat-model-results"> <CommandPalette.Results className="space-y-0"> <CommandPalette.Item value="default" onClick={() => select(null)}> <span className="chat-model-option"> Installation default {selected === "default" && ( <Check size={14} aria-hidden="true" /> )} </span> </CommandPalette.Item> {modelChoices.map((choice) => ( <CommandPalette.Item key={choice.value} value={choice.value} textValue={`${choice.configuration.model}${providerReady(choice.configuration.provider) ? "" : ` ${MODEL_PROVIDERS[choice.configuration.provider].name} · Set up in Settings`}`} disabled={!providerReady(choice.configuration.provider)} onClick={() => select(choice.configuration)} > <div className="chat-model-option"> <div> <span className="chat-model-name"> {choice.configuration.model} </span> {!providerReady(choice.configuration.provider) && ( <span className="chat-model-unavailable"> {MODEL_PROVIDERS[choice.configuration.provider].name}{" "} · Set up in Settings </span> )} </div> {selected === choice.value && ( <Check size={14} aria-hidden="true" /> )} </div> </CommandPalette.Item> ))} </CommandPalette.Results> <CommandPalette.Empty>No models found.</CommandPalette.Empty> </CommandPalette.List> </CommandPalette.Panel> </Popover.Content> </Popover> );}
function ModelSearchInput({ onClose }: { onClose: () => void }) { const input = useRef<HTMLInputElement | null>(null); useEffect(() => { const field = input.current; const dialog = field?.closest(".chat-model-dropdown"); if (!field || !dialog) return; // The pinned ARIA adapter loses the active descendant when filtering replaces rows. const syncFocus = () => { const list = dialog.querySelector('[role="listbox"]'); const option = dialog.querySelector( '[role="option"][data-focused="true"]', ); if (list?.id) field.setAttribute("aria-controls", list.id); if (option?.id) field.setAttribute("aria-activedescendant", option.id); else field.removeAttribute("aria-activedescendant"); }; const observer = new MutationObserver(syncFocus); observer.observe(dialog, { subtree: true, childList: true, attributes: true, attributeFilter: ["data-focused"], }); syncFocus(); return () => observer.disconnect(); }, []); return ( <SearchField aria-label="Search models" className="chat-model-search-field"> <MagnifyingGlass size={16} aria-hidden="true" /> <AriaInput ref={input} autoFocus aria-label="Search models" placeholder="Search models…" className={`${inputVariants()} chat-model-search`} onKeyDown={(event: KeyboardEvent) => { if (event.key === "Escape") { event.preventDefault(); onClose(); return; } if (event.key !== "Enter" || event.isComposing) return; const input = event.target as HTMLInputElement; const option = input .closest(".chat-model-dropdown") ?.querySelector<HTMLElement>( '[role="option"][data-focused="true"]', ); if (!option || option.getAttribute("aria-disabled") === "true") return; event.preventDefault(); option.click(); }} /> </SearchField> );}