@bitbaum/chatkit 0.18.1 → 0.19.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 (37) hide show
  1. package/README.md +161 -0
  2. package/dist/react/AttachMenu.d.ts +18 -1
  3. package/dist/react/AttachMenu.d.ts.map +1 -1
  4. package/dist/react/AttachMenu.js +8 -4
  5. package/dist/react/AttachMenu.js.map +1 -1
  6. package/dist/react/ChatApp.d.ts +79 -5
  7. package/dist/react/ChatApp.d.ts.map +1 -1
  8. package/dist/react/ChatApp.js +113 -7
  9. package/dist/react/ChatApp.js.map +1 -1
  10. package/dist/react/ChatHistory.d.ts +7 -1
  11. package/dist/react/ChatHistory.d.ts.map +1 -1
  12. package/dist/react/ChatHistory.js +2 -2
  13. package/dist/react/ChatHistory.js.map +1 -1
  14. package/dist/react/Composer.d.ts +20 -3
  15. package/dist/react/Composer.d.ts.map +1 -1
  16. package/dist/react/Composer.js +19 -4
  17. package/dist/react/Composer.js.map +1 -1
  18. package/dist/react/Thread.d.ts +48 -2
  19. package/dist/react/Thread.d.ts.map +1 -1
  20. package/dist/react/Thread.js +13 -6
  21. package/dist/react/Thread.js.map +1 -1
  22. package/dist/react/attach.d.ts +1 -1
  23. package/dist/react/attach.d.ts.map +1 -1
  24. package/dist/react/index.d.ts +10 -10
  25. package/dist/react/index.d.ts.map +1 -1
  26. package/dist/react/index.js +4 -4
  27. package/dist/react/index.js.map +1 -1
  28. package/dist/react/use-attachments.d.ts +17 -0
  29. package/dist/react/use-attachments.d.ts.map +1 -1
  30. package/dist/react/use-attachments.js +89 -72
  31. package/dist/react/use-attachments.js.map +1 -1
  32. package/dist/react/use-chat-app.d.ts +53 -3
  33. package/dist/react/use-chat-app.d.ts.map +1 -1
  34. package/dist/react/use-chat-app.js +126 -15
  35. package/dist/react/use-chat-app.js.map +1 -1
  36. package/package.json +1 -1
  37. package/styles.css +64 -1
package/README.md CHANGED
@@ -174,11 +174,172 @@ reopens the chat, a picture staged above the text and removed, delete, the
174
174
  queue and Stop — and checks for sideways scroll, 44px targets and cut words
175
175
  at every step.
176
176
 
