@bitbaum/chatkit 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -613,9 +613,12 @@ Photos, Files — each its own file input. One input with a mixed `accept`
613
613
  apps (Camera, Camera, Recorder, "Photos & Videos"), where the screenshot you
614
614
  came to send is three levels down and the file browser is not offered at all.
615
615
  `ATTACH_SOURCE_INPUT` is the SSOT: Camera captures, Photos opens the system
616
- photo picker, Files has no `accept` and opens the real document browser.
617
- Because Files takes anything, `isTextFile` decides afterwards what can be read
618
- inline; a PDF gets a sentence, not a prompt full of bytes. With a mouse the
616
+ photo picker, Files asks for documents only (`FILES_ACCEPT`: PDF and text)
617
+ and opens the real document browser. It used to ask for nothing, which Brave
618
+ on Android answered with the capture chooser and no Files at all: an empty
619
+ `accept` means "anything", and anything includes the camera. A picker can
620
+ still hand over other files, so `isTextFile` and `@bitbaum/chatkit/pdf`
621
+ decide afterwards what can be read. With a mouse the
619
622
  paperclip opens the file dialog directly — on a desktop all three are the
620
623
  same dialog. A box that is not chatkit's composer renders `<AttachMenu
621
624
  onFiles={…} />` rather than a bare `<input type="file">`. An app with more to
@@ -644,6 +647,45 @@ import { AttachMenu, shrinkImage } from "@bitbaum/chatkit/attach";
644
647
  maxBytes)` is the in-browser downscale the composer uses. `acceptFamilies` and
645
648
  `ATTACH_SOURCE_INPUT` are the rule itself.
646
649
 
650
+ ### PDFs — `@bitbaum/chatkit/pdf`
651
+
652
+ No model on a free chain reads a PDF file. So a PDF someone attaches is read
653
+ in the browser and handed on the way the model already understands:
654
+
655
+ ```ts
656
+ import { isPdfFile, readPdf, sheetName, PDF_MAX_BYTES } from "@bitbaum/chatkit/pdf";
657
+
658
+ if (isPdfFile(file) && file.size <= PDF_MAX_BYTES) {
659
+ const r = await readPdf(file, { maxSheets: picturesLeft, assetsUrl: "/pdfjs/" });
660
+ if (r.kind === "text") attachText(file.name, r.text); // "--- page 3 ---" markers kept
661
+ else
662
+ r.sheets.forEach((s) => attachImage(sheetName(file.name, s.firstPage, s.lastPage), s.dataUrl));
663
+ // r.pagesShown < r.pageCount → tell the person which pages went
664
+ }
665
+ ```
666
+
667
+ - **A PDF with a text layer** (Word, Docs, a typed report) comes back as text.
668
+ - **A PDF without one** (slides exported as pictures, a scan) comes back as
669
+ pictures of its pages, **four to a sheet** at 784px a page — slide text stays
670
+ legible, and a 20-page deck is 5 pictures. Send them to a model that sees.
671
+ - pdf.js loads only on the first PDF (its own entry point and a dynamic
672
+ import), and the legacy build is used so older iPhones read PDFs too.
673
+
674
+ **One step per app**: pdf.js fetches its worker and image decoders by URL at
675
+ runtime, which no bundler follows. Copy them as the first step of `dev` and
676
+ `build` (pnpm does not run `prebuild` hooks by default):
677
+
678
+ ```json
679
+ "build": "chatkit-copy-pdf-assets public/pdfjs && next build"
680
+ ```
681
+
682
+ and pass `assetsUrl: "/pdfjs/"`. Keep that folder out of git. Served
683
+ from the app's own origin they pass a strict CSP, and they include the
684
+ decoders without which pages whose pictures are JPEG 2000 — macOS's "reduce
685
+ file size" export — draw blank. `test/browser-pdf.mjs` checks both
686
+ kinds in Chromium; `node test/browser-pdf.mjs your.pdf` prints what any PDF
687
+ turns into.
688
+
647
689
  ### The check every app runs in CI
648
690
 
