@bitbaum/chatkit 0.18.1 → 0.20.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 (42) hide show
  1. package/README.md +200 -0
  2. package/bin/chatkit-copy-pdf-assets.mjs +38 -0
  3. package/dist/pdf.d.ts +96 -0
  4. package/dist/pdf.d.ts.map +1 -0
  5. package/dist/pdf.js +212 -0
  6. package/dist/pdf.js.map +1 -0
  7. package/dist/react/AttachMenu.d.ts +18 -1
  8. package/dist/react/AttachMenu.d.ts.map +1 -1
  9. package/dist/react/AttachMenu.js +8 -4
  10. package/dist/react/AttachMenu.js.map +1 -1
  11. package/dist/react/ChatApp.d.ts +79 -5
  12. package/dist/react/ChatApp.d.ts.map +1 -1
  13. package/dist/react/ChatApp.js +113 -7
  14. package/dist/react/ChatApp.js.map +1 -1
  15. package/dist/react/ChatHistory.d.ts +7 -1
  16. package/dist/react/ChatHistory.d.ts.map +1 -1
  17. package/dist/react/ChatHistory.js +2 -2
  18. package/dist/react/ChatHistory.js.map +1 -1
  19. package/dist/react/Composer.d.ts +20 -3
  20. package/dist/react/Composer.d.ts.map +1 -1
  21. package/dist/react/Composer.js +19 -4
  22. package/dist/react/Composer.js.map +1 -1
  23. package/dist/react/Thread.d.ts +48 -2
  24. package/dist/react/Thread.d.ts.map +1 -1
  25. package/dist/react/Thread.js +13 -6
  26. package/dist/react/Thread.js.map +1 -1
  27. package/dist/react/attach.d.ts +1 -1
  28. package/dist/react/attach.d.ts.map +1 -1
  29. package/dist/react/index.d.ts +10 -10
  30. package/dist/react/index.d.ts.map +1 -1
  31. package/dist/react/index.js +4 -4
  32. package/dist/react/index.js.map +1 -1
  33. package/dist/react/use-attachments.d.ts +17 -0
  34. package/dist/react/use-attachments.d.ts.map +1 -1
  35. package/dist/react/use-attachments.js +89 -72
  36. package/dist/react/use-attachments.js.map +1 -1
  37. package/dist/react/use-chat-app.d.ts +53 -3
  38. package/dist/react/use-chat-app.d.ts.map +1 -1
  39. package/dist/react/use-chat-app.js +126 -15
  40. package/dist/react/use-chat-app.js.map +1 -1
  41. package/package.json +13 -4
  42. package/styles.css +64 -1
@@ -39,72 +39,18 @@ export function useAttachments(limits = {}, notes = {}) {
39
39
  return prev;
40
40
  return [...prev, item];
41
41
  }), [maxFiles]);
