Download demo/wall.html from FluidInference/citenet-coreml: direct link, hf CLI and curl.
- Browser
- Download file 18 kB
-
https://huggingface.co/FluidInference/citenet-coreml/resolve/main/demo/wall.html
- Command line
-
hf download hf://FluidInference/citenet-coreml/demo/wall.html
-
curl -L -o wall.html https://huggingface.co/FluidInference/citenet-coreml/resolve/main/demo/wall.html
18 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>CiteNet Wall</title> | |
| <style> | |
| :root { | |
| --bg: #f7f6f2; --panel: #ffffff; --ink: #1c1b19; --muted: #77746b; --faint: #8f8b80; --line: #e6e3da; | |
| --ok: #1f7a4d; --ok-bg: #dcefe3; --bad: #c2321f; --bad-bg: #f9dcd6; --warn: #a86a00; --warn-bg: #f8ebcc; | |
| --mono: ui-monospace, "SF Mono", Menlo, monospace; | |
| --serif: "Iowan Old Style", Palatino, Georgia, serif; | |
| } | |
| @media (prefers-color-scheme: dark) { | |
| :root { | |
| --bg: #121210; --panel: #1a1917; --ink: #ecebe6; --muted: #918e85; --faint: #8a877e; --line: #2b2a26; | |
| --ok: #4fc487; --ok-bg: #1b3325; --bad: #ff7a66; --bad-bg: #4a221b; --warn: #f0b545; --warn-bg: #3b2f17; | |
| } | |
| } | |
| * { box-sizing: border-box; } | |
| html, body { height: 100%; } | |
| body { margin: 0; background: var(--bg); color: var(--ink); font: 14px/1.5 -apple-system, BlinkMacSystemFont, system-ui, sans-serif; | |
| display: flex; flex-direction: column; overflow: hidden; } | |
| header { padding: 14px 22px 10px; display: flex; gap: 10px 18px; align-items: center; flex-wrap: wrap; border-bottom: 1px solid var(--line); } | |
| h1 { margin: 0; font-size: 20px; letter-spacing: -0.02em; } | |
| .group { display: flex; gap: 4px; align-items: center; } | |
| .group span { color: var(--muted); font-size: 12px; margin-right: 4px; } | |
| button { font: inherit; font-size: 13px; border: 1px solid var(--line); background: var(--panel); color: var(--ink); padding: 4px 10px; border-radius: 999px; cursor: pointer; } | |
| button.on { border-color: var(--ink); background: var(--ink); color: var(--bg); } | |
| header a { color: var(--muted); font-size: 13px; } | |
| .stats { padding: 8px 22px; display: flex; gap: 6px 22px; flex-wrap: wrap; color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; border-bottom: 1px solid var(--line); } | |
| .stats b { color: var(--ink); font-weight: 600; } | |
| .stats .bad { color: var(--bad); } | |
| main { flex: 1; display: flex; min-height: 0; } | |
| .viewport { flex: 1; overflow: hidden; position: relative; cursor: pointer; min-width: 0; | |
| -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 6%, #000 88%, transparent 100%); | |
| mask-image: linear-gradient(to bottom, transparent 0, #000 6%, #000 88%, transparent 100%); } | |
| .wall { position: absolute; left: 0; right: 0; top: 0; will-change: transform; } | |
| .chunk { max-width: 1180px; margin: 0 auto; padding: 0 22px; font: 14.5px/1.62 var(--serif); color: var(--faint); white-space: pre-wrap; overflow-wrap: break-word; } | |
| .case { display: inline-block; color: var(--ink); font: 600 13px/1.4 -apple-system, system-ui, sans-serif; margin-top: 10px; } | |
| mark { border-radius: 3px; padding: 0 1px; color: var(--ink); } | |
| mark.ok { background: var(--ok-bg); } | |
| mark.unknown { background: var(--warn-bg); } | |
| mark.phantom { background: var(--bad-bg); box-shadow: inset 0 -2px 0 var(--bad); font-weight: 600; } | |
| mark.missed { outline: 2px dashed var(--bad); } | |
| .tag.miss { background: var(--muted); } | |
| .q .b { font: 600 10px/1 -apple-system, system-ui, sans-serif; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-left: 6px; } | |
| .tag { font: 600 11px/1 -apple-system, system-ui, sans-serif; color: #fff; background: var(--bad); border-radius: 4px; padding: 2px 6px; margin-left: 4px; white-space: nowrap; vertical-align: 1px; } | |
| .readline { position: absolute; left: 0; right: 0; top: 55%; border-top: 1px dashed var(--line); pointer-events: none; } | |
| .readline span { position: absolute; right: 10px; top: -9px; font-size: 11px; color: var(--muted); background: var(--bg); padding: 0 4px; } | |
| mark.hit { animation: hit .6s ease-out; } | |
| @keyframes hit { from { outline: 3px solid var(--ink); } to { outline: 3px solid transparent; } } | |
| aside { width: 380px; border-left: 1px solid var(--line); display: flex; flex-direction: column; min-height: 0; background: var(--panel); } | |
| aside h2 { margin: 0; padding: 10px 14px 6px; font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); font-weight: 600; | |
| display: flex; justify-content: space-between; } | |
| aside h2 b { color: var(--ink); font-variant-numeric: tabular-nums; } | |
| .reds { max-height: 38%; overflow-y: auto; border-bottom: 1px solid var(--line); padding-bottom: 6px; } | |
| .reds .empty { color: var(--muted); font-size: 13px; padding: 2px 14px 6px; } | |
| .lights { flex: 1; overflow: hidden; -webkit-mask-image: linear-gradient(to bottom, #000 75%, transparent); mask-image: linear-gradient(to bottom, #000 75%, transparent); } | |
| .q { display: grid; grid-template-columns: 9px minmax(0, 1fr); gap: 1px 9px; padding: 4px 14px; align-items: baseline; } | |
| .q i { width: 9px; height: 9px; border-radius: 50%; background: var(--ok); display: inline-block; } | |
| .q.unknown i { background: var(--warn); } | |
| .q.phantom i { background: var(--bad); } | |
| .q.missedrow i { background: transparent; border: 2px solid var(--bad); } | |
| .q .c { font: 12.5px/1.35 var(--mono); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } | |
| .q.phantom .c { color: var(--bad); white-space: normal; overflow-wrap: anywhere; } | |
| .q .w { grid-column: 2; color: var(--muted); font-size: 12.5px; line-height: 1.35; } | |
| .q.new { animation: slide .35s ease-out; } | |
| @keyframes slide { from { background: var(--line); } to { background: transparent; } } | |
| button.play { font-weight: 600; border-color: var(--bad); color: var(--bad); } | |
| button.play.on { background: var(--bad); color: #fff; border-color: var(--bad); } | |
| .runbar { position: absolute; left: 0; top: 0; height: 3px; background: var(--bad); width: 0; z-index: 2; } | |
| .summary { position: absolute; left: 50%; top: 38%; transform: translate(-50%, -50%); z-index: 3; display: none; | |
| background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 18px 26px; text-align: center; | |
| box-shadow: 0 10px 40px rgba(0,0,0,.35); min-width: 360px; cursor: pointer; } | |
| .summary .t { font-size: 12px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); } | |
| .summary .big { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; margin: 4px 0 8px; font-variant-numeric: tabular-nums; } | |
| .summary .row { color: var(--muted); font-size: 14px; font-variant-numeric: tabular-nums; line-height: 1.7; } | |
| .summary .row b { color: var(--ink); } | |
| .summary .row .bad { color: var(--bad); } | |
| .summary .hint { color: var(--faint); font-size: 12px; margin-top: 8px; } | |
| .paused { position: absolute; right: 22px; top: 14px; font-size: 12px; color: var(--muted); background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 3px 8px; display: none; } | |
| @media (max-width: 900px) { | |
| main { flex-direction: column; } | |
| aside { width: auto; height: 34vh; border-left: 0; border-top: 1px solid var(--line); } | |
| header, .stats { padding-left: 16px; padding-right: 16px; } .chunk { padding: 0 16px; font-size: 14px; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>CiteNet Wall</h1> | |
| <div class="group"><span>Text</span> | |
| <button data-src="planted" class="on" title="real opinions with ~1 in 11 citations mutated into impossible ones">Opinions + planted fakes</button> | |
| <button data-src="cold">Court opinions</button> | |
| <button data-src="lepc">LePhantomCite briefs</button> | |
| </div> | |
| <div class="group"><span>Speed</span> | |
| <button data-speed="45">Read</button> | |
| <button data-speed="400" class="on">Fast</button> | |
| <button data-speed="2500">Faster</button> | |
| <button data-speed="9000">Max</button> | |
| </div> | |
| <button id="play" class="play" title="fresh run for exactly 30 seconds at the selected speed, then stop">▶ 30 s</button> | |
| <button id="pause" title="pause / resume (space, or click the text)">❚❚ Pause</button> | |
| <a href="/live">Full-speed scan</a> <a href="/">Back</a> | |
| </header> | |
| <div class="stats" id="stats"> </div> | |
| <main> | |
| <div class="viewport" id="vp"> | |
| <div class="wall" id="wall"></div> | |
| <div class="readline"><span>citations join the queue here</span></div> | |
| <div class="paused" id="paused">paused</div> | |
| <div class="runbar" id="runbar"></div> | |
| <div class="summary" id="summary"></div> | |
| </div> | |
| <aside> | |
| <h2><span>Phantoms</span><b id="n-red">0</b></h2> | |
| <div class="reds" id="reds"><div class="empty">None yet.</div></div> | |
| <h2><span>Queue</span><b id="n-lit">0</b></h2> | |
| <div class="lights" id="lights"></div> | |
| </aside> | |
| </main> | |
| <script> | |
| const $ = (id) => document.getElementById(id); | |
| const vp = $("vp"), wall = $("wall"); | |
| const ENT = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\u00a0", sect: "\u00a7", para: "\u00b6" }; | |
| const unent = (s) => s.replace(/&(#\d+|[a-z]+);/g, (m, e) => e[0] === "#" ? String.fromCharCode(+e.slice(1)) : (ENT[e] ?? m)); | |
| const esc = (s) => unent(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); | |
| let pending = [], lit = 0, reds = 0; | |
| const RUN_S = 30; | |
| let runLeft = 0; // seconds left in a 30 s run (0 = not in a run) | |
| let src = "planted", speed = 400, y = 0, next = 0, inflight = false, paused = false, gen = 0; | |
| let tot = { bytes: 0, cites: 0, flagged: 0, chunks: 0, planted: 0, caught: 0, missed: 0 }, recentMs = [], lastModel = "", px = 0, lastPx = 0, lastT = performance.now(); | |
| function render(c, first) { | |
| const t = c.text; let h = "", pos = 0; | |
| for (const [s, e, st, why, planted] of c.spans) { | |
| const cls = st + (planted ? " planted" : "") + (planted && st !== "phantom" ? " missed" : ""); | |
| h += esc(t.slice(pos, s)) + `<mark class="${cls}" data-why="${esc(why)}"${why ? ` title="${esc(why)}"` : ""}>${esc(t.slice(s, e))}</mark>`; | |
| if (st === "phantom") h += `<span class="tag">${planted ? "planted · " : ""}${esc(why)}</span>`; | |
| else if (planted) h += `<span class="tag miss">missed planted fake</span>`; | |
| pos = e; | |
| } | |
| h += esc(t.slice(pos)); | |
| // a decision starts after a triple line break with a "case name, court, date" line | |
| h = h.replace(/\n\n\n([^\n<]{3,220})\n/g, "\n\n\n<span class=\"case\">$1</span>\n"); | |
| if (first && src === "cold") h = h.replace(/^([^\n<]{3,220})\n/, "<span class=\"case\">$1</span>\n"); | |
| const d = document.createElement("div"); | |
| d.className = "chunk"; d.innerHTML = h; | |
| return d; | |
| } | |
| async function fetchMore(g) { | |
| inflight = true; | |
| const size = speed >= 2500 ? 65536 : 12288; // bigger chunks when scrolling fast | |
| const c = await fetch(`/wall/chunk?src=${src}&off=${next}&size=${size}`).then(r => r.json()); | |
| inflight = false; | |
| if (g !== gen) return; | |
| next = c.next; | |
| const d = render(c, tot.chunks === 0); d.dataset.bytes = c.bytes; | |
| wall.appendChild(d); | |
| pending.push(...d.querySelectorAll("mark")); | |
| tot.bytes += c.bytes; tot.cites += c.n; tot.flagged += c.flagged; tot.chunks += 1; | |
| tot.planted += c.planted || 0; tot.caught += c.caught || 0; tot.missed += c.missed || 0; | |
| recentMs.push([c.model_ms, c.bytes]); if (recentMs.length > 25) recentMs.shift(); | |
| lastModel = c.model; | |
| } | |
| function stats() { | |
| // scroll rate in bytes: pixels scrolled per second times bytes per pixel of the text on screen | |
| const now = performance.now(), dt = (now - lastT) / 1000; | |
| const pps = (px - lastPx) / Math.max(dt, 1e-3); lastPx = px; lastT = now; | |
| let b = 0; for (const ch of wall.children) b += +ch.dataset.bytes || 0; | |
| const kbs = pps * (b / Math.max(1, wall.scrollHeight)) / 1000; | |
| const unit = lastModel.includes("ANE") ? "Neural Engine" : "CPU"; | |
| const sorted = [...recentMs].sort((a, b) => a[0] - b[0]), mid = sorted[sorted.length >> 1] || [0, 0]; | |
| const per = mid[0], chunkKB = Math.round(mid[1] / 1024); | |
| $("stats").innerHTML = | |
| `<span><b>${(tot.bytes / 1e6).toFixed(2)} MB</b> read</span>` + | |
| `<span><b>${tot.cites.toLocaleString()}</b> citations highlighted</span>` + | |
| (src === "planted" | |
| ? `<span><b>${tot.planted.toLocaleString()}</b> fakes planted</span><span class="bad"><b class="bad">${tot.caught.toLocaleString()}</b> caught</span>` + | |
| `<span><b>${tot.missed.toLocaleString()}</b> missed</span><span><b>${(tot.flagged - tot.caught).toLocaleString()}</b> found in the source</span>` | |
| : `<span class="bad"><b class="bad">${tot.flagged.toLocaleString()}</b> phantom</span>`) + | |
| `<span>each ${chunkKB} KB chunk checked in <b>${per.toFixed(2)} ms</b> on the ${unit}</span>` + | |
| `<span>scrolling <b>${kbs < 1 ? Math.round(kbs * 1000) + " B/s" : kbs < 1000 ? kbs.toFixed(0) + " KB/s" : (kbs / 1000).toFixed(1) + " MB/s"}</b></span>`; | |
| } | |
| let last = performance.now(); | |
| function frame(t) { | |
| const dt = Math.min(0.1, (t - last) / 1000); last = t; | |
| const vh = vp.clientHeight; | |
| if (runLeft > 0 && !paused) { | |
| runLeft -= dt; | |
| $("runbar").style.width = (100 * (1 - Math.max(0, runLeft) / RUN_S)) + "%"; | |
| $("play").textContent = "0:" + String(Math.ceil(Math.max(0, runLeft))).padStart(2, "0"); | |
| if (runLeft <= 0) finishRun(); | |
| } | |
| if (!paused) { | |
| const room = wall.scrollHeight - y - vh; | |
| const step = Math.min(speed * dt, Math.max(0, room)); // never scroll past what has been checked | |
| y += step; px += step; | |
| } | |
| // drop chunks that have scrolled off the top | |
| while (wall.firstChild && wall.firstChild.offsetHeight < y - vh * 0.2) { | |
| y -= wall.firstChild.offsetHeight; | |
| wall.removeChild(wall.firstChild); | |
| } | |
| wall.style.transform = `translate3d(0, ${-y}px, 0)`; | |
| // citations that crossed the reading line join the queue, in reading order | |
| const line = vp.getBoundingClientRect().top + vh * 0.55; | |
| let moved = 0; | |
| while (pending.length && moved < 40) { | |
| const m = pending[0]; | |
| if (!m.isConnected) { pending.shift(); continue; } | |
| if (m.getBoundingClientRect().top > line) break; | |
| pending.shift(); enqueue(m); moved++; | |
| } | |
| if (!inflight && wall.scrollHeight - y < vh * 3 + speed * 1.5) fetchMore(gen); // keep ~1.5 s of text ahead | |
| requestAnimationFrame(frame); | |
| } | |
| function enqueue(m) { | |
| const st = m.className.split(" ")[0], why = m.dataset.why; | |
| const planted = m.classList.contains("planted"), missed = m.classList.contains("missed"); | |
| m.classList.add("hit"); | |
| const row = document.createElement("div"); | |
| row.className = `q ${missed ? "phantom missedrow" : st} new`; | |
| const badge = src === "planted" && st === "phantom" ? `<span class="b">${planted ? "planted" : "in source"}</span>` : missed ? `<span class="b">missed</span>` : ""; | |
| row.innerHTML = `<i></i><span class="c">${esc(m.textContent)}${badge}</span>` + (why && st !== "ok" ? `<span class="w">${esc(why)}</span>` : ""); | |
| const L = $("lights"); | |
| L.prepend(row); | |
| while (L.children.length > 80) L.lastChild.remove(); | |
| $("n-lit").textContent = (++lit).toLocaleString(); | |
| if (st === "phantom" || missed) { | |
| const R = $("reds"); | |
| if (!reds) R.innerHTML = ""; | |
| R.prepend(row.cloneNode(true)); | |
| while (R.children.length > 40) R.lastChild.remove(); | |
| $("n-red").textContent = (++reds).toLocaleString(); | |
| } | |
| } | |
| async function start(newSrc) { | |
| src = newSrc; gen += 1; y = 0; wall.innerHTML = ""; | |
| pending = []; lit = 0; reds = 0; | |
| $("lights").innerHTML = ""; $("reds").innerHTML = `<div class="empty">None yet.</div>`; | |
| $("n-lit").textContent = "0"; $("n-red").textContent = "0"; | |
| tot = { bytes: 0, cites: 0, flagged: 0, chunks: 0, planted: 0, caught: 0, missed: 0 }; recentMs = []; | |
| document.querySelectorAll("[data-src]").forEach(b => b.classList.toggle("on", b.dataset.src === src)); | |
| next = (await fetch(`/wall/random?src=${src}`).then(r => r.json())).off; | |
| inflight = false; | |
| } | |
| document.querySelectorAll("[data-src]").forEach(b => b.onclick = () => start(b.dataset.src)); | |
| document.querySelectorAll("[data-speed]").forEach(b => b.onclick = () => { | |
| speed = +b.dataset.speed; | |
| document.querySelectorAll("[data-speed]").forEach(x => x.classList.toggle("on", x === b)); | |
| }); | |
| function syncPause() { | |
| $("pause").textContent = paused ? "▶ Resume" : "❚❚ Pause"; | |
| $("pause").classList.toggle("on", paused); | |
| $("paused").style.display = paused && $("summary").style.display !== "block" ? "block" : "none"; | |
| } | |
| const toggle = () => { | |
| if ($("summary").style.display === "block") { $("summary").style.display = "none"; } | |
| paused = !paused; | |
| syncPause(); | |
| }; | |
| function finishRun() { | |
| runLeft = 0; paused = true; | |
| $("play").classList.remove("on"); $("play").textContent = "↻ 30 s"; | |
| const planted = src === "planted"; | |
| const kb = tot.bytes / 1000; | |
| $("summary").innerHTML = | |
| `<div class="t">${RUN_S} seconds · ${{ planted: "opinions + planted fakes", cold: "court opinions", lepc: "LePhantomCite briefs" }[src]} · ${document.querySelector("[data-speed].on").textContent}</div>` + | |
| `<div class="big">${tot.cites.toLocaleString()} citations checked</div>` + | |
| `<div class="row"><b>${kb < 1000 ? kb.toFixed(0) + " KB" : (kb / 1000).toFixed(2) + " MB"}</b> of text read · ` + | |
| `each chunk checked in <b>${$("stats").querySelector("span:nth-last-child(2) b")?.textContent || "–"}</b> on the Neural Engine</div>` + | |
| `<div class="row">` + (planted | |
| ? `<b>${tot.planted}</b> fakes planted · <b class="bad">${tot.caught}</b> caught · <b>${tot.missed}</b> missed · <b>${tot.flagged - tot.caught}</b> found in the source` | |
| : `<b class="bad">${tot.flagged}</b> phantom citations flagged`) + `</div>` + | |
| `<div class="hint">click to keep reading</div>`; | |
| $("summary").style.display = "block"; | |
| syncPause(); | |
| } | |
| $("play").onclick = async (e) => { | |
| e.stopPropagation(); | |
| $("summary").style.display = "none"; $("paused").style.display = "none"; | |
| await start(src); | |
| paused = false; runLeft = RUN_S; syncPause(); | |
| $("play").classList.add("on"); $("runbar").style.width = "0"; | |
| }; | |
| vp.onclick = toggle; | |
| $("pause").onclick = (e) => { e.stopPropagation(); toggle(); }; | |
| document.addEventListener("keydown", (e) => { if (e.code === "Space") { e.preventDefault(); toggle(); } }); | |
| const q = new URLSearchParams(location.search); | |
| if (q.get("speed")) { const b = document.querySelector(`[data-speed="${q.get("speed")}"]`); if (b) b.click(); } | |
| start(q.get("src") || "planted").then(() => { | |
| requestAnimationFrame(frame); | |
| if (q.get("play")) $("play").click(); | |
| }); | |
| setInterval(stats, 250); | |
| </script> | |
| </body> | |
| </html> | |