// A searchable model picker: frontier models first, each row with provider, price, speed and context.
import { esc } from "./util.js";
import { icon } from "./icons.js";
const short = (id) => id.split("/")[1] || id;
const org = (id) => id.split("/")[0];
const ctx = (n) => (n ? (n >= 1e6 ? `${(n / 1e6).toFixed(n % 1e6 ? 1 : 0)}M` : `${Math.round(n / 1000)}k`) : "");
export function picker(host, { models, value, onChange, note, groupLabel }) {
let current = models.find((m) => m.id === value) || models[0];
let open = false, q = "", active = 0;
host.classList.add("picker");
const render = () => {
const list = filtered();
host.innerHTML = `
${open ? `
${icon("search", 15)}
${list.length ? list.map((m, i) => `${i === 0 || m.featured !== list[i - 1].featured
? `
${groupLabel || (m.featured ? "Frontier" : "All with tool calling")}
` : ""}
`).join("") : `
No model matches “${esc(q)}”.
`}
${note ? `
${note}
` : ""}
` : ""}`;
if (open) {
const inp = host.querySelector(".pk-q");
inp.focus(); inp.setSelectionRange(q.length, q.length);
host.querySelector(".pk-row.act")?.scrollIntoView({ block: "nearest" });
}
};
const filtered = () => {
const t = q.trim().toLowerCase();
return t ? models.filter((m) => (m.id + " " + m.provider).toLowerCase().includes(t)) : models;
};
const choose = (id) => {
current = models.find((m) => m.id === id) || current;
open = false; q = ""; render(); onChange(current);
};
host.addEventListener("click", (e) => {
if (e.target.closest(".pk-btn")) { open = !open; active = 0; render(); return; }
const row = e.target.closest(".pk-row");
if (row) choose(row.dataset.id);
});
host.addEventListener("input", (e) => { if (e.target.classList.contains("pk-q")) { q = e.target.value; active = 0; render(); } });
host.addEventListener("keydown", (e) => {
if (!open) return;
const list = filtered();
if (e.key === "ArrowDown") { active = Math.min(list.length - 1, active + 1); render(); e.preventDefault(); }
else if (e.key === "ArrowUp") { active = Math.max(0, active - 1); render(); e.preventDefault(); }
else if (e.key === "Enter" && list[active]) { choose(list[active].id); e.preventDefault(); }
else if (e.key === "Escape") { open = false; render(); e.stopPropagation(); }
});
// composedPath is fixed when the click happens; host.contains(target) is not, because render() replaces the target
document.addEventListener("click", (e) => { if (open && !e.composedPath().includes(host)) { open = false; render(); } });
render();
return { get value() { return current.id; }, get model() { return current; } };
}