;
}
/* ---- tiny markdown -> react (bold, lists, citations) ---- */
function inlineNodes(text, onCite) {
const out = [];
const re = /\*\*(.+?)\*\*|\[(F?\d+)\]/g;
let last = 0, m, k = 0;
while ((m = re.exec(text))) {
if (m.index > last) out.push(text.slice(last, m.index));
if (m[1] !== undefined) {
// Recurse so citations inside bold text stay clickable chips.
out.push({inlineNodes(m[1], onCite)});
} else {
const tag = m[2];
const isFig = tag[0] === "F";
// F-tags have no click target in chat (the handler drops them) — render
// them as plain markers, not a dead "Jump to evidence" affordance.
out.push(isFig
? {tag}
: onCite && onCite(tag)} title="Jump to evidence">{tag}
);
}
last = re.lastIndex;
}
if (last < text.length) out.push(text.slice(last));
return out;
}
function Markdown({ text, onCite }) {
// Group consecutive lines of the same kind: models often put the intro and
// the bullets in one block ("Compared are:\n* a\n* b"), so a per-block
// all-or-nothing test renders the bullets as literal asterisks.
const lineKind = (l) => (/^\d+\.\s/.test(l) ? "ol" : /^[-*•]\s/.test(l) ? "ul" : "p");
const stripMarker = (l) => l.replace(/^(\d+\.|[-*•])\s/, "");
const blocks = text.split("\n\n");
return (
{blocks.map((b, i) => {
const segs = [];
for (const l of b.split("\n")) {
const kind = lineKind(l);
const prev = segs[segs.length - 1];
if (prev && prev.kind === kind) prev.lines.push(l);
else segs.push({ kind, lines: [l] });
}
return segs.map((s, j) => {
const key = i + "-" + j;
if (s.kind === "ol") {
return {s.lines.map((l, k) =>
{inlineNodes(stripMarker(l), onCite)}
)};
}
if (s.kind === "ul") {
return
{s.lines.map((l, k) =>
{inlineNodes(stripMarker(l), onCite)}
)}
;
}
return
{inlineNodes(s.lines.join(" "), onCite)}
;
});
})}
);
}
/* shared small components */
function Segmented({ options, value, onChange }) {
return (
{options.map((o) => (
))}
);
}
function Tooltip({ label, children }) {
return {children};
}
/* ---- source-page modal: real page image + cited-region bbox overlay ---- */
function PageRegionModal({ item, onClose, paperTitle }) {
const [ov, setOv] = useState(null); // pixel overlay box, measured off the image
const [norm, setNorm] = useState(null); // normalized fractions for the side panel
const imgRef = useRef(null);
// Place the overlay in pixels off the image's own offset + rendered size. The
// page PNG renders at 150 DPI (1 pt = 150/72 px). Pixels not % because
// .pm-page carries CSS padding + a fixed aspect-ratio, so a %-based overlay
// would measure that box, not the image (ADR 0009 figures/tables, 0021 text).
const place = useCallback(() => {
const img = imgRef.current;
if (!img || !img.naturalWidth || !item || !Array.isArray(item.bbox) || item.bbox.length !== 4) {
setOv(null); setNorm(null); return;
}
const DPI = 150, pW = (img.naturalWidth * 72) / DPI, pH = (img.naturalHeight * 72) / DPI;
const [x0, y0, x1, y1] = item.bbox;
const fl = x0 / pW, ft = y0 / pH, fw = (x1 - x0) / pW, fh = (y1 - y0) / pH;
setNorm({ left: fl, top: ft, width: fw, height: fh });
setOv({
top: img.offsetTop + ft * img.clientHeight,
left: img.offsetLeft + fl * img.clientWidth,
width: fw * img.clientWidth,
height: fh * img.clientHeight,
});
}, [item]);
useEffect(() => { setOv(null); setNorm(null); }, [item]);
useEffect(() => {
if (!item) return;
const onEsc = (e) => { if (e.key === "Escape") onClose(); };
const onResize = () => place();
document.addEventListener("keydown", onEsc);
window.addEventListener("resize", onResize);
return () => { document.removeEventListener("keydown", onEsc); window.removeEventListener("resize", onResize); };
}, [item, onClose, place]);
if (!item) return null;
const isVis = item.kind === "visual";
const title = paperTitle ? paperTitle(item.paper) : item.paper;
const hasBbox = Array.isArray(item.bbox) && item.bbox.length === 4;
const rawQuote = String(item.quote || item.text || "").replace(/\s+/g, " ").trim();
const quote = rawQuote.length > 320 ? rawQuote.slice(0, 320).trim() + "…" : rawQuote;
return ReactDOM.createPortal(
{/* Rerank scores are logits — a 0..1 bar only makes sense for
similarity-scaled values; otherwise the number stands alone. */}
{item.score >= 0 && item.score <= 1 && }
)}
{quote &&
{"“" + quote + "”"}
}
{item.page_cite
? "Cited as a page image: the model read this page directly when describing the figure, so the whole page is the evidence."
: isVis
? (hasBbox ? "Retrieved from the visual store over page images; the box marks the figure or table region on the source page." : "Retrieved from the visual store over page images.")
: (hasBbox ? "Retrieved from the text store by the dense retriever; the box marks the cited passage on the page." : "Retrieved from the text store by the dense retriever. This chunk spans page boundaries, so no single region box is available.")}