'use client'; import { Bookmark, Copy, Play, Trash2 } from 'lucide-react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useEffect, useMemo, useState } from 'react'; import { Hint } from '@/components/ui/hint'; import { cn } from '@/lib/cn'; import { fmtInt } from '@/lib/format'; import { OPENNESS_LABELS, STATUS_LABELS, typeLabel } from '@/lib/site'; /* Structured query builder (client). Type selector → type-specific form → a `/models?…` (or `/explore/?…`) URL that updates as you type. The builder's own URL (`/explore?type=model&min_params=…`) is shareable; saved queries live in localStorage['aia-saved-queries'] (name + URL). Filters the API cannot express are shown disabled with the reason. */ export type BuilderOptions = { types: { entity_type: string; count: number; label: string }[]; organizations: { slug: string; name: string; count: number }[]; families: { value: string; label: string; count: number }[]; licenses: { value: string; label: string; count: number }[]; openness: { value: string; count: number }[]; modalities: { value: string; count: number }[]; status: { value: string; count: number }[]; benchmarks: { slug: string; name: string }[]; }; type Saved = { name: string; url: string; created_at: string }; const KEY = 'aia-saved-queries'; const MODEL_KEYS = ['org', 'family', 'min_params', 'max_params', 'min_context', 'license', 'openness', 'modality', 'year_from', 'year_to', 'status', 'reasoning', 'sort', 'order', 'q', 'trust', 'include'] as const; const GENERIC_KEYS = ['q', 'org', 'sort'] as const; const PARAM_PRESETS: { label: string; value: string }[] = [ { label: 'any', value: '' }, { label: '1B', value: '1000000000' }, { label: '7B', value: '7000000000' }, { label: '30B', value: '30000000000' }, { label: '70B', value: '70000000000' }, { label: '100B', value: '100000000000' }, { label: '400B', value: '400000000000' }, { label: '1T', value: '1000000000000' }, ]; const CONTEXT_PRESETS = [ { label: 'any', value: '' }, { label: '8K', value: '8192' }, { label: '32K', value: '32768' }, { label: '128K', value: '131072' }, { label: '200K', value: '200000' }, { label: '1M', value: '1000000' }, ]; const MODEL_SORTS = [ { value: '', label: 'Default (recently updated)' }, { value: 'release', label: 'Release date' }, { value: 'params', label: 'Parameters' }, { value: 'name', label: 'Name' }, { value: 'quality', label: 'Data quality' }, { value: 'cheapest', label: 'Cheapest output price' }, ]; function readSaved(): Saved[] { try { const v = JSON.parse(localStorage.getItem(KEY) ?? '[]'); return Array.isArray(v) ? v.filter((x) => x && typeof x.url === 'string') : []; } catch { return []; } } export function QueryBuilder({ options, initial }: { options: BuilderOptions; initial: Record }) { const router = useRouter(); const [type, setType] = useState(initial.type && options.types.some((t) => t.entity_type === initial.type) ? initial.type : 'model'); const [f, setF] = useState>(() => { const out: Record = {}; for (const k of [...MODEL_KEYS, ...GENERIC_KEYS]) if (initial[k]) out[k] = initial[k]!; return out; }); const [saved, setSaved] = useState([]); const [ready, setReady] = useState(false); const [name, setName] = useState(''); const [copied, setCopied] = useState(false); useEffect(() => { setSaved(readSaved()); setReady(true); }, []); const set = (k: string, v: string) => setF((cur) => { const next = { ...cur }; if (v === '') delete next[k]; else next[k] = v; return next; }); const isModel = type === 'model'; const target = useMemo(() => { const p = new URLSearchParams(); const keys = isModel ? MODEL_KEYS : GENERIC_KEYS; for (const k of keys) if (f[k]) p.set(k, f[k]!); const s = p.toString(); return isModel ? `/models${s ? `?${s}` : ''}` : `/explore/${encodeURIComponent(type)}${s ? `?${s}` : ''}`; }, [f, isModel, type]); const shareUrl = useMemo(() => { const p = new URLSearchParams({ type }); const keys = isModel ? MODEL_KEYS : GENERIC_KEYS; for (const k of keys) if (f[k]) p.set(k, f[k]!); return `/explore?${p.toString()}`; }, [f, isModel, type]); // mirror the builder state into the URL (shareable) without a navigation useEffect(() => { if (!ready) return; window.history.replaceState(null, '', shareUrl); }, [shareUrl, ready]); const persist = (list: Saved[]) => { setSaved(list); try { localStorage.setItem(KEY, JSON.stringify(list)); } catch { /* ignore */ } }; const save = () => { const n = name.trim() || target.replace(/^\//, '').slice(0, 60); persist([{ name: n, url: target, created_at: new Date().toISOString() }, ...saved.filter((s) => s.url !== target)].slice(0, 30)); setName(''); }; const copy = async () => { try { await navigator.clipboard.writeText(`${location.origin}${shareUrl}`); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { /* ignore */ } }; const active = Object.keys(f).length; const cls = 'h-10 w-full border border-rule bg-surface px-2 text-sm text-ink focus:border-accent focus:outline-none'; const label = 'eyebrow block pb-1'; return (

Entity type

    {options.types.map((t) => (
  • ))}
{isModel ? (
) : (

{typeLabel(type, true)} use the generic listing filters (name, organization, sort). Dedicated listings have more: models, benchmarks, prices, hardware.

)}
Run query {target} {active} filter{active === 1 ? '' : 's'}
); }