649
691
  ```sh
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ // Copies pdf.js's runtime files where an app serves static files, so
3
+ // `readPdf(file, { assetsUrl })` from @bitbaum/chatkit/pdf can fetch them.
4
+ //
5
+ // chatkit-copy-pdf-assets public/pdfjs → readPdf(file, { assetsUrl: "/pdfjs/" })
6
+ //
7
+ // pdf.js loads its worker and its image decoders (JPEG 2000, JBIG2) by URL at
8
+ // runtime, which no bundler follows. Without them a page whose pictures are
9
+ // JPEG 2000 — macOS's "reduce file size" PDF export — draws blank (found
10
+ // 2026-10-27 on an investor deck). Run it as the first step of the app's dev
11
+ // and build scripts (pnpm skips `prebuild` hooks by default) so the files
12
+ // always match the installed pdf.js; the folder stays out of git. Idempotent.
13
+ import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
14
+ import { createRequire } from "node:module";
15
+ import { dirname, join, resolve } from "node:path";
16
+ import { PDF_ASSET_DIRS, PDF_WORKER_FILE } from "../dist/pdf.js";
17
+
18
+ const dest = resolve(process.argv[2] ?? "public/pdfjs");
19
+ const require = createRequire(import.meta.url);
20
+ const pkgJson = require.resolve("pdfjs-dist/package.json");
21
+ const root = dirname(pkgJson);
22
+ const { version } = require(pkgJson);
23
+
24
+ rmSync(dest, { recursive: true, force: true });
25
+ mkdirSync(dest, { recursive: true });
26
+ cpSync(join(root, "legacy", "build", PDF_WORKER_FILE), join(dest, PDF_WORKER_FILE));
27
+ for (const dir of PDF_ASSET_DIRS) {
28
+ const from = join(root, dir);
29
+ if (!existsSync(from)) {
30
+ console.error(`chatkit-copy-pdf-assets: pdfjs-dist ${version} has no ${dir}/`);
31
+ process.exit(1);
32
+ }
33
+ // quickjs-eval is pdf.js's sandbox for a PDF's own JavaScript, which a chat
34
+ // never runs. Leaving it out keeps a document's scripts from having anywhere to go.
35
+ cpSync(from, join(dest, dir), { recursive: true, filter: (p) => !p.includes("quickjs") });
36
+ }
37
+ writeFileSync(join(dest, "VERSION"), `pdfjs-dist ${version}\n`);
38
+ console.log(`chatkit-copy-pdf-assets: pdfjs-dist ${version} → ${dest}`);
@@ -62,11 +62,11 @@ export declare function fitWithin(width: number, height: number, maxEdge: number
62
62
  export declare function dataUrlBytes(dataUrl: string): number;
63
63
  export declare function isImageMime(mime: string): boolean;
64
64
  /**
65
- * Whether a file is text the model can read inline. The Files picker accepts
66
- * anything — that is what keeps it the phone's real document browser instead
67
- * of a camera chooser — so the decision about what can actually be sent moves
68
- * here, after the pick, where a PDF gets a sentence instead of being read as
69
- * binary noise and pasted into a prompt.
65
+ * Whether a file is text the model can read inline. The Files picker asks for
66
+ * documents (FILES_ACCEPT) but a picker may still hand over anything, so the
67
+ * decision about what can actually be sent stays here, after the pick, where
68
+ * a PDF gets a sentence instead of being read as binary noise and pasted into
69
+ * a prompt.
70
70
  */
71
71
  export declare function isTextFile(name: string, mime: string): boolean;
72
72
  /**
@@ -80,10 +80,24 @@ export declare function isTextFile(name: string, mime: string): boolean;
80
80
  * - camera: `capture` opens the camera straight away.
81
81
  * - photos: `image/*` alone opens the system photo picker (Screenshots is its
82
82
  * first album), not a chooser.
83
- * - files: no `accept` at all opens the real document browser — Downloads,
84
- * Drive, any folder. The type check happens after, in `isTextFile`.
83
+ * - files: `FILES_ACCEPT`, documents only (PDF and text), opens the real
84
+ * document browser — Downloads, Drive, any folder. It used to be no `accept`
85
+ * at all, which Chrome answered with that browser but Brave on Android
86
+ * (2026-10-27, OrangeCat) answered with the capture chooser above, Files
87
+ * missing: an empty `accept` means "anything", and "anything" includes the
88
+ * camera and the recorder. Naming only document types takes every capture
89
+ * app off the table on both. The type check still happens after the pick,
90
+ * in `isTextFile` and the app's own reader (`@bitbaum/chatkit/pdf`).
85
91
  */
