/** 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.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) => (
handleSelect(item)}
style={{
display: "flex",
alignItems: "center",
gap: "10px",
width: "100%",
padding: "8px 12px",
background: "none",
border: "none",
borderBottom:
i < results.length - 1
? `1px solid ${B.lightGray}`
: "none",
cursor: "pointer",
textAlign: "left",
transition: "background 0.15s ease",
}}
onMouseEnter={(e) =>
(e.currentTarget.style.background = B.offWhite)
}
onMouseLeave={(e) =>
(e.currentTarget.style.background = "none")
}
>
{item.image_url ? (
) : (
)}
{item.title}
{[item.isbn ? "ISBN: " + item.isbn : null, item.publisher]
.filter(Boolean)
.join(" · ")}
))}
)}
{/* 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 (
);
}
// ─── 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 */}
ISO 24138 · Similarity Search · 3M Book Covers
{/* 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")}
/>
▶ Search
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}
← Try again
)}
{(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( );