Spaces:
Running
Running
| /** | |
| * The five tools, declared once. | |
| * | |
| * Each Space linked to the others from its own footer, which meant the | |
| * same five URLs written out in five files — twenty-five places for a | |
| * rename to break, and a Space rename is invisible from the linking | |
| * side: the link keeps returning a page, and the page is a 404. | |
| * | |
| * So the registry lives here and is copied into each Space at deploy | |
| * time, next to `progress.js`. One file to change when a tool moves. | |
| * | |
| * The order is the order somebody actually needs them: find a model, | |
| * see how much context it can serve, learn how to run it, then check | |
| * what the pack really contains before spending the download. | |
| */ | |
| export const TOOLS = { | |
| search: { | |
| space: 'model-search-that-answers', | |
| title: 'Find a model that fits', | |
| asks: 'which models fit my card at all', | |
| // What the visitor has just learned, and why the next tool follows. | |
| leadsTo: 'context', | |
| takes: 'query', | |
| }, | |
| context: { | |
| space: 'what-context-fits', | |
| title: 'What context will actually fit?', | |
| asks: 'how long a conversation this one can serve', | |
| leadsTo: 'parser', | |
| takes: 'repo', | |
| }, | |
| parser: { | |
| space: 'reasoning-parser-advisor', | |
| title: 'Does this model need a reasoning parser?', | |
| asks: 'how to serve it without silently empty answers', | |
| leadsTo: 'precision', | |
| takes: 'repo', | |
| }, | |
| precision: { | |
| space: 'pack-precision-map', | |
| title: 'How much of this pack is actually 4-bit?', | |
| asks: 'what a quantized pack really compressed, and what it kept', | |
| leadsTo: 'integrity', | |
| takes: 'repo', | |
| }, | |
| integrity: { | |
| space: 'pack-integrity-check', | |
| title: 'Does this pack keep what it claims?', | |
| asks: 'whether anything was dropped or left undeclared', | |
| leadsTo: null, | |
| takes: 'repo', | |
| }, | |
| }; | |
| export const ORDER = ['search', 'context', 'parser', 'precision', 'integrity']; | |
| const BASE = 'https://huggingface.co/spaces/aleada'; | |
| const HUB = `${BASE}/start-here`; | |
| /** | |
| * A link to one tool, carrying the model with it. | |
| * | |
| * A funnel where each step starts empty is five separate tools with | |
| * links between them. `?repo=` is read on load by every page that takes | |
| * one, so clicking through keeps the model you were looking at. | |
| */ | |
| export function url(key, repo = null) { | |
| const t = TOOLS[key]; | |
| if (!t) return BASE; | |
| const q = repo && t.takes === 'repo' | |
| ? `?repo=${encodeURIComponent(repo)}` | |
| : repo && t.takes === 'query' | |
| ? `?q=${encodeURIComponent(repo)}` : ''; | |
| return `${BASE}/${t.space}${q}`; | |
| } | |
| /** The model this page was opened with, if a link carried one over. */ | |
| export function repoFromUrl() { | |
| const p = new URLSearchParams(window.location.search); | |
| return (p.get('repo') || p.get('q') || '').trim() || null; | |
| } | |
| const esc = (s) => String(s).replace(/[&<>"]/g, (c) => | |
| ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); | |
| /** | |
| * The next questions, rendered under a result. | |
| * | |
| * The whole chain is rendered, not just the parts that come after: the | |
| * current step is marked so a visitor who arrived from a link can see | |
| * where they are, and the steps behind stay reachable because the order | |
| * is a suggestion and not a gate. | |
| * | |
| * Offered only once there is an answer on screen — a next step shown | |
| * before the current one is finished is clutter, and one that cannot | |
| * carry the model over is a link to an empty form. | |
| */ | |
| export function nextSteps(fromKey, repo = null) { | |
| const here = ORDER.indexOf(fromKey); | |
| const items = ORDER.map((k, i) => { | |
| const t = TOOLS[k]; | |
| if (i === here) { | |
| // The step you are on, shown rather than hidden. A list of "other | |
| // tools" tells you what exists; a chain with your place marked | |
| // tells you how far along you are, which is the thing somebody | |
| // arriving from a link actually wants to know. | |
| return `<li class="ns-here"><span class="ns-mark">✓</span> | |
| <span class="ns-title">${esc(t.title)}</span> | |
| <span class="ns-why">you are here</span></li>`; | |
| } | |
| const ahead = i > here; | |
| return `<li class="${ahead ? 'ns-next' : 'ns-back'}"> | |
| <span class="ns-mark">${ahead ? '→' : '·'}</span> | |
| <a href="${url(k, repo)}" target="_blank" rel="noopener">${esc(t.title)}</a> | |
| <span class="ns-why">${esc(t.asks)}</span></li>`; | |
| }).join(''); | |
| return `<nav class="nextsteps" aria-label="The rest of the chain"> | |
| <h4>Your next question${repo ? ` for <code>${esc(repo)}</code>` : ''}</h4> | |
| <ul>${items}</ul> | |
| <a class="ns-hub" href="${HUB}${repo ? `?repo=${encodeURIComponent(repo)}` : ''}" | |
| target="_blank" rel="noopener">All five, and why they are in this order →</a> | |
| </nav>`; | |
| } | |
| /** Styles for the block above, injected once so each Space need not carry them. */ | |
| export function ensureNextStepStyles() { | |
| if (document.getElementById('ns-style')) return; | |
| const el = document.createElement('style'); | |
| el.id = 'ns-style'; | |
| el.textContent = ` | |
| .nextsteps { margin: 1.6rem 0 0; padding-top: 1rem; border-top: 1px solid var(--line); } | |
| .nextsteps h4 { margin: 0 0 .5rem; font-size: .74rem; font-weight: 600; | |
| text-transform: uppercase; letter-spacing: .05em; color: var(--muted); } | |
| .nextsteps h4 code { text-transform: none; letter-spacing: 0; } | |
| .nextsteps ul { list-style: none; margin: 0; padding: 0; } | |
| .nextsteps li { margin: .32rem 0; font-size: .89rem; display: flex; | |
| gap: .5rem; align-items: baseline; } | |
| .ns-mark { font-family: var(--mono); font-size: .8rem; color: var(--muted); | |
| width: 1rem; flex: 0 0 1rem; } | |
| .ns-here .ns-mark { color: var(--accent); } | |
| .ns-here .ns-title { font-weight: 600; } | |
| .ns-back { opacity: .72; } | |
| .ns-hub { display: inline-block; margin-top: .7rem; font-size: .84rem; } | |
| .nextsteps a { color: var(--accent); text-decoration: none; } | |
| .nextsteps a:hover { text-decoration: underline; } | |
| .ns-why { color: var(--muted); } | |
| .ns-why::before { content: " — "; }`; | |
| document.head.appendChild(el); | |
| } | |