@bitbaum/chatkit 0.8.2 → 0.10.0

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.
Files changed (51) hide show
  1. package/README.md +55 -2
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/markdown.d.ts +39 -5
  7. package/dist/markdown.d.ts.map +1 -1
  8. package/dist/markdown.js +108 -11
  9. package/dist/markdown.js.map +1 -1
  10. package/dist/react/AnswerActions.d.ts +39 -0
  11. package/dist/react/AnswerActions.d.ts.map +1 -0
  12. package/dist/react/AnswerActions.js +49 -0
  13. package/dist/react/AnswerActions.js.map +1 -0
  14. package/dist/react/AttachMenu.d.ts +11 -2
  15. package/dist/react/AttachMenu.d.ts.map +1 -1
  16. package/dist/react/AttachMenu.js +21 -24
  17. package/dist/react/AttachMenu.js.map +1 -1
  18. package/dist/react/Markdown.d.ts +53 -4
  19. package/dist/react/Markdown.d.ts.map +1 -1
  20. package/dist/react/Markdown.js +92 -28
  21. package/dist/react/Markdown.js.map +1 -1
  22. package/dist/react/Sheet.d.ts +20 -0
  23. package/dist/react/Sheet.d.ts.map +1 -0
  24. package/dist/react/Sheet.js +37 -0
  25. package/dist/react/Sheet.js.map +1 -0
  26. package/dist/react/Thread.d.ts +19 -2
  27. package/dist/react/Thread.d.ts.map +1 -1
  28. package/dist/react/Thread.js +10 -8
  29. package/dist/react/Thread.js.map +1 -1
  30. package/dist/react/icons.d.ts +3 -0
  31. package/dist/react/icons.d.ts.map +1 -1
  32. package/dist/react/icons.js +3 -0
  33. package/dist/react/icons.js.map +1 -1
  34. package/dist/react/index.d.ts +7 -2
  35. package/dist/react/index.d.ts.map +1 -1
  36. package/dist/react/index.js +4 -1
  37. package/dist/react/index.js.map +1 -1
  38. package/dist/react/use-speech.d.ts +25 -0
  39. package/dist/react/use-speech.d.ts.map +1 -0
  40. package/dist/react/use-speech.js +66 -0
  41. package/dist/react/use-speech.js.map +1 -0
  42. package/dist/replies.d.ts +6 -3
  43. package/dist/replies.d.ts.map +1 -1
  44. package/dist/replies.js +8 -5
  45. package/dist/replies.js.map +1 -1
  46. package/dist/speech.d.ts +17 -0
  47. package/dist/speech.d.ts.map +1 -0
  48. package/dist/speech.js +81 -0
  49. package/dist/speech.js.map +1 -0
  50. package/package.json +1 -1
  51. package/styles.css +288 -0
package/README.md CHANGED
@@ -15,7 +15,7 @@ interface that calls them.)
15
15
  ## Install
16
16
 
17
17
  ```sh
18
- pnpm add @bitbaum/chatkit@^0.8.0
18
+ pnpm add @bitbaum/chatkit@^0.9.0
19
19
  ```
20
20
 
21
21
  **Install releases from npm, never a commit or a branch.** A semver range is
@@ -121,6 +121,31 @@ while a turn runs. Pass `replies` on a message to supply your own instead.
121
121
  An app with its own thread uses `extractReplies(text)` and `<ChatReplies>`, so
122
122
  the buttons look and behave the same everywhere.
123
123
 
124
+ The model leads. The instruction asks for replies on EVERY answer, the
125
+ recommended next step first, and the first button is drawn as the lead
126
+ (`ck-reply-lead`): on a phone the answer is read and the next move is one
127
+ tap, the way Grok does it. Only an answer that ends the conversation has no
128
+ buttons.
129
+
130
+ ### Under every answer — Copy, Listen, Share
131
+
132
+ `AnswerActions` is the row under an answer, the same in every product:
133
+
134
+ ```tsx
135
+ <AnswerActions text={answer} onRetry={retry} speaker={studioVoice} share={{ url }}>
136
+ <YourOwnButton />
137
+ </AnswerActions>
138
+ ```
139
+
140
+ Copy copies the markdown. Listen reads the answer aloud as prose (headings,
141
+ emphasis, citations and code fences stripped — `plainTextForSpeech`) with the
142
+ browser's own voice, or with the `speaker` you pass (`{ speak(text), cancel() }`
143
+ — loki hands in its studio voice); while it reads, the row becomes a small
144
+ player: Stop and the time so far. Share opens the system share sheet with the
145
+ answer's prose (or what you pass), only where one exists. A button that does
146
+ nothing is never shown. `ChatThread` renders it under every answer; pass
147
+ `speaker` and `share` through.
148
+
124
149
  ### The next message while a turn runs
125
150
 
126
151
  `queue` keeps Send live while `sending`, beside Stop, and the placeholder
