Spaces:
Paused
Paused
File size: 3,416 Bytes
8c1b9fe | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | import { Citation } from "@/lib/api";
import { displaySource } from "@/lib/format";
import { citationColor, scoreStrength, STRENGTH_META } from "@/lib/citations";
// Clean, user-facing locator (page / speaker+timestamp) — never raw char spans.
function cleanLocator(c: Citation): string {
if (c.speaker || c.start_s != null) {
const t = (s?: number | null) => {
if (s == null) return "";
const m = Math.floor(s / 60);
const sec = Math.round(s % 60);
return `${m}:${String(sec).padStart(2, "0")}`;
};
const range = c.start_s != null ? `${t(c.start_s)}–${t(c.end_s)}` : "";
return [c.speaker, range].filter(Boolean).join(" · ");
}
if (c.page != null) return `p.${c.page}`;
return "";
}
function rgba(rgb: string, a: number): string {
return rgb.replace("rgb(", "rgba(").replace(")", `,${a})`);
}
export function Citations({
citations,
onOpenSource,
}: {
citations: Citation[];
onOpenSource?: (marker: number) => void;
}) {
if (!citations?.length) return null;
return (
<div className="mt-3 space-y-1.5">
<div className="overline text-fg3">
Sources · {citations.length}
</div>
<ol className="space-y-1.5">
{citations.map((c) => {
const loc = cleanLocator(c);
const color = citationColor(c.marker);
const strength = scoreStrength(c.score);
const sm = strength ? STRENGTH_META[strength] : null;
const clickable = !!onOpenSource;
const Tag = clickable ? "button" : "div";
return (
<li key={c.marker}>
<Tag
{...(clickable
? {
type: "button" as const,
onClick: () => onOpenSource!(c.marker),
"aria-label": `Open source ${c.marker}: ${displaySource(c.source)}`,
}
: {})}
className={`flex w-full items-center gap-2.5 rounded-lg border border-edge bg-panel2 px-2.5 py-1.5 text-left text-sm transition ${
clickable ? "hover:border-edge2 hover:bg-panel" : ""
}`}
>
<span
className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-xs font-semibold"
style={{ color, backgroundColor: rgba(color, 0.15) }}
>
{c.marker}
</span>
<span className="min-w-0 flex-1 truncate text-fg" title={displaySource(c.source)}>
{displaySource(c.source)}
{loc && <span className="ml-1 font-mono text-xs text-fg3">· {loc}</span>}
</span>
{sm && c.score != null && (
<span className="inline-flex shrink-0 items-center gap-1" title={`${sm.label} · retrieval score ${c.score.toFixed(2)}`}>
<span className={`h-1.5 w-1.5 rounded-full ${sm.dot}`} aria-hidden />
<span className="font-mono text-xs text-fg3">{c.score.toFixed(2)}</span>
</span>
)}
{c.method && (
<span className="tag hidden shrink-0 font-mono text-[10px] uppercase sm:inline-flex">
{c.method}
</span>
)}
</Tag>
</li>
);
})}
</ol>
</div>
);
}
|