42
- const stageImage = useCallback((file) => {
43
- const name = file.name || "screenshot.png";
44
- if (!isImageMime(file.type)) {
45
- setNote(say.current.wrongType(name));
46
- return;
47
- }
48
- const tooLarge = () => setNote(say.current.imageTooLarge(name, Math.round(maxImageBytes / 1_000_000)));
49
- if (maxImageEdge > 0) {
50
- // Shrink first, then judge the size: a 4 MB screenshot is fine once it
51
- // is the 300 KB it needed to be.
52
- shrink(file, maxImageEdge, maxImageBytes)
53
- .then((dataUrl) => {
54
- if (!dataUrl) {
55
- tooLarge();
56
- return;
57
- }
58
- const previewUrl = URL.createObjectURL(file);
59
- previews.current.add(previewUrl);
60
- add({
61
- kind: "image",
62
- name: name.replace(/\.(png|gif|webp|jpe?g)$/i, "") + ".jpg",
63
- mimeType: "image/jpeg",
64
- dataBase64: stripDataUrlBase64(dataUrl),
65
- previewUrl,
66
- });
67
- })
68
- .catch(() => setNote(say.current.unreadable(name)));
69
- return;
70
- }
71
- if (file.size > maxImageBytes) {
72
- tooLarge();
73
- return;
74
- }
75
- const reader = new FileReader();
76
- reader.onload = () => {
77
- const previewUrl = URL.createObjectURL(file);
78
- previews.current.add(previewUrl);
79
- add({
80
- kind: "image",
81
- name,
82
- mimeType: file.type,
83
- dataBase64: stripDataUrlBase64(String(reader.result ?? "")),
84
- previewUrl,
85
- });
86
- };
87
- reader.onerror = () => setNote(say.current.unreadable(name));
88
- reader.readAsDataURL(file);
89
- }, [add, maxImageBytes, maxImageEdge]);
90
- const stageText = useCallback((file) => {
91
- if (!isTextFile(file.name, file.type)) {
92
- setNote(say.current.wrongType(file.name));
93
- return;
94
- }
95
- if (file.size > maxTextChars) {
96
- setNote(say.current.textTooLarge(file.name, Math.round(maxTextChars / 1000)));
97
- return;
98
- }
99
- const reader = new FileReader();
100
- reader.onload = () => add({
101
- kind: "text",
102
- name: file.name,
103
- content: String(reader.result ?? "").slice(0, maxTextChars),
42
+ const stage = useCallback((file) => {
43
+ const limits = { maxFiles, maxImageBytes, maxImageEdge, maxTextChars };
44
+ void readAttachmentFile(file, limits, say.current, { preview: true }).then((read) => {
45
+ if (read.note !== undefined) {
46
+ setNote(read.note);
47
+ return;
48
+ }
49
+ if (read.attachment.previewUrl)
50
+ previews.current.add(read.attachment.previewUrl);
51
+ add(read.attachment);
104
52
  });
105
- reader.onerror = () => setNote(say.current.unreadable(file.name));
106
- reader.readAsText(file);
107
- }, [add, maxTextChars]);
53
+ }, [add, maxFiles, maxImageBytes, maxImageEdge, maxTextChars]);
108
54
  const addFiles = useCallback((files) => {
109
55
  if (!files)
110
56
  return;
@@ -115,12 +61,9 @@ export function useAttachments(limits = {}, notes = {}) {
115
61
  return;
116
62
  }
117
63
  for (const file of Array.from(files).slice(0, room)) {
118
- if (isImageMime(file.type))
119
- stageImage(file);
120
- else
121
- stageText(file);
64
+ stage(file);
122
65
  }
123
- }, [maxFiles, stageImage, stageText]);
66
+ }, [maxFiles, stage]);
124
67
  const addFromPaste = useCallback((e) => {
125
68
  const items = e.clipboardData?.items;
126
69
  if (!items)
@@ -132,10 +75,10 @@ export function useAttachments(limits = {}, notes = {}) {
132
75
  for (const item of images) {
133
76
  const file = item.getAsFile();
134
77
  if (file)
135
- stageImage(file);
78
+ stage(file);
136
79
  }
137
80
  return true;
138
- }, [stageImage]);
81
+ }, [stage]);
139
82
  const revoke = (a) => {
140
83
  if (!a.previewUrl)
141
84
  return;
@@ -171,4 +114,78 @@ export function useAttachments(limits = {}, notes = {}) {
171
114
  limits: lim,
172
115
  };
173
116
  }