@@ -154,7 +179,11 @@ Because Files takes anything, `isTextFile` decides afterwards what can be read
154
179
  inline; a PDF gets a sentence, not a prompt full of bytes. With a mouse the
155
180
  paperclip opens the file dialog directly — on a desktop all three are the
156
181
  same dialog. A box that is not chatkit's composer renders `<AttachMenu
157
- onFiles={…} />` rather than a bare `<input type="file">`. Labels:
182
+ onFiles={…} />` rather than a bare `<input type="file">`. An app with more to
183
+ bring into a message than files — one of the person's own things, a note the
184
+ model reads every time — passes `extra={(close) => <YourRows />}`: the rows
185
+ sit under the three sources in the same sheet, and the sheet then opens on a
186
+ mouse too, since a file dialog cannot hold them. Labels:
158
187
  `labels={{ attachMenu: { title, close, sources: { camera, photos, files } } }}`.
159
188
 
160
189
  ### Picking files anywhere — `@bitbaum/chatkit/attach`
@@ -200,6 +229,30 @@ Every reason a file did not attach is a label — `labels={{ attachNotes: {
200
229
  imageTooLarge: (name, mb) => … } }}` — so it reaches the reader in their
201
230
  language.
202
231
 
232
+ ### The look of an answer
233
+
234
+ `Markdown` renders what a model writes, never as HTML: paragraphs, headings
235
+ with their level, bullet, numbered, nested and task lists, tables (they scroll
236
+ sideways on a phone instead of breaking the page), rules, bold, italic, code,
237
+ links, web images and `[F1]` citation handles. Three things make an answer
238
+ read like the ChatGPT app's rather than a wall of text, and a model does all
239
+ three unprompted:
240
+
241
+ - **A list of named things becomes cards.** `1. **Option.** why` — every item
242
+ opening with a bold lead — renders as a badge (the number, or the emoji the
243
+ lead opens with: `**🤖 Robot.** …`), the name, and the body under it.
244
+ - **A table is a table**, with the header row kept.
245
+ - **Sources are listed once**, under the answer, from the handles the text
246
+ cites and the `citations` map you pass (`{ F1: { label, detail, href? } }`).
247
+ On a mouse they are a row of chips; on a touch screen ONE "Sources · n"
248
+ button opens a sheet with every record — label, detail, link — because a
249
+ row of chips on a phone was a row of cut-off words. `renderCitation`
250
+ replaces the in-text superscript with your own; `renderSources` replaces
251
+ the row with your own; `sources={false}` drops it. `SourcesRow` and `Sheet`
252
+ are exported for an app that lists sources from elsewhere.
253
+
254
+ `demo/screen.html` shows all of it.
255
+
203
256
  ### Look
204
257
 
205
258
  Every colour, radius and face is a `--ck-*` variable. They default to
package/dist/index.d.ts CHANGED
@@ -10,4 +10,5 @@ export * from "./scroll.js";
10
10
  export * from "./attachments.js";
11
11
  export * from "./image.js";
12
12
  export * from "./replies.js";
13
+ export * from "./speech.js";
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -10,4 +10,5 @@ export * from "./scroll.js";
10
10
  export * from "./attachments.js";
11
11
  export * from "./image.js";
12
12
  export * from "./replies.js";
13
+ export * from "./speech.js";
13
14
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
@@ -5,22 +5,32 @@
5
5
  *
6
6
  * From loki's `MarkdownText`, the one renderer in the fleet that four repos
7
7
  * did NOT have: those rendered model output with the literal `**` still in it.
8
- * Supports what answers use — paragraphs, bullet and numbered lists, headings,
9
- * **bold**, *italic*, `code`, fenced code, [links](url) and `[F1]` citation
10
- * handles — and deliberately nothing else.
8
+ * Supports what answers use — paragraphs, bullet, numbered, nested and task
9
+ * lists, headings with their level, tables, rules, **bold**, *italic*, `code`,
10
+ * fenced code, [links](url), ![images](url) and `[F1]` citation handles — and
11
+ * deliberately nothing else.
11
12
  */
