mlabonne's picture
ColBERT semantic tool selection (LFM2.5)
6d59171
Raw
History Blame Contribute Delete
26.7 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ColBERT tool selection · Liquid AI</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<style>
/* Liquid AI brand: black & white forward, light purple as the single meaningful pop.
Sans = Inter, Mono = JetBrains Mono. Tight tracking, generous leading. */
:root {
--white:#fff; --lpurple:#CD82F0; --purple:#5505AF; --orange:#FF5F1E;
--b50:#808080; --b30:#B3B3B3; --b10:#E6E6E6;
--lp30:#F0DAFA; --lp10:#FAF2FE; --border:#E3BAF7;
--text-dark:#000; --text-mid:#4D4D4D; --text-light:#808080;
--sans:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
--mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
}
* { box-sizing:border-box; }
body { margin:0; background:var(--white); color:var(--text-dark); font-family:var(--sans);
font-size:16px; line-height:1.4; letter-spacing:-0.01em; min-height:100vh; }
/* subtle purple "aurora curtains": soft vertical columns slowly drifting behind the page */
.aurora { position:fixed; inset:0; z-index:-1; overflow:hidden; pointer-events:none; opacity:.5; }
.aurora i { position:absolute; top:-25%; height:150%; width:40%; filter:blur(85px); will-change:transform,opacity;
background:linear-gradient(180deg,rgba(205,130,240,0) 0%,rgba(205,130,240,.5) 45%,rgba(85,5,175,.28) 72%,rgba(205,130,240,0) 100%); }
.aurora i:nth-child(1){ left:0%; animation:drift1 18s ease-in-out infinite; }
.aurora i:nth-child(2){ left:34%; animation:drift2 24s ease-in-out infinite;
background:linear-gradient(180deg,rgba(205,130,240,0),rgba(205,130,240,.38) 50%,rgba(205,130,240,0)); }
.aurora i:nth-child(3){ left:64%; animation:drift3 21s ease-in-out infinite; }
@keyframes drift1{0%,100%{transform:translateX(-6%) scaleX(1);opacity:.24}50%{transform:translateX(14%) scaleX(1.18);opacity:.38}}
@keyframes drift2{0%,100%{transform:translateX(6%) scaleX(1.1);opacity:.16}50%{transform:translateX(-12%) scaleX(.92);opacity:.32}}
@keyframes drift3{0%,100%{transform:translateX(0) scaleX(1);opacity:.28}50%{transform:translateX(10%) scaleX(1.12);opacity:.14}}
@media (prefers-reduced-motion:reduce){ .aurora i{animation:none} }
/* UMAP constellation: a fixed background scatter of all 151 tools, positioned by a 2D
UMAP of their embeddings. Non-interactive; the dots a query retrieves light up. */
#umap { position:fixed; inset:0; z-index:-1; width:100vw; height:100vh; pointer-events:none; }
/* passive float: each dot drifts + breathes opacity (staggered per-dot via --dx/--dy/--td/--tw) */
#umap circle { transform-box:fill-box; transform-origin:center;
transition:r .45s cubic-bezier(.2,1.45,.4,1), fill .4s, fill-opacity .4s, filter .4s;
animation:float var(--tw,9s) ease-in-out var(--td,0s) infinite; }
#umap.searching circle { fill-opacity:.34; } /* the ambient field of all tools */
#umap circle.on { fill:var(--purple) !important; fill-opacity:.6 !important; r:4.5; animation:none; }
#umap circle.on.focus { fill-opacity:.85 !important; r:8; filter:drop-shadow(0 0 6px rgba(85,5,175,.6)); }
/* lit-dot names: translucent text in the background; the focused one grows but stays translucent */
#umap text.lbl { font-family:var(--mono); font-size:13px; fill:var(--purple); fill-opacity:.4;
paint-order:stroke; stroke:#fff; stroke-width:2.4px; stroke-linejoin:round;
opacity:0; animation:lblin .3s ease forwards; transition:fill-opacity .25s, font-size .25s; }
#umap text.lbl.focus { font-size:18px; fill-opacity:.7; font-weight:600; }
@keyframes lblin { to { opacity:1; } }
@keyframes float {
0%,100% { transform:translate(0,0); opacity:.5; }
50% { transform:translate(var(--dx,0px), var(--dy,0px)); opacity:1; }
}
@media (prefers-reduced-motion:reduce){ #umap circle{ transition:none; animation:none } #umap text.lbl{ animation:none; opacity:1 } }
.wrap { max-width:760px; margin:0 auto; padding:2.8rem 1.2rem 3.2rem; position:relative; }
header { text-align:center; margin-bottom:2.3rem; }
.eyebrow { font-family:var(--mono); font-size:.72rem; text-transform:uppercase; letter-spacing:.18em; color:var(--text-light); margin:0 0 1.1rem; }
.eyebrow a { color:var(--purple); text-decoration:none; }
.eyebrow a:hover { text-decoration:underline; }
h1 { font-weight:700; font-size:2.1rem; letter-spacing:-0.03em; line-height:1.15; margin:.5rem 0 .9rem; }
.sub { font-family:var(--sans); font-size:.95rem; line-height:1.5; color:var(--text-mid); margin:0 auto; max-width:560px; letter-spacing:-0.01em; }
.sub b { color:var(--purple); font-weight:600; }
/* the pipeline, communicated at a glance: request -> router -> few tools -> LLM */
.flow { display:flex; align-items:center; justify-content:center; gap:.45rem; flex-wrap:wrap;
margin:1.3rem auto 0; font-family:var(--mono); font-size:.68rem; color:var(--text-light); }
.flow .step { border:1px solid var(--b10); border-radius:999px; padding:.24rem .7rem; white-space:nowrap;
background:rgba(255,255,255,.45); backdrop-filter:blur(6px) saturate(1.25); -webkit-backdrop-filter:blur(6px) saturate(1.25); }
.flow .arr { color:var(--b30); }
/* scope hint: sets expectations (the catalog can't answer off-topic requests) */
.scope { text-align:center; font-family:var(--mono); font-size:.7rem; color:var(--text-light);
margin:1.8rem auto 0; max-width:600px; letter-spacing:-0.01em; }
/* search bar — full width, matching the result cards */
.searchwrap { position:relative; max-width:none; margin:0 auto; }
.searchwrap .mag { position:absolute; left:17px; top:0; bottom:0; display:flex; align-items:center; color:var(--b30); pointer-events:none; }
#q { width:100%; border:1px solid var(--border); border-radius:16px; padding:1rem 1.1rem 1rem 3rem;
font-family:var(--sans); font-size:1.12rem; color:var(--text-dark); outline:none; background:var(--white);
box-shadow:0 6px 30px rgba(85,5,175,.06); transition:border-color .15s, box-shadow .15s; }
#q:focus { border-color:var(--lpurple); box-shadow:0 0 0 3px rgba(205,130,240,.28); }
/* meta line */
.meta { min-height:0; margin:.5rem auto 0; text-align:center; font-family:var(--mono);
font-size:.78rem; color:var(--text-light); letter-spacing:-0.01em; display:flex; gap:.5rem; align-items:center; justify-content:center; }
.meta b { color:var(--purple); font-weight:500; }
.spinner { width:12px; height:12px; border:2px solid rgba(85,5,175,.25); border-top-color:var(--purple); border-radius:50%; display:inline-block; animation:spin .7s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }
/* results */
.results { margin-top:1rem; display:flex; flex-direction:column; gap:.6rem; }
/* frosted glass: semi-transparent so the constellation reads through as soft glows,
blurred + lightly saturated so the card itself stays crisp and legible */
.result { border:1px solid rgba(205,130,240,.28); border-radius:14px; padding:.85rem 1rem; cursor:pointer;
background:rgba(255,255,255,.4); backdrop-filter:blur(6px) saturate(1.25);
-webkit-backdrop-filter:blur(6px) saturate(1.25);
box-shadow:0 4px 22px rgba(85,5,175,.06); animation:cardin .4s cubic-bezier(.2,1.3,.45,1) backwards;
transition:transform .18s ease, box-shadow .2s, border-color .2s, background .25s; }
.result.best { border-color:var(--lpurple); background:rgba(250,242,254,.46);
box-shadow:0 8px 30px rgba(85,5,175,.12); }
.result:hover { transform:translateY(-3px); border-color:var(--lpurple);
box-shadow:0 12px 36px rgba(85,5,175,.18); background:rgba(255,255,255,.62); }
.result.best:hover { background:rgba(250,242,254,.68); }
@keyframes cardin { 0%{ transform:translateY(8px) scale(.985); opacity:0; } 100%{ transform:none; opacity:1; } }
.chev { font-family:var(--mono); color:var(--text-light); flex:none; transition:transform .22s, color .2s; }
.result:hover .chev, .result.open .chev { color:var(--purple); }
.result.open .chev { transform:rotate(90deg); }
/* click-to-expand details (grid-rows trick = animated auto height) */
.det-wrap { display:grid; grid-template-rows:0fr; transition:grid-template-rows .3s ease; }
.result.open .det-wrap { grid-template-rows:1fr; }
.det-inner { overflow:hidden; min-height:0; }
.details { margin-top:.8rem; padding-top:.85rem; border-top:1px solid var(--b10); }
.det-title { font-family:var(--mono); font-size:.64rem; text-transform:uppercase; letter-spacing:.13em; color:var(--text-light); margin:.1rem 0 .55rem; }
.args + .det-title { margin-top:1.3rem; padding-top:1.1rem; border-top:1px solid var(--b10); } /* separate Tool definition from Arguments */
.args { display:flex; flex-direction:column; gap:.5rem; }
.arg { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; font-size:.86rem; }
.arg code { font-family:var(--mono); color:var(--text-dark); }
.arg.muted { color:var(--text-light); font-family:var(--mono); font-size:.8rem; }
.atype { font-family:var(--mono); font-size:.68rem; color:var(--purple); background:var(--lp10); border:1px solid var(--border); border-radius:6px; padding:.04rem .42rem; }
.areq { font-family:var(--mono); font-size:.6rem; text-transform:uppercase; letter-spacing:.06em; color:var(--text-mid); border:1px solid var(--b30); border-radius:6px; padding:.04rem .42rem; }
.adesc { color:var(--text-light); font-size:.8rem; }
.aenum { display:flex; gap:.3rem; flex-wrap:wrap; width:100%; padding-left:.1rem; }
.aenum span { font-family:var(--mono); font-size:.66rem; color:var(--text-mid); background:#f4f0f8; border:1px solid var(--b10); border-radius:5px; padding:.02rem .38rem; }
.json { font-family:var(--mono); font-size:.72rem; line-height:1.5; color:var(--text-mid);
background:rgba(245,242,250,.75); border:1px solid var(--b10); border-radius:10px;
padding:.8rem .9rem; margin:0; white-space:pre-wrap; overflow-wrap:anywhere; }
.rtop { display:flex; align-items:center; gap:.6rem; }
.rank { font-family:var(--mono); font-size:.72rem; color:var(--text-light); min-width:1.1em; text-align:center; flex:none; }
.best .rank { color:var(--purple); }
.tname { font-family:var(--mono); font-weight:500; font-size:1rem; color:var(--text-dark); word-break:break-word; }
.best .tname { color:var(--purple); }
.rtop .gap { flex:1; }
.domain { font-family:var(--mono); font-size:.66rem; text-transform:uppercase; letter-spacing:.08em; color:var(--text-light);
border:1px solid var(--b10); border-radius:999px; padding:.16rem .55rem; flex:none; }
.best .domain { border-color:var(--border); color:var(--purple); background:var(--white); }
.score { font-family:var(--mono); font-size:.74rem; color:var(--purple); background:var(--lp10); border:1px solid var(--border);
border-radius:999px; padding:.16rem .55rem; flex:none; }
.best .score { background:var(--white); }
.rdesc { color:var(--text-mid); font-size:.92rem; line-height:1.45; margin:.5rem 0 0; padding-left:1.7em; }
.rparams { display:flex; gap:.34rem; flex-wrap:wrap; margin:.55rem 0 0; padding-left:1.7em; }
.param { font-family:var(--mono); font-size:.7rem; color:var(--text-light); background:#f7f4fb; border:1px solid var(--b10);
border-radius:7px; padding:.1rem .42rem; }
.param.enum { color:var(--purple); border-color:var(--border); cursor:help; }
.param.req { color:var(--text-mid); border-color:var(--b30); }
.param.req.enum { color:var(--purple); }
.empty { text-align:center; color:var(--text-light); font-family:var(--mono); font-size:.9rem; padding:2rem 0; }
.errbox { font-family:var(--mono); font-size:.78rem; color:#c2400f; background:#fff5f1; border:1px solid #ffd2c2;
border-radius:12px; padding:1rem; white-space:pre-wrap; text-align:left; }
/* examples */
.ex-title { font-family:var(--mono); font-size:.7rem; text-transform:uppercase; letter-spacing:.16em; color:var(--text-light); margin:1.8rem 0 .8rem; text-align:center; }
.examples { display:flex; gap:.5rem; flex-wrap:wrap; justify-content:center; }
.chip { border:1px solid var(--b10); background:var(--white); border-radius:999px; padding:.34rem .9rem; font-size:.88rem; color:var(--text-mid); cursor:pointer; transition:all .12s; }
.chip:hover { border-color:var(--lpurple); color:var(--purple); background:var(--lp10); }
.chip .dom { font-family:var(--mono); font-size:.6rem; color:var(--b30); margin-right:.45rem; text-transform:uppercase; letter-spacing:.06em; }
</style>
</head>
<body>
<div class="aurora" aria-hidden="true"><i></i><i></i><i></i></div>
<svg id="umap" aria-hidden="true"></svg>
<div class="wrap">
<header>
<p class="eyebrow" id="eyebrow">Powered by <a href="https://huggingface.co/LiquidAI/LFM2.5-Embedding-350M" target="_blank" rel="noopener">LiquidAI/LFM2.5-Embedding-350M</a></p>
<h1>Smart Tool Selection</h1>
<p class="sub" id="sub">An agent with <b>151 tools</b> can't fit them all in one prompt. Send a request and the embedding model pre-selects the <b>5 most relevant</b> ones to reduce context rot.</p>
<div class="flow" aria-hidden="true">
<span class="step">user request</span><span class="arr"></span>
<span class="step">embedding router</span><span class="arr"></span>
<span class="step">top 5 tools</span><span class="arr"></span>
<span class="step">LLM prompt</span>
</div>
</header>
<div class="searchwrap">
<span class="mag" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><line x1="16.5" y1="16.5" x2="21" y2="21"/></svg></span>
<input id="q" type="text" autocomplete="off" spellcheck="false" autofocus
placeholder="describe what the user wants to do…" />
</div>
<div class="meta" id="meta"></div>
<div class="results" id="results"></div>
<div class="empty" id="empty">Type a request above to retrieve the tools that match ✨</div>
<div class="ex-title">Try a request</div>
<div class="examples" id="examples"></div>
<p class="scope" id="scope" aria-hidden="true"></p>
</div>
<script>
const $ = (id) => document.getElementById(id);
const EXAMPLES = [
["ecommerce", "show me cheap blue outdoor chairs"],
["devops", "spin up a new postgres database on aws"],
["travel", "find me a business class flight to tokyo"],
["finance", "there's a charge I didn't make, dispute it"],
["healthcare", "book a telehealth visit with a dermatologist"],
["support", "bump this ticket up to a manager"],
["workplace", "request a week of vacation"],
["ecommerce", "where is my delivery right now"],
];
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
let lastQ = "", timer = null, seq = 0, TOTAL = 151;
// ---- UMAP constellation background ----------------------------------------
// Each tool is a dot, positioned by a precomputed 2D UMAP of its embedding (layout.json,
// keyed by "domain|name"). The field is static; a search lights up the dots it retrieves.
const DOMAIN_COLORS = {
devops: "#6AA9E9", ecommerce: "#CD82F0", finance: "#4FB286",
healthcare: "#E98AA9", support: "#F0A35E", travel: "#7B6EF0", workplace: "#9AA0A8",
};
const DOTS = new Map();
const dotKey = (domain, name) => domain + "|" + name;
async function loadUmap() {
try {
const r = await fetch("/layout.json");
if (!r.ok) return;
const pts = await r.json();
const NS = "http://www.w3.org/2000/svg", frag = document.createDocumentFragment();
pts.forEach((p) => {
const c = document.createElementNS(NS, "circle");
c.dataset.nx = p.x; c.dataset.ny = p.y; // normalized [0,1]; placed in px by place()
c.setAttribute("r", "3.2");
c.setAttribute("fill", DOMAIN_COLORS[p.domain] || "#CD82F0");
c.setAttribute("fill-opacity", "0.34");
const ang = Math.random() * 6.283, dist = 9 + Math.random() * 11; // per-dot drift vector
c.style.setProperty("--dx", (Math.cos(ang) * dist).toFixed(1) + "px");
c.style.setProperty("--dy", (Math.sin(ang) * dist).toFixed(1) + "px");
c.style.setProperty("--td", (-Math.random() * 9).toFixed(2) + "s"); // stagger the float
c.style.setProperty("--tw", (7 + Math.random() * 5).toFixed(2) + "s");
const t = document.createElementNS(NS, "title");
t.textContent = p.name + " · " + p.domain;
c.appendChild(t);
frag.appendChild(c);
DOTS.set(p.key || dotKey(p.domain, p.name), c);
});
$("umap").appendChild(frag);
place();
} catch (e) { /* background flourish only — never block search */ }
}
// Map normalized [0,1] coords to pixels so the field fills the whole viewport with a border margin.
function place() {
const W = window.innerWidth, H = window.innerHeight, pad = Math.max(30, Math.min(W, H) * 0.06);
DOTS.forEach((c) => {
c.setAttribute("cx", (pad + (+c.dataset.nx) * (W - 2 * pad)).toFixed(1));
c.setAttribute("cy", (pad + (+c.dataset.ny) * (H - 2 * pad)).toFixed(1));
});
if (litDots.length) drawLabels(); // labels follow the dots on resize
}
window.addEventListener("resize", place);
const NS = "http://www.w3.org/2000/svg";
let litDots = []; // [{name, c, label}] in result rank order (index 0 = top result)
let curFocus = 0; // dot currently emphasised
let openIdx = -1; // index of the unrolled card (it becomes the resting highlight), or -1
const restIdx = () => (openIdx >= 0 ? openIdx : 0); // highlight the open card, else the top one
function activate(results) {
const svg = $("umap");
DOTS.forEach((c) => c.classList.remove("on", "focus"));
svg.querySelectorAll("text.lbl").forEach((t) => t.remove());
litDots = []; curFocus = 0; openIdx = -1;
if (!results || !results.length) { svg.classList.remove("searching"); return; }
svg.classList.add("searching");
results.forEach((r) => {
const c = DOTS.get(dotKey(r.domain, r.name));
if (c) {
c.classList.add("on");
c.parentNode.appendChild(c); // raise lit dots above the floating field
litDots.push({ name: r.name, c, label: null });
}
});
drawLabels();
}
// (Re)draw a label beside each lit dot from its current pixel position; greedy vertical
// de-clutter so a tight same-cluster set stacks instead of overlapping. Re-runnable on resize.
function drawLabels() {
const svg = $("umap");
svg.querySelectorAll("text.lbl").forEach((t) => t.remove());
if (!litDots.length) return;
const W = window.innerWidth, GAP = 17;
const items = litDots
.map((d, idx) => ({ idx, name: d.name, cx: +d.c.getAttribute("cx"), cy: +d.c.getAttribute("cy") }))
.sort((a, b) => a.cy - b.cy);
let last = -1e9;
items.forEach((it) => { it.ly = Math.max(it.cy, last + GAP); last = it.ly; });
litDots.forEach((d) => { d.label = null; });
items.forEach((it) => {
const right = it.cx > W / 2; // flip to the left on the right half of the screen
const t = document.createElementNS(NS, "text");
t.setAttribute("x", (right ? it.cx - 9 : it.cx + 9).toFixed(1));
t.setAttribute("y", it.ly.toFixed(1));
t.setAttribute("text-anchor", right ? "end" : "start");
t.setAttribute("dominant-baseline", "middle");
t.setAttribute("class", "lbl");
t.textContent = it.name;
svg.appendChild(t);
litDots[it.idx].label = t;
});
applyFocus();
}
function applyFocus() {
litDots.forEach((d, j) => {
d.c.classList.toggle("focus", j === curFocus);
if (d.label) d.label.classList.toggle("focus", j === curFocus);
});
}
// Emphasise one lit dot + its label; driven by hovering its result card, default the top one.
function focusDot(i) {
if (!litDots.length) return;
curFocus = Math.max(0, Math.min(i, litDots.length - 1));
applyFocus();
}
// The purple card highlight sits on the unrolled card (or the top result when none is open).
function highlightCard() {
const hi = restIdx();
$("results").querySelectorAll(".result").forEach((c, j) => c.classList.toggle("best", j === hi));
}
function setMeta(html) { $("meta").innerHTML = html; }
function renderResults(data) {
const results = data.results || [];
if (!results.length) {
$("results").innerHTML = "";
$("empty").style.display = "block";
$("empty").textContent = "No match — try describing it differently.";
activate([]);
return;
}
$("empty").style.display = "none";
activate(results);
$("results").innerHTML = results.map((r, i) => {
const params = (r.params || []).map((p) => {
const cls = "param" + (p.required ? " req" : "") + (p.enum ? " enum" : "");
const title = p.enum ? ' title="' + esc(p.enum.join(", ")) + '"' : "";
const mark = p.enum ? " ▾" : "";
return '<span class="' + cls + '"' + title + '>' + esc(p.name) + mark + '</span>';
}).join("");
const argRows = (r.params || []).map((p) =>
'<div class="arg"><code>' + esc(p.name) + '</code>' +
'<span class="atype">' + esc(p.type || "string") + '</span>' +
(p.required ? '<span class="areq">required</span>' : "") +
(p.description ? '<span class="adesc">' + esc(p.description) + '</span>' : "") +
(p.enum ? '<div class="aenum">' + p.enum.map((v) => '<span>' + esc(v) + '</span>').join("") + '</div>' : "") +
'</div>'
).join("") || '<div class="arg muted">No arguments.</div>';
const json = esc(JSON.stringify(r.schema || { name: r.name, description: r.description }, null, 2));
return '<div class="result" data-i="' + i + '" style="animation-delay:' + (i * 45) + 'ms">' +
'<div class="rtop">' +
'<span class="rank">' + r.rank + '</span>' +
'<code class="tname">' + esc(r.name) + '</code>' +
'<span class="gap"></span>' +
'<span class="domain">' + esc(r.domain) + '</span>' +
'<span class="score">' + Math.round(r.score * 100) + '%</span>' +
'<span class="chev">▸</span>' +
'</div>' +
(r.description ? '<div class="rdesc">' + esc(r.description) + '</div>' : "") +
(params ? '<div class="rparams">' + params + '</div>' : "") +
'<div class="det-wrap"><div class="det-inner"><div class="details">' +
'<div class="det-title">Arguments</div><div class="args">' + argRows + '</div>' +
'<div class="det-title">Tool definition (what the LLM receives)</div>' +
'<pre class="json">' + json + '</pre>' +
'</div></div></div>' +
'</div>';
}).join("");
highlightCard();
}
async function runSearch(text) {
text = (text || "").trim();
if (!text) {
$("results").innerHTML = ""; setMeta(""); activate([]);
$("empty").style.display = "block";
$("empty").textContent = "Type a request above to retrieve the tools that match ✨";
return;
}
const my = ++seq;
setMeta('<span class="spinner"></span> selecting…');
try {
const r = await fetch("/api/search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ q: text, k: 5 }) });
if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.error ? "model error" : ("HTTP " + r.status)); }
const data = await r.json();
if (my !== seq) return; // a newer keystroke superseded this
TOTAL = data.total || TOTAL;
renderResults(data);
setMeta("");
} catch (err) {
if (my === seq) {
setMeta(""); activate([]);
$("empty").style.display = "none";
$("results").innerHTML = '<div class="errbox">Couldn\'t reach the model.\n\n' + esc(err.message) +
'\n\nIf the model is private, check the HF_TOKEN secret in the Space settings.</div>';
}
}
}
function schedule() { clearTimeout(timer); const v = $("q").value; timer = setTimeout(() => { if (v !== lastQ) { lastQ = v; runSearch(v); } }, 250); }
$("q").addEventListener("input", schedule);
$("q").addEventListener("keydown", (e) => { if (e.key === "Enter") { clearTimeout(timer); lastQ = $("q").value; runSearch(lastQ); } });
// Hover a result card -> emphasise its dot in the background; leave the list -> back to the top result.
$("results").addEventListener("mouseover", (e) => {
const card = e.target.closest(".result");
if (card && card.dataset.i !== undefined) focusDot(+card.dataset.i);
});
$("results").addEventListener("mouseleave", () => focusDot(restIdx()));
// Click a card -> unroll its arguments + JSON tool definition (accordion: only one open at a time;
// the open card becomes the resting highlight; don't toggle while selecting text inside the panel).
$("results").addEventListener("click", (e) => {
if (e.target.closest(".details")) return;
const card = e.target.closest(".result");
if (!card) return;
const wasOpen = card.classList.contains("open");
$("results").querySelectorAll(".result.open").forEach((c) => c.classList.remove("open"));
openIdx = wasOpen ? -1 : +card.dataset.i;
if (!wasOpen) card.classList.add("open");
focusDot(restIdx());
highlightCard();
});
const exWrap = $("examples");
EXAMPLES.forEach(([dom, ex]) => {
const c = document.createElement("span");
c.className = "chip";
c.innerHTML = '<span class="dom">' + dom + '</span>' + esc(ex);
c.onclick = () => { $("q").value = ex; lastQ = ex; runSearch(ex); window.scrollTo({ top: 0, behavior: "smooth" }); };
exWrap.appendChild(c);
});
// load the constellation, reflect the real catalog size, then run a lively first query
const umapReady = loadUmap();
fetch("/api/health").then((r) => r.json()).then((d) => {
if (d.tools) {
TOTAL = d.tools;
$("sub").innerHTML = "An agent with <b>" + d.tools + " tools</b> can't fit them all in one prompt. " +
"Send a request and the model pre-selects the <b>5 most relevant</b> ones to reduce context rot.";
}
if (d.model) {
$("eyebrow").innerHTML = 'Powered by <a href="https://huggingface.co/' + d.model +
'" target="_blank" rel="noopener">' + d.model + "</a>";
}
if (d.domains && d.domains.length) {
$("scope").textContent = "Covers " + d.domains.join(", ") +
". Off-topic requests won't have a real match.";
}
}).catch(() => {});
window.addEventListener("load", () => {
const d = EXAMPLES[0][1]; $("q").value = d; lastQ = d;
umapReady.then(() => { if (lastQ === d) runSearch(d); }); // run once dots exist so they light up
});
</script>
</body>
</html>