117
+ /**
118
+ * One file read the way the composer stages it: an image shrunk to
119
+ * `maxImageEdge` and judged by its size after that, a text file read up to
120
+ * `maxTextChars`, anything else refused — with the reason as a sentence from
121
+ * `notes`, never a throw. `preview: true` adds an object URL the caller must
122
+ * revoke. `useAttachments` and `ChatApp`'s `send` both read files through it.
123
+ */
124
+ export async function readAttachmentFile(file, limits = {}, notes = {}, { preview = false } = {}) {
125
+ const { maxImageBytes, maxTextChars, maxImageEdge } = { ...DEFAULT_ATTACHMENT_LIMITS, ...limits };
126
+ const say = { ...DEFAULT_ATTACHMENT_NOTES, ...notes };
127
+ const previewOf = () => (preview ? URL.createObjectURL(file) : undefined);
128
+ if (isImageMime(file.type)) {
129
+ const name = file.name || "screenshot.png";
130
+ const tooLarge = { note: say.imageTooLarge(name, Math.round(maxImageBytes / 1_000_000)) };
131
+ try {
132
+ if (maxImageEdge > 0) {
133
+ // Shrink first, then judge the size: a 4 MB screenshot is fine once it
134
+ // is the 300 KB it needed to be.
135
+ const dataUrl = await shrink(file, maxImageEdge, maxImageBytes);
136
+ if (!dataUrl)
137
+ return tooLarge;
138
+ return {
139
+ attachment: {
140
+ kind: "image",
141
+ name: name.replace(/\.(png|gif|webp|jpe?g)$/i, "") + ".jpg",
142
+ mimeType: "image/jpeg",
143
+ dataBase64: stripDataUrlBase64(dataUrl),
144
+ previewUrl: previewOf(),
145
+ },
146
+ };
147
+ }
148
+ if (file.size > maxImageBytes)
149
+ return tooLarge;
150
+ const dataUrl = await readAs(file, "dataUrl");
151
+ return {
152
+ attachment: {
153
+ kind: "image",
154
+ name,
155
+ mimeType: file.type,
156
+ dataBase64: stripDataUrlBase64(dataUrl),
157
+ previewUrl: previewOf(),
158
+ },
159
+ };
160
+ }
161
+ catch {
162
+ return { note: say.unreadable(name) };
163
+ }
164
+ }
165
+ if (!isTextFile(file.name, file.type))
166
+ return { note: say.wrongType(file.name) };
167
+ if (file.size > maxTextChars) {
168
+ return { note: say.textTooLarge(file.name, Math.round(maxTextChars / 1000)) };
169
+ }
170
+ try {
171
+ const content = await readAs(file, "text");
172
+ return {
173
+ attachment: { kind: "text", name: file.name, content: content.slice(0, maxTextChars) },
174
+ };
175
+ }
176
+ catch {
177
+ return { note: say.unreadable(file.name) };
178
+ }
179
+ }
180
+ function readAs(file, as) {
181
+ return new Promise((resolve, reject) => {
182
+ const reader = new FileReader();
183
+ reader.onload = () => resolve(String(reader.result ?? ""));
184
+ reader.onerror = () => reject(reader.error);
185
+ if (as === "text")
186
+ reader.readAsText(file);
187
+ else
188
+ reader.readAsDataURL(file);
189
+ });
190
+ }
174
191
  //# sourceMappingURL=use-attachments.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-attachments.js","sourceRoot":"","sources":["../../src/react/use-attachments.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-attachments.js","sourceRoot":"","sources":["../../src/react/use-attachments.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,14 @@
1
1
  import type { Attachment } from "../attachments.js";
2
2
  import { type ConversationStore, type ConversationSummary } from "../conversations.js";
3
- import type { ChatMessageData, LiveTurn } from "./Thread.js";
3
+ import type { ChatFailure, ChatMessageData, LiveTurn, MessageChip } from "./Thread.js";
4
4
  /** One turn, as ChatApp hands it to the product. */