13
+ export type ListItem = {
14
+ text: string;
15
+ /** `- [ ]` / `- [x]`: a task, and whether it is done. */
16
+ task?: boolean;
17
+ /** One level of nesting: the items indented under this one. */
18
+ children?: ListItem[];
19
+ };
12
20
  export type MarkdownBlock = {
13
21
  kind: "p";
14
22
  text: string;
15
23
  } | {
16
24
  kind: "h";
17
25
  text: string;
26
+ level: 1 | 2 | 3;
18
27
  } | {
19
28
  kind: "ul";
20
- items: string[];
29
+ items: ListItem[];
21
30
  } | {
22
31
  kind: "ol";
23
- items: string[];
32
+ items: ListItem[];
33
+ start: number;
24
34
  } | {
25
35
  kind: "code";
26
36
  lang: string;
@@ -28,8 +38,26 @@ export type MarkdownBlock = {
28
38
  } | {
29
39
  kind: "quote";
30
40
  text: string;
41
+ } | {
42
+ kind: "table";
43
+ head: string[];
44
+ rows: string[][];
45
+ } | {
46
+ kind: "hr";
31
47
  };
32
48
  export declare function parseBlocks(text: string): MarkdownBlock[];
49
+ /**
50
+ * A list where every item opens with a bold lead — `**Title.** body` — is a
51
+ * list of named things, and reads as cards: a numbered or bulleted badge, the
52
+ * name, the body under it. That is how the ChatGPT app lays out "three
53
+ * options" or "two levels"; a model writes it that way unprompted.
54
+ */
55
+ export declare function listLead(text: string): {
56
+ lead: string;
57
+ rest: string;
58
+ icon?: string;
59
+ } | null;
60
+ export declare function isCardList(items: ListItem[]): boolean;
33
61
  export type InlineSpan = {
34
62
  kind: "text";
35
63
  text: string;
@@ -48,11 +76,17 @@ export type InlineSpan = {
48
76
  kind: "link";
49
77
  text: string;
50
78
  href: string;
79
+ } | {
80
+ kind: "image";
81
+ alt: string;
82
+ src: string;
51
83
  } | {
52
84
  kind: "cite";
53
85
  ids: string[];
54
86
  };
55
87
  export declare function parseInline(text: string): InlineSpan[];
88
+ /** Every citation handle an answer uses, in order of first use. */
89
+ export declare function citedIds(text: string): string[];
56
90
  /**
57
91
  * Only http(s), mailto and same-site paths become links. A `javascript:` URL in
58
92
  * model output is text, never a link — the answer is written by a model that
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAkDzD;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAI9B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAKpC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CA2BtD;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYpD;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,QAAQ,EAAE,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC;AAmBnB,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CA2FzD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAS3F;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAErD;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAI9B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAKpC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAmCtD;AAED,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAM/C;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYpD;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD"}
package/dist/markdown.js CHANGED
@@ -5,10 +5,26 @@
5
5
  *
6
6
  * From loki's `MarkdownText`, the one renderer in the fleet that four repos
7
7
  * did NOT have: those rendered model output with the literal `**` still in it.
8
- * Supports what answers use — paragraphs, bullet and numbered lists, headings,
9
- * **bold**, *italic*, `code`, fenced code, [links](url) and `[F1]` citation
10
- * handles — and deliberately nothing else.
8
+ * Supports what answers use — paragraphs, bullet, numbered, nested and task
9
+ * lists, headings with their level, tables, rules, **bold**, *italic*, `code`,
10
+ * fenced code, [links](url), ![images](url) and `[F1]` citation handles — and
11
+ * deliberately nothing else.
11
12
  */
13
+ const BULLET_RE = /^([-*•])\s+(.*)$/;
14
+ const NUMBERED_RE = /^(\d+)[.)]\s+(.*)$/;
15
+ const TASK_RE = /^\[([ xX])\]\s+(.*)$/;
16
+ function listItem(text) {
17
+ const task = TASK_RE.exec(text);
18
+ if (task)
19
+ return { text: task[2] ?? "", task: task[1] !== " " };
20
+ return { text };
21
+ }
22
+ /** `| a | b |` → ["a", "b"]; a row may omit its outer pipes. */
23
+ function tableCells(line) {
24
+ const inner = line.replace(/^\|/, "").replace(/\|$/, "");
25
+ return inner.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, "|").trim());
26
+ }
27
+ const TABLE_RULE_RE = /^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?$/;
12
28
  export function parseBlocks(text) {
13
29
  const blocks = [];
14
30
  let list = null;
@@ -34,24 +50,64 @@ export function parseBlocks(text) {
34
50
  list = null;
35
51
  continue;
36
52
  }
37
- const bullet = /^[-*•]\s+(.*)$/.exec(line);
38
- const numbered = /^\d+[.)]\s+(.*)$/.exec(line);
53
+ // A table: a header row, a rule of dashes, then rows. The rule is what
54
+ // tells a table from a sentence with a pipe in it.
55
+ if (line.includes("|") && TABLE_RULE_RE.test((lines[i + 1] ?? "").trim())) {
56
+ list = null;
57
+ const head = tableCells(line);
58
+ const rows = [];
59
+ i += 2;
60
+ while (i < lines.length && (lines[i] ?? "").trim().includes("|")) {
61
+ rows.push(tableCells((lines[i] ?? "").trim()));
62
+ i++;
63
+ }
64
+ i--;
65
+ blocks.push({ kind: "table", head, rows });
66
+ continue;
67
+ }
68
+ if (/^(-{3,}|\*{3,}|_{3,})$/.test(line)) {
69
+ list = null;
70
+ blocks.push({ kind: "hr" });
71
+ continue;
72
+ }
73
+ const bullet = BULLET_RE.exec(line);
74
+ const numbered = NUMBERED_RE.exec(line);
39
75
  if (bullet || numbered) {
40
76
  const kind = bullet ? "ul" : "ol";
77
+ const indent = raw.length - raw.trimStart().length;
78
+ const item = listItem((bullet ? bullet[2] : numbered[2]) ?? "");
79
+ // Indented under the previous item: one level of nesting, whichever
80
+ // marker it uses.
81
+ if (list && indent >= 2 && list.items.length > 0) {
82
+ const parent = list.items[list.items.length - 1];
83
+ (parent.children ??= []).push(item);
84
+ continue;
85
+ }
41
86
  if (!list || list.kind !== kind) {
42
- list = { kind, items: [] };
87
+ list =
88
+ kind === "ol"
89
+ ? { kind: "ol", items: [], start: Number(numbered[1]) || 1 }
90
+ : { kind: "ul", items: [] };
43
91
  blocks.push(list);
44
92
  }
45
- list.items.push((bullet ?? numbered)[1] ?? "");
93
+ list.items.push(item);
46
94
  continue;
47
95
  }
