Skip to content

Commit 35c46c7

Browse files
committed
Make citation chips resolve reliably with reference fallbacks
1 parent 3093d14 commit 35c46c7

3 files changed

Lines changed: 73 additions & 11 deletions

File tree

backend/agent.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,20 +1338,40 @@ def _build_content_nodes(created_main_block, tool_results=None):
13381338
nodes.append({"type": "keyTerms", "terms": terms})
13391339

13401340
refs = created_main_block.get("references")
1341+
items = []
13411342
if isinstance(refs, list) and refs:
1342-
items = []
13431343
for i, r in enumerate(refs):
13441344
if isinstance(r, dict) and r.get("text"):
13451345
item = {
1346-
"id": f"ref-{i}",
1346+
"id": f"ref-{i + 1}",
13471347
"label": str(i + 1),
13481348
"text": str(r["text"]),
13491349
}
13501350
if r.get("url"):
13511351
item["url"] = str(r["url"])
13521352
items.append(item)
1353-
if items:
1354-
nodes.append({"type": "references", "items": items})
1353+
1354+
# Fallback: when OpenAI-style citation tokens are present in content but
1355+
# structured references were not returned, still generate placeholder
1356+
# entries so inline chips can scroll somewhere meaningful.
1357+
if not items:
1358+
seen_refs = set()
1359+
for m in _re.finditer(r"turn\d+search(\d+)", content, flags=_re.IGNORECASE):
1360+
try:
1361+
one_based = int(m.group(1)) + 1
1362+
except Exception:
1363+
continue
1364+
if one_based <= 0 or one_based in seen_refs:
1365+
continue
1366+
seen_refs.add(one_based)
1367+
items.append({
1368+
"id": f"ref-{one_based}",
1369+
"label": str(one_based),
1370+
"text": f"Web source #{one_based} (details unavailable in this response metadata).",
1371+
})
1372+
1373+
if items:
1374+
nodes.append({"type": "references", "items": items})
13551375

13561376
quiz = created_main_block.get("quiz_questions")
13571377
if isinstance(quiz, list) and quiz:

frontend/src/components/ui/RichText.tsx

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,38 @@ function renderInline(text: string, variant: Variant, COLORS: ThemeColors, VS: R
383383
const nodes: React.ReactNode[] = []
384384
const codeFontSize = VS[variant].code.fontSize
385385

386+
const scrollToCitationTarget = (ids: number[]): boolean => {
387+
if (typeof document === "undefined") return false
388+
389+
const candidates: string[] = []
390+
if (ids.length > 1) candidates.push("references")
391+
for (const id of ids) {
392+
if (Number.isFinite(id) && id > 0) {
393+
candidates.push(`ref-${id}`)
394+
if (id - 1 >= 0) candidates.push(`ref-${id - 1}`) // backward-compat for old ref-0 IDs
395+
}
396+
}
397+
candidates.push("references")
398+
399+
const seen = new Set<string>()
400+
for (const c of candidates) {
401+
if (!c || seen.has(c)) continue
402+
seen.add(c)
403+
const el = document.getElementById(c)
404+
if (el) {
405+
el.scrollIntoView({ behavior: "smooth", block: "start" })
406+
return true
407+
}
408+
}
409+
410+
const byAria = document.querySelector('nav[aria-label="References"]') as HTMLElement | null
411+
if (byAria) {
412+
byAria.scrollIntoView({ behavior: "smooth", block: "start" })
413+
return true
414+
}
415+
return false
416+
}
417+
386418
let cursor = 0
387419
let key = 0
388420

@@ -429,16 +461,19 @@ function renderInline(text: string, variant: Variant, COLORS: ThemeColors, VS: R
429461
const token = parseOpenAICitationToken(text, cursor)
430462
if (token) {
431463
const citationText = token.ids.length ? `[${token.ids.join(",")}]` : "[source]"
432-
const isSingle = token.ids.length === 1
433-
const targetId = isSingle ? `ref-${token.ids[0]}` : "references"
434464
const canScroll = token.ids.length > 0
435465

436466
if (canScroll) {
437467
nodes.push(
438468
<button
439469
key={`cite-openai-${key++}`}
440470
type="button"
441-
onClick={() => document.getElementById(targetId)?.scrollIntoView({ behavior: "smooth", block: "start" })}
471+
onClick={() => {
472+
const ok = scrollToCitationTarget(token.ids)
473+
if (!ok && typeof window !== "undefined") {
474+
window.alert("No reference details are attached to this response.")
475+
}
476+
}}
442477
style={{
443478
font: "inherit",
444479
fontSize: "0.7em",
@@ -526,13 +561,20 @@ function renderInline(text: string, variant: Variant, COLORS: ThemeColors, VS: R
526561
const isCitation = /^\d+([,\-\s]\d+)*$/.test(inner.trim()) && text[labelEnd + 1] !== "("
527562
if (isCitation) {
528563
const trimmed = inner.trim()
529-
const isSingle = /^\d+$/.test(trimmed)
530-
const targetId = isSingle ? `ref-${trimmed}` : "references"
564+
const ids = trimmed
565+
.split(/[,\-\s]+/)
566+
.map((v) => Number(v))
567+
.filter((v) => Number.isFinite(v) && v > 0)
531568
nodes.push(
532569
<button
533570
key={`cite-${key++}`}
534571
type="button"
535-
onClick={() => document.getElementById(targetId)?.scrollIntoView({ behavior: "smooth", block: "start" })}
572+
onClick={() => {
573+
const ok = scrollToCitationTarget(ids)
574+
if (!ok && typeof window !== "undefined") {
575+
window.alert("No reference details are attached to this response.")
576+
}
577+
}}
536578
style={{
537579
font: "inherit",
538580
fontSize: "0.7em",

frontend/src/lib/blockSession.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ function buildContentNodesFromMetadata(
236236
const items = (node.items as Record<string, unknown>[])
237237
.filter((r) => typeof r.text === "string")
238238
.map((r, i) => ({
239-
id: String(r.id ?? `ref-${i}`),
239+
id: String(r.id ?? `ref-${i + 1}`),
240240
label: String(r.label ?? i + 1),
241241
text: String(r.text),
242242
...(typeof r.url === "string" && r.url ? { url: r.url } : {}),

0 commit comments

Comments
 (0)