177
+ #### What a product adds to it (0.19)
178
+
179
+ The first four products to adopt ChatApp (Heidi, OrangeCat, Solon,
180
+ Substrata) each listed what it could not do. These close those gaps; every
181
+ one is optional, and 0.18 code keeps working unchanged.
182
+
183
+ **Send from the page — the handle.** `<ChatApp ref={chat}>` gives
184
+ `ChatAppHandle`: `send(text, attachments?, { newChat?, chips? })`,
185
+ `fill(text, files?)`, `focus()`, `newChat()`, `openConversation(id)`.
186
+ `send` behaves exactly like the composer's Send (queued behind a running
187
+ answer, a chat created when none is open); `File`s are read like the
188
+ paperclip's, and if one cannot be attached nothing is sent — the words and
189
+ files go into the box with the reason. Never click `.ck-send` from outside.
190
+
191
+ ```tsx
192
+ const chat = useRef<ChatAppHandle>(null);
193
+ <ChatApp ref={chat} transport={transport} />;
194
+ <button onClick={() => chat.current?.send("Check this figure", [], { newChat: true })}>Check this</button>;
195
+ ```
196
+
197
+ **A message the page arrives with.** `initialMessage` is sent once, in a new
198
+ chat, after the list has loaded (StrictMode's double mount sends it once).
199
+ `send: false` only fills the box. Clear it from your URL or state yourself.
200
+
201
+ ```tsx
202
+ <ChatApp transport={transport}
203
+ initialMessage={handoff ? { text: handoff.text, attachments: handoff.files } : null} />
204
+ ```
205
+
206
+ **`?q=` links.** `deepLinkParam="q"` reads the parameter into a new chat's
207
+ box and takes it off the address. A link never sends for someone: only with
208
+ `deepLinkAutoSend` too.
209
+
210
+ ```tsx
211
+ <ChatApp transport={transport} deepLinkParam="q" />
212
+ // /chat?q=How%20do%20invoices%20expire → the box says it; the person sends.
213
+ ```
214
+
215
+ **Why a turn failed, per turn.** Throw `ChatTurnError(message, { kind })`
216
+ from the transport: that bubble shows `message` and keeps `failure.kind`
217
+ (for a footer to link the fix). Two failures in one chat keep their own
218
+ words. Any other error shows the `failed` label, never its raw message —
219
+ unless `failureMessage(error)` maps it.
220
+
221
+ ```tsx
222
+ import { ChatTurnError } from "@bitbaum/chatkit/react";
223
+ if (res.status === 503) throw new ChatTurnError("The model is not set up here.", { kind: "not-configured" });
224
+ <ChatApp transport={transport} failureMessage={(e) => (navigator.onLine ? null : "You are offline.")} />;
225
+ ```
226
+
227
+ **Retry sends the pictures again.** The files of a failed turn stay with it
228
+ until its answer lands; Retry re-sends the words and the files. Nothing to
229
+ configure.
230
+
231
+ **Stop and the half answer.** Stop keeps what had arrived (as the apps do).
232
+ `stopKeepsPartial={false}` drops it for every turn; a transport decides per
233
+ turn with `turn.stopKeepsPartial(keep)` — `false` while the text is unchecked.
234
+
235
+ ```tsx
236
+ transport={async (turn) => {
237
+ turn.stopKeepsPartial(false); // the gate has not checked this text yet
238
+ /* … stream … */ turn.stopKeepsPartial(true); return answer; }}
239
+ ```
240
+
241
+ **Starters with a language.** A starter is `string | { label, prompt?, lang? }
242
+ | { text, label?, lang? }`; `lang` goes on the button.
243
+
244
+ ```tsx
245
+ <ChatApp transport={transport}
246
+ starters={["Explain this word", { text: "Grüezi mitenand", lang: "gsw" }]} />
247
+ ```
248
+
249
+ **The chat list's own line.** `historySlot` sits under the search box, above
250
+ the chats; `historyEmpty` replaces "No chats yet…".
251
+
252
+ ```tsx
253
+ <ChatApp transport={transport}
254
+ historySlot={signedOut && <a href="/login">Only in this browser · Sign in to keep it</a>}
255
+ historyEmpty={<p>What you ask is kept here.</p>} />
256
+ ```
257
+
258
+ **Product rows in the attach sheet.** `composer.attachExtras` — `{ id,
259
+ label, detail?, icon?, onPick }[]` — are rows under Camera · Photos · Files,
260
+ one anatomy (icon, label, second line), in the same sheet. With `attach:
261
+ false` the paperclip opens a sheet of only these rows. (`AttachMenu rows`
262
+ takes the same list.)
263
+
264
+ ```tsx
265
+ <ChatApp transport={transport}
266
+ composer={{ attachExtras: [{ id: "things", label: "Your things", onPick: openThings }],
267
+ heldAttachments: held.length }} />
268
+ ```
269
+
270
+ **The product's things on the sent message.** `chipsFor({ text, attachments
271
+ })` returns `MessageChip[]` (`{ label, href?, kind? }`) shown on the person's
272
+ message the moment it is sent, and kept with it (`ChatMessageData.chips`).
273
+ `send(…, { chips })` does the same from the handle.
274
+
275
+ ```tsx
276
+ <ChatApp transport={transport}
277
+ chipsFor={() => held.map((r) => ({ label: r.title, href: r.url }))} />
278
+ ```
279
+
280
+ **A temporary chat ended.** `onTemporaryEnd(chatId)` fires once when a
281
+ temporary chat is left (New chat, another chat, Temporary off), the page is
282
+ hidden, or ChatApp unmounts. `turn.chatId` is the same id, so a server row
283
+ kept for the chat can be keyed by it and deleted here.
284
+
285
+ ```tsx
286
+ <ChatApp transport={(turn) => myApi.send({ ...turn, hiddenRowKey: turn.chatId })}
287
+ onTemporaryEnd={(chatId) => myApi.deleteHiddenRow(chatId)} />
288
+ ```
289
+
290
+ **Footers that know the latest answer, and can send.** `thread.renderFooter(m,
291
+ { isLatest, index, send, fill })`: put "what next" only under the latest,
292
+ and let a chip whose label differs from its sentence send in one tap.
293
+
294
+ ```tsx
295
+ thread={{ renderFooter: (m, { isLatest, send }) =>
296
+ isLatest && <button className="ck-reply" onClick={() => send("Make it shorter.")}>Shorter</button> }}
297
+ ```
298
+
299
+ **After the last turn.** `thread.after` is drawn inside the thread, after the
300
+ last turn (a pending action, a picker), not over the composer.
301
+
302
+ `demo/app.html?lab=1` is the page these are checked on: StrictMode around
303
+ ChatApp, the handle on `window.ckChat`. `test/browser-app.mjs` drives it at
304
+ 390px and 1440px.
305
+
177
306
  #### Moving a product onto ChatApp
178
307
 
179
308
  Per product, from the matrix: what goes, what stays. What stays is always
180
309
  the same two things — the transport and the words.
181
310
 
311
+ **Workarounds 0.19 makes deletable** (each was written in an adoption PR
312
+ because ChatApp 0.18 could not do it — bitbaum/heidi#226, bitbaum/orangecat#1447,
313
+ bitbaum/solon#309, bitbaum/substrata#262):
314
+
315
+ - **Substrata** — `lib/chat/drive-composer.ts` (types into the composer and
316
+ presses Send) goes: `components/portal/chat/useAutoAsk.ts` and
317
+ `lib/chat/handoff.ts` hand their words and pictures to `initialMessage` (or
318
+ `ref.send(text, files, { newChat: true })`), and `/chat?q=` becomes
319
+ `deepLinkParam="q"`. Retry now re-sends the pictures.
320
+ - **OrangeCat** — in `src/components/ai-chat/ModernChatPanel/index.tsx`, the
321
+ bridge that sets the composer value and clicks `button.ck-send` goes (`?q=`
322
+ and `askCat` → the handle), and so does the ephemeral-row cleanup hung on
323
+ the empty state mounting and `onConversationChange` (→ `onTemporaryEnd`,
324
+ keyed by `turn.chatId` in `hooks/useCatTransport.ts`). "Your things" moves
325
+ from a second button into the sheet (`composer.attachExtras`), its staged
326
+ refs show on the sent bubble (`chipsFor`), `CatAnswerFooter` shows the
327
+ create links and upgrade nudge only under `isLatest`, and pending actions
328
+ move from `composer.above` to `thread.after`. `TurnFailure` reads the
329
+ turn's own `failure.kind` (throw `ChatTurnError`).
330
+ - **Heidi** — in `app/[locale]/_components/chat/workspace.tsx`, the
331
+ latest-failure state passed as `thread.labels.failed` goes (throw
332
+ `ChatTurnError` from `chat-app.ts`'s transport mapping), and so does
333
+ `turn.update({ text: "" })` before a throw on Stop (→
334
+ `turn.stopKeepsPartial(false)` until the variety gate has checked the
335
+ text). The move and learn chips and the word pick send in one tap again
336
+ through `renderFooter`'s `send`; the dialect starter gets `lang`; the
337
+ signed-out "Sign in to keep it" line moves from `startersTitle` to
338
+ `historySlot`.
339
+ - **Solon** — nothing to delete. The per-answer "Answered by <model>" fits
340
+ `thread.renderFooter` (it existed in 0.18; it now also gets `isLatest`).
341
+ ChatApp's own words still come only in English: pass `labels`.
342
+
182
343
  - **Loki** — goes: `ThreadRail.tsx`, the history half of
183
344
  `WorkspaceDrawers.tsx` and `WorkspaceChrome.tsx`, `ThreadTitle.tsx`,
184
345
  `hooks/use-temporary-chat.ts`, `hooks/use-new-chat.ts`,
@@ -9,6 +9,19 @@ export type AttachMenuLabels = {
9
9
  sources: Record<AttachSource, string>;
10
10
  };
11
11
  export declare const DEFAULT_ATTACH_MENU_LABELS: AttachMenuLabels;
12
+ /**
13
+ * One of the app's own rows in the attach sheet, under Camera · Photos ·
14
+ * Files: an icon, what it adds (`label`), an optional second line. Every row
15
+ * has this one anatomy, so a product's "Your things" reads as part of the
16
+ * same sheet, not a second menu. Picking a row closes the sheet first.
17
+ */
18
+ export type AttachExtra = {
19
+ id: string;
20
+ label: string;
21
+ detail?: string;
22
+ icon?: ReactNode;
23
+ onPick: () => void;
24
+ };
12
25
  /**
13
26
  * The paperclip, and what it opens on a phone: a sheet with Camera, Photos and
14
27
  * Files — one input each. See `ATTACH_SOURCE_INPUT` for why three inputs and
@@ -27,7 +40,7 @@ export declare const DEFAULT_ATTACH_MENU_LABELS: AttachMenuLabels;
27
40
  * they sit under Camera · Photos · Files in the same sheet, on every pointer,
28
41
  * since a mouse cannot reach them through a file dialog.
29
42
  */
30
- export declare function AttachMenu({ onFiles, disabled, labels, sources, trigger, extra, onOpenChange, }: {
43
+ export declare function AttachMenu({ onFiles, disabled, labels, sources, trigger, extra, rows, onOpenChange, }: {
31
44
  onFiles: (files: FileList | null) => void;
32
45
  disabled?: boolean;
33
46
  labels?: AttachMenuLabels;
@@ -41,6 +54,10 @@ export declare function AttachMenu({ onFiles, disabled, labels, sources, trigger
41
54
  * sheet opens on every pointer. A row that should close the sheet calls
42
55
  * the `close` it is given. */
43
56
  extra?: (close: () => void) => ReactNode;
57
+ /** The app's rows as data — drawn with one anatomy (see `AttachExtra`).
58
+ * With them the sheet opens on every pointer; with no `sources` it holds
59
+ * only them. */
60
+ rows?: readonly AttachExtra[];
44
61
  onOpenChange?: (open: boolean) => void;
45
62
  }): import("react").JSX.Element;
46
63
  //# sourceMappingURL=AttachMenu.d.ts.map
@@ -1 +1 @@
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
+ {"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;AAEF;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AASF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAgB,EAChB,MAAmC,EACnC,OAAiB,EACjB,OAAO,EACP,KAAK,EACL,IAAI,EACJ,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;;qBAEiB;IACjB,IAAI,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC,+BAkGA"}
@@ -34,7 +34,7 @@ const ICONS = {
34
34
  * they sit under Camera · Photos · Files in the same sheet, on every pointer,
35
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, extra, onOpenChange, }) {
37
+ export function AttachMenu({ onFiles, disabled = false, labels = DEFAULT_ATTACH_MENU_LABELS, sources = SOURCES, trigger, extra, rows, onOpenChange, }) {
38
38
  const inputs = useRef({});
39
39
  const [sheetOpen, setSheetOpen] = useState(false);
40
40
  const setOpen = (next) => {
@@ -46,8 +46,9 @@ export function AttachMenu({ onFiles, disabled = false, labels = DEFAULT_ATTACH_
46
46
  close();
47
47
  inputs.current[source]?.click();
48
48
  };
49
+ const hasRows = Boolean(extra) || (rows?.length ?? 0) > 0;
49
50
  const open = () => {
50
- if (extra || (sources.length > 1 && prefersSheet()))
51
+ if (hasRows || (sources.length > 1 && prefersSheet()))
51
52
  setOpen(true);
52
53
  // A mouse gets the broadest picker offered: Files when it is one of the
53
54
  // sources, else the last one (Photos, for an image uploader).
@@ -59,9 +60,12 @@ export function AttachMenu({ onFiles, disabled = false, labels = DEFAULT_ATTACH_
59
60
  }, type: "file", hidden: true, multiple: source !== "camera", "data-ck-source": source, ...ATTACH_SOURCE_INPUT[source], onChange: (e) => {
60
61
  onFiles(e.target.files);
61
62
  e.target.value = "";
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
+ } }, 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: [sources.length > 0 && (_jsx("div", { className: "ck-attach-sources", children: sources.map((source) => {
63
64
  const Icon = ICONS[source];
64
65
  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) })] })] }));
66
+ }) })), hasRows && (_jsxs("div", { className: sources.length > 0 ? "ck-sheet-extra" : "ck-sheet-extra ck-sheet-only", children: [rows?.map((row) => (_jsxs("button", { type: "button", className: "ck-attach-extra", "data-ck-extra": row.id, onClick: () => {
67
+ close();
68
+ row.onPick();
69
+ }, children: [_jsx("span", { className: "ck-attach-extra-icon", "aria-hidden": true, children: row.icon ?? _jsx(IconPaperclip, {}) }), _jsxs("span", { className: "ck-attach-extra-text", children: [_jsx("span", { className: "ck-truncate", children: row.label }), row.detail && _jsx("small", { className: "ck-truncate", children: row.detail })] })] }, row.id))), extra?.(close)] }))] })] }));
66
70
  }
67
71
  //# sourceMappingURL=AttachMenu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AttachMenu.js","sourceRoot":"","sources":["../../src/react/AttachMenu.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AttachMenu.js","sourceRoot":"","sources":["../../src/react/AttachMenu.tsx"],"names":[],"mappings":"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"}
@@ -1,12 +1,13 @@
1
1
  import { type ReactNode } from "react";
2
+ import type { Attachment } from "../attachments.js";
2
3
  import type { ConversationStore, ConversationSummary } from "../conversations.js";
3
4
  import type { Speaker } from "../speech.js";
4
5
  import type { AnswerFeedback } from "../review.js";
5
- import { type ChatMessageData, type Starter, type ThreadLabels } from "./Thread.js";
6
+ import { type ChatMessageData, type FooterInfo, type MessageChip, type Starter, type ThreadLabels } from "./Thread.js";
6
7
  import { type ComposerProps } from "./Composer.js";
7
8
  import type { RenderLink } from "./Markdown.js";
8
9
  import type { RichRenderers } from "./renderers.js";
9
- import { type ChatAppProblem, type ChatTransport } from "./use-chat-app.js";
10
+ import { type ChatAppProblem, type ChatTransport, type SendOptions } from "./use-chat-app.js";
10
11
  export type ChatAppLabels = {
11
12
  chats: string;
12
13
  openChats: string;
@@ -42,6 +43,42 @@ export type ChatAppLabels = {
42
43
  problems: Record<ChatAppProblem, string>;
43
44
  };
44
45
  export declare const DEFAULT_CHAT_APP_LABELS: ChatAppLabels;
46
+ /**
47
+ * What a page can do to the chat it holds (`<ChatApp ref={chat}>`): send for
48
+ * the person ("Check this", `/chat?q=`, a message handed over from a home
49
+ * box), put words in the box for them to finish, and move between chats.
50
+ */
51
+ export type ChatAppHandle = {
52
+ /**
53
+ * Send now, exactly as if typed and sent: queued behind a running answer,
54
+ * a new chat created when none is open (or always, with `newChat`). Files
55
+ * are read like the paperclip's (limits, shrinking); if one cannot be
56
+ * attached, nothing is sent — the words and files go into the box with the
57
+ * reason, for the person to decide. Resolves whether it was sent.
58
+ */
59
+ send: (text: string, attachments?: readonly (Attachment | File)[], options?: SendOptions) => Promise<boolean>;
60
+ /** Put words (and files) in the box and the cursor after them; nothing is sent. */
61
+ fill: (text: string, files?: readonly File[]) => void;
62
+ focus: () => void;
63
+ newChat: () => void;
64
+ openConversation: (id: string) => void;
65
+ };
66
+ /** A message the page arrives with (see `initialMessage`). */
67
+ export type InitialMessage = {
68
+ text: string;
69
+ /** Pictures and files with it: `File`s as picked, or wire attachments. */
70
+ attachments?: readonly (Attachment | File)[];
71
+ chips?: readonly MessageChip[];
72
+ /** Send it (the default), or only put it in the box (`false`). */
73
+ send?: boolean;
74
+ };
75
+ /** What `thread.renderFooter` gets beside the message. */
76
+ export type ChatAppFooterInfo = FooterInfo & {
77
+ /** Send for the person — a chip whose label differs from what it says. */
78
+ send: (text: string, options?: SendOptions) => void;
79
+ /** Put words in the box for the person to finish. */
80
+ fill: (text: string) => void;
81
+ };
45
82
  export type ChatAppProps = {
46
83
  /** Send a turn and resolve with the answer (stream through `turn.update`). */
47
84
  transport: ChatTransport;
@@ -63,11 +100,13 @@ export type ChatAppProps = {
63
100
  /** Composer options: `voice` (give it `transcribeUrl`), `attach` (on by
64
101
  * default; `false` only when the product's API takes no files), `tools`,
65
102
  * `labels`… Sending, Stop and the queue are ChatApp's. */
66
- composer?: Partial<Omit<ComposerProps, "onSend" | "sending" | "onStop" | "queue">>;
103
+ composer?: Partial<Omit<ComposerProps, "onSend" | "sending" | "onStop" | "queue" | "handleRef">>;
67
104
  /** Thread options, passed to ChatThread. */
68
105
  thread?: {
69
106
  renderLink?: RenderLink;
70
- renderFooter?: (m: ChatMessageData) => ReactNode;
107
+ renderFooter?: (m: ChatMessageData, info: ChatAppFooterInfo) => ReactNode;
108
+ /** After the last turn, inside the thread (a pending action, a picker). */
109
+ after?: ReactNode;
71
110
  renderers?: RichRenderers;
72
111
  speaker?: Speaker | null;
73
112
  share?: {
@@ -107,7 +146,42 @@ export type ChatAppProps = {
107
146
  railQuery?: string;
108
147
  labels?: Partial<ChatAppLabels>;
109
148
  className?: string;
149
+ /** A message to start with, in a NEW chat, once the list has loaded — the
150
+ * home box handing its words and pictures to `/chat`. Used once per mount
151
+ * (StrictMode's double mount included); clear it from your URL or state
152
+ * yourself so a reload does not send it again. */
153
+ initialMessage?: InitialMessage | null;
154
+ /** Read this query parameter (e.g. `"q"`) into a new chat's box, then take
155
+ * it off the address. The words are only put in the box: a link can never
156
+ * send for someone unless `deepLinkAutoSend` is set too. */
157
+ deepLinkParam?: string;
158
+ /** Send the `deepLinkParam` words instead of filling the box. Off by default. */
159
+ deepLinkAutoSend?: boolean;
160
+ /** Stop keeps the half answer that had arrived. Default true; a transport
161
+ * can decide per turn with `turn.stopKeepsPartial(keep)`. */
162
+ stopKeepsPartial?: boolean;
163
+ /** The words for a failed turn whose error is not a `ChatTurnError`
164
+ * (return null for the thread's `failed` label). */
165
+ failureMessage?: (error: unknown) => string | null | undefined;
166
+ /** A temporary chat ended (left, page hidden, unmounted): delete what the
167
+ * server kept for it. Gets `turn.chatId`; called once per chat. */
168
+ onTemporaryEnd?: (chatId: string) => void;
169
+ /** The product's things shown on the person's message when it is sent
170
+ * from the box (the ones staged with `heldAttachments`). */
171
+ chipsFor?: (turn: {
172
+ text: string;
173
+ attachments: Attachment[];
174
+ }) => readonly MessageChip[] | null | undefined;
175
+ /** In the chat list, under the search box: the product's own line. */
176
+ historySlot?: ReactNode;
177
+ /** In the chat list, instead of "No chats yet…" when there are none. */
178
+ historyEmpty?: ReactNode;
110
179
  };
180
+ /** The words a `?q=`-style link carries, and the address without them. Pure. */
181
+ export declare function takeDeepLink(href: string, param: string): {
182
+ text: string;
183
+ href: string;
184
+ } | null;
111
185
  /**
112
186
  * The whole chat app, the way the Claude, ChatGPT and Grok phone apps are
113
187
  * laid out: the conversation edge to edge, a frosted header (Chats · title ·
@@ -119,5 +193,5 @@ export type ChatAppProps = {
119
193
  * `store` that keeps chats (or nothing: the device then keeps them) — plus
120
194
  * its words. Everything a person sees and does is the same in every product.
121
195
  */
122
- export declare function ChatApp({ transport, store: givenStore, placeholder, name, makeTitle, starters, startersTitle, empty, composer, thread, footnote, fill, temporaryChats, onShare, conversationId, onConversationChange, rememberKey, search, headerStart, headerEnd, locale, railQuery, labels: labelOverrides, className, }: ChatAppProps): import("react").JSX.Element;
196
+ export declare const ChatApp: import("react").ForwardRefExoticComponent<ChatAppProps & import("react").RefAttributes<ChatAppHandle>>;
123
197
  //# sourceMappingURL=ChatApp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatApp.d.ts","sourceRoot":"","sources":["../../src/react/ChatApp.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChatApp.d.ts","sourceRoot":"","sources":["../../src/react/ChatApp.tsx"],"names":[],"mappings":"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"}
@@ -1,14 +1,15 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
4
4
  import { localConversationStore } from "../stores.js";
5
5
  import { ChatScreen } from "./Screen.js";
6
6
  import { ChatStarters, ChatThread, } from "./Thread.js";
7
7
  import { Composer } from "./Composer.js";
8
+ import { readAttachmentFile } from "./use-attachments.js";
8
9
  import { ChatHistory } from "./ChatHistory.js";
9
10
  import { useViewportHeight } from "./hooks.js";
10
11
  import { IconGhost, IconMenu, IconMore, IconNewChat, IconX } from "./icons.js";
11
- import { useChatApp } from "./use-chat-app.js";
12
+ import { useChatApp, } from "./use-chat-app.js";
12
13
  export const DEFAULT_CHAT_APP_LABELS = {
13
14
  chats: "Chats",
14
15
  openChats: "Open your chats",
@@ -45,6 +46,23 @@ export const DEFAULT_CHAT_APP_LABELS = {
45
46
  open: "That chat did not open. Try again.",
46
47
  },
47
48
  };
49
+ /** The words a `?q=`-style link carries, and the address without them. Pure. */
50
+ export function takeDeepLink(href, param) {
51
+ let url;
52
+ try {
53
+ url = new URL(href);
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ const raw = url.searchParams.get(param);
59
+ if (raw === null)
60
+ return null;
61
+ url.searchParams.delete(param);
62
+ const text = raw.trim().slice(0, DEEP_LINK_MAX);
63
+ return { text, href: url.toString() };
64
+ }
65
+ const DEEP_LINK_MAX = 4000;
48
66
  function useMediaQuery(query) {
49
67
  const [match, setMatch] = useState(false);
50
68
  useEffect(() => {
@@ -70,13 +88,23 @@ let fallbackStore = null;
70
88
  * `store` that keeps chats (or nothing: the device then keeps them) — plus
71
89
  * its words. Everything a person sees and does is the same in every product.
72
90
  */
73
- export function ChatApp({ transport, store: givenStore, placeholder = "Ask anything", name = "", makeTitle, starters, startersTitle, empty, composer, thread, footnote, fill = "viewport", temporaryChats = true, onShare, conversationId, onConversationChange, rememberKey = "ck-chat:open", search, headerStart, headerEnd, locale, railQuery = "(min-width: 64rem)", labels: labelOverrides, className, }) {
91
+ export const ChatApp = forwardRef(function ChatApp({ transport, store: givenStore, placeholder = "Ask anything", name = "", makeTitle, starters, startersTitle, empty, composer, thread, footnote, fill = "viewport", temporaryChats = true, onShare, conversationId, onConversationChange, rememberKey = "ck-chat:open", search, headerStart, headerEnd, locale, railQuery = "(min-width: 64rem)", labels: labelOverrides, className, initialMessage, deepLinkParam, deepLinkAutoSend = false, stopKeepsPartial, failureMessage, onTemporaryEnd, chipsFor, historySlot, historyEmpty, }, ref) {
74
92
  const labels = {
75
93
  ...DEFAULT_CHAT_APP_LABELS,
76
94
  ...labelOverrides,
77
95
  problems: { ...DEFAULT_CHAT_APP_LABELS.problems, ...labelOverrides?.problems },
78
96
  };
79
97
  const store = useMemo(() => givenStore ?? (fallbackStore ??= localConversationStore()), [givenStore]);
98
+ // What the page arrived with, read once: a message handed over, or a
99
+ // `?q=` link. Either starts a new chat, so the reload's chat is not reopened.
100
+ const [arrival] = useState(() => {
101
+ if (initialMessage)
102
+ return initialMessage;
103
+ if (!deepLinkParam || typeof window === "undefined")
104
+ return null;
105
+ const link = takeDeepLink(window.location.href, deepLinkParam);
106
+ return link?.text ? { text: link.text, send: deepLinkAutoSend } : null;
107
+ });
80
108
  const app = useChatApp({
81
109
  store,
82
110
  transport,
@@ -84,7 +112,67 @@ export function ChatApp({ transport, store: givenStore, placeholder = "Ask anyth
84
112
  conversationId,
85
113
  onConversationChange,
86
114
  rememberKey,
115
+ stopKeepsPartial,
116
+ failureMessage,
117
+ onTemporaryEnd,
118
+ restore: !arrival,
87
119
  });
120
+ const composerRef = useRef(null);
121
+ const attachLimits = typeof composer?.attach === "object" ? composer.attach : {};
122
+ const fillBox = (text, files) => {
123
+ const box = composerRef.current;
124
+ if (!box)
125
+ return;
126
+ box.setText(text);
127
+ if (files && files.length > 0)
128
+ box.addFiles([...files]);
129
+ box.focus();
130
+ };
131
+ const sendFor = async (text, given = [], options) => {
132
+ await app.whenReady();
133
+ const files = given.filter((a) => typeof File !== "undefined" && a instanceof File);
134
+ const wire = given.filter((a) => !(typeof File !== "undefined" && a instanceof File));
135
+ const read = await Promise.all(files.map((f) => readAttachmentFile(f, attachLimits, composer?.labels?.attachNotes)));
136
+ if (read.some((r) => r.note !== undefined)) {
137
+ // One file cannot go: send nothing behind the person's back. The box
138
+ // gets the words and the files, and says why (the composer's own note).
139
+ if (options?.newChat)
140
+ app.newChat();
141
+ fillBox(text, files);
142
+ return false;
143
+ }
144
+ const attachments = [...wire, ...read.map((r) => stripPreview(r.attachment))];
145
+ return app.send(text, attachments, options);
146
+ };
147
+ const handle = {
148
+ send: sendFor,
149
+ fill: fillBox,
150
+ focus: () => composerRef.current?.focus(),
151
+ newChat: app.newChat,
152
+ openConversation: app.open,
153
+ };
154
+ useImperativeHandle(ref, () => handle);
155
+ // The arrival, once — StrictMode's rehearsal mount included (`taken` is
156
+ // kept across it), and only after the list is in so nothing moves it.
157
+ const taken = useRef(false);
158
+ useEffect(() => {
159
+ if (!app.ready || !arrival || taken.current)
160
+ return;
161
+ taken.current = true;
162
+ if (deepLinkParam && !initialMessage && typeof window !== "undefined") {
163
+ const link = takeDeepLink(window.location.href, deepLinkParam);
164
+ if (link)
165
+ window.history.replaceState(window.history.state, "", link.href);
166
+ }
167
+ if (arrival.send === false) {
168
+ app.newChat();
169
+ fillBox(arrival.text, (arrival.attachments ?? []).filter((a) => typeof File !== "undefined" && a instanceof File));
170
+ }
171
+ else {
172
+ void sendFor(arrival.text, arrival.attachments, { newChat: true, chips: arrival.chips });
173
+ }
174
+ // `arrival` is fixed at mount; the rest is read when it fires.
175
+ }, [app.ready]);
88
176
  const rootRef = useRef(null);
89
177
  useViewportHeight(rootRef, fill === "viewport");
90
178
  const wide = useMediaQuery(railQuery);
@@ -122,7 +210,7 @@ export function ChatApp({ transport, store: givenStore, placeholder = "Ask anyth
122
210
  }, onNew: () => {
123
211
  setDrawerOpen(false);
124
212
  app.newChat();
125
- }, onRename: (id, t) => void app.rename(id, t), onRemove: (id) => void app.remove(id), onRetry: app.reloadList, onClose: wide ? undefined : () => setDrawerOpen(false), search: search, locale: locale, labels: labels }));
213
+ }, onRename: (id, t) => void app.rename(id, t), onRemove: (id) => void app.remove(id), onRetry: app.reloadList, onClose: wide ? undefined : () => setDrawerOpen(false), search: search, locale: locale, labels: labels, slot: historySlot, empty: historyEmpty }));
126
214
  const [renaming, setRenaming] = useState(false);
127
215
  const [deleting, setDeleting] = useState(false);
128
216
  const menu = [];
@@ -155,8 +243,12 @@ export function ChatApp({ transport, store: givenStore, placeholder = "Ask anyth
155
243
  setDeleting(false);
156
244
  void app.remove(current.id);
157
245
  }, children: labels.delete }), _jsx("button", { type: "button", onClick: () => setDeleting(false), children: labels.cancel })] })] })) : null;
158
- const dock = (_jsxs(_Fragment, { children: [above, app.problem && (_jsxs("p", { className: "ck-problem", role: "status", children: [_jsx("span", { children: labels.problems[app.problem] }), _jsx("button", { type: "button", className: "ck-problem-x", onClick: app.clearProblem, "aria-label": labels.dismiss, children: _jsx(IconX, {}) })] })), app.queued.length > 0 && (_jsx("ul", { className: "ck-app-queue", "aria-label": labels.queued, children: app.queued.map((q) => (_jsxs("li", { children: [_jsxs("span", { className: "ck-app-queue-text", children: [_jsx("span", { className: "ck-truncate", children: q.text }), _jsx("small", { children: labels.queued })] }), _jsx("button", { type: "button", className: "ck-icon-btn", onClick: () => app.unqueue(q.id), "aria-label": labels.unqueue(q.text), title: labels.unqueue(q.text), children: _jsx(IconX, {}) })] }, q.id))) })), _jsx(Composer, { placeholder: placeholder, attach: true, stagedPosition: "above", ...composer, onSend: (text, attachments) => app.send(text, attachments), sending: app.busy, onStop: app.stop, queue: true })] }));
246
+ const dock = (_jsxs(_Fragment, { children: [above, app.problem && (_jsxs("p", { className: "ck-problem", role: "status", children: [_jsx("span", { children: labels.problems[app.problem] }), _jsx("button", { type: "button", className: "ck-problem-x", onClick: app.clearProblem, "aria-label": labels.dismiss, children: _jsx(IconX, {}) })] })), app.queued.length > 0 && (_jsx("ul", { className: "ck-app-queue", "aria-label": labels.queued, children: app.queued.map((q) => (_jsxs("li", { children: [_jsxs("span", { className: "ck-app-queue-text", children: [_jsx("span", { className: "ck-truncate", children: q.text }), _jsx("small", { children: labels.queued })] }), _jsx("button", { type: "button", className: "ck-icon-btn", onClick: () => app.unqueue(q.id), "aria-label": labels.unqueue(q.text), title: labels.unqueue(q.text), children: _jsx(IconX, {}) })] }, q.id))) })), _jsx(Composer, { placeholder: placeholder, attach: true, stagedPosition: "above", ...composer, handleRef: composerRef, onSend: (text, attachments) => {
247
+ const chips = chipsFor?.({ text, attachments });
248
+ return app.send(text, attachments, chips ? { chips } : undefined);
249
+ }, sending: app.busy, onStop: app.stop, queue: true })] }));
159
250
  const lastFailed = app.messages[app.messages.length - 1]?.failed;
251
+ const renderFooter = thread?.renderFooter;
160
252
  const onFeedback = thread?.onFeedback;
161
253
  const cls = ["ck-app"];
162
254
  if (fill === "viewport")
@@ -168,8 +260,22 @@ export function ChatApp({ transport, store: givenStore, placeholder = "Ask anyth
168
260
  return (_jsxs("div", { ref: rootRef, className: cls.join(" "), children: [wide ? (_jsx("aside", { className: "ck-app-rail", children: history })) : (_jsx("dialog", { ref: drawerRef, className: "ck-app-drawer", "aria-label": labels.chats, onClose: () => setDrawerOpen(false), onClick: (e) => {
169
261
  if (e.target === e.currentTarget)
170
262
  setDrawerOpen(false);
171
- }, children: drawerOpen && _jsx("div", { className: "ck-app-drawer-panel", children: history }) })), _jsx(ChatScreen, { fill: "parent", className: "ck-app-screen", header: header, composer: dock, footnote: footnote, renderers: thread?.renderers, children: _jsx(ChatThread, { messages: app.messages, loading: app.loading, live: app.live, stopped: app.stopped, onStop: app.stop, onRetry: lastFailed || app.stopped ? app.retry : undefined, onReply: (text) => void app.send(text), renderLink: thread?.renderLink, renderFooter: thread?.renderFooter, speaker: thread?.speaker, share: thread?.share, onFeedback: onFeedback ? (f) => onFeedback({ ...f, conversationId: app.stored }) : undefined, labels: thread?.labels, empty: empty ??
172
- (starters && starters.length > 0 ? (_jsx(ChatStarters, { starters: starters, title: startersTitle, onPick: (text) => void app.send(text) })) : (_jsx("div", { className: "ck-starters", children: startersTitle && _jsx("div", { className: "ck-starters-title", children: startersTitle }) }))) }, app.activeId ?? (app.temporary ? "temporary" : "new")) })] }));
263
+ }, children: drawerOpen && _jsx("div", { className: "ck-app-drawer-panel", children: history }) })), _jsx(ChatScreen, { fill: "parent", className: "ck-app-screen", header: header, composer: dock, footnote: footnote, renderers: thread?.renderers, children: _jsx(ChatThread, { messages: app.messages, loading: app.loading, live: app.live, stopped: app.stopped, onStop: app.stop, onRetry: lastFailed || app.stopped ? app.retry : undefined, onReply: (text) => void app.send(text), renderLink: thread?.renderLink, renderFooter: renderFooter
264
+ ? (m, info) => renderFooter(m, {
265
+ ...info,
266
+ send: (text, options) => void app.send(text, [], options),
267
+ fill: (text) => fillBox(text),
268
+ })
269
+ : undefined, speaker: thread?.speaker, share: thread?.share, onFeedback: onFeedback ? (f) => onFeedback({ ...f, conversationId: app.stored }) : undefined, labels: thread?.labels, empty: empty ??
270
+ (starters && starters.length > 0 ? (_jsx(ChatStarters, { starters: starters, title: startersTitle, onPick: (text) => void app.send(text) })) : (_jsx("div", { className: "ck-starters", children: startersTitle && _jsx("div", { className: "ck-starters-title", children: startersTitle }) }))), children: thread?.after }, app.activeId ?? (app.temporary ? "temporary" : "new")) })] }));
271
+ });
272
+ /** The wire shape only: a preview URL never leaves the page. */
273
+ function stripPreview(a) {
274
+ if (!("previewUrl" in a))
275
+ return a;
276
+ const { previewUrl: _preview, ...wire } = a;
277
+ void _preview;
278
+ return wire;
173
279
  }
174
280
  /** The header's "…": a small menu under the button. Escape and a tap
175
281
  * outside close it and give focus back to the button; arrows move. */