Skip to content

Commit 940481a

Browse files
fix(web): show added figure-caption evidence in the retrieval panel
The injected caption chunk is citable, so answers cite [1] against it, but the panel only listed retrieval results - the most load-bearing citation had no visible counterpart anywhere. Injected captions now render in their own "Named-figure evidence" section above the ranked candidates: the citation number badge, the caption preview, a wrapping plain-language note ("your question names this figure, so its caption joined the evidence directly"), and a click-through to the source page with the figure region box.
1 parent be6e29c commit 940481a

3 files changed

Lines changed: 36 additions & 4 deletions

File tree

web/app/api.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@
243243
for (const paperId of papers) {
244244
const f = figureIndex.find((g) => g.paper_id === paperId && re.test(String(g.caption || "").trim()));
245245
if (f && typeof f.page_number === "number") {
246-
out.push({ paperId, page: f.page_number, chunkId: f.chunk_id, caption: String(f.caption || "") });
246+
out.push({ paperId, page: f.page_number, chunkId: f.chunk_id, caption: String(f.caption || ""), bbox: f.bbox || null });
247247
// One page per reference — matching the same "Figure 2" in a second
248248
// paper would crowd out the question's other references.
249249
break;
@@ -310,7 +310,8 @@
310310
// captions rarely share words with the question ("Fig. 1: (a) Previous
311311
// driving world models…" vs "What does Figure 1 illustrate?") — and the
312312
// model then transplants text about a DIFFERENT figure onto the asked one.
313-
for (const ref of referencedFigurePages(latestUserText, chunks, figureIndex)) {
313+
const injected = referencedFigurePages(latestUserText, chunks, figureIndex);
314+
for (const ref of injected) {
314315
if (ref.chunkId && ref.caption) {
315316
content.push({
316317
type: "text",
@@ -329,7 +330,9 @@
329330
}
330331
content.push({ type: "text", text: `\nQuestion: ${latestUserText}` });
331332
messages.push({ role: "user", content });
332-
return messages;
333+
// `injected` rides along so the UI can show this evidence in the panel —
334+
// it is context the model saw, but it is not a retrieval result.
335+
return { messages, injected };
333336
}
334337

335338
// Read an OpenRouter-style SSE stream, invoking onDelta(text) per token.

web/app/chat.jsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,33 @@ function RetrievalPanel({ turn, highlight, settings, paperTitle, routingAvailabl
229229
)}
230230
</div>
231231

232+
{/* Caption chunks injected for figures/tables the question names —
233+
context the model saw and can cite, but not retrieval output, so
234+
they get their own labeled section instead of a ranked row. */}
235+
{turn.injected && turn.injected.length > 0 && (
236+
<div className="retr-section">
237+
<h4>Named-figure evidence <span className="n">{turn.injected.length}</span></h4>
238+
{turn.injected.map((f, i) => {
239+
const ct = (turn.citations || []).find((x) => x.id === f.chunkId);
240+
return (
241+
<div key={f.chunkId || i} className="cand row-clickable"
242+
onClick={() => setPageItem({ chunk_id: f.chunkId, paper: f.paperId, page: f.page, pages: [f.page], kind: "visual", bbox: f.bbox || null, text: f.caption || "" })}
243+
title="View source region on page">
244+
<div className="cand-top">
245+
<span className="cand-num visual">{ct ? ct.n : "·"}</span>
246+
<span className="cand-src">{f.paperId} · p.{f.page}</span>
247+
<span className="cand-score">added</span>
248+
</div>
249+
{f.caption && <div className="cand-quote">{previewQuote(f.caption)}</div>}
250+
<div className="cand-meta">
251+
<span className="pin-note">your question names this figure, so its caption joined the evidence directly</span>
252+
</div>
253+
</div>
254+
);
255+
})}
256+
</div>
257+
)}
258+
232259
<div className="retr-section">
233260
<h4>Ranked candidates <span className="n">{total} chunks</span></h4>
234261
{cands.map((c, i) => {
@@ -425,7 +452,8 @@ function ChatView({ settings, set, layout, resetSignal, apiKey, model, papers, f
425452
// attach page images when pages are served.
426453
const useImages = pagesAvailable && (hasKey ? window.RAG.supportsVision(model) : true);
427454
if (useImages) live(() => setStatus(hasKey ? "Reading the retrieved page images…" : "Free demo model is reading the retrieved pages…"));
428-
const messages = await window.RAG.buildMessages(priorTurns, q, results, useImages, figures);
455+
const { messages, injected } = await window.RAG.buildMessages(priorTurns, q, results, useImages, figures);
456+
if (injected && injected.length) upd({ injected });
429457
const tGen = performance.now();
430458
const onDelta = (delta) => upd((prev) => ({ answer: prev.answer + delta }));
431459
const { text, usage } = hasKey

web/app/styles.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,7 @@ body {
611611
.cand-score { margin-left: auto; font-family: "IBM Plex Mono", monospace; font-size: 11px; color: var(--text); font-weight: 500; }
612612
.cand-quote { font-family: "IBM Plex Serif", serif; font-size: 12.5px; line-height: 1.55; color: var(--text-dim); margin: 6px 0 8px; padding-left: 9px; border-left: 2px solid var(--border); }
613613
.cand-meta { display: flex; align-items: center; gap: 8px; margin-top: 7px; }
614+
.pin-note { font-size: 10.5px; color: var(--text-faint); line-height: 1.45; white-space: normal; }
614615
.cand-status { font-family: "IBM Plex Mono", monospace; font-size: 10px; letter-spacing: .04em; }
615616
.cand-status.kept { color: var(--good); }
616617
.cand-status.dropped { color: var(--text-faint); }

0 commit comments

Comments
 (0)