/** ISCC Cover Match — served by iscc-covers FastAPI backend. */ const { useState, useEffect, useRef, useCallback } = React; // ─── API Endpoint ────────────────────────────────────────────────────── const API_ISCC = "/iscc"; const API_TITLES = "/search/titles"; // ─── ISCC Brand Constants ────────────────────────────────────────────── const B = { blue: "#0054b2", navy: "#123663", yellow: "#ffc300", coral: "#f56169", lime: "#a6db50", sky: "#4596f5", cyan: "#7ac2f7", white: "#ffffff", offWhite: "#f8f9fa", pageBg: "#f4f6f8", lightGray: "#e9ecef", medGray: "#6c757d", darkGray: "#343a40", nearBlack: "#212529", }; const ISCC_LOGO = "logo.png"; const AMLET_LOGO = "amlet-logo.png"; const PROCESSING_STEPS = [ { key: "upload", label: "Uploading image", detail: "Transferring to server…" }, { key: "extract_title", label: "Extracting title", detail: "Running vision model…" }, { key: "generate_iscc", label: "Generating ISCC", detail: "Computing fingerprint…" }, { key: "search", label: "Querying index", detail: "Searching 3M covers…" }, { key: "ranking", label: "Ranking results", detail: "Applying TSR scoring…" }, ]; const STEP_INDEX = PROCESSING_STEPS.reduce((acc, s, i) => { acc[s.key] = i; return acc; }, {}); // ─── Helpers ─────────────────────────────────────────────────────────── function classifyScore(score) { if (score >= 0.95) return "exact"; if (score >= 0.9) return "near-duplicate"; if (score >= 0.8) return "similar"; return "related"; } const UNIT_INFO = { META: { color: B.sky, icon: "M", name: "Meta-Code" }, SEMANTIC: { color: B.lime, icon: "S", name: "Semantic-Code" }, CONTENT: { color: B.blue, icon: "C", name: "Content-Code" }, DATA: { color: B.cyan, icon: "D", name: "Data-Code" }, INSTANCE: { color: B.navy, icon: "I", name: "Instance-Code" }, }; function unitDisplayInfo(readable) { const type = readable.split("-")[0]; return ( UNIT_INFO[type] || { color: B.medGray, icon: "?", name: readable.split("-")[0] + "-Code", } ); } function findUnitScore(perUnit, unitType) { if (!perUnit) return null; return perUnit[unitType] !== undefined ? perUnit[unitType] : null; } // ─── TSR: Tiered Semantic Ranking (per tsr-spec.md) ──────────────────── // Preset: adaptation — weights SEMANTIC and CONTENT for visual cover match. const TSR_TAU_HIGH = 0.85; const TSR_TAU_SOFT = 0.70; const TSR_TAU_WEAK = 0.60; const TSR_WEIGHTS = { M: 0.5, S: 2.0, C: 0.8, D: 0.3, I: 0.5 }; const TSR_TIER_ORDER = { T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T0: 8 }; const TSR_TIER_STATUS = { T1: "exact", T2: "near-duplicate", T3: "near-duplicate", T4: "similar", T5: "corroborated", T6: "title-match", T7: "weak", T0: "incoherent", }; const TSR_TIER_LABEL = { T1: "EXACT", T2: "NEAR-IDENTICAL", T3: "RE-ENCODED", T4: "ADAPTATION", T5: "CORROBORATED", T6: "META-ONLY", T7: "ISOLATED", T0: "INCOHERENT", }; // Per §8.6: tier-based score caps. T5 corroborated matches earn their score; // T6 meta-only and T7 isolated are clamped because their evidence is // categorically weaker than a multi-signal or high-confidence match. const TSR_TIER_CAP = { T0: 0.0, T1: 1.0, T2: 1.0, T3: 1.0, T4: 1.0, T5: 1.0, T6: TSR_TAU_SOFT, T7: TSR_TAU_SOFT * 0.9, }; // Per §8.7: when a T7 candidate has a single MainType above τ_soft, scale // its raw score by the reliability of that signal type before capping. // Content-Code is penalised hardest because perceptual-hash coincidences on // layout/tonal structure are common in a cover-match corpus. const TSR_ISOLATED_PENALTY = { SEMANTIC: 1.00, DATA: 0.95, META: 0.90, CONTENT: 0.80, INSTANCE: 1.00, }; function tsrAggregate(queryMainTypes, typesDict) { var agg = {}; for (var i = 0; i < queryMainTypes.length; i++) { var mt = queryMainTypes[i]; var maxScore = null; for (var key in typesDict) { if (key.indexOf(mt + "_") === 0) { if (maxScore === null || typesDict[key] > maxScore) maxScore = typesDict[key]; } } if (maxScore !== null) agg[mt] = maxScore; } return agg; } function tsrClassifyTier(agg) { var sI = agg.INSTANCE, sD = agg.DATA, sC = agg.CONTENT; var sS = agg.SEMANTIC, sM = agg.META; if (sI !== undefined && sI >= TSR_TAU_HIGH && ( (sD !== undefined && sD < TSR_TAU_SOFT) || (sC !== undefined && sC < TSR_TAU_SOFT) )) return "T0"; if (sI !== undefined && sI >= TSR_TAU_HIGH) return "T1"; if (sD !== undefined && sD >= TSR_TAU_HIGH && (sC === undefined || sC >= TSR_TAU_HIGH)) return "T2"; var countSoft = 0, countWeak = 0; for (var k in agg) { if (agg[k] >= TSR_TAU_SOFT) countSoft++; if (agg[k] >= TSR_TAU_WEAK) countWeak++; } // T3 requires C-high AND at least one other unit at τ_soft (countSoft ≥ 2). // Content-Code alone is too noisy on templated corpora (book covers) to promote. if (sC !== undefined && sC >= TSR_TAU_HIGH && countSoft >= 2) return "T3"; if (sS !== undefined && sS >= TSR_TAU_HIGH) return "T4"; // T5: primary signal at τ_soft AND a distinct secondary at the weaker τ_weak floor. if (countSoft >= 1 && countWeak >= 2) return "T5"; if (sM !== undefined && sM >= TSR_TAU_HIGH && countWeak === 1) return "T6"; if (countSoft === 1) return "T7"; return null; } function tsrWeightedScore(agg) { var wMap = { META: TSR_WEIGHTS.M, SEMANTIC: TSR_WEIGHTS.S, CONTENT: TSR_WEIGHTS.C, DATA: TSR_WEIGHTS.D, INSTANCE: TSR_WEIGHTS.I, }; var num = 0, den = 0; for (var k in agg) { if (wMap[k] > 0) { num += wMap[k] * agg[k]; den += wMap[k]; } } var base = den > 0 ? num / den : 0; var intrinsic = []; ["INSTANCE", "DATA", "CONTENT", "SEMANTIC"].forEach(function(k) { if (agg[k] !== undefined) intrinsic.push(agg[k]); }); var alpha = 1.0; if (intrinsic.length >= 2) { var allHigh = intrinsic.every(function(v) { return v >= TSR_TAU_HIGH; }); var allLow = intrinsic.every(function(v) { return v < TSR_TAU_SOFT; }); if (allHigh || allLow) alpha = 1.05; } return Math.min(1.0, base * alpha); } function tsrRank(queryMainTypes, candidates) { var ranked = []; for (var i = 0; i < candidates.length; i++) { var cand = candidates[i]; var agg = tsrAggregate(queryMainTypes, cand.types || {}); if (Object.keys(agg).length === 0) continue; var tier = tsrClassifyTier(agg); if (tier === null) continue; var score; if (tier === "T0") score = 0.0; else if (tier === "T1") score = 1.0; else { var raw = tsrWeightedScore(agg); if (tier === "T7") { var sole = null; for (var sk in agg) { if (agg[sk] >= TSR_TAU_SOFT) { sole = sk; break; } } if (sole && TSR_ISOLATED_PENALTY[sole] !== undefined) { raw = raw * TSR_ISOLATED_PENALTY[sole]; } } score = Math.min(raw, TSR_TIER_CAP[tier]); } ranked.push(Object.assign({}, cand, { tier: tier, score: score, per_unit: agg, status: TSR_TIER_STATUS[tier], })); } ranked.sort(function(a, b) { var ta = TSR_TIER_ORDER[a.tier] - TSR_TIER_ORDER[b.tier]; if (ta !== 0) return ta; if (b.score !== a.score) return b.score - a.score; var keys = ["INSTANCE", "DATA", "CONTENT", "SEMANTIC", "META"]; for (var n = 0; n < keys.length; n++) { var av = a.per_unit[keys[n]] !== undefined ? a.per_unit[keys[n]] : -1; var bv = b.per_unit[keys[n]] !== undefined ? b.per_unit[keys[n]] : -1; if (bv !== av) return bv - av; } return 0; }); return ranked; } function formatFileSize(bytes) { if (bytes < 1024) return bytes + " B"; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; return (bytes / (1024 * 1024)).toFixed(1) + " MB"; } function formatMs(ms) { if (ms == null) return ""; if (ms < 1000) return ms + " ms"; return (ms / 1000).toFixed(2) + " s"; } // Map unit dict keys to display-compatible array const UNIT_KEY_TO_TYPE = { iscc_meta: "META", iscc_semantic: "SEMANTIC", iscc_content: "CONTENT", iscc_data: "DATA", iscc_instance: "INSTANCE", }; function unitsToDisplayArray(unitsDict) { if (!unitsDict) return null; var arr = []; for (var key in UNIT_KEY_TO_TYPE) { if (unitsDict[key]) { arr.push({ readable: UNIT_KEY_TO_TYPE[key] + "-CODE", iscc_unit: unitsDict[key], }); } } return arr.length > 0 ? arr : null; } // ─── API Functions ───────────────────────────────────────────────────── async function generateIscc(imageBytes, filename, onStep) { const t0 = performance.now(); const blob = new Blob([imageBytes]); const form = new FormData(); form.append("file", blob, filename); const resp = await fetch(API_ISCC, { method: "POST", body: form }); if (!resp.ok) throw new Error("ISCC generation failed (" + resp.status + ")"); const reader = resp.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let result = null; let uploadReported = false; const handleLine = (line) => { const trimmed = line.trim(); if (!trimmed) return; const evt = JSON.parse(trimmed); if (!uploadReported) { onStep("upload", Math.round(performance.now() - t0)); uploadReported = true; // Server emits a bare {"step":"upload"} marker first; ignore it // so we keep the client-measured network time. if (evt.step === "upload") return; } if (evt.event === "result") result = evt.data; else if (evt.event === "error") throw new Error(evt.detail || "Server error"); else if (evt.step) onStep(evt.step, evt.ms); }; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const parts = buffer.split("\n"); buffer = parts.pop(); for (const part of parts) handleLine(part); } if (buffer.trim()) handleLine(buffer); if (!result) throw new Error("No result received"); return result; } async function fetchImageAsBytes(url) { var resp; try { resp = await fetch(url); } catch (_) { throw new Error( "Cannot fetch image from this URL (likely blocked by CORS). " + "Please download the image and drag it here instead.", ); } if (!resp.ok) throw new Error("Failed to fetch image (" + resp.status + ")"); const blob = await resp.blob(); const filename = url.split("/").pop().split("?")[0] || "image.jpg"; const arrayBuf = await blob.arrayBuffer(); return { bytes: new Uint8Array(arrayBuf), filename, blob }; } // ─── Placeholder Cover SVG ───────────────────────────────────────────── function CoverPlaceholder({ seed, w = 72, h = 100 }) { const hue = (seed * 53 + 200) % 360; return ( ); } // ─── Hex Spinner ─────────────────────────────────────────────────────── function HexSpinner({ size = 120, spinning = false, progress = 0 }) { const hex = (cx, cy, r) => { const p = []; for (let i = 0; i < 6; i++) { const a = (Math.PI / 3) * i - Math.PI / 2; p.push(`${cx + r * Math.cos(a)},${cy + r * Math.sin(a)}`); } return p.join(" "); }; const c = size / 2; return ( ); } // ─── Scan Line ───────────────────────────────────────────────────────── function ScanLine({ active }) { if (!active) return null; return (
); } // ─── Processing Steps ────────────────────────────────────────────────── function ProcessingSteps({ currentStep, steps, timings, outcome }) { return (
{steps.map((step, i) => { const isActive = i === currentStep; const isDone = i < currentStep; const ms = timings && timings[step.key]; return (
currentStep + 1 ? 0.35 : 1, }} >
{i + 1}
{step.label}
{isActive && step.detail && (
{step.detail}
)}
{isDone && ms != null && (
{formatMs(ms)}
)} {isActive && (
)}
); })} {outcome && (
{steps.length + 1}
{outcome.label}
{outcome.value != null && (
{outcome.value}
)}
)}
); } // ─── ISCC Code Display ───────────────────────────────────────────────── function IsccCodeDisplay({ units, animate, unitScores }) { return (
Best Match
{units && units.length > 0 && (
{units.map((unit, i) => { const info = unitDisplayInfo(unit.readable); const type = unit.readable.split("-")[0]; const score = findUnitScore(unitScores, type); return (
{info.icon}
{info.name} {score !== null && (
{(score * 100).toFixed(1)}% )}
); })}
)}
); } // ─── Similarity Bar ──────────────────────────────────────────────────── function SimilarityBar({ value, status }) { const color = status === "exact" ? "#2e8b57" : status === "near-duplicate" ? "#c89200" : status === "similar" ? B.sky : status === "corroborated" ? B.cyan : B.medGray; return (
{status} {(value * 100).toFixed(1)}%
); } // ─── Per-Unit Score Dots (compact inline breakdown) ──────────────────── const UNIT_DOT_ORDER = ["META", "SEMANTIC", "CONTENT", "DATA", "INSTANCE"]; function UnitScoreDots({ perUnit }) { if (!perUnit) return null; const present = UNIT_DOT_ORDER.filter((t) => perUnit[t] !== undefined); if (present.length === 0) return null; return (
{present.map((type) => { const info = UNIT_INFO[type]; const score = Math.max(0, Math.min(1, perUnit[type])); const pct = Math.round(score * 100); const opacity = 0.55 + 0.45 * score; return (
{info.icon} {pct}
); })}
); } // ─── Result Card ─────────────────────────────────────────────────────── function ResultCard({ result, index, visible }) { const [imgOk, setImgOk] = useState(false); const bdr = result.status === "exact" ? "#2e8b57" + "40" : result.status === "near-duplicate" ? "#c89200" + "30" : result.status === "corroborated" ? B.cyan + "40" : B.lightGray; const coverSrc = result.metadata && result.metadata.source; const title = (result.metadata && result.metadata.name) || "Unknown Title"; const subtitle = [ result.metadata && result.metadata.isbn ? "ISBN: " + result.metadata.isbn : null, result.metadata && result.metadata.source_row_id ? "ASIN: " + result.metadata.source_row_id : null, result.metadata && result.metadata.publisher, ] .filter(Boolean) .join(" · "); return (
{coverSrc && ( { var w = e.target.naturalWidth; var h = e.target.naturalHeight; setImgOk(w > 10 && h > 10); }} onError={() => setImgOk(false)} style={{ width: "100%", height: "100%", objectFit: "contain", display: imgOk ? "block" : "none", }} /> )} {!imgOk && } {result.status === "exact" && (
)}
{title}
{subtitle || "\u00A0"}
{result.iscc_id}
); } // ─── Query Image Display ─────────────────────────────────────────────── function QueryImage({ src, thumbnail, processing, interactive, dragOver }) { const [imgOk, setImgOk] = useState(false); const [hovering, setHovering] = useState(false); const displaySrc = imgOk || !thumbnail ? src : thumbnail; useEffect(() => { setImgOk(false); }, [src]); useEffect(() => { if (!interactive) setHovering(false); }, [interactive]); return (
setHovering(true) : undefined} onMouseLeave={interactive ? () => setHovering(false) : undefined} style={{ position: "relative", borderRadius: "8px", overflow: "hidden", border: `1px solid ${processing ? B.blue + "40" : dragOver ? B.blue : B.lightGray}`, background: B.white, minHeight: "180px", display: "flex", alignItems: "center", justifyContent: "center", transition: "border-color 0.3s ease", cursor: interactive ? "pointer" : "default", }} > {src && ( setImgOk(true)} onError={() => setImgOk(false)} style={{ width: "100%", display: imgOk ? "block" : "none", maxHeight: "360px", objectFit: "contain", }} /> )} {!imgOk && thumbnail && ( )} {!imgOk && !thumbnail && (
COVER
{src ? "Loading image…" : "No image"}
)} {interactive && (dragOver || hovering) && (
{dragOver ? "Drop image to search" : "Click or drop to search another"}
)}
); } // ─── Dot Grid Background ─────────────────────────────────────────────── function DotGrid() { return ( ); } // ─── Score Threshold Slider ──────────────────────────────────────────── function ScoreThresholdSlider({ value, onChange, maxWidth }) { return (
Score Threshold {(value * 100).toFixed(0)}%
onChange(Number(e.target.value) / 100)} style={{ width: "100%", height: "4px", appearance: "none", WebkitAppearance: "none", background: `linear-gradient(to right, ${B.blue} ${value * 100}%, ${B.lightGray} ${value * 100}%)`, borderRadius: "2px", outline: "none", cursor: "pointer", }} />
); } // ─── Title Typeahead Search ──────────────────────────────────────────── function TitleSearch() { const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [open, setOpen] = useState(false); const [selected, setSelected] = useState(null); const timerRef = useRef(null); const wrapRef = useRef(null); useEffect(() => { function handleClickOutside(e) { if (wrapRef.current && !wrapRef.current.contains(e.target)) { setOpen(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); function doSearch(q) { if (!q || q.trim().length < 2) { setResults([]); setOpen(false); return; } setLoading(true); fetch(API_TITLES + "?q=" + encodeURIComponent(q.trim()) + "&limit=8") .then((r) => r.json()) .then((data) => { setResults(data.results || []); setOpen((data.results || []).length > 0); setLoading(false); }) .catch(() => { setResults([]); setLoading(false); }); } function handleInput(e) { var val = e.target.value; setQuery(val); setSelected(null); clearTimeout(timerRef.current); timerRef.current = setTimeout(() => doSearch(val), 200); } function handleSelect(item) { setSelected(item); setQuery(item.title || ""); setOpen(false); } function handleClear() { setQuery(""); setResults([]); setSelected(null); setOpen(false); } const googleSearchUrl = selected ? `https://www.google.com/search?tbm=isch&q=${encodeURIComponent( selected.isbn || selected.title + " book cover", )}` : null; return (
results.length > 0 && setOpen(true)} placeholder="Search by title, publisher, or ISBN…" style={{ width: "100%", padding: "11px 36px 11px 36px", borderRadius: "8px", border: `1px solid ${open ? B.blue : B.lightGray}`, background: B.white, color: B.nearBlack, fontSize: "13.5px", outline: "none", transition: "border-color 0.2s ease", boxSizing: "border-box", }} /> {query && ( )} {loading && (
)}
{/* Dropdown */} {open && results.length > 0 && (
{results.map((item, i) => ( ))}
)} {/* Selected confirmation */} {selected && (
{selected.image_url ? ( ) : ( )}
{selected.title}
{[ selected.isbn ? "ISBN: " + selected.isbn : null, selected.publisher, ] .filter(Boolean) .join(" · ")}
In index
)} {selected && ( { e.preventDefault(); window.open( googleSearchUrl, "_blank", "popup=true,width=1200,height=900", ); }} style={{ display: "flex", alignItems: "center", gap: "12px", marginTop: "10px", padding: "10px 14px", borderRadius: "8px", background: B.offWhite, border: `1px solid ${B.lightGray}`, color: B.nearBlack, textDecoration: "none", fontSize: "12.5px", fontWeight: 500, transition: "all 0.15s ease", animation: "fadeSlideUp 0.25s ease both", }} onMouseEnter={(e) => { e.currentTarget.style.background = B.white; e.currentTarget.style.borderColor = B.blue; }} onMouseLeave={(e) => { e.currentTarget.style.background = B.offWhite; e.currentTarget.style.borderColor = B.lightGray; }} >
2
Find cover variations on Google Images
)}
); } // ─── Step Card ───────────────────────────────────────────────────────── function StepCard({ number, title, accent, children }) { return (
{number}
{title}
{children}
); } // ─── Main App ────────────────────────────────────────────────────────── function App() { const [phase, setPhase] = useState("idle"); const [previewSrc, setPreviewSrc] = useState(null); const [currentStep, setCurrentStep] = useState(0); const [results, setResults] = useState([]); const [showResults, setShowResults] = useState(false); const [urlInput, setUrlInput] = useState(""); const [dragOver, setDragOver] = useState(false); const [isccData, setIsccData] = useState(null); const [error, setError] = useState(null); const [isMobile, setIsMobile] = useState(window.innerWidth < 700); const [scoreThreshold, setScoreThreshold] = useState(0.65); const [stepTimings, setStepTimings] = useState({}); const fileRef = useRef(null); const DEMO_URL = "demo.jpg"; useEffect(() => { const onResize = () => setIsMobile(window.innerWidth < 700); window.addEventListener("resize", onResize); return () => window.removeEventListener("resize", onResize); }, []); const processImage = useCallback(async (imageBytes, filename, previewUrl) => { setPreviewSrc(previewUrl); setPhase("processing"); setCurrentStep(0); setShowResults(false); setResults([]); setIsccData(null); setError(null); setStepTimings({}); try { const onStep = (key, ms) => { setStepTimings((prev) => ({ ...prev, [key]: ms })); const idx = STEP_INDEX[key]; if (idx != null && idx + 1 < PROCESSING_STEPS.length) { setCurrentStep(idx + 1); } }; const iscc = await generateIscc(imageBytes, filename, onStep); setIsccData(iscc); const rankT0 = performance.now(); const queryMainTypes = Object.keys(UNIT_KEY_TO_TYPE) .filter((k) => iscc.units && iscc.units[k]) .map((k) => UNIT_KEY_TO_TYPE[k]); const matches = tsrRank( queryMainTypes, (iscc.search && iscc.search.global_matches) || [], ); const rankingMs = Math.round(performance.now() - rankT0); setStepTimings((prev) => ({ ...prev, ranking: rankingMs })); setCurrentStep(PROCESSING_STEPS.length); setPhase("results"); setResults(matches); setTimeout(() => setShowResults(true), 80); } catch (err) { setError(err.message || "An unexpected error occurred"); setPhase("error"); } }, []); const handleFile = useCallback( (file) => { if (!file || !file.type.startsWith("image/")) return; const readerUrl = new FileReader(); readerUrl.onload = (e) => { const previewUrl = e.target.result; const readerBuf = new FileReader(); readerBuf.onload = (e2) => { processImage(new Uint8Array(e2.target.result), file.name, previewUrl); }; readerBuf.readAsArrayBuffer(file); }; readerUrl.readAsDataURL(file); }, [processImage], ); const handleUrlSubmit = useCallback(async () => { const url = urlInput.trim() || DEMO_URL; setPhase("processing"); setCurrentStep(0); setShowResults(false); setResults([]); setIsccData(null); setError(null); setPreviewSrc(url); try { const { bytes, filename } = await fetchImageAsBytes(url); processImage(bytes, filename, url); } catch (err) { setError(err.message || "Failed to fetch image from URL"); setPhase("error"); } }, [urlInput, processImage]); const handleDrop = useCallback( (e) => { e.preventDefault(); setDragOver(false); handleFile(e.dataTransfer.files && e.dataTransfer.files[0]); }, [handleFile], ); const handlePaste = useCallback( (e) => { const items = e.clipboardData && e.clipboardData.items; if (!items) return; for (let i = 0; i < items.length; i++) { if (items[i].type.startsWith("image/")) { e.preventDefault(); handleFile(items[i].getAsFile()); return; } } }, [handleFile], ); useEffect(() => { window.addEventListener("paste", handlePaste); return () => window.removeEventListener("paste", handlePaste); }, [handlePaste]); const reset = () => { setPhase("idle"); setPreviewSrc(null); setResults([]); setShowResults(false); setUrlInput(""); setCurrentStep(0); setIsccData(null); setError(null); setScoreThreshold(0.65); setStepTimings({}); }; const gridCols = isMobile ? "minmax(0, 1fr)" : "300px minmax(0, 1fr)"; // Filter results by score threshold and compute category counts const filteredResults = results.filter((r) => r.score >= scoreThreshold); const resultCounts = filteredResults.reduce((acc, r) => { acc[r.status] = (acc[r.status] || 0) + 1; return acc; }, {}); return (
{/* Header */}
ISCC
COVER MATCH
Tech Demo
ISO 24138 · Similarity Search · 3M Book Covers
iscc/iscc-book-covers
🤗 HuggingFace
{/* Main */}
handleFile(e.target.files && e.target.files[0]) } /> {phase === "idle" && (
Prepared for the{" "} W3C Anti-Counterfeit Taskforce {" "} · Community Group Meeting

Find matching book covers
across 3 million listings

Upload, paste, or drag a cover image. The ISCC fingerprint engine generates content-derived codes and searches for exact matches, near-duplicates, and similar covers.

{ e.preventDefault(); setDragOver(true); }} onDragLeave={() => setDragOver(false)} onDrop={handleDrop} onClick={() => fileRef.current && fileRef.current.click()} style={{ borderRadius: "12px", border: `2px dashed ${dragOver ? B.blue : `${B.sky}80`}`, background: dragOver ? `${B.sky}14` : `${B.sky}08`, padding: "40px 24px", textAlign: "center", transition: "all 0.2s ease", cursor: "pointer", }} >
Drop a cover image here
or click to browse · supports paste from clipboard (Ctrl+V)
e.stopPropagation()} style={{ display: "flex", gap: "6px", maxWidth: "440px", margin: "0 auto", }} > setUrlInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleUrlSubmit()} placeholder="Paste image URL or press Enter for demo…" style={{ flex: 1, padding: "6px 12px", borderRadius: "6px", border: `1px solid ${B.blue}`, background: B.white, color: B.nearBlack, fontSize: "12px", fontFamily: "'JetBrains Mono', Consolas, monospace", outline: "none", transition: "box-shadow 0.15s ease", }} onFocus={(e) => (e.target.style.boxShadow = `0 0 0 3px ${B.blue}33`)} onBlur={(e) => (e.target.style.boxShadow = "none")} />
e.stopPropagation()} style={{ marginTop: "16px" }}>
{[ { label: "Title Search", color: B.blue }, { label: "Image URL", color: B.sky }, { label: "Drag & Drop", color: B.cyan }, { label: "Clipboard Paste", color: B.lime }, { label: "ISCC Fingerprinting", color: B.yellow }, { label: "Similarity Search", color: B.coral }, { label: "ISO 24138", color: B.navy }, ].map((f, i) => (
{f.label}
))}
)} {phase === "error" && (
Something went wrong
{error}
)} {(phase === "processing" || phase === "results") && (
{/* Left */}

Query Image

{ e.preventDefault(); setDragOver(true); } : undefined } onDragLeave={ phase === "results" ? () => setDragOver(false) : undefined } onDrop={phase === "results" ? handleDrop : undefined} onClick={ phase === "results" ? () => fileRef.current && fileRef.current.click() : undefined } >
{(phase === "processing" || phase === "results") && ( 0, label: filteredResults.length > 0 ? "Matches found" : "No matches found", value: filteredResults.length > 0 ? filteredResults.length : null, } : null } /> )} {phase === "results" && isccData && isccData.name && (
Extracted Title
{isccData.name}
)} {phase === "results" && results.length > 0 && ( )} {isccData && ( 0 ? filteredResults[0].per_unit : null} /> )}
{/* Right */}
{phase === "processing" && (
{PROCESSING_STEPS[currentStep].label}
{PROCESSING_STEPS[currentStep].detail}
)} {phase === "results" && filteredResults.length === 0 && (
🔍
No matches found
{results.length > 0 ? "All " + results.length + " matches are below the " + (scoreThreshold * 100).toFixed(0) + "% score threshold. Try lowering it." : "This cover did not match any entries in the book covers index. Try a different image."}
)} {phase === "results" && filteredResults.length > 0 && (

Match Results

{isccData && (
{isccData.iscc}
)}
{filteredResults.length} results from covers index
{[ { label: "Exact", color: "#2e8b57", key: "exact" }, { label: "Near-dup", color: "#c89200", key: "near-duplicate", }, { label: "Similar", color: B.sky, key: "similar" }, { label: "Corroborated", color: B.cyan, key: "corroborated", }, { label: "Title-match", color: B.medGray, key: "title-match", }, { label: "Weak", color: B.medGray, key: "weak" }, ] .filter((cat) => resultCounts[cat.key]) .map((cat) => (
{resultCounts[cat.key]} {cat.label}
))}
{filteredResults.map((r, i) => ( ))}
)}
)}
{/* Footer */}
); } ReactDOM.createRoot(document.getElementById("root")).render();