📦 EqualifyEverything / equalify-iris

📄 pageindex.ts · 43 lines
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// A compact, ordered index of the document's source pages: page number + a short
// excerpt of the HTML that was extracted from it.
//
// Two agents need to map something in the assembled document back to the page it
// came from without being resent the whole document: the Feedback Agent (which
// pages does this feedback concern?) and the Reader (which page is this issue on?).
// Page attribution is what lets the Copy Editor be handed only the images it
// actually needs, and provenance comments are stripped from the document, so this
// index is the only page map either one gets.

// Enough to recognize a page, far short of resending it. Callers that index on
// every model call (the Reader, once per chunk per round) pass a smaller value.
const EXCERPT_CHARS = 400;

export interface IndexedPage {
  order: number; // 1-based page order
  innerHtml: string;
}

export function pageIndex(pages: IndexedPage[], excerptChars = EXCERPT_CHARS): string {
  return [...pages]
    .sort((a, b) => a.order - b.order)
    .map((p) => {
      const excerpt = p.innerHtml.replace(/\s+/g, " ").trim().slice(0, excerptChars);
      return `### Page ${p.order}\n${excerpt}`;
    })
    .join("\n\n");
}

// Keep only page numbers that exist in this document, deduped and sorted. Model
// answers are the only source of page attribution, so a hallucinated page 99 must
// be dropped rather than acted on.
export function knownPages(claimed: unknown, pages: IndexedPage[]): number[] {
  const known = new Set(pages.map((p) => p.order));
  return [
    ...new Set(
      (Array.isArray(claimed) ? claimed : [])
        .map((p) => (typeof p === "number" ? p : Number(p)))
        .filter((p) => Number.isInteger(p) && known.has(p)),
    ),
  ].sort((a, b) => a - b);
}