86
92
  export type AttachSource = "camera" | "photos" | "files";
93
+ /**
94
+ * What the Files source asks the phone for: PDFs and text, one "document"
95
+ * family. MIME types first (what Android and iOS map to their pickers), then
96
+ * the extensions a desktop file dialog filters by. Only types every platform
97
+ * knows: an extension a phone cannot map can widen the request back to
98
+ * "anything" — the capture chooser this exists to avoid.
99
+ */
100
+ export declare const FILES_ACCEPT = "application/pdf,text/*,application/json,.pdf,.txt,.md,.markdown,.csv,.tsv,.json";
87
101
  export declare const ATTACH_SOURCE_INPUT: Record<AttachSource, {
88
102
  accept?: string;
89
103
  capture?: "environment";
@@ -1 +1 @@
1
- {"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AACF,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,eAAe,CAAC;AAE1D,uDAAuD;AACvD,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;;OAQG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,gBAKvC,CAAC;AAEF;yBACyB;AACzB,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACvD,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,KAAK,MAAM,CAAC;IACjE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CAClC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,eAMtC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CASpD;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD;AAID,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAQD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAI9D;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEzD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CACtC,YAAY,EACZ;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,aAAa,CAAA;CAAE,CAK7C,CAAC;AAEF,4DAA4D;AAC5D,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC;AAyBpE;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAgB7D;AAED;mCACmC;AACnC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED;6DAC6D;AAC7D,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAInD;AAED,wDAAwD;AACxD,wBAAgB,MAAM,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,UAAU,EAAE,CAM/D"}
1
+ {"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"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"}
@@ -45,11 +45,11 @@ export function isImageMime(mime) {
45
45
  const TEXT_EXTENSIONS = /\.(txt|md|markdown|json|jsonl|csv|tsv|log|ya?ml|toml|ini|xml|html?|css|scss|js|jsx|mjs|cjs|ts|tsx|py|rb|go|rs|java|kt|swift|c|h|cpp|hpp|cs|php|sh|bash|zsh|sql|env|diff|patch|srt|vtt)$/i;
46
46
  const TEXT_MIME = /^(text\/|application\/(json|xml|x-yaml|yaml|javascript|x-sh|sql))/i;
47
47
  /**
48
- * Whether a file is text the model can read inline. The Files picker accepts
49
- * anything — that is what keeps it the phone's real document browser instead
50
- * of a camera chooser — so the decision about what can actually be sent moves
51
- * here, after the pick, where a PDF gets a sentence instead of being read as
52
- * binary noise and pasted into a prompt.
48
+ * Whether a file is text the model can read inline. The Files picker asks for
49
+ * documents (FILES_ACCEPT) but a picker may still hand over anything, so the
50
+ * decision about what can actually be sent stays here, after the pick, where
51
+ * a PDF gets a sentence instead of being read as binary noise and pasted into
52
+ * a prompt.
53
53
  */
54
54
  export function isTextFile(name, mime) {
55
55
  if (TEXT_MIME.test(mime))
@@ -58,10 +58,18 @@ export function isTextFile(name, mime) {
58
58
  return false;
59
59
  return TEXT_EXTENSIONS.test(name);
60
60
  }
61
+ /**
62
+ * What the Files source asks the phone for: PDFs and text, one "document"
63
+ * family. MIME types first (what Android and iOS map to their pickers), then
64
+ * the extensions a desktop file dialog filters by. Only types every platform
65
+ * knows: an extension a phone cannot map can widen the request back to
66
+ * "anything" — the capture chooser this exists to avoid.
67
+ */
68
+ export const FILES_ACCEPT = "application/pdf,text/*,application/json,.pdf,.txt,.md,.markdown,.csv,.tsv,.json";
61
69
  export const ATTACH_SOURCE_INPUT = {
62
70
  camera: { accept: "image/*", capture: "environment" },
63
71
  photos: { accept: "image/*" },
64
- files: {},
72
+ files: { accept: FILES_ACCEPT },
65
73
  };
66
74
  const FAMILY_BY_EXTENSION = {};
67
75
  for (const ext of [
@@ -1 +1 @@
1
- {"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"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"}
package/dist/pdf.d.ts ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @bitbaum/chatkit/pdf — a PDF a person attaches, read in the browser and
3
+ * handed on the way a chat model already understands: as text, or as
4
+ * pictures of the pages.
5
+ *
6
+ * import { isPdfFile, readPdf } from "@bitbaum/chatkit/pdf";
7
+ *
8
+ * No model on a free chain reads a PDF file, and most paid ones bill for the
9
+ * privilege. So the PDF never leaves the device as a PDF:
10
+ *
11
+ * - A PDF with a text layer (exported from Word, Docs, a typed report) comes
12
+ * back as `{ kind: "text" }` with page markers kept, so the model can say
13
+ * "on page 4". Attach it like a .txt file.
14
+ * - A PDF without one (a deck exported as slide images, a scan) comes back as
15
+ * `{ kind: "pages" }`: the pages rendered and packed four to a sheet.
16
+ * Attach the sheets like photos, to a model that can see. Four to a sheet
17
+ * keeps each page ~780px wide — slide text stays legible — and turns a
18
+ * 20-page deck into 5 pictures instead of 20.
19
+ *
20
+ * Found 2026-10-27 in OrangeCat: an investor deck (17 slide images, 13.7 MB,
21
+ * no text layer) could not be shared with the Cat at all.
22
+ *
23
+ * Its own entry point, so a chat that never sees a PDF never loads pdf.js;
24
+ * `readPdf` imports it on the first PDF. The pure parts are exported for
25
+ * tests and for apps that word their own notices.
26
+ */
27
+ /** Pages per sheet, as a grid. */
28
+ export declare const PDF_SHEET_COLUMNS = 2;
29
+ export declare const PDF_SHEET_ROWS = 2;
30
+ export declare const PDF_PAGES_PER_SHEET: number;
31
+ /** Width of one page on a sheet; a sheet is two of these side by side. */
32
+ export declare const PDF_PAGE_WIDTH_PX = 784;
33
+ /** A PDF this large is refused before pdf.js opens it. */
34
+ export declare const PDF_MAX_BYTES: number;
35
+ /**
36
+ * Below this many non-space characters per page, on average, the text layer
37
+ * is treated as absent (a slide deck's page numbers, a scanner's stamp) and
38
+ * the pages are looked at instead.
39
+ */
40
+ export declare const PDF_MIN_TEXT_CHARS_PER_PAGE = 60;
41
+ export type PdfReading = {
42
+ kind: "text";
43
+ text: string;
44
+ pageCount: number;
45
+ } | {
46
+ kind: "pages";
47
+ sheets: PdfSheet[];
48
+ pageCount: number;
49
+ pagesShown: number;
50
+ };
51
+ export interface PdfSheet {
52
+ /** 1-based, inclusive. */
53
+ firstPage: number;
54
+ lastPage: number;
55
+ dataUrl: string;
56
+ }
57
+ export declare function isPdfFile(file: {
58
+ name: string;
59
+ type: string;
60
+ }): boolean;
61
+ /** True when the extracted text is the document, not a few stray labels. */
62
+ export declare function isTextRich(pageTexts: string[]): boolean;
63
+ /** The pages as one text, each under a marker the Cat can cite. */
64
+ export declare function joinPageTexts(pageTexts: string[]): string;
65
+ /**
66
+ * Which pages go on which sheet, for at most `maxSheets` sheets. Pages past
67
+ * what fits are left out, and the caller says so.
68
+ */
69
+ export declare function planSheets(pageCount: number, maxSheets: number): number[][];
70
+ /** "deck.pdf · pages 5–8", or "· page 9" for a sheet of one. */
71
+ export declare function sheetName(fileName: string, firstPage: number, lastPage: number): string;
72
+ /**
73
+ * Where pdf.js's own runtime files live — the worker, the image decoders
74
+ * (JPEG 2000, JBIG2), CMaps, standard fonts, ICC profiles. They are fetched by
75
+ * URL at runtime, which a bundler cannot follow, so an app copies them into its
76
+ * static folder with `chatkit-copy-pdf-assets public/pdfjs` and passes
77
+ * `assetsUrl: "/pdfjs/"`. Served from the app's own origin, they also pass a
78
+ * strict Content-Security-Policy. Missing decoders are why a page whose
79
+ * pictures are JPEG 2000 — macOS's "reduce file size" export, the deck this
80
+ * was written for — draws blank.
81
+ */
82
+ export declare const PDF_ASSET_DIRS: readonly ["wasm", "cmaps", "standard_fonts", "iccs"];
83
+ export declare const PDF_WORKER_FILE = "pdf.worker.min.mjs";
84
+ export interface ReadPdfOptions {
85
+ /** How many pictures the message still has room for. A text PDF needs none. */
86
+ maxSheets: number;
87
+ /** Base URL of the files `chatkit-copy-pdf-assets` copied, e.g. "/pdfjs/". */
88
+ assetsUrl: string;
89
+ }
90
+ /**
91
+ * Read a picked PDF. Throws on a file pdf.js cannot open (encrypted, corrupt)
92
+ * — the app words that for the person. `PDF_MAX_BYTES` is checked by the app
93
+ * before calling, so it can say so in its own words.
94
+ */
95
+ export declare function readPdf(file: Blob | Uint8Array, opts: ReadPdfOptions): Promise<PdfReading>;
96
+ //# sourceMappingURL=pdf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf.d.ts","sourceRoot":"","sources":["../src/pdf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,kCAAkC;AAClC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,eAAO,MAAM,mBAAmB,QAAqC,CAAC;AAEtE,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,0DAA0D;AAC1D,eAAO,MAAM,aAAa,QAAmB,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAI9C,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjF,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAEvE;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAMvD;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAKzD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAW3E;AAED,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIvF;AAWD;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,sDAAuD,CAAC;AACnF,eAAO,MAAM,eAAe,uBAAuB,CAAC;AAwFpD,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CA6BhG"}
package/dist/pdf.js ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * @bitbaum/chatkit/pdf — a PDF a person attaches, read in the browser and
3
+ * handed on the way a chat model already understands: as text, or as
4
+ * pictures of the pages.
5
+ *
6
+ * import { isPdfFile, readPdf } from "@bitbaum/chatkit/pdf";
7
+ *
8
+ * No model on a free chain reads a PDF file, and most paid ones bill for the
9
+ * privilege. So the PDF never leaves the device as a PDF:
10
+ *
11
+ * - A PDF with a text layer (exported from Word, Docs, a typed report) comes
12
+ * back as `{ kind: "text" }` with page markers kept, so the model can say
13
+ * "on page 4". Attach it like a .txt file.
14
+ * - A PDF without one (a deck exported as slide images, a scan) comes back as
15
+ * `{ kind: "pages" }`: the pages rendered and packed four to a sheet.
16
+ * Attach the sheets like photos, to a model that can see. Four to a sheet
17
+ * keeps each page ~780px wide — slide text stays legible — and turns a
18
+ * 20-page deck into 5 pictures instead of 20.
19
+ *
20
+ * Found 2026-10-27 in OrangeCat: an investor deck (17 slide images, 13.7 MB,
21
+ * no text layer) could not be shared with the Cat at all.
22
+ *
23
+ * Its own entry point, so a chat that never sees a PDF never loads pdf.js;
24
+ * `readPdf` imports it on the first PDF. The pure parts are exported for
25
+ * tests and for apps that word their own notices.
26
+ */
27
+ /** Pages per sheet, as a grid. */
28
+ export const PDF_SHEET_COLUMNS = 2;
29
+ export const PDF_SHEET_ROWS = 2;
30
+ export const PDF_PAGES_PER_SHEET = PDF_SHEET_COLUMNS * PDF_SHEET_ROWS;
31
+ /** Width of one page on a sheet; a sheet is two of these side by side. */
32
+ export const PDF_PAGE_WIDTH_PX = 784;
33
+ /** A PDF this large is refused before pdf.js opens it. */
34
+ export const PDF_MAX_BYTES = 40 * 1024 * 1024;
35
+ /**
36
+ * Below this many non-space characters per page, on average, the text layer
37
+ * is treated as absent (a slide deck's page numbers, a scanner's stamp) and
38
+ * the pages are looked at instead.
39
+ */
40
+ export const PDF_MIN_TEXT_CHARS_PER_PAGE = 60;
41
+ const SHEET_JPEG_QUALITY = 0.82;
42
+ export function isPdfFile(file) {
43
+ return file.type === "application/pdf" || file.name.toLowerCase().endsWith(".pdf");
44
+ }
45
+ /** True when the extracted text is the document, not a few stray labels. */
46
+ export function isTextRich(pageTexts) {
47
+ if (pageTexts.length === 0) {
48
+ return false;
49
+ }
50
+ const chars = pageTexts.reduce((n, t) => n + t.replace(/\s+/g, "").length, 0);
51
+ return chars / pageTexts.length >= PDF_MIN_TEXT_CHARS_PER_PAGE;
52
+ }
53
+ /** The pages as one text, each under a marker the Cat can cite. */
54
+ export function joinPageTexts(pageTexts) {
55
+ return pageTexts
56
+ .map((t, i) => `--- page ${i + 1} ---\n${t.trim()}`)
57
+ .join("\n\n")
58
+ .trim();
59
+ }
60
+ /**
61
+ * Which pages go on which sheet, for at most `maxSheets` sheets. Pages past
62
+ * what fits are left out, and the caller says so.
63
+ */
64
+ export function planSheets(pageCount, maxSheets) {
65
+ const shown = Math.min(pageCount, Math.max(0, maxSheets) * PDF_PAGES_PER_SHEET);
66
+ const sheets = [];
67
+ for (let first = 1; first <= shown; first += PDF_PAGES_PER_SHEET) {
68
+ const pages = [];
69
+ for (let p = first; p < first + PDF_PAGES_PER_SHEET && p <= shown; p++) {
70
+ pages.push(p);
71
+ }
72
+ sheets.push(pages);
73
+ }
74
+ return sheets;
75
+ }
76
+ /** "deck.pdf · pages 5–8", or "· page 9" for a sheet of one. */
77
+ export function sheetName(fileName, firstPage, lastPage) {
78
+ return firstPage === lastPage
79
+ ? `${fileName} · page ${firstPage}`
80
+ : `${fileName} · pages ${firstPage}–${lastPage}`;
81
+ }
82
+ let pdfjsPromise = null;
83
+ /**
84
+ * Where pdf.js's own runtime files live — the worker, the image decoders
85
+ * (JPEG 2000, JBIG2), CMaps, standard fonts, ICC profiles. They are fetched by
86
+ * URL at runtime, which a bundler cannot follow, so an app copies them into its
87
+ * static folder with `chatkit-copy-pdf-assets public/pdfjs` and passes
88
+ * `assetsUrl: "/pdfjs/"`. Served from the app's own origin, they also pass a
89
+ * strict Content-Security-Policy. Missing decoders are why a page whose
90
+ * pictures are JPEG 2000 — macOS's "reduce file size" export, the deck this
91
+ * was written for — draws blank.
92
+ */
93
+ export const PDF_ASSET_DIRS = ["wasm", "cmaps", "standard_fonts", "iccs"];
94
+ export const PDF_WORKER_FILE = "pdf.worker.min.mjs";
95
+ function withSlash(url) {
96
+ return url.endsWith("/") ? url : `${url}/`;
97
+ }
98
+ /** pdf.js, loaded on the first PDF — never part of the chat's first paint. */
99
+ function loadPdfJs(assetsUrl) {
100
+ if (!pdfjsPromise) {
101
+ pdfjsPromise = import("pdfjs-dist/legacy/build/pdf.mjs").then((pdfjs) => {
102
+ pdfjs.GlobalWorkerOptions.workerSrc = `${withSlash(assetsUrl)}${PDF_WORKER_FILE}`;
103
+ return pdfjs;
104
+ });
105
+ // A failed load (offline, blocked chunk) must not poison the next try.
106
+ pdfjsPromise.catch(() => {
107
+ pdfjsPromise = null;
108
+ });
109
+ }
110
+ return pdfjsPromise;
111
+ }
112
+ async function pageTextsOf(doc) {
113
+ const texts = [];
114
+ for (let n = 1; n <= doc.numPages; n++) {
115
+ const page = await doc.getPage(n);
116
+ const content = await page.getTextContent();
117
+ let text = "";
118
+ for (const item of content.items) {
119
+ if ("str" in item) {
120
+ text += item.str + (item.hasEOL ? "\n" : " ");
121
+ }
122
+ }
123
+ texts.push(text.replace(/[ \t]+\n/g, "\n").replace(/[ \t]{2,}/g, " "));
124
+ page.cleanup();
125
+ }
126
+ return texts;
127
+ }
128
+ async function renderSheet(doc, pages) {
129
+ // Rendering needs a canvas; outside a browser the pages are simply not drawn.
130
+ if (typeof document === "undefined")
131
+ return null;
132
+ const rendered = [];
133
+ for (const n of pages) {
134
+ const page = await doc.getPage(n);
135
+ const base = page.getViewport({ scale: 1 });
136
+ const viewport = page.getViewport({ scale: PDF_PAGE_WIDTH_PX / base.width });
137
+ const canvas = document.createElement("canvas");
138
+ canvas.width = Math.ceil(viewport.width);
139
+ canvas.height = Math.ceil(viewport.height);
140
+ const ctx = canvas.getContext("2d");
141
+ if (!ctx) {
142
+ return null;
143
+ }
144
+ // White, not transparent: a JPEG turns transparency black.
145
+ ctx.fillStyle = "#fff";
146
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
147
+ await page.render({ canvas, canvasContext: ctx, viewport }).promise;
148
+ rendered.push(canvas);
149
+ page.cleanup();
150
+ }
151
+ if (rendered.length === 0) {
152
+ return null;
153
+ }
154
+ const cols = Math.min(PDF_SHEET_COLUMNS, rendered.length);
155
+ const rows = Math.ceil(rendered.length / PDF_SHEET_COLUMNS);
156
+ const cellW = PDF_PAGE_WIDTH_PX;
157
+ const cellH = Math.max(...rendered.map((c) => c.height));
158
+ const gap = 8;
159
+ const sheet = document.createElement("canvas");
160
+ sheet.width = cols * cellW + (cols - 1) * gap;
161
+ sheet.height = rows * cellH + (rows - 1) * gap;
162
+ const ctx = sheet.getContext("2d");
163
+ if (!ctx) {
164
+ return null;
165
+ }
166
+ // The gaps read as page edges; grey so a white page still has one.
167
+ ctx.fillStyle = "#9ca3af";
168
+ ctx.fillRect(0, 0, sheet.width, sheet.height);
169
+ rendered.forEach((c, i) => {
170
+ const x = (i % PDF_SHEET_COLUMNS) * (cellW + gap);
171
+ const y = Math.floor(i / PDF_SHEET_COLUMNS) * (cellH + gap);
172
+ ctx.drawImage(c, x, y);
173
+ });
174
+ return sheet.toDataURL("image/jpeg", SHEET_JPEG_QUALITY);
175
+ }
176
+ /**
177
+ * Read a picked PDF. Throws on a file pdf.js cannot open (encrypted, corrupt)
178
+ * — the app words that for the person. `PDF_MAX_BYTES` is checked by the app
179
+ * before calling, so it can say so in its own words.
180
+ */
181
+ export async function readPdf(file, opts) {
182
+ const pdfjs = await loadPdfJs(opts.assetsUrl);
183
+ const data = file instanceof Uint8Array ? file : new Uint8Array(await file.arrayBuffer());
184
+ const base = withSlash(opts.assetsUrl);
185
+ const task = pdfjs.getDocument({
186
+ data,
187
+ wasmUrl: `${base}wasm/`,
188
+ cMapUrl: `${base}cmaps/`,
189
+ standardFontDataUrl: `${base}standard_fonts/`,
190
+ iccUrl: `${base}iccs/`,
191
+ });
192
+ const doc = await task.promise;
193
+ try {
194
+ const pageTexts = await pageTextsOf(doc);
195
+ if (isTextRich(pageTexts)) {
196
+ return { kind: "text", text: joinPageTexts(pageTexts), pageCount: doc.numPages };
197
+ }
198
+ const sheets = [];
199
+ for (const pages of planSheets(doc.numPages, opts.maxSheets)) {
200
+ const dataUrl = await renderSheet(doc, pages);
201
+ if (dataUrl) {
202
+ sheets.push({ firstPage: pages[0], lastPage: pages[pages.length - 1], dataUrl });
203
+ }
204
+ }
205
+ const pagesShown = sheets.length > 0 ? sheets[sheets.length - 1].lastPage : 0;
206
+ return { kind: "pages", sheets, pageCount: doc.numPages, pagesShown };
207
+ }
208
+ finally {
209
+ void task.destroy();
210
+ }
211
+ }
212
+ //# sourceMappingURL=pdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf.js","sourceRoot":"","sources":["../src/pdf.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,6 @@
12
12
  */
13
13
  export { AttachMenu, DEFAULT_ATTACH_MENU_LABELS } from "./AttachMenu.js";
14
14
  export type { AttachExtra, AttachMenuLabels } from "./AttachMenu.js";
15
- export { ATTACH_SOURCE_INPUT, acceptFamilies, isImageMime, isTextFile, type AcceptFamily, type AttachSource, } from "../attachments.js";
15
+ export { ATTACH_SOURCE_INPUT, FILES_ACCEPT, acceptFamilies, isImageMime, isTextFile, type AcceptFamily, type AttachSource, } from "../attachments.js";
16
16
  export { shrinkImage } from "../image.js";
17
17
  //# sourceMappingURL=attach.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../../src/react/attach.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AACzE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,WAAW,EACX,UAAU,EACV,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../../src/react/attach.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AACzE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,UAAU,EACV,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -11,6 +11,6 @@
11
11
  * `chatkit-check-file-inputs`.
12
12
  */
13
13
  export { AttachMenu, DEFAULT_ATTACH_MENU_LABELS } from "./AttachMenu.js";
14
- export { ATTACH_SOURCE_INPUT, acceptFamilies, isImageMime, isTextFile, } from "../attachments.js";
14
+ export { ATTACH_SOURCE_INPUT, FILES_ACCEPT, acceptFamilies, isImageMime, isTextFile, } from "../attachments.js";
15
15
  export { shrinkImage } from "../image.js";
16
16
  //# sourceMappingURL=attach.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"attach.js","sourceRoot":"","sources":["../../src/react/attach.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAEzE,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,WAAW,EACX,UAAU,GAGX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"attach.js","sourceRoot":"","sources":["../../src/react/attach.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAEzE,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,UAAU,GAGX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitbaum/chatkit",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "description": "One chat for every product: a composer with a microphone that always works, a thread that does not fight the reader, messages with copy and retry, and LLM markdown — the fleet's chat standard as a package.",
5
5
  "license": "MIT",
6
6
  "author": "Cato",
@@ -20,7 +20,8 @@
20
20
  "speech-to-text",
21
21
  "markdown",
22
22
  "react",
23
- "ai"
23
+ "ai",
24
+ "pdf"
24
25
  ],
25
26
  "type": "module",
26
27
  "sideEffects": [
@@ -40,7 +41,8 @@
40
41
  "styles.css"
41
42
  ],
42
43
  "bin": {
43
- "chatkit-check-file-inputs": "./bin/chatkit-check-file-inputs.mjs"
44
+ "chatkit-check-file-inputs": "./bin/chatkit-check-file-inputs.mjs",
45
+ "chatkit-copy-pdf-assets": "./bin/chatkit-copy-pdf-assets.mjs"
44
46
  },
45
47
  "exports": {
46
48
  ".": {
@@ -59,6 +61,10 @@
59
61
  "types": "./dist/react/attach.d.ts",
60
62
  "default": "./dist/react/attach.js"
61
63
  },
64
+ "./pdf": {
65
+ "types": "./dist/pdf.d.ts",
66
+ "default": "./dist/pdf.js"
67
+ },
62
68
  "./styles.css": "./styles.css"
63
69
  },
64
70
  "scripts": {
@@ -71,7 +77,7 @@
71
77
  "format": "prettier --write .",
72
78
  "format:check": "prettier --check .",
73
79
  "demo": "esbuild demo/demo.tsx --bundle --jsx=automatic --format=iife --define:process.env.NODE_ENV='\"production\"' --outfile=demo/dist/demo.js --log-level=warning",
74
- "test:browser": "pnpm run demo && node test/browser.mjs && node test/browser-scroll.mjs && node test/browser-app.mjs"
80
+ "test:browser": "pnpm run demo && node test/browser.mjs && node test/browser-scroll.mjs && node test/browser-app.mjs && node test/browser-pdf.mjs"
75
81
  },
76
82
  "devDependencies": {
77
83
  "@bitbaum/design-tokens": "^1.2.0",
@@ -91,5 +97,8 @@
91
97
  },
92
98
  "peerDependencies": {
93
99
  "react": ">=18"
100
+ },
101
+ "dependencies": {
102
+ "pdfjs-dist": "^6.4.299"
94
103
  }
95
104
  }