48
96
  list = null;
49
- if (/^#{1,6}\s/.test(line)) {
50
- blocks.push({ kind: "h", text: line.replace(/^#+\s+/, "") });
97
+ const heading = /^(#{1,6})\s+(.*)$/.exec(line);
98
+ if (heading) {
99
+ const level = Math.min(heading[1].length, 3);
100
+ blocks.push({ kind: "h", text: heading[2] ?? "", level });
51
101
  }
52
102
  else if (line.startsWith(">")) {
53
103
  // A model's `>` leaked into prose was one of orangecat's fixed bugs.
54
- blocks.push({ kind: "quote", text: line.replace(/^>\s?/, "") });
104
+ // Consecutive quoted lines are one quote.
105
+ const prev = blocks[blocks.length - 1];
106
+ const quoted = line.replace(/^>\s?/, "");
107
+ if (prev && prev.kind === "quote" && (lines[i - 1] ?? "").trim().startsWith(">"))
108
+ prev.text += `\n${quoted}`;
109
+ else
110
+ blocks.push({ kind: "quote", text: quoted });
55
111
  }
56
112
  else {
57
113
  const prev = blocks[blocks.length - 1];
@@ -64,7 +120,28 @@ export function parseBlocks(text) {
64
120
  }
65
121
  return blocks;
66
122
  }
67
- const INLINE_RE = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*\s][^*]*\*|\[[^\]]+\]\([^)\s]+\)|\[[FD]\d+(?:,\s*[FD]\d+)*\]|https?:\/\/[^\s<>()]+[^\s<>().,;:!?'"])/g;
123
+ /**
124
+ * A list where every item opens with a bold lead — `**Title.** body` — is a
125
+ * list of named things, and reads as cards: a numbered or bulleted badge, the
126
+ * name, the body under it. That is how the ChatGPT app lays out "three
127
+ * options" or "two levels"; a model writes it that way unprompted.
128
+ */
129
+ export function listLead(text) {
130
+ const m = /^\*\*([^*]+?)\*\*\s*(?:[—–:-]\s*)?([\s\S]*)$/.exec(text);
131
+ if (!m)
132
+ return null;
133
+ let lead = (m[1] ?? "").trim().replace(/[.:]$/, "");
134
+ // A lead that opens with an emoji — `**🤖 Robot.** …` — hands it to the
135
+ // badge: the icon tile the ChatGPT app draws, from the model's own choice.
136
+ const icon = /^(\p{Extended_Pictographic}\uFE0F?)\s+(.+)$/u.exec(lead);
137
+ if (icon)
138
+ lead = icon[2] ?? lead;
139
+ return { lead, rest: (m[2] ?? "").trim(), ...(icon ? { icon: icon[1] } : {}) };
140
+ }
141
+ export function isCardList(items) {
142
+ return items.length >= 2 && items.every((it) => listLead(it.text) !== null);
143
+ }
144
+ const INLINE_RE = /(`[^`]+`|!\[[^\]]*\]\([^)\s]+\)|\*\*[^*]+\*\*|\*[^*\s][^*]*\*|\[[^\]]+\]\([^)\s]+\)|\[[FD]\d+(?:,\s*[FD]\d+)*\]|https?:\/\/[^\s<>()]+[^\s<>().,;:!?'"])/g;
68
145
  export function parseInline(text) {
69
146
  const out = [];
70
147
  for (const part of text.split(INLINE_RE)) {
@@ -85,6 +162,16 @@ export function parseInline(text) {
85
162
  out.push({ kind: "em", text: inner, children: parseInline(inner) });
86
163
  }
87
164
  else {
165
+ const image = /^!\[([^\]]*)\]\(([^)\s]+)\)$/.exec(part);
166
+ if (image) {
167
+ // Only a web image: a data: or javascript: "image" from a model is text.
168
+ const src = safeHref(image[2] ?? "");
169
+ if (src && /^https?:/.test(src))
170
+ out.push({ kind: "image", alt: image[1] ?? "", src });
171
+ else
172
+ out.push({ kind: "text", text: image[1] ?? part });
173
+ continue;
174
+ }
88
175
  const link = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(part);
89
176
  const href = link
90
177
  ? safeHref(link[2] ?? "")
@@ -101,6 +188,16 @@ export function parseInline(text) {
101
188
  }
102
189
  return out;
103
190
  }
191
+ /** Every citation handle an answer uses, in order of first use. */
192
+ export function citedIds(text) {
193
+ const ids = [];
194
+ for (const m of text.matchAll(/\[([FD]\d+(?:,\s*[FD]\d+)*)\]/g)) {
195
+ for (const id of (m[1] ?? "").split(/,\s*/))
196
+ if (!ids.includes(id))
197
+ ids.push(id);
198
+ }
199
+ return ids;
200
+ }
104
201
  /**
105
202
  * Only http(s), mailto and same-site paths become links. A `javascript:` URL in
106
203
  * model output is text, never a link — the answer is written by a model that
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAoBH,MAAM,SAAS,GAAG,kBAAkB,CAAC;AACrC,MAAM,WAAW,GAAG,oBAAoB,CAAC;AACzC,MAAM,OAAO,GAAG,sBAAsB,CAAC;AAEvC,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;IAChE,OAAO,EAAE,IAAI,EAAE,CAAC;AAClB,CAAC;AAED,gEAAgE;AAChE,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzD,OAAO,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAC7E,CAAC;AACD,MAAM,aAAa,GAAG,6CAA6C,CAAC;AAEpE,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,IAAI,IAAI,GAEN,IAAI,CAAC;IACP,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,wEAAwE;QACxE,6BAA6B;QAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,IAAI,GAAa,EAAE,CAAC;YAC1B,CAAC,EAAE,CAAC;YACJ,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;gBACtE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC1B,CAAC,EAAE,CAAC;YACN,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3D,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,GAAG,IAAI,CAAC;YACZ,SAAS;QACX,CAAC;QACD,uEAAuE;QACvE,mDAAmD;QACnD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC1E,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAC9B,MAAM,IAAI,GAAe,EAAE,CAAC;YAC5B,CAAC,IAAI,CAAC,CAAC;YACP,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAC/C,CAAC,EAAE,CAAC;YACN,CAAC;YACD,CAAC,EAAE,CAAC;YACJ,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,SAAS;QACX,CAAC;QACD,IAAI,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5B,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,MAAM,IAAI,QAAQ,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YAClC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC;YACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YACjE,oEAAoE;YACpE,kBAAkB;YAClB,IAAI,IAAI,IAAI,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;gBAClD,CAAC,MAAM,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpC,SAAS;YACX,CAAC;YACD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBAChC,IAAI;oBACF,IAAI,KAAK,IAAI;wBACX,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,QAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE;wBAC7D,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;gBAChC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtB,SAAS;QACX,CAAC;QACD,IAAI,GAAG,IAAI,CAAC;QACZ,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,MAAM,EAAE,CAAC,CAAc,CAAC;YAC3D,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5D,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAChC,qEAAqE;YACrE,0CAA0C;YAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACzC,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;gBAC9E,IAAI,CAAC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;;gBACxB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACpD,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACvC,qDAAqD;YACrD,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;gBAAE,IAAI,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;;gBAClF,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,CAAC,GAAG,8CAA8C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpE,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACpD,wEAAwE;IACxE,2EAA2E;IAC3E,MAAM,IAAI,GAAG,8CAA8C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvE,IAAI,IAAI;QAAE,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACjC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAiB;IAC1C,OAAO,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;AAC9E,CAAC;AAcD,MAAM,SAAS,GACb,0JAA0J,CAAC;AAE7J,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,IAAI,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtD,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAChC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC;aAAM,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAChC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxD,IAAI,KAAK,EAAE,CAAC;gBACV,yEAAyE;gBACzE,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;gBACrC,IAAI,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;;oBAClF,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBACxD,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,MAAM,IAAI,GAAG,IAAI;gBACf,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBACzB,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;oBACzB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAChB,CAAC,CAAC,IAAI,CAAC;YACX,IAAI,IAAI,IAAI,IAAI;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;iBACrE,IAAI,CAAC,IAAI,IAAI,IAAI;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;;gBAChE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,gCAAgC,CAAC,EAAE,CAAC;QAChE,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACtB,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACvD,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QACrB,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS;YAClF,CAAC,CAAC,CAAC,CAAC,IAAI;YACR,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { type ReactNode } from "react";
2
+ import { type Speaker } from "../speech.js";
3
+ export type AnswerActionsLabels = {
4
+ copy: string;
5
+ copied: string;
6
+ listen: string;
7
+ stop: string;
8
+ share: string;
9
+ retry: string;
10
+ };
11
+ export declare const DEFAULT_ANSWER_ACTIONS_LABELS: AnswerActionsLabels;
12
+ /**
13
+ * The row under an answer — Copy, Listen, Share, Try again — the same in every
14
+ * product, so an answer is handled the same way wherever it was written.
15
+ *
16
+ * Every button is one that works here: Listen only where something can
17
+ * speak, Share only where the system share sheet exists (phones), Try again
18
+ * only when the caller can re-ask. A button that does nothing is worse than
19
+ * a missing one. While an answer is being read, a small player takes the
20
+ * row's place: a stop and the time read so far.
21
+ *
22
+ * `children` are the app's own buttons after these (loki's thumbs-down).
23
+ */
24
+ export declare function AnswerActions({ text, onRetry, speaker, share, labels: overrides, children, }: {
25
+ /** The answer's markdown. Copied as written; read as prose. */
26
+ text: string;
27
+ onRetry?: () => void;
28
+ /** The app's voice. Without it, the browser's; without either, no Listen. */
29
+ speaker?: Speaker | null;
30
+ /** What the share sheet gets. Defaults to the answer's prose. `false` hides Share. */
31
+ share?: {
32
+ title?: string;
33
+ text?: string;
34
+ url?: string;
35
+ } | false;
36
+ labels?: Partial<AnswerActionsLabels>;
37
+ children?: ReactNode;
38
+ }): import("react").JSX.Element;
39
+ //# sourceMappingURL=AnswerActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnswerActions.d.ts","sourceRoot":"","sources":["../../src/react/AnswerActions.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7D,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAOhE,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,mBAO3C,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,OAAO,EACP,OAAO,EACP,KAAK,EACL,MAAM,EAAE,SAAS,EACjB,QAAQ,GACT,EAAE;IACD,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,sFAAsF;IACtF,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC;IAChE,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,+BAoFA"}
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useSyncExternalStore } from "react";
4
+ import { formatElapsed } from "../composer.js";
5
+ import { plainTextForSpeech } from "../speech.js";
6
+ import { useClipboard } from "./hooks.js";
7
+ import { useSpeech } from "./use-speech.js";
8
+ import { IconCheck, IconCopy, IconRetry, IconShare, IconSpeaker, IconStop } from "./icons.js";
9
+ const noSubscribe = () => () => { };
10
+ export const DEFAULT_ANSWER_ACTIONS_LABELS = {
11
+ copy: "Copy",
12
+ copied: "Copied",
13
+ listen: "Listen",
14
+ stop: "Stop",
15
+ share: "Share",
16
+ retry: "Try again",
17
+ };
18
+ /**
19
+ * The row under an answer — Copy, Listen, Share, Try again — the same in every
20
+ * product, so an answer is handled the same way wherever it was written.
21
+ *
22
+ * Every button is one that works here: Listen only where something can
23
+ * speak, Share only where the system share sheet exists (phones), Try again
24
+ * only when the caller can re-ask. A button that does nothing is worse than
25
+ * a missing one. While an answer is being read, a small player takes the
26
+ * row's place: a stop and the time read so far.
27
+ *
28
+ * `children` are the app's own buttons after these (loki's thumbs-down).
29
+ */
30
+ export function AnswerActions({ text, onRetry, speaker, share, labels: overrides, children, }) {
31
+ const labels = { ...DEFAULT_ANSWER_ACTIONS_LABELS, ...overrides };
32
+ const { copied, copy } = useClipboard();
33
+ const speech = useSpeech(speaker);
34
+ const canShare = useSyncExternalStore(noSubscribe, () => typeof navigator !== "undefined" && typeof navigator.share === "function", () => false);
35
+ if (speech.speaking) {
36
+ return (_jsxs("div", { className: "ck-turn-actions ck-player", role: "status", "aria-live": "polite", children: [_jsxs("button", { type: "button", className: "ck-action ck-action-stop", onClick: speech.stop, "aria-label": labels.stop, children: [_jsx(IconStop, {}), _jsx("span", { children: labels.stop })] }), _jsx("span", { className: "ck-player-wave", "aria-hidden": true, children: Array.from({ length: 7 }).map((_, i) => (_jsx("span", {}, i))) }), _jsx("span", { className: "ck-player-time", children: formatElapsed(speech.elapsed) })] }));
37
+ }
38
+ return (_jsxs("div", { className: "ck-turn-actions", children: [_jsxs("button", { type: "button", className: "ck-action", onClick: () => copy(text), "aria-label": copied ? labels.copied : labels.copy, children: [copied ? _jsx(IconCheck, {}) : _jsx(IconCopy, {}), _jsx("span", { children: copied ? labels.copied : labels.copy })] }), speech.supported && (_jsxs("button", { type: "button", className: "ck-action", onClick: () => speech.speak(text), "aria-label": labels.listen, children: [_jsx(IconSpeaker, {}), _jsx("span", { children: labels.listen })] })), canShare && share !== false && (_jsxs("button", { type: "button", className: "ck-action",
39
+ // A dismissed share sheet rejects; that is the person changing their
40
+ // mind, not an error to report.
41
+ onClick: () => void navigator
42
+ .share({
43
+ title: share?.title,
44
+ text: share?.text ?? plainTextForSpeech(text),
45
+ url: share?.url,
46
+ })
47
+ .catch(() => { }), "aria-label": labels.share, children: [_jsx(IconShare, {}), _jsx("span", { children: labels.share })] })), onRetry && (_jsxs("button", { type: "button", className: "ck-action", onClick: onRetry, children: [_jsx(IconRetry, {}), _jsx("span", { children: labels.retry })] })), children] }));
48
+ }
49
+ //# sourceMappingURL=AnswerActions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnswerActions.js","sourceRoot":"","sources":["../../src/react/AnswerActions.tsx"],"names":[],"mappings":"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"}
@@ -21,9 +21,13 @@ export declare const DEFAULT_ATTACH_MENU_LABELS: AttachMenuLabels;
21
21
  * browser supplies Escape, the focus trap and focus restore.
22
22
  *
23
23
  * Every app that attaches anything gets this from the composer. An app with a
24
- * custom box renders `<AttachMenu>` itself rather than a bare file input.
24
+ * custom box renders `<AttachMenu>` itself rather than a bare file input, and
25
+ * an app with more to bring into a message than files — one of the person's
26
+ * own things, a note the model reads every time — puts those rows in `extra`:
27
+ * they sit under Camera · Photos · Files in the same sheet, on every pointer,
28
+ * since a mouse cannot reach them through a file dialog.
25
29
  */
26
- export declare function AttachMenu({ onFiles, disabled, labels, sources, trigger, }: {
30
+ export declare function AttachMenu({ onFiles, disabled, labels, sources, trigger, extra, onOpenChange, }: {
27
31
  onFiles: (files: FileList | null) => void;
28
32
  disabled?: boolean;
29
33
  labels?: AttachMenuLabels;
@@ -33,5 +37,10 @@ export declare function AttachMenu({ onFiles, disabled, labels, sources, trigger
33
37
  /** The button's content. Defaults to a paperclip icon; an uploader passes
34
38
  * its own ("Add photo"). The button keeps chatkit's 44px target. */
35
39
  trigger?: ReactNode;
40
+ /** The app's own rows under the three sources (see above). With it the
41
+ * sheet opens on every pointer. A row that should close the sheet calls
42
+ * the `close` it is given. */
43
+ extra?: (close: () => void) => ReactNode;
44
+ onOpenChange?: (open: boolean) => void;
36
45
  }): import("react").JSX.Element;
37
46
  //# sourceMappingURL=AttachMenu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AttachMenu.d.ts","sourceRoot":"","sources":["../../src/react/AttachMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG3E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,gBAKxC,CAAC;AAeF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAgB,EAChB,MAAmC,EACnC,OAAiB,EACjB,OAAO,GACR,EAAE;IACD,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;8EAC0E;IAC1E,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC;yEACqE;IACrE,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB,+BA4FA"}
1
+ {"version":3,"file":"AttachMenu.d.ts","sourceRoot":"","sources":["../../src/react/AttachMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAI3E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,gBAKxC,CAAC;AASF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAgB,EAChB,MAAmC,EACnC,OAAiB,EACjB,OAAO,EACP,KAAK,EACL,YAAY,GACb,EAAE;IACD,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;8EAC0E;IAC1E,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC;yEACqE;IACrE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;mCAE+B;IAC/B,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;IACzC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC,+BAuEA"}
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useRef } from "react";
3
+ import { useRef, useState } from "react";
4
4
  import { ATTACH_SOURCE_INPUT } from "../attachments.js";
5
- import { IconCamera, IconFileUp, IconImage, IconPaperclip, IconX } from "./icons.js";
5
+ import { IconCamera, IconFileUp, IconImage, IconPaperclip } from "./icons.js";
6
+ import { Sheet, prefersSheet } from "./Sheet.js";
6
7
  export const DEFAULT_ATTACH_MENU_LABELS = {
7
8
  attach: "Attach a screenshot or file",
8
9
  title: "Add to message",
@@ -15,11 +16,6 @@ const ICONS = {
15
16
  photos: IconImage,
16
17
  files: IconFileUp,
17
18
  };
18
- /** A touch screen gets the sheet; a mouse gets the file dialog in one click,
19
- * because on a desktop Camera / Photos / Files are all the same dialog. */
20
- function prefersSheet() {
21
- return typeof window !== "undefined" && window.matchMedia?.("(pointer: coarse)").matches;
22
- }
23
19
  /**
24
20
  * The paperclip, and what it opens on a phone: a sheet with Camera, Photos and
25
21
  * Files — one input each. See `ATTACH_SOURCE_INPUT` for why three inputs and
@@ -32,20 +28,27 @@ function prefersSheet() {
32
28
  * browser supplies Escape, the focus trap and focus restore.
33
29
  *
34
30
  * Every app that attaches anything gets this from the composer. An app with a
35
- * custom box renders `<AttachMenu>` itself rather than a bare file input.
31
+ * custom box renders `<AttachMenu>` itself rather than a bare file input, and
32
+ * an app with more to bring into a message than files — one of the person's
33
+ * own things, a note the model reads every time — puts those rows in `extra`:
34
+ * they sit under Camera · Photos · Files in the same sheet, on every pointer,
35
+ * since a mouse cannot reach them through a file dialog.
36
36
  */
37
- export function AttachMenu({ onFiles, disabled = false, labels = DEFAULT_ATTACH_MENU_LABELS, sources = SOURCES, trigger, }) {
37
+ export function AttachMenu({ onFiles, disabled = false, labels = DEFAULT_ATTACH_MENU_LABELS, sources = SOURCES, trigger, extra, onOpenChange, }) {
38
38
  const inputs = useRef({});
39
- const dialog = useRef(null);
40
- const close = () => dialog.current?.close();
39
+ const [sheetOpen, setSheetOpen] = useState(false);
40
+ const setOpen = (next) => {
41
+ setSheetOpen(next);
42
+ onOpenChange?.(next);
43
+ };
44
+ const close = () => setOpen(false);
41
45
  const pick = (source) => {
42
46
  close();
43
47
  inputs.current[source]?.click();
44
48
  };
45
49
  const open = () => {
46
- const d = dialog.current;
47
- if (sources.length > 1 && prefersSheet() && d && typeof d.showModal === "function")
48
- d.showModal();
50
+ if (extra || (sources.length > 1 && prefersSheet()))
51
+ setOpen(true);
49
52
  // A mouse gets the broadest picker offered: Files when it is one of the
50
53
  // sources, else the last one (Photos, for an image uploader).
51
54
  else
@@ -56,15 +59,9 @@ export function AttachMenu({ onFiles, disabled = false, labels = DEFAULT_ATTACH_
56
59
  }, type: "file", hidden: true, multiple: source !== "camera", "data-ck-source": source, ...ATTACH_SOURCE_INPUT[source], onChange: (e) => {
57
60
  onFiles(e.target.files);
58
61
  e.target.value = "";
59
- } }, source))), _jsx("button", { type: "button", className: trigger ? "ck-attach-trigger" : "ck-icon-btn", onClick: open, disabled: disabled, "aria-label": labels.attach, "aria-haspopup": "dialog", title: labels.attach, children: trigger ?? _jsx(IconPaperclip, {}) }), _jsx("dialog", { ref: dialog, className: "ck-sheet", "aria-label": labels.title,
60
- // A tap on the backdrop lands on the <dialog> itself, never on the
61
- // panel inside it: that is the scrim.
62
- onClick: (e) => {
63
- if (e.target === e.currentTarget)
64
- close();
65
- }, children: _jsxs("div", { className: "ck-sheet-panel", children: [_jsxs("div", { className: "ck-sheet-head", children: [_jsx("span", { className: "ck-sheet-title", children: labels.title }), _jsx("button", { type: "button", className: "ck-icon-btn ck-sheet-close", onClick: close, "aria-label": labels.close, title: labels.close, children: _jsx(IconX, {}) })] }), _jsx("div", { className: "ck-attach-sources", children: sources.map((source) => {
66
- const Icon = ICONS[source];
67
- return (_jsxs("button", { type: "button", className: "ck-attach-source", "data-ck-source": source, onClick: () => pick(source), children: [_jsx(Icon, {}), _jsx("span", { children: labels.sources[source] })] }, source));
68
- }) })] }) })] }));
62
+ } }, source))), _jsx("button", { type: "button", className: trigger ? "ck-attach-trigger" : "ck-icon-btn", onClick: open, disabled: disabled, "aria-label": labels.attach, "aria-haspopup": "dialog", title: labels.attach, children: trigger ?? _jsx(IconPaperclip, {}) }), _jsxs(Sheet, { open: sheetOpen, onClose: close, title: labels.title, closeLabel: labels.close, children: [_jsx("div", { className: "ck-attach-sources", children: sources.map((source) => {
63
+ const Icon = ICONS[source];
64
+ return (_jsxs("button", { type: "button", className: "ck-attach-source", "data-ck-source": source, onClick: () => pick(source), children: [_jsx(Icon, {}), _jsx("span", { children: labels.sources[source] })] }, source));
65
+ }) }), extra && _jsx("div", { className: "ck-sheet-extra", children: extra(close) })] })] }));
69
66
  }
70
67
  //# sourceMappingURL=AttachMenu.js.map