Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214import React, { useEffect, useContext, useId, useLayoutEffect, useRef, useState,} from "react";import { createPortal } from "react-dom";import { ReaderThemeContext } from "./theme.js";import { CaretDown, Check, MagnifyingGlass, Sparkle,} from "@phosphor-icons/react";
export function ModelPicker({ choices, selected, onChange, onSettings, inline = false,}) { const theme = useContext(ReaderThemeContext); const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const [active, setActive] = useState(0); const [position, setPosition] = useState(null); const trigger = useRef(null); const popup = useRef(null); const search = useRef(null); const id = useId(); const filtered = choices.filter((choice) => `${choice.name} ${choice.providerName}` .toLowerCase() .includes(query.toLowerCase()), ); const close = (restore = false) => { setOpen(false); if (restore) trigger.current?.focus(); }; const choose = (choice) => { if (!choice) return; onChange({ providerId: choice.providerId, model: choice.name }); close(true); }; useLayoutEffect(() => { if (!open || inline) return; const align = () => { const rect = trigger.current.getBoundingClientRect(); const width = Math.min(Math.max(rect.width, 300), window.innerWidth - 24); const height = Math.min(330, Math.max(180, rect.top - 24)); setPosition({ width, maxHeight: height, left: Math.max(12, Math.min(rect.left, window.innerWidth - width - 12)), bottom: window.innerHeight - rect.top + 8, }); }; align(); window.addEventListener("resize", align); window.addEventListener("scroll", align, true); return () => { window.removeEventListener("resize", align); window.removeEventListener("scroll", align, true); }; }, [open, inline]); useEffect(() => { if (!open) return; setQuery(""); setActive( Math.max( 0, choices.findIndex((choice) => choice.value === selected?.value), ), ); search.current?.focus(); const outside = (event) => { if ( !popup.current?.contains(event.target) && !trigger.current?.contains(event.target) ) close(); }; const escape = (event) => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); close(true); } }; document.addEventListener("pointerdown", outside); document.addEventListener("keydown", escape, true); return () => { document.removeEventListener("pointerdown", outside); document.removeEventListener("keydown", escape, true); }; }, [open]); useEffect(() => { popup.current ?.querySelector('[aria-selected="true"]') ?.scrollIntoView({ block: "nearest" }); }, [active, query]); const popupContent = ( <div className={"model-picker-popup " + (inline ? "model-picker-inline" : "")} ref={popup} style={inline ? undefined : position || { visibility: "hidden" }} > <div className="model-picker-search"> <MagnifyingGlass size={16} aria-hidden="true" /> <input ref={search} aria-label="Find a model" role="combobox" aria-expanded="true" aria-controls={id} aria-autocomplete="list" aria-activedescendant={ filtered[active] ? `${id}-${active}` : undefined } placeholder="Find a model…" value={query} onChange={(event) => { setQuery(event.target.value); setActive(0); }} onKeyDown={(event) => { if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) { event.preventDefault(); setActive((current) => event.key === "Home" ? 0 : event.key === "End" ? Math.max(0, filtered.length - 1) : (current + (event.key === "ArrowDown" ? 1 : -1) + filtered.length) % (filtered.length || 1), ); } else if (event.key === "Enter") { event.preventDefault(); choose(filtered[active]); } else if (event.key === "Tab") close(); }} /> </div> <div className="model-picker-list" id={id} role="listbox" aria-label="Available models" > {filtered.map((choice, index) => ( <button type="button" key={choice.value} role="option" id={`${id}-${index}`} aria-selected={active === index} tabIndex={-1} className={ "model-picker-option " + (active === index ? "focused" : "") } onPointerMove={() => setActive(index)} onClick={() => choose(choice)} > <span> <strong>{choice.name}</strong> <small>{choice.providerName}</small> </span> {selected?.value === choice.value && ( <Check size={16} aria-label="Current model" /> )} </button> ))} {!filtered.length && ( <p className="model-picker-empty">No matching models</p> )} </div> </div> ); return ( <> <button type="button" className="model-picker-trigger" ref={trigger} aria-label="Model for this request" aria-haspopup="listbox" aria-expanded={open} aria-controls={open ? id : undefined} onClick={() => (choices.length ? setOpen(!open) : onSettings())} onKeyDown={(event) => { if (["ArrowDown", "ArrowUp"].includes(event.key)) { event.preventDefault(); setOpen(true); } }} > <Sparkle size={16} aria-hidden="true" /> <span> {selected?.name || (choices.length ? "Choose a model" : "Add a model")} </span> <CaretDown size={13} aria-hidden="true" /> </button> {open && (inline ? popupContent : createPortal(<div className="odre-reader reader-portal" data-reader-theme={theme}>{popupContent}</div>, document.body))} </> );}