feat: source citations with footnotes in the transcript #21

Merged
adrianbonpin merged 10 commits from feat/source-citations into prod 2026-10-07 08:12:52 +00:00
Showing only changes of commit c767595a76 - Show all commits
@@ -1056,18 +1056,26 @@ export function renderModelOutput(markdown: string, options: RenderOptions = {})
const sources = options.sources ?? [];
const cited = new Set<number>();
// Citations run first. The pass produces a markdown link, and the maths and
// markup passes after it would only ever be looking at a marker, which is the
// one thing they now cannot misread.
const segments = splitSegments(repairCodeBlocks(markdown)).map((segment) => {
/**
* Maths first, then citations, then markup.
*
* The order is forced by one collision. A link label has to escape its brackets
* or the parser ends the link text early, and `[\[1\]]` is exactly the shape of
* LaTeX's own `\[...\]` display delimiter, which the maths pass would eat and
* hand back as a bare `1`. So the model's maths is converted before any marker
* becomes a link: the citation pass writes final form markdown, which is not
* input for anything but the markup pass, and that one only reads angle brackets.
*/
const segments = mathPass(splitSegments(repairCodeBlocks(markdown))).map((segment) => {
if (segment.fence || sources.length === 0) return segment;
const result = linkCitations(segment.lines.join("\n"), sources);
for (const index of result.cited) cited.add(index);
return { fence: false, language: "", lines: result.text.split("\n") };
});
return { text: flatten(htmlPass(mathPass(segments))), cited };
return { text: flatten(htmlPass(segments)), cited };
}
```