5
5
  export type ChatTurn = {
6
6
  /** The stored conversation, or null in a temporary chat (nothing is kept). */
7
7
  conversationId: string | null;
8
+ /** This chat's id on the page: the stored id, or — in a temporary chat — a
9
+ * page-only id, the one `onTemporaryEnd` names when the chat ends. A
10
+ * product that keeps a hidden server row for a temporary chat keys it here. */
11
+ chatId: string;
8
12
  temporary: boolean;
9
13
  /** What the person wrote (or the composer's attachment-only words). */
10
14
  text: string;
@@ -16,6 +20,38 @@ export type ChatTurn = {
16
20
  /** What has arrived so far — the WHOLE text each time, a status line before
17
21
  * the first word, the steps taken. Call it as often as you like. */
18
22
  update: (live: LiveTurn) => void;
23
+ /** Whether Stop keeps what has arrived for THIS turn, overriding ChatApp's
24
+ * `stopKeepsPartial` — `false` while the text so far is unchecked (Heidi's
25
+ * variety gate), `true` once it is. Call it any time before the turn ends. */
26
+ stopKeepsPartial: (keep: boolean) => void;
27
+ };
28
+ /**
29
+ * Throw this from a transport to say why a turn failed in the reader's
30
+ * words: the failed bubble shows `message`, and `kind` (the product's own
31
+ * code, e.g. "quota" or "no-vision") travels with that message as
32
+ * `failure.kind` for a footer to link the fix. Any other error shows the
33
+ * thread's `failed` label — a raw `TypeError: Failed to fetch` is never
34
+ * shown to a reader — unless ChatApp's `failureMessage` maps it.
35
+ */
36
+ export declare class ChatTurnError extends Error {
37
+ readonly kind?: string;
38
+ constructor(message: string, options?: {
39
+ kind?: string;
40
+ cause?: unknown;
41
+ });
42
+ }
43
+ /**
44
+ * What a failed turn keeps: a `ChatTurnError`'s words and kind, a plain
45
+ * `{ message, kind }` object as thrown, or what `map` makes of anything
46
+ * else. Never an ordinary Error's message — that is for the console.
47
+ */
48
+ export declare function chatTurnFailure(error: unknown, map?: (error: unknown) => string | null | undefined): ChatFailure;
49
+ /** How a page sends for the person (`ChatAppHandle.send`). */
50
+ export type SendOptions = {
51
+ /** Start a new chat for it instead of adding to the open one. */
52
+ newChat?: boolean;
53
+ /** The product's own things shown on the person's message (see `MessageChip`). */
54
+ chips?: readonly MessageChip[];
19
55
  };
20
56
  /** The answer: its words, or the whole message (replies, citations, thinking…). */
21
57
  export type ChatTurnResult = string | (Omit<ChatMessageData, "id" | "role"> & {
@@ -32,6 +68,7 @@ type Queued = {
32
68
  conversation: string;
33
69
  text: string;
34
70
  attachments: Attachment[];
71
+ chips?: readonly MessageChip[];
35
72
  };
36
73
  export declare const isTemporaryId: (id: string | null | undefined) => boolean;
37
74
  export type UseChatAppOptions = {
@@ -44,15 +81,28 @@ export type UseChatAppOptions = {
44
81
  conversationId?: string | null;
45
82
  onConversationChange?: (id: string | null) => void;
46
83
  rememberKey: string | null;
84
+ /** Stop keeps the half answer that had arrived. Default true. */
85
+ stopKeepsPartial?: boolean;
86
+ /** The words for a failed turn whose error is not a `ChatTurnError`. */
87
+ failureMessage?: (error: unknown) => string | null | undefined;
88
+ /** A temporary chat ended: left for a new or another chat, the page hidden
89
+ * or ChatApp unmounted. Called once per chat, with `turn.chatId`. */
90
+ onTemporaryEnd?: (chatId: string) => void;
91
+ /** Reopen the chat from before the reload. False when the page arrives
92
+ * with a message to send (it starts a new chat). Default true. */
93
+ restore?: boolean;
47
94
  };
48
95
  /**
49
96
  * Everything ChatApp does that is not drawing: the list, the open chat, new
50
97
  * and temporary chats, turns (one per conversation, each running on when the
51
98
  * person opens another chat), the queue, titles and the reopen-on-reload.
52
99
  */
53
- export declare function useChatApp({ store, transport, makeTitle, conversationId, onConversationChange, rememberKey, }: UseChatAppOptions): {
100
+ export declare function useChatApp({ store, transport, makeTitle, conversationId, onConversationChange, rememberKey, stopKeepsPartial, failureMessage, onTemporaryEnd, restore, }: UseChatAppOptions): {
54
101
  list: ConversationSummary[];
55
102
  listState: "error" | "loading" | "ready";
103
+ /** The list has loaded (or failed) and the reopened chat is chosen. */
104
+ ready: boolean;
105
+ whenReady: () => Promise<void>;
56
106
  reloadList: () => void;
57
107
  activeId: string | null;
58
108
  stored: string | null;
@@ -67,7 +117,7 @@ export declare function useChatApp({ store, transport, makeTitle, conversationId
67
117
  unqueue: (qid: string) => void;
68
118
  problem: ChatAppProblem | null;
69
119
  clearProblem: () => void;
70
- send: (text: string, attachments?: Attachment[]) => Promise<boolean>;
120
+ send: (text: string, attachments?: Attachment[], options?: SendOptions) => Promise<boolean>;
71
121
  stop: () => void;
72
122
  retry: () => void;
73
123
  open: (id: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"use-chat-app.d.ts","sourceRoot":"","sources":["../../src/react/use-chat-app.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAEzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE7D,oDAAoD;AACpD,MAAM,MAAM,QAAQ,GAAG;IACrB,8EAA8E;IAC9E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,mEAAmE;IACnE,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,+EAA+E;IAC/E,MAAM,EAAE,WAAW,CAAC;IACpB;yEACqE;IACrE,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEnG;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAExE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEtD,KAAK,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAG5F,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,GAAG,IAAI,GAAG,SAAS,YAAsC,CAAC;AA6BlG,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,aAAa,CAAC;IACzB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAChG,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,oBAAoB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACnD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,SAAS,EACT,SAAS,EACT,cAAc,EACd,oBAAoB,EACpB,WAAW,GACZ,EAAE,iBAAiB;;;;;;;;;;;;;;mBAiUD,MAAM;;;iBAvGG,MAAM,gBAAe,UAAU,EAAE,KAAQ,OAAO,CAAC,OAAO,CAAC;;;eAnKjE,MAAM;;;iBAgOE,MAAM,SAAS,MAAM;iBASrB,MAAM;EA6CjC;AAED,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC"}
1
+ {"version":3,"file":"use-chat-app.d.ts","sourceRoot":"","sources":["../../src/react/use-chat-app.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,45 @@
1
1
  "use client";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { deriveTitle, newId, pickRestored, } from "../conversations.js";
4
+ /**
5
+ * Throw this from a transport to say why a turn failed in the reader's
6
+ * words: the failed bubble shows `message`, and `kind` (the product's own
7
+ * code, e.g. "quota" or "no-vision") travels with that message as
8
+ * `failure.kind` for a footer to link the fix. Any other error shows the
9
+ * thread's `failed` label — a raw `TypeError: Failed to fetch` is never
10
+ * shown to a reader — unless ChatApp's `failureMessage` maps it.
11
+ */
12
+ export class ChatTurnError extends Error {
13
+ kind;
14
+ constructor(message, options = {}) {
15
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
16
+ this.name = "ChatTurnError";
17
+ this.kind = options.kind;
18
+ }
19
+ }
20
+ /**
21
+ * What a failed turn keeps: a `ChatTurnError`'s words and kind, a plain
22
+ * `{ message, kind }` object as thrown, or what `map` makes of anything
23
+ * else. Never an ordinary Error's message — that is for the console.
24
+ */
25
+ export function chatTurnFailure(error, map) {
26
+ if (error instanceof ChatTurnError) {
27
+ return { message: error.message, ...(error.kind ? { kind: error.kind } : {}) };
28
+ }
29
+ const mapped = map?.(error);
30
+ if (mapped)
31
+ return { message: mapped };
32
+ if (error && typeof error === "object" && !(error instanceof Error)) {
33
+ const { message, kind } = error;
34
+ const out = {};
35
+ if (typeof message === "string" && message.trim())
36
+ out.message = message;
37
+ if (typeof kind === "string" && kind)
38
+ out.kind = kind;
39
+ return out;
40
+ }
41
+ return {};
42
+ }
4
43
  const TEMP = "tmp";
5
44
  export const isTemporaryId = (id) => !!id && id.startsWith(`${TEMP}-`);
6
45
  function messageAttachments(list) {
@@ -38,7 +77,7 @@ function writeRemembered(key, id) {
38
77
  * and temporary chats, turns (one per conversation, each running on when the
39
78
  * person opens another chat), the queue, titles and the reopen-on-reload.
40
79
  */
41
- export function useChatApp({ store, transport, makeTitle, conversationId, onConversationChange, rememberKey, }) {
80
+ export function useChatApp({ store, transport, makeTitle, conversationId, onConversationChange, rememberKey, stopKeepsPartial = true, failureMessage, onTemporaryEnd, restore = true, }) {
42
81
  const [list, setList] = useState([]);
43
82
  const [listState, setListState] = useState("loading");
44
83
  const [activeId, setActiveId] = useState(null);
@@ -50,6 +89,7 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
50
89
  const [queue, setQueue] = useState([]);
51
90
  const [problem, setProblem] = useState(null);
52
91
  const [reload, setReload] = useState(0);
92
+ const [ready, setReady] = useState(false);
53
93
  // Async work reads the CURRENT state through these, never a stale closure.
54
94
  const threadsRef = useRef(threads);
55
95
  const queueRef = useRef(queue);
@@ -59,10 +99,32 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
59
99
  const restored = useRef(false);
60
100
  const transportRef = useRef(transport);
61
101
  const titleRef = useRef(makeTitle);
102
+ const optionsRef = useRef({ stopKeepsPartial, failureMessage, onTemporaryEnd });
62
103
  useEffect(() => {
63
104
  transportRef.current = transport;
64
105
  titleRef.current = makeTitle;
106
+ optionsRef.current = { stopKeepsPartial, failureMessage, onTemporaryEnd };
65
107
  });
108
+ // The files of each person's message, by its id, kept until its answer
109
+ // lands: Retry after a failure sends the pictures again, not only the words.
110
+ const turnFiles = useRef(new Map());
111
+ const ended = useRef(new Set());
112
+ const readyWaiters = useRef([]);
113
+ const markReady = () => {
114
+ restored.current = true;
115
+ setReady(true);
116
+ for (const go of readyWaiters.current.splice(0))
117
+ go();
118
+ };
119
+ /** Resolves once the list has loaded and the reopened chat is chosen, so a
120
+ * message sent from outside never lands before the restore moves away. */
121
+ const whenReady = () => restored.current ? Promise.resolve() : new Promise((go) => readyWaiters.current.push(go));
122
+ const endTemporary = (id) => {
123
+ if (!isTemporaryId(id) || ended.current.has(id))
124
+ return;
125
+ ended.current.add(id);
126
+ optionsRef.current.onTemporaryEnd?.(id);
127
+ };
66
128
  const setThreadsNow = (fn) => {
67
129
  threadsRef.current = fn(threadsRef.current);
68
130
  setThreads(threadsRef.current);
@@ -83,13 +145,28 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
83
145
  if (!isTemporaryId(id))
84
146
  return;
85
147
  controllers.current.get(id)?.abort();
148
+ for (const m of threadsRef.current[id] ?? [])
149
+ turnFiles.current.delete(m.id);
86
150
  setThreadsNow((t) => {
87
151
  const next = { ...t };
88
152
  delete next[id];
89
153
  return next;
90
154
  });
91
155
  setQueueNow((q) => q.filter((item) => item.conversation !== id));
156
+ endTemporary(id);
92
157
  };
158
+ // A temporary chat also ends when the page goes away or ChatApp unmounts.
159
+ // (StrictMode's rehearsal unmount finds no temporary chat: one only exists
160
+ // after a first message.)
161
+ useEffect(() => {
162
+ const hide = () => endTemporary(activeRef.current);
163
+ globalThis.addEventListener?.("pagehide", hide);
164
+ return () => {
165
+ globalThis.removeEventListener?.("pagehide", hide);
166
+ hide();
167
+ };
168
+ // Reads everything through refs.
169
+ }, []);
93
170
  const open = (id) => {
94
171
  if (id === activeRef.current)
95
172
  return;
@@ -126,17 +203,21 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
126
203
  setList(rows);
127
204
  setListState("ready");
128
205
  if (!restored.current) {
129
- restored.current = true;
130
- const id = pickRestored(rows, conversationId, readRemembered(rememberKey));
131
- if (id)
206
+ const id = restore
207
+ ? pickRestored(rows, conversationId, readRemembered(rememberKey))
208
+ : null;
209
+ // An outside send may already have opened a chat: never move off it.
210
+ if (id && activeRef.current === null)
132
211
  open(id);
212
+ markReady();
133
213
  }
134
214
  })
135
215
  .catch(() => {
136
216
  if (!alive)
137
217
  return;
138
- restored.current = true;
139
218
  setListState("error");
219
+ if (!restored.current)
220
+ markReady();
140
221
  });
141
222
  return () => {
142
223
  alive = false;
@@ -181,9 +262,12 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
181
262
  setLive((l) => ({ ...l, [id]: {} }));
182
263
  let soFar = {};
183
264
  let ok = false;
265
+ let keepPartial = null;
266
+ const asked = [...(threadsRef.current[id] ?? [])].reverse().find((m) => m.role === "user");
184
267
  try {
185
268
  const result = await transportRef.current({
186
269
  conversationId: temp ? null : id,
270
+ chatId: id,
187
271
  temporary: temp,
188
272
  text,
189
273
  attachments,
@@ -194,6 +278,9 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
194
278
  if (!controller.signal.aborted)
195
279
  setLive((l) => (id in l ? { ...l, [id]: soFar } : l));
196
280
  },
281
+ stopKeepsPartial: (keep) => {
282
+ keepPartial = keep;
283
+ },
197
284
  });
198
285
  if (controller.signal.aborted)
199
286
  throw new DOMException("stopped", "AbortError");
@@ -202,6 +289,8 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
202
289
  push(id, message);
203
290
  save(id, message);
204
291
  ok = true;
292
+ if (asked)
293
+ turnFiles.current.delete(asked.id);
205
294
  if (!temp && history.length === 0 && titleRef.current) {
206
295
  void Promise.resolve(titleRef.current({ text, answer: answer.content }))
207
296
  .then((title) => (title?.trim() ? rename(id, title.trim()) : undefined))
@@ -210,10 +299,14 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
210
299
  });
211
300
  }
212
301
  }
213
- catch {
302
+ catch (error) {
214
303
  if (controller.signal.aborted) {
215
- // Stopped: what had arrived is kept, as the apps keep a half answer.
216
- if (soFar.text?.trim()) {
304
+ // Stopped: what had arrived is kept, as the apps keep a half answer —
305
+ // unless the product says this turn's text is not fit to keep.
306
+ const keep = keepPartial ?? optionsRef.current.stopKeepsPartial;
307
+ if (keep && soFar.text?.trim()) {
308
+ if (asked)
309
+ turnFiles.current.delete(asked.id);
217
310
  const message = {
218
311
  id: newId("m"),
219
312
  role: "assistant",
@@ -226,7 +319,14 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
226
319
  setStopped((s) => ({ ...s, [id]: true }));
227
320
  }
228
321
  else {
229
- push(id, { id: newId("m"), role: "assistant", content: "", failed: true });
322
+ const failure = chatTurnFailure(error, optionsRef.current.failureMessage);
323
+ push(id, {
324
+ id: newId("m"),
325
+ role: "assistant",
326
+ content: failure.message ?? "",
327
+ failed: true,
328
+ ...(failure.message || failure.kind ? { failure } : {}),
329
+ });
230
330
  }
231
331
  }
232
332
  finally {
@@ -243,27 +343,33 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
243
343
  const next = queueRef.current.find((q) => q.conversation === id);
244
344
  if (next) {
245
345
  setQueueNow((q) => q.filter((item) => item.id !== next.id));
246
- void sendIn(id, next.text, next.attachments);
346
+ void sendIn(id, next.text, next.attachments, next.chips);
247
347
  }
248
348
  }
249
349
  };
250
- const sendIn = async (id, text, attachments) => {
350
+ const sendIn = async (id, text, attachments, chips) => {
251
351
  const history = (threadsRef.current[id] ?? []).filter((m) => !m.failed);
252
352
  const message = {
253
353
  id: newId("m"),
254
354
  role: "user",
255
355
  content: text,
256
356
  ...(attachments.length ? { attachments: messageAttachments(attachments) } : {}),
357
+ ...(chips && chips.length ? { chips } : {}),
257
358
  };
359
+ if (attachments.length)
360
+ turnFiles.current.set(message.id, attachments);
258
361
  push(id, message);
259
362
  save(id, message);
260
363
  await runTurn(id, text, attachments, history);
261
364
  };
262
365
  /** Composer's onSend. Resolves false to keep the draft (nothing was sent). */
263
- const send = async (text, attachments = []) => {
366
+ const send = async (text, attachments = [], options = {}) => {
367
+ const { chips } = options;
368
+ if (options.newChat)
369
+ newChat();
264
370
  let id = activeRef.current;
265
371
  if (id && controllers.current.has(id)) {
266
- setQueueNow((q) => [...q, { id: newId("q"), conversation: id, text, attachments }]);
372
+ setQueueNow((q) => [...q, { id: newId("q"), conversation: id, text, attachments, chips }]);
267
373
  return true;
268
374
  }
269
375
  if (!id) {
@@ -284,7 +390,7 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
284
390
  setThreadsNow((t) => ({ ...t, [id]: [] }));
285
391
  select(id, temporaryRef.current);
286
392
  }
287
- void sendIn(id, text, attachments);
393
+ void sendIn(id, text, attachments, chips);
288
394
  return true;
289
395
  };
290
396
  const stop = () => {
@@ -304,7 +410,7 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
304
410
  return;
305
411
  setThreadsNow((t) => ({ ...t, [id]: kept }));
306
412
  const history = kept.slice(0, kept.indexOf(lastUser));
307
- void runTurn(id, lastUser.content, [], history);
413
+ void runTurn(id, lastUser.content, turnFiles.current.get(lastUser.id) ?? [], history);
308
414
  };
309
415
  const newChat = () => {
310
416
  leaveTemporary();
@@ -332,6 +438,8 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
332
438
  };
333
439
  const remove = async (id) => {
334
440
  controllers.current.get(id)?.abort();
441
+ for (const m of threadsRef.current[id] ?? [])
442
+ turnFiles.current.delete(m.id);
335
443
  setList((l) => l.filter((c) => c.id !== id));
336
444
  setThreadsNow((t) => {
337
445
  const next = { ...t };
@@ -353,6 +461,9 @@ export function useChatApp({ store, transport, makeTitle, conversationId, onConv
353
461
  return {
354
462
  list,
355
463
  listState,
464
+ /** The list has loaded (or failed) and the reopened chat is chosen. */
465
+ ready,
466
+ whenReady,
356
467
  reloadList: () => setReload((n) => n + 1),
357
468
  activeId,
358
469
  stored,