@pipecat-ai/client-react 1.0.1 → 1.2.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.
package/dist/index.js CHANGED
@@ -18,15 +18,24 @@ $parcel$export(module.exports, "PipecatClientAudio", () => $a2a1c5f475114d56$exp
18
18
  $parcel$export(module.exports, "PipecatClientCamToggle", () => $666ceba11158b494$export$dc9a029eeca8213f);
19
19
  $parcel$export(module.exports, "PipecatClientMicToggle", () => $1a9931980e271aa3$export$bc8133b69ff660a2);
20
20
  $parcel$export(module.exports, "PipecatClientProvider", () => $8df0e777e4d7dd49$export$bb43666ced7a20d0);
21
+ $parcel$export(module.exports, "PipecatClientScreenShareToggle", () => $0a9d1dc5be2e1cc2$export$93764714dfab3a46);
21
22
  $parcel$export(module.exports, "PipecatClientVideo", () => $0f97689637ada1d8$export$85974db6d0cc43b3);
22
23
  $parcel$export(module.exports, "usePipecatClient", () => $172f489fc5d91d99$export$777fa8498be78705);
23
24
  $parcel$export(module.exports, "usePipecatClientCamControl", () => $d9b24817de62910a$export$3ea2601427f0430f);
24
25
  $parcel$export(module.exports, "usePipecatClientMediaDevices", () => $9bd3e7d3a9d7acd1$export$642bc4d2d2a376f1);
25
26
  $parcel$export(module.exports, "usePipecatClientMediaTrack", () => $630203d8dad1dd45$export$9813dcd2d0c26814);
26
27
  $parcel$export(module.exports, "usePipecatClientMicControl", () => $4af0eba414c586fd$export$388e706586309ef0);
28
+ $parcel$export(module.exports, "usePipecatClientScreenShareControl", () => $5fc67d7ca05dec34$export$be63b19bd7f7d4f5);
27
29
  $parcel$export(module.exports, "usePipecatClientTransportState", () => $810478f6ae107062$export$30aee278309a867b);
28
30
  $parcel$export(module.exports, "useRTVIClientEvent", () => $8a6b68ebf0332682$export$33a6ac53b8f02625);
29
31
  $parcel$export(module.exports, "VoiceVisualizer", () => $a1dfa75b13e6bb9b$export$59bf27bd43679db6);
32
+ $parcel$export(module.exports, "useConversationContext", () => $984246ab4e966dee$export$8eec679bf8249822);
33
+ $parcel$export(module.exports, "usePipecatConversation", () => $fe6dfdd0b8560c78$export$acb832b064cb6c09);
34
+ $parcel$export(module.exports, "deduplicateFunctionCalls", () => $cbb62fb16c5d45ff$export$dfa183f99573a822);
35
+ $parcel$export(module.exports, "filterEmptyMessages", () => $cbb62fb16c5d45ff$export$3d3951a55577516);
36
+ $parcel$export(module.exports, "isMessageEmpty", () => $cbb62fb16c5d45ff$export$f4610a88b89838b);
37
+ $parcel$export(module.exports, "mergeMessages", () => $cbb62fb16c5d45ff$export$23a96e868837e158);
38
+ $parcel$export(module.exports, "sortByCreatedAt", () => $cbb62fb16c5d45ff$export$cbefc5865f8cbd89);
30
39
  /**
31
40
  * Copyright (c) 2024, Daily.
32
41
  *
@@ -53,13 +62,734 @@ $parcel$export(module.exports, "VoiceVisualizer", () => $a1dfa75b13e6bb9b$export
53
62
 
54
63
 
55
64
  var $d1f89db81f967f39$exports = {};
56
- $d1f89db81f967f39$exports = JSON.parse("{\"name\":\"@pipecat-ai/client-react\",\"version\":\"1.0.1\",\"license\":\"BSD-2-Clause\",\"main\":\"dist/index.js\",\"module\":\"dist/index.module.js\",\"types\":\"dist/index.d.ts\",\"source\":\"src/index.ts\",\"repository\":{\"type\":\"git\",\"url\":\"git+https://github.com/pipecat-ai/pipecat-client-web.git\"},\"files\":[\"dist\",\"package.json\",\"README.md\"],\"scripts\":{\"build\":\"parcel build --no-cache\",\"dev\":\"parcel watch\",\"lint\":\"eslint . --report-unused-disable-directives --max-warnings 0 --ignore-pattern 'dist/'\"},\"devDependencies\":{\"@pipecat-ai/client-js\":\"*\",\"@types/react\":\"^18.3.3\",\"@types/react-dom\":\"^18.3.0\",\"@typescript-eslint/eslint-plugin\":\"^8.32.0\",\"eslint\":\"^9.11.1\",\"eslint-config-prettier\":\"^9.1.0\",\"eslint-plugin-react-hooks\":\"^5.2.0\",\"eslint-plugin-simple-import-sort\":\"^12.1.1\",\"parcel\":\"^2.12.0\",\"react\":\"^18.3.1\",\"react-dom\":\"^18.3.1\",\"typescript\":\"^5.2.2\"},\"peerDependencies\":{\"@pipecat-ai/client-js\":\"*\",\"react\":\">=18\",\"react-dom\":\">=18\"},\"dependencies\":{\"jotai\":\"^2.9.0\"}}");
65
+ $d1f89db81f967f39$exports = JSON.parse("{\"name\":\"@pipecat-ai/client-react\",\"version\":\"1.2.0\",\"license\":\"BSD-2-Clause\",\"main\":\"dist/index.js\",\"module\":\"dist/index.module.js\",\"types\":\"dist/index.d.ts\",\"source\":\"src/index.ts\",\"repository\":{\"type\":\"git\",\"url\":\"git+https://github.com/pipecat-ai/pipecat-client-web.git\"},\"exports\":{\".\":{\"types\":\"./dist/index.d.ts\",\"import\":\"./dist/index.module.js\",\"require\":\"./dist/index.js\"}},\"files\":[\"dist\",\"package.json\",\"README.md\"],\"scripts\":{\"build\":\"parcel build --no-cache\",\"dev\":\"parcel watch\",\"lint\":\"eslint . --report-unused-disable-directives --max-warnings 0 --ignore-pattern 'dist/'\",\"test\":\"jest\"},\"jest\":{\"preset\":\"ts-jest\",\"testEnvironment\":\"jsdom\",\"moduleNameMapper\":{\"^@/(.*)$\":\"<rootDir>/src/$1\"},\"transform\":{\"^.+\\\\.tsx?$\":[\"ts-jest\",{\"tsconfig\":{\"jsx\":\"react-jsx\",\"module\":\"commonjs\",\"moduleResolution\":\"node\",\"esModuleInterop\":true,\"allowImportingTsExtensions\":false,\"noUnusedLocals\":false,\"noUnusedParameters\":false}}]}},\"devDependencies\":{\"@jest/globals\":\"^30.2.0\",\"@pipecat-ai/client-js\":\"*\",\"@types/jest\":\"^30.0.0\",\"@types/react\":\"^18.3.3\",\"@types/react-dom\":\"^18.3.0\",\"@typescript-eslint/eslint-plugin\":\"^8.32.0\",\"eslint\":\"^9.11.1\",\"eslint-config-prettier\":\"^9.1.0\",\"eslint-plugin-react-hooks\":\"^5.2.0\",\"eslint-plugin-simple-import-sort\":\"^12.1.1\",\"jest\":\"^30.2.0\",\"jest-environment-jsdom\":\"^30.2.0\",\"parcel\":\"^2.12.0\",\"react\":\"^18.3.1\",\"react-dom\":\"^18.3.1\",\"ts-jest\":\"^29.4.6\",\"typescript\":\"^5.2.2\"},\"peerDependencies\":{\"@pipecat-ai/client-js\":\"*\",\"react\":\">=18\",\"react-dom\":\">=18\"},\"dependencies\":{\"jotai\":\"^2.9.0\"}}");
57
66
 
58
67
 
59
68
 
60
69
 
61
70
 
62
71
 
72
+ /**
73
+ * Copyright (c) 2024, Daily.
74
+ *
75
+ * SPDX-License-Identifier: BSD-2-Clause
76
+ */ /**
77
+ * Copyright (c) 2024, Daily.
78
+ *
79
+ * SPDX-License-Identifier: BSD-2-Clause
80
+ */ const $ad0c8ea1ee293a79$var$normalizeForMatching = (text)=>{
81
+ return text.toLowerCase().replace(/[^\w\s]/g, "");
82
+ };
83
+ const $ad0c8ea1ee293a79$var$skipWhitespace = (text, start)=>{
84
+ let i = start;
85
+ while(i < text.length && /\s/.test(text[i]))i++;
86
+ return i;
87
+ };
88
+ /**
89
+ * Finds where `spoken` appears in `unspoken`, starting from `startPosition`.
90
+ * - Best-effort sequential word matching (normalized, punctuation-stripped)
91
+ * - Returns the original start position on mismatch (no advancement)
92
+ */ const $ad0c8ea1ee293a79$var$findSpokenPositionInUnspoken = (spoken, unspoken, startPosition)=>{
93
+ if (!spoken || !unspoken || startPosition >= unspoken.length) return startPosition;
94
+ // If spoken text includes a leading separator space, skip leading whitespace in unspoken.
95
+ let actualStart = startPosition;
96
+ let spokenForMatching = spoken;
97
+ if (spoken.startsWith(" ") && startPosition < unspoken.length) {
98
+ actualStart = $ad0c8ea1ee293a79$var$skipWhitespace(unspoken, startPosition);
99
+ spokenForMatching = spoken.trimStart();
100
+ } else if (startPosition === 0 && startPosition < unspoken.length) // If we're at the start, also skip leading whitespace (e.g. newlines)
101
+ actualStart = $ad0c8ea1ee293a79$var$skipWhitespace(unspoken, 0);
102
+ const remainder = unspoken.slice(actualStart);
103
+ // Sentence-level: if spoken exactly matches the remainder (normalized), consume the whole part
104
+ // so we never leave a word unspoken due to word-matching edge cases.
105
+ if ($ad0c8ea1ee293a79$var$normalizeForMatching(spokenForMatching).trim() === $ad0c8ea1ee293a79$var$normalizeForMatching(remainder).trim()) return unspoken.length;
106
+ const spokenWords = $ad0c8ea1ee293a79$var$normalizeForMatching(spokenForMatching).split(/\s+/).filter(Boolean);
107
+ if (spokenWords.length === 0) return actualStart;
108
+ const unspokenWords = $ad0c8ea1ee293a79$var$normalizeForMatching(unspoken.slice(actualStart)).split(/\s+/).filter(Boolean);
109
+ // Sequential match, allowing prefix match for contractions (e.g. "I" vs "I'm")
110
+ // and limited skipping of mismatched unspoken words (e.g. punctuation artifacts).
111
+ let matchedWords = 0;
112
+ let consecutiveSkips = 0;
113
+ const MAX_CONSECUTIVE_SKIPS = 2;
114
+ for(let i = 0; i < unspokenWords.length && matchedWords < spokenWords.length; i++){
115
+ const target = spokenWords[matchedWords];
116
+ const candidate = unspokenWords[i];
117
+ if (candidate === target || candidate.startsWith(target)) {
118
+ matchedWords++;
119
+ consecutiveSkips = 0;
120
+ continue;
121
+ }
122
+ consecutiveSkips++;
123
+ if (consecutiveSkips > MAX_CONSECUTIVE_SKIPS) return actualStart;
124
+ // Skip this unspoken word and try matching the next one
125
+ }
126
+ if (matchedWords < spokenWords.length) return actualStart;
127
+ // Convert word matches back into a character position in the original unspoken string.
128
+ const isWordChar = (char)=>/[a-zA-Z0-9]/.test(char);
129
+ let wordCount = 0;
130
+ let i = actualStart;
131
+ let inWord = false;
132
+ while(i < unspoken.length){
133
+ const charIsWord = isWordChar(unspoken[i]);
134
+ if (charIsWord && !inWord) {
135
+ inWord = true;
136
+ wordCount++;
137
+ if (wordCount === matchedWords) {
138
+ // Consume the rest of this word
139
+ i++;
140
+ while(i < unspoken.length && isWordChar(unspoken[i]))i++;
141
+ // Include any punctuation after the word until the next space, then include the space
142
+ while(i < unspoken.length){
143
+ if (unspoken[i] === " ") {
144
+ i++;
145
+ break;
146
+ }
147
+ i++;
148
+ }
149
+ return i;
150
+ }
151
+ } else if (!charIsWord && inWord) inWord = false;
152
+ i++;
153
+ }
154
+ return unspoken.length;
155
+ };
156
+ function $ad0c8ea1ee293a79$export$802e9b947136afbf(cursor, parts) {
157
+ if (parts.length === 0) return false;
158
+ for(let i = 0; i < parts.length; i++){
159
+ if (typeof parts[i]?.text !== "string") continue;
160
+ if (!cursor.partFinalFlags[i]) return true;
161
+ }
162
+ return false;
163
+ }
164
+ function $ad0c8ea1ee293a79$export$9b39fd8438081d6d(cursor, parts, spokenText) {
165
+ if (parts.length === 0) return false;
166
+ // Find the next part that should be spoken (skip parts already marked final/skipped)
167
+ let partToMatch = cursor.currentPartIndex;
168
+ while(partToMatch < parts.length && cursor.partFinalFlags[partToMatch])partToMatch++;
169
+ if (partToMatch >= parts.length) return false;
170
+ if (partToMatch > cursor.currentPartIndex) {
171
+ cursor.currentPartIndex = partToMatch;
172
+ cursor.currentCharIndex = 0;
173
+ }
174
+ const currentPart = parts[cursor.currentPartIndex];
175
+ if (typeof currentPart.text !== "string") return false;
176
+ const partText = currentPart.text;
177
+ const startChar = cursor.currentCharIndex;
178
+ const newPosition = $ad0c8ea1ee293a79$var$findSpokenPositionInUnspoken(spokenText, partText, startChar);
179
+ const whitespaceEnd = $ad0c8ea1ee293a79$var$skipWhitespace(partText, startChar);
180
+ if (newPosition > whitespaceEnd) {
181
+ cursor.currentCharIndex = newPosition;
182
+ if (newPosition >= partText.length) {
183
+ cursor.partFinalFlags[cursor.currentPartIndex] = true;
184
+ if (cursor.currentPartIndex < parts.length - 1) {
185
+ cursor.currentPartIndex++;
186
+ cursor.currentCharIndex = 0;
187
+ }
188
+ }
189
+ return true;
190
+ }
191
+ // Intra-part scan-ahead recovery: if matching failed at the current position,
192
+ // scan forward word-by-word within the same part. This prevents the cursor
193
+ // from getting permanently stuck mid-part when a single word mismatch occurs
194
+ // (e.g. TTS variation, punctuation boundary like `apexes."Sometimes`).
195
+ if (startChar > 0) {
196
+ const MAX_SCAN_WORDS = 8;
197
+ let scanPos = startChar;
198
+ for(let scan = 0; scan < MAX_SCAN_WORDS; scan++){
199
+ // Advance past current word
200
+ while(scanPos < partText.length && !/\s/.test(partText[scanPos]))scanPos++;
201
+ // Advance past whitespace to next word
202
+ while(scanPos < partText.length && /\s/.test(partText[scanPos]))scanPos++;
203
+ if (scanPos >= partText.length) break;
204
+ const retryPos = $ad0c8ea1ee293a79$var$findSpokenPositionInUnspoken(spokenText, partText, scanPos);
205
+ const scanWsEnd = $ad0c8ea1ee293a79$var$skipWhitespace(partText, scanPos);
206
+ if (retryPos > scanWsEnd) {
207
+ cursor.currentCharIndex = retryPos;
208
+ if (retryPos >= partText.length) {
209
+ cursor.partFinalFlags[cursor.currentPartIndex] = true;
210
+ if (cursor.currentPartIndex < parts.length - 1) {
211
+ cursor.currentPartIndex++;
212
+ cursor.currentCharIndex = 0;
213
+ }
214
+ }
215
+ return true;
216
+ }
217
+ }
218
+ }
219
+ // Mismatch recovery: try to find the spoken text in a later part.
220
+ for(let nextPartIdx = cursor.currentPartIndex + 1; nextPartIdx < parts.length; nextPartIdx++){
221
+ const nextPart = parts[nextPartIdx];
222
+ if (typeof nextPart.text !== "string") continue;
223
+ const match = $ad0c8ea1ee293a79$var$findSpokenPositionInUnspoken(spokenText, nextPart.text, 0);
224
+ const nextWhitespaceEnd = $ad0c8ea1ee293a79$var$skipWhitespace(nextPart.text, 0);
225
+ if (match > nextWhitespaceEnd) {
226
+ // Mark skipped parts as final and jump to the matched part
227
+ for(let i = cursor.currentPartIndex; i < nextPartIdx; i++)cursor.partFinalFlags[i] = true;
228
+ cursor.currentPartIndex = nextPartIdx;
229
+ cursor.currentCharIndex = match;
230
+ return true;
231
+ }
232
+ }
233
+ // If we're stuck at the start, mark the current part as skipped to avoid deadlock.
234
+ if (startChar === 0 && cursor.currentPartIndex < parts.length - 1) {
235
+ cursor.partFinalFlags[cursor.currentPartIndex] = true;
236
+ cursor.currentPartIndex++;
237
+ cursor.currentCharIndex = 0;
238
+ return true;
239
+ }
240
+ return false;
241
+ }
242
+
243
+
244
+ /**
245
+ * Copyright (c) 2024, Daily.
246
+ *
247
+ * SPDX-License-Identifier: BSD-2-Clause
248
+ */
249
+ const $28e6b30207a0392e$export$92a4076839a978d0 = (0, $5Zyvw$jotai.atom)([]);
250
+ const $28e6b30207a0392e$export$bd01e11bc95333a1 = (0, $5Zyvw$jotai.atom)(new Map());
251
+ const $28e6b30207a0392e$export$b0cd27059c88d0ab = (0, $5Zyvw$jotai.atom)(new Map());
252
+ const $28e6b30207a0392e$export$57b1a1df06a7e728 = (0, $5Zyvw$jotai.atom)(null);
253
+
254
+
255
+ /**
256
+ * Copyright (c) 2024, Daily.
257
+ *
258
+ * SPDX-License-Identifier: BSD-2-Clause
259
+ */ // ES2020-compatible polyfills for findLast / findLastIndex
260
+ function $f4fa178c5adc67bb$export$8855a8be7bd3e9f8(arr, predicate) {
261
+ for(let i = arr.length - 1; i >= 0; i--){
262
+ if (predicate(arr[i], i, arr)) return i;
263
+ }
264
+ return -1;
265
+ }
266
+ function $f4fa178c5adc67bb$export$296de88ccac4bedb(arr, predicate) {
267
+ const idx = $f4fa178c5adc67bb$export$8855a8be7bd3e9f8(arr, predicate);
268
+ return idx === -1 ? undefined : arr[idx];
269
+ }
270
+
271
+
272
+ /** Max time gap (ms) between consecutive same-role messages for merging. */ const $cbb62fb16c5d45ff$var$MERGE_WINDOW_MS = 30000;
273
+ const $cbb62fb16c5d45ff$export$cbefc5865f8cbd89 = (a, b)=>{
274
+ return a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : 0;
275
+ };
276
+ const $cbb62fb16c5d45ff$export$f4610a88b89838b = (message)=>{
277
+ if (message.role === "function_call") return false;
278
+ const parts = message.parts || [];
279
+ if (parts.length === 0) return true;
280
+ return parts.every((p)=>{
281
+ if (typeof p.text === "string") return p.text.trim().length === 0;
282
+ // Check BotOutputText objects
283
+ if (typeof p.text === "object" && p.text !== null && "spoken" in p.text && "unspoken" in p.text) {
284
+ const botText = p.text;
285
+ return botText.spoken.trim().length === 0 && botText.unspoken.trim().length === 0;
286
+ }
287
+ // For ReactNode, consider it non-empty
288
+ return false;
289
+ });
290
+ };
291
+ const $cbb62fb16c5d45ff$export$3d3951a55577516 = (messages)=>{
292
+ return messages.filter((message, index, array)=>{
293
+ if (!$cbb62fb16c5d45ff$export$f4610a88b89838b(message)) return true;
294
+ // For empty messages, keep only if no following non-empty message with same role
295
+ const nextMessageWithSameRole = array.slice(index + 1).find((m)=>m.role === message.role && !$cbb62fb16c5d45ff$export$f4610a88b89838b(m));
296
+ return !nextMessageWithSameRole;
297
+ });
298
+ };
299
+ const $cbb62fb16c5d45ff$export$23a96e868837e158 = (messages)=>{
300
+ const mergedMessages = [];
301
+ for(let i = 0; i < messages.length; i++){
302
+ const currentMessage = messages[i];
303
+ const lastMerged = mergedMessages[mergedMessages.length - 1];
304
+ const timeDiff = lastMerged ? Math.abs(new Date(currentMessage.createdAt).getTime() - new Date(lastMerged.createdAt).getTime()) : Infinity;
305
+ const shouldMerge = lastMerged && lastMerged.role === currentMessage.role && currentMessage.role !== "system" && currentMessage.role !== "function_call" && timeDiff < $cbb62fb16c5d45ff$var$MERGE_WINDOW_MS;
306
+ if (shouldMerge) mergedMessages[mergedMessages.length - 1] = {
307
+ ...lastMerged,
308
+ parts: [
309
+ ...lastMerged.parts || [],
310
+ ...currentMessage.parts || []
311
+ ],
312
+ updatedAt: currentMessage.updatedAt || currentMessage.createdAt,
313
+ final: currentMessage.final !== false
314
+ };
315
+ else mergedMessages.push(currentMessage);
316
+ }
317
+ return mergedMessages;
318
+ };
319
+ const $cbb62fb16c5d45ff$var$statusPriority = {
320
+ started: 0,
321
+ in_progress: 1,
322
+ completed: 2
323
+ };
324
+ const $cbb62fb16c5d45ff$export$dfa183f99573a822 = (messages)=>{
325
+ const bestByToolCallId = new Map();
326
+ const toRemove = new Set();
327
+ for(let i = 0; i < messages.length; i++){
328
+ const msg = messages[i];
329
+ const tcid = msg.functionCall?.tool_call_id;
330
+ if (msg.role !== "function_call" || !tcid) continue;
331
+ const existingIdx = bestByToolCallId.get(tcid);
332
+ if (existingIdx !== undefined) {
333
+ const existingPriority = $cbb62fb16c5d45ff$var$statusPriority[messages[existingIdx].functionCall.status] ?? 0;
334
+ const currentPriority = $cbb62fb16c5d45ff$var$statusPriority[msg.functionCall.status] ?? 0;
335
+ if (currentPriority >= existingPriority) {
336
+ toRemove.add(existingIdx);
337
+ bestByToolCallId.set(tcid, i);
338
+ } else toRemove.add(i);
339
+ } else bestByToolCallId.set(tcid, i);
340
+ }
341
+ if (toRemove.size === 0) return messages;
342
+ return messages.filter((_, i)=>!toRemove.has(i));
343
+ };
344
+ const $cbb62fb16c5d45ff$var$normalizeMessagesForUI = (messages)=>{
345
+ return $cbb62fb16c5d45ff$export$23a96e868837e158($cbb62fb16c5d45ff$export$dfa183f99573a822($cbb62fb16c5d45ff$export$3d3951a55577516([
346
+ ...messages
347
+ ].sort($cbb62fb16c5d45ff$export$cbefc5865f8cbd89))));
348
+ };
349
+ // ---------------------------------------------------------------------------
350
+ // Internal helpers
351
+ // ---------------------------------------------------------------------------
352
+ const $cbb62fb16c5d45ff$var$callCallbacks = (callbacksMap, type, message)=>{
353
+ callbacksMap.forEach((callbacks)=>{
354
+ try {
355
+ callbacks[type]?.(message);
356
+ } catch (error) {
357
+ console.error(`Error in ${type} callback:`, error);
358
+ }
359
+ });
360
+ };
361
+ function $cbb62fb16c5d45ff$export$f2820c5e040afe17(get, set, id, callbacks) {
362
+ const map = new Map(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)));
363
+ map.set(id, callbacks);
364
+ set((0, $28e6b30207a0392e$export$b0cd27059c88d0ab), map);
365
+ }
366
+ function $cbb62fb16c5d45ff$export$46ae1ba121fdc956(get, set, id) {
367
+ const map = new Map(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)));
368
+ map.delete(id);
369
+ set((0, $28e6b30207a0392e$export$b0cd27059c88d0ab), map);
370
+ }
371
+ function $cbb62fb16c5d45ff$export$f2434643f2abff11(_get, set) {
372
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), []);
373
+ set((0, $28e6b30207a0392e$export$bd01e11bc95333a1), new Map());
374
+ }
375
+ function $cbb62fb16c5d45ff$export$16fdb433d434f08(get, set, messageData) {
376
+ const now = new Date();
377
+ const message = {
378
+ ...messageData,
379
+ createdAt: now.toISOString(),
380
+ updatedAt: now.toISOString()
381
+ };
382
+ const current = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
383
+ const updatedMessages = [
384
+ ...current,
385
+ message
386
+ ];
387
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(updatedMessages);
388
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageCreated", message);
389
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
390
+ }
391
+ function $cbb62fb16c5d45ff$export$a05f96a2aa40873e(get, set, role, updates) {
392
+ const messages = [
393
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
394
+ ];
395
+ const lastMessageIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === role);
396
+ if (lastMessageIndex === -1) return;
397
+ const existing = messages[lastMessageIndex];
398
+ const updatedMessage = {
399
+ ...existing,
400
+ ...updates,
401
+ updatedAt: new Date().toISOString()
402
+ };
403
+ messages[lastMessageIndex] = updatedMessage;
404
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
405
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageUpdated", updatedMessage);
406
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
407
+ }
408
+ function $cbb62fb16c5d45ff$export$78bd074993ab081f(get, set, role) {
409
+ const messages = [
410
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
411
+ ];
412
+ const lastMessageIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === role);
413
+ if (lastMessageIndex === -1) return;
414
+ const lastMessage = messages[lastMessageIndex];
415
+ // Check if message is empty
416
+ if ($cbb62fb16c5d45ff$export$f4610a88b89838b(lastMessage)) // Remove empty message only if it has no text in streams either
417
+ messages.splice(lastMessageIndex, 1);
418
+ else {
419
+ // Finalize message and its last part
420
+ const parts = [
421
+ ...lastMessage.parts || []
422
+ ];
423
+ if (parts.length > 0) parts[parts.length - 1] = {
424
+ ...parts[parts.length - 1],
425
+ final: true
426
+ };
427
+ messages[lastMessageIndex] = {
428
+ ...lastMessage,
429
+ parts: parts,
430
+ final: true,
431
+ updatedAt: new Date().toISOString()
432
+ };
433
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageUpdated", messages[lastMessageIndex]);
434
+ }
435
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
436
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
437
+ }
438
+ function $cbb62fb16c5d45ff$export$cd74f809c89ea10c(get, set, role) {
439
+ const messages = [
440
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
441
+ ];
442
+ const lastMessageIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === role);
443
+ if (lastMessageIndex === -1) return;
444
+ const lastMessage = messages[lastMessageIndex];
445
+ if ($cbb62fb16c5d45ff$export$f4610a88b89838b(lastMessage)) {
446
+ messages.splice(lastMessageIndex, 1);
447
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
448
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
449
+ }
450
+ }
451
+ function $cbb62fb16c5d45ff$export$c3a007efc2f315a0(get, set, messageData) {
452
+ const now = new Date();
453
+ const message = {
454
+ role: messageData.role,
455
+ final: true,
456
+ parts: [
457
+ ...messageData.parts
458
+ ],
459
+ createdAt: now.toISOString(),
460
+ updatedAt: now.toISOString()
461
+ };
462
+ const current = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
463
+ const updatedMessages = [
464
+ ...current,
465
+ message
466
+ ];
467
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(updatedMessages);
468
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageCreated", message);
469
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
470
+ }
471
+ function $cbb62fb16c5d45ff$export$57de01211e3df548(get, set, text, final) {
472
+ const now = new Date();
473
+ const messages = [
474
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
475
+ ];
476
+ // Find last user message
477
+ const lastUserIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (m)=>m.role === "user");
478
+ if (lastUserIndex !== -1 && !messages[lastUserIndex].final) {
479
+ // Update existing user message
480
+ const target = {
481
+ ...messages[lastUserIndex]
482
+ };
483
+ const parts = Array.isArray(target.parts) ? [
484
+ ...target.parts
485
+ ] : [];
486
+ const lastPart = parts[parts.length - 1];
487
+ if (!lastPart || lastPart.final) // Start a new part
488
+ parts.push({
489
+ text: text,
490
+ final: final,
491
+ createdAt: now.toISOString()
492
+ });
493
+ else // Update in-progress part
494
+ parts[parts.length - 1] = {
495
+ ...lastPart,
496
+ text: text,
497
+ final: final
498
+ };
499
+ const updatedMessage = {
500
+ ...target,
501
+ parts: parts,
502
+ updatedAt: now.toISOString()
503
+ };
504
+ messages[lastUserIndex] = updatedMessage;
505
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
506
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageUpdated", updatedMessage);
507
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
508
+ return;
509
+ }
510
+ // Create a new user message initialized with this transcript
511
+ const newMessage = {
512
+ role: "user",
513
+ final: false,
514
+ parts: [
515
+ {
516
+ text: text,
517
+ final: final,
518
+ createdAt: now.toISOString()
519
+ }
520
+ ],
521
+ createdAt: now.toISOString(),
522
+ updatedAt: now.toISOString()
523
+ };
524
+ const updatedMessages = [
525
+ ...messages,
526
+ newMessage
527
+ ];
528
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(updatedMessages);
529
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageCreated", newMessage);
530
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
531
+ }
532
+ function $cbb62fb16c5d45ff$export$d78810e35f742a16(get, set, text, final, spoken, aggregatedBy) {
533
+ const now = new Date();
534
+ const messages = [
535
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
536
+ ];
537
+ const botOutputMessageState = new Map(get((0, $28e6b30207a0392e$export$bd01e11bc95333a1)));
538
+ const lastAssistantIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "assistant");
539
+ let messageId;
540
+ let isNewMessage = false;
541
+ if (lastAssistantIndex === -1) {
542
+ // Create new assistant message
543
+ isNewMessage = true;
544
+ messageId = now.toISOString();
545
+ const newMessage = {
546
+ role: "assistant",
547
+ final: final,
548
+ parts: [],
549
+ createdAt: messageId,
550
+ updatedAt: messageId
551
+ };
552
+ messages.push(newMessage);
553
+ // Initialize message state
554
+ botOutputMessageState.set(messageId, {
555
+ currentPartIndex: 0,
556
+ currentCharIndex: 0,
557
+ partFinalFlags: []
558
+ });
559
+ } else {
560
+ // Update existing assistant message
561
+ const lastMessage = messages[lastAssistantIndex];
562
+ messageId = lastMessage.createdAt;
563
+ messages[lastAssistantIndex] = {
564
+ ...lastMessage,
565
+ final: final ? true : lastMessage.final,
566
+ updatedAt: now.toISOString()
567
+ };
568
+ // Ensure message state exists
569
+ if (!botOutputMessageState.has(messageId)) botOutputMessageState.set(messageId, {
570
+ currentPartIndex: 0,
571
+ currentCharIndex: 0,
572
+ partFinalFlags: []
573
+ });
574
+ }
575
+ const messageState = botOutputMessageState.get(messageId);
576
+ const message = messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex];
577
+ const parts = [
578
+ ...message.parts || []
579
+ ];
580
+ if (!spoken) {
581
+ // UNSPOKEN EVENT: Create/update message parts immediately
582
+ // Only append when both current and last part are word-level; sentence-level
583
+ // and other types each get their own part so spoken events can match 1:1.
584
+ const isDefaultType = aggregatedBy === "sentence" || aggregatedBy === "word" || !aggregatedBy;
585
+ const lastPart = parts[parts.length - 1];
586
+ const shouldAppend = lastPart && aggregatedBy === "word" && lastPart.aggregatedBy === "word" && typeof lastPart.text === "string";
587
+ if (shouldAppend) {
588
+ // Append to last part (word-level only)
589
+ const lastPartText = lastPart.text;
590
+ const separator = lastPartText && !lastPartText.endsWith(" ") && !text.startsWith(" ") ? " " : "";
591
+ parts[parts.length - 1] = {
592
+ ...lastPart,
593
+ text: lastPartText + separator + text
594
+ };
595
+ } else {
596
+ // Create new part (sentence-level, custom types, or first word chunk)
597
+ // Default to inline; custom types get displayMode from metadata in the hook
598
+ const defaultDisplayMode = isDefaultType ? "inline" : undefined;
599
+ const newPart = {
600
+ text: text,
601
+ final: false,
602
+ createdAt: now.toISOString(),
603
+ aggregatedBy: aggregatedBy,
604
+ displayMode: defaultDisplayMode
605
+ };
606
+ parts.push(newPart);
607
+ // Extend partFinalFlags array
608
+ messageState.partFinalFlags.push(false);
609
+ }
610
+ // Update message with new parts
611
+ messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex] = {
612
+ ...message,
613
+ parts: parts
614
+ };
615
+ } else {
616
+ // SPOKEN EVENT: advance cursor into existing text, or add as new part if
617
+ // there is none (bots that only send spoken: true, never unspoken).
618
+ const advanced = parts.length > 0 && (0, $ad0c8ea1ee293a79$export$9b39fd8438081d6d)(messageState, parts, text);
619
+ if (!advanced) {
620
+ // No unspoken content to advance: add this text as a part already fully spoken
621
+ const isDefaultType = aggregatedBy === "sentence" || aggregatedBy === "word" || !aggregatedBy;
622
+ const defaultDisplayMode = isDefaultType ? "inline" : undefined;
623
+ const newPart = {
624
+ text: text,
625
+ final: false,
626
+ createdAt: now.toISOString(),
627
+ aggregatedBy: aggregatedBy,
628
+ displayMode: defaultDisplayMode
629
+ };
630
+ parts.push(newPart);
631
+ messageState.partFinalFlags.push(true);
632
+ messageState.currentPartIndex = parts.length - 1;
633
+ messageState.currentCharIndex = text.length;
634
+ messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex] = {
635
+ ...message,
636
+ parts: parts
637
+ };
638
+ }
639
+ }
640
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
641
+ const cbs = get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab));
642
+ const updatedMsg = messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex];
643
+ if (isNewMessage) $cbb62fb16c5d45ff$var$callCallbacks(cbs, "onMessageCreated", updatedMsg);
644
+ else $cbb62fb16c5d45ff$var$callCallbacks(cbs, "onMessageUpdated", updatedMsg);
645
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
646
+ set((0, $28e6b30207a0392e$export$bd01e11bc95333a1), botOutputMessageState);
647
+ }
648
+ function $cbb62fb16c5d45ff$export$e450805545c7dd86(get, set, data) {
649
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
650
+ // If a tool_call_id is provided, check for an existing entry to avoid duplicates
651
+ if (data.tool_call_id) {
652
+ const existingIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "function_call" && msg.functionCall?.tool_call_id === data.tool_call_id);
653
+ if (existingIndex !== -1) return;
654
+ }
655
+ const now = new Date();
656
+ const message = {
657
+ role: "function_call",
658
+ final: false,
659
+ parts: [],
660
+ createdAt: now.toISOString(),
661
+ updatedAt: now.toISOString(),
662
+ functionCall: {
663
+ function_name: data.function_name,
664
+ tool_call_id: data.tool_call_id,
665
+ args: data.args,
666
+ status: "started"
667
+ }
668
+ };
669
+ const updatedMessages = [
670
+ ...messages,
671
+ message
672
+ ];
673
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(updatedMessages);
674
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageCreated", message);
675
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
676
+ }
677
+ function $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, tool_call_id, updates) {
678
+ const messages = [
679
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
680
+ ];
681
+ const index = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "function_call" && msg.functionCall?.tool_call_id === tool_call_id);
682
+ if (index === -1) return false;
683
+ const existing = messages[index];
684
+ const updated = {
685
+ ...existing,
686
+ updatedAt: new Date().toISOString(),
687
+ final: updates.status === "completed" ? true : existing.final,
688
+ functionCall: {
689
+ ...existing.functionCall,
690
+ ...updates
691
+ }
692
+ };
693
+ messages[index] = updated;
694
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
695
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageUpdated", updated);
696
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
697
+ return true;
698
+ }
699
+ function $cbb62fb16c5d45ff$export$54b3765e641b1813(get, set, updates) {
700
+ const messages = [
701
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
702
+ ];
703
+ const index = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "function_call" && msg.functionCall?.status === "started" && !msg.functionCall?.tool_call_id);
704
+ if (index === -1) return false;
705
+ const existing = messages[index];
706
+ const updated = {
707
+ ...existing,
708
+ updatedAt: new Date().toISOString(),
709
+ functionCall: {
710
+ ...existing.functionCall,
711
+ ...updates
712
+ }
713
+ };
714
+ messages[index] = updated;
715
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
716
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageUpdated", updated);
717
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
718
+ return true;
719
+ }
720
+ function $cbb62fb16c5d45ff$export$809efdca1a10761a(get, set, data) {
721
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
722
+ const lastFc = (0, $f4fa178c5adc67bb$export$296de88ccac4bedb)(messages, (m)=>m.role === "function_call");
723
+ // Check if InProgress already created an entry (events arrived out of order).
724
+ if (lastFc?.functionCall && lastFc.functionCall.status !== "started" && Date.now() - new Date(lastFc.createdAt).getTime() < 2000) {
725
+ if (data.function_name && !lastFc.functionCall.function_name && lastFc.functionCall.tool_call_id) $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, lastFc.functionCall.tool_call_id, {
726
+ function_name: data.function_name
727
+ });
728
+ return;
729
+ }
730
+ $cbb62fb16c5d45ff$export$e450805545c7dd86(get, set, {
731
+ function_name: data.function_name
732
+ });
733
+ }
734
+ function $cbb62fb16c5d45ff$export$568cde5f4aa4dcba(get, set, data) {
735
+ // Tier 1: Try to update the last "started" entry (from LLMFunctionCallStarted)
736
+ const updated = $cbb62fb16c5d45ff$export$54b3765e641b1813(get, set, {
737
+ function_name: data.function_name,
738
+ tool_call_id: data.tool_call_id,
739
+ args: data.args,
740
+ status: "in_progress"
741
+ });
742
+ if (!updated) {
743
+ // Tier 2: Try updating an existing entry by tool_call_id
744
+ const found = $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, data.tool_call_id, {
745
+ function_name: data.function_name,
746
+ args: data.args,
747
+ status: "in_progress"
748
+ });
749
+ if (!found) {
750
+ // Tier 3: No existing entry at all; create a new one as in_progress
751
+ $cbb62fb16c5d45ff$export$e450805545c7dd86(get, set, {
752
+ function_name: data.function_name,
753
+ tool_call_id: data.tool_call_id,
754
+ args: data.args
755
+ });
756
+ $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, data.tool_call_id, {
757
+ status: "in_progress"
758
+ });
759
+ }
760
+ }
761
+ }
762
+ function $cbb62fb16c5d45ff$export$a3cea3f7508e820b(get, set, data) {
763
+ // Tier 1: Try updating by tool_call_id
764
+ const found = $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, data.tool_call_id, {
765
+ function_name: data.function_name,
766
+ status: "completed",
767
+ result: data.result,
768
+ cancelled: data.cancelled
769
+ });
770
+ if (!found) {
771
+ // Tier 2: No match by tool_call_id (e.g. InProgress was skipped).
772
+ const matched = $cbb62fb16c5d45ff$export$54b3765e641b1813(get, set, {
773
+ function_name: data.function_name,
774
+ tool_call_id: data.tool_call_id
775
+ });
776
+ if (matched) $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, data.tool_call_id, {
777
+ status: "completed",
778
+ result: data.result,
779
+ cancelled: data.cancelled
780
+ });
781
+ }
782
+ }
783
+
784
+
785
+
786
+ /**
787
+ * Copyright (c) 2024, Daily.
788
+ *
789
+ * SPDX-License-Identifier: BSD-2-Clause
790
+ */
791
+
792
+
63
793
  /**
64
794
  * Copyright (c) 2024, Daily.
65
795
  *
@@ -88,6 +818,239 @@ const $8a6b68ebf0332682$export$33a6ac53b8f02625 = (event, handler)=>{
88
818
  };
89
819
 
90
820
 
821
+
822
+
823
+
824
+
825
+ /**
826
+ * Checks if a version meets a minimum version requirement.
827
+ * Inlined to avoid adding a `semver` dependency.
828
+ */ function $40515932ecd2dc41$var$isMinVersion(currentVersion, minVersion) {
829
+ // Strip pre-release suffix (e.g. "1.1.0-beta.1" -> "1.1.0")
830
+ const parts = currentVersion.split("-")[0].split(".").map(Number);
831
+ for(let i = 0; i < 3; i++){
832
+ if ((parts[i] || 0) > minVersion[i]) return true;
833
+ if ((parts[i] || 0) < minVersion[i]) return false;
834
+ }
835
+ return true; // equal
836
+ }
837
+ /** Delay (ms) before finalizing the assistant message after bot stops speaking. */ const $40515932ecd2dc41$var$BOT_STOPPED_FINALIZE_DELAY_MS = 2500;
838
+ function $40515932ecd2dc41$export$52680e26621bab39() {
839
+ const userStoppedTimeout = (0, $5Zyvw$react.useRef)(undefined);
840
+ const botStoppedSpeakingTimeoutRef = (0, $5Zyvw$react.useRef)(undefined);
841
+ const assistantStreamResetRef = (0, $5Zyvw$react.useRef)(0);
842
+ const botOutputLastChunkRef = (0, $5Zyvw$react.useRef)({
843
+ spoken: "",
844
+ unspoken: ""
845
+ });
846
+ // Clean up pending timeouts on unmount
847
+ (0, $5Zyvw$react.useEffect)(()=>{
848
+ return ()=>{
849
+ clearTimeout(userStoppedTimeout.current);
850
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
851
+ };
852
+ }, []);
853
+ // -- helpers ---------------------------------------------------------------
854
+ const finalizeLastAssistantMessageIfPending = (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
855
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
856
+ botStoppedSpeakingTimeoutRef.current = undefined;
857
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
858
+ const lastAssistant = (0, $f4fa178c5adc67bb$export$296de88ccac4bedb)(messages, (m)=>m.role === "assistant");
859
+ if (lastAssistant && !lastAssistant.final) (0, $cbb62fb16c5d45ff$export$78bd074993ab081f)(get, set, "assistant");
860
+ }, []));
861
+ const ensureAssistantMessage = (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
862
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
863
+ const lastAssistantIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "assistant");
864
+ const lastAssistant = lastAssistantIndex !== -1 ? messages[lastAssistantIndex] : undefined;
865
+ if (!lastAssistant || lastAssistant.final) {
866
+ // If the message was finalized but still has unspoken content, it was
867
+ // finalized prematurely (e.g. BotStoppedSpeaking timer fired during a
868
+ // TTS pause mid-response). Un-finalize it instead of creating a new
869
+ // message bubble — but only when no user message followed.
870
+ if (lastAssistant?.final && lastAssistantIndex === messages.length - 1) {
871
+ const messageId = lastAssistant.createdAt;
872
+ const botOutputState = get((0, $28e6b30207a0392e$export$bd01e11bc95333a1));
873
+ const cursor = botOutputState.get(messageId);
874
+ if (cursor && (0, $ad0c8ea1ee293a79$export$802e9b947136afbf)(cursor, lastAssistant.parts || [])) {
875
+ (0, $cbb62fb16c5d45ff$export$a05f96a2aa40873e)(get, set, "assistant", {
876
+ final: false
877
+ });
878
+ return false;
879
+ }
880
+ }
881
+ (0, $cbb62fb16c5d45ff$export$16fdb433d434f08)(get, set, {
882
+ role: "assistant",
883
+ final: false,
884
+ parts: []
885
+ });
886
+ assistantStreamResetRef.current += 1;
887
+ return true;
888
+ }
889
+ return false;
890
+ }, []));
891
+ // -- event handlers --------------------------------------------------------
892
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).Connected, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
893
+ (0, $cbb62fb16c5d45ff$export$f2434643f2abff11)(get, set);
894
+ set((0, $28e6b30207a0392e$export$57b1a1df06a7e728), null);
895
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
896
+ botStoppedSpeakingTimeoutRef.current = undefined;
897
+ botOutputLastChunkRef.current = {
898
+ spoken: "",
899
+ unspoken: ""
900
+ };
901
+ }, [])));
902
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).BotReady, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((_get, set, botData)=>{
903
+ const rtviVersion = botData.version;
904
+ const supportsBotOutput = $40515932ecd2dc41$var$isMinVersion(rtviVersion, [
905
+ 1,
906
+ 1,
907
+ 0
908
+ ]);
909
+ set((0, $28e6b30207a0392e$export$57b1a1df06a7e728), supportsBotOutput);
910
+ }, [])));
911
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).BotOutput, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, data)=>{
912
+ // A BotOutput event means the response is still active; cancel any
913
+ // pending finalize timer from BotStoppedSpeaking.
914
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
915
+ botStoppedSpeakingTimeoutRef.current = undefined;
916
+ ensureAssistantMessage();
917
+ // Handle spacing for BotOutput chunks
918
+ let textToAdd = data.text;
919
+ const lastChunk = data.spoken ? botOutputLastChunkRef.current.spoken : botOutputLastChunkRef.current.unspoken;
920
+ // Add space separator if needed between BotOutput chunks
921
+ if (lastChunk) textToAdd = " " + textToAdd;
922
+ // Update the appropriate last chunk tracker
923
+ if (data.spoken) botOutputLastChunkRef.current.spoken = textToAdd;
924
+ else botOutputLastChunkRef.current.unspoken = textToAdd;
925
+ // Update both spoken and unspoken text streams
926
+ const isFinal = data.aggregated_by === "sentence";
927
+ (0, $cbb62fb16c5d45ff$export$d78810e35f742a16)(get, set, textToAdd, isFinal, data.spoken, data.aggregated_by);
928
+ }, [
929
+ ensureAssistantMessage
930
+ ])));
931
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).BotStoppedSpeaking, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
932
+ // Don't finalize immediately; start a timer. Bot may start speaking again (pause).
933
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
934
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
935
+ const lastAssistant = (0, $f4fa178c5adc67bb$export$296de88ccac4bedb)(messages, (m)=>m.role === "assistant");
936
+ if (!lastAssistant || lastAssistant.final) return;
937
+ botStoppedSpeakingTimeoutRef.current = setTimeout(()=>{
938
+ botStoppedSpeakingTimeoutRef.current = undefined;
939
+ // Snap the speech-progress cursor to the end of all parts.
940
+ // The bot finished speaking normally (not interrupted), so all
941
+ // text should render as "spoken". Without this, text from the
942
+ // last sentence can remain grey if the spoken BotOutput event
943
+ // didn't match the unspoken text exactly.
944
+ const msgs = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
945
+ const cursorMap = new Map(get((0, $28e6b30207a0392e$export$bd01e11bc95333a1)));
946
+ const last = (0, $f4fa178c5adc67bb$export$296de88ccac4bedb)(msgs, (m)=>m.role === "assistant");
947
+ if (last) {
948
+ const cursor = cursorMap.get(last.createdAt);
949
+ if (cursor && last.parts && last.parts.length > 0) {
950
+ const lastPartIdx = last.parts.length - 1;
951
+ const lastPartText = last.parts[lastPartIdx]?.text;
952
+ cursor.currentPartIndex = lastPartIdx;
953
+ cursor.currentCharIndex = typeof lastPartText === "string" ? lastPartText.length : 0;
954
+ for(let i = 0; i <= lastPartIdx; i++)cursor.partFinalFlags[i] = true;
955
+ set((0, $28e6b30207a0392e$export$bd01e11bc95333a1), cursorMap);
956
+ }
957
+ }
958
+ (0, $cbb62fb16c5d45ff$export$78bd074993ab081f)(get, set, "assistant");
959
+ }, $40515932ecd2dc41$var$BOT_STOPPED_FINALIZE_DELAY_MS);
960
+ }, [])));
961
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).BotStartedSpeaking, (0, $5Zyvw$react.useCallback)(()=>{
962
+ // Bot is speaking again; reset the finalize timer (bot was just pausing).
963
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
964
+ botStoppedSpeakingTimeoutRef.current = undefined;
965
+ }, []));
966
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).UserStartedSpeaking, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
967
+ // User started a new turn; bot's turn is done. Fast-forward: finalize immediately.
968
+ finalizeLastAssistantMessageIfPending();
969
+ clearTimeout(userStoppedTimeout.current);
970
+ // Only finalize the previous user message if the bot has responded since
971
+ // the user last spoke. This prevents finalizing during VAD gaps (brief
972
+ // breathing pauses within the same user turn where UserStoppedSpeaking/
973
+ // UserStartedSpeaking fire without an actual turn change).
974
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
975
+ const lastUserIdx = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (m)=>m.role === "user");
976
+ if (lastUserIdx !== -1 && !messages[lastUserIdx].final) {
977
+ const hasBotActivityAfterUser = messages.slice(lastUserIdx + 1).some((m)=>m.role === "assistant");
978
+ if (hasBotActivityAfterUser) (0, $cbb62fb16c5d45ff$export$78bd074993ab081f)(get, set, "user");
979
+ }
980
+ }, [
981
+ finalizeLastAssistantMessageIfPending
982
+ ])));
983
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).UserTranscript, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, data)=>{
984
+ const text = data.text ?? "";
985
+ const final = Boolean(data.final);
986
+ (0, $cbb62fb16c5d45ff$export$57de01211e3df548)(get, set, text, final);
987
+ // If we got any transcript, cancel pending cleanup
988
+ clearTimeout(userStoppedTimeout.current);
989
+ }, [])));
990
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).UserStoppedSpeaking, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
991
+ clearTimeout(userStoppedTimeout.current);
992
+ // If no transcript ends up arriving, ensure any accidental empty placeholder is removed.
993
+ userStoppedTimeout.current = setTimeout(()=>{
994
+ // Re-read state at timeout time
995
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
996
+ const lastUser = (0, $f4fa178c5adc67bb$export$296de88ccac4bedb)(messages, (m)=>m.role === "user");
997
+ const hasParts = Array.isArray(lastUser?.parts) && lastUser.parts.length > 0;
998
+ if (!lastUser || !hasParts) (0, $cbb62fb16c5d45ff$export$cd74f809c89ea10c)(get, set, "user");
999
+ else if (!lastUser.final) (0, $cbb62fb16c5d45ff$export$78bd074993ab081f)(get, set, "user");
1000
+ }, 3000);
1001
+ }, [])));
1002
+ // LLM Function Call lifecycle events
1003
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).LLMFunctionCallStarted, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, data)=>{
1004
+ (0, $cbb62fb16c5d45ff$export$809efdca1a10761a)(get, set, {
1005
+ function_name: data.function_name
1006
+ });
1007
+ }, [])));
1008
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).LLMFunctionCallInProgress, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, data)=>{
1009
+ (0, $cbb62fb16c5d45ff$export$568cde5f4aa4dcba)(get, set, {
1010
+ function_name: data.function_name,
1011
+ tool_call_id: data.tool_call_id,
1012
+ args: data.arguments
1013
+ });
1014
+ }, [])));
1015
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).LLMFunctionCallStopped, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, data)=>{
1016
+ (0, $cbb62fb16c5d45ff$export$a3cea3f7508e820b)(get, set, {
1017
+ function_name: data.function_name,
1018
+ tool_call_id: data.tool_call_id,
1019
+ result: data.result,
1020
+ cancelled: data.cancelled
1021
+ });
1022
+ }, [])));
1023
+ }
1024
+
1025
+
1026
+ const $984246ab4e966dee$export$4d68d6035e4164b1 = /*#__PURE__*/ (0, $5Zyvw$react.createContext)(null);
1027
+ const $984246ab4e966dee$export$e372a2172e7d18e8 = ({ children: children })=>{
1028
+ (0, $40515932ecd2dc41$export$52680e26621bab39)();
1029
+ const injectMessage = (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, message)=>{
1030
+ (0, $cbb62fb16c5d45ff$export$c3a007efc2f315a0)(get, set, message);
1031
+ }, []));
1032
+ const botOutputSupported = (0, $5Zyvw$jotai.useAtomValue)((0, $28e6b30207a0392e$export$57b1a1df06a7e728));
1033
+ return (0, $5Zyvw$reactjsxruntime.jsx)($984246ab4e966dee$export$4d68d6035e4164b1.Provider, {
1034
+ value: {
1035
+ injectMessage: injectMessage,
1036
+ botOutputSupported: botOutputSupported
1037
+ },
1038
+ children: children
1039
+ });
1040
+ };
1041
+ $984246ab4e966dee$export$e372a2172e7d18e8.displayName = "PipecatConversationProvider";
1042
+ const $984246ab4e966dee$export$8eec679bf8249822 = ()=>{
1043
+ const context = (0, $5Zyvw$react.useContext)($984246ab4e966dee$export$4d68d6035e4164b1);
1044
+ if (!context) throw new Error("useConversationContext must be used within a PipecatClientProvider");
1045
+ return context;
1046
+ };
1047
+
1048
+
1049
+
1050
+
1051
+
1052
+
1053
+
91
1054
  const $44c6278d186dd94f$export$d6bdcccacef16204 = /*#__PURE__*/ (0, $5Zyvw$react.createContext)({
92
1055
  enableCam: ()=>{
93
1056
  throw new Error("PipecatClientCamStateContext: enableCam() called outside of provider");
@@ -100,17 +1063,25 @@ const $44c6278d186dd94f$export$802b42df0e0d8153 = /*#__PURE__*/ (0, $5Zyvw$react
100
1063
  },
101
1064
  isMicEnabled: false
102
1065
  });
1066
+ const $44c6278d186dd94f$export$8e633e67c760098b = /*#__PURE__*/ (0, $5Zyvw$react.createContext)({
1067
+ enableScreenShare: ()=>{
1068
+ throw new Error("PipecatClientScreenShareStateContext: enableScreenShare() called outside of provider");
1069
+ },
1070
+ isScreenShareEnabled: false
1071
+ });
103
1072
  const $44c6278d186dd94f$export$db79fdf85ddd6b65 = /*#__PURE__*/ (0, $5Zyvw$react.createContext)("disconnected");
104
1073
  const $44c6278d186dd94f$export$4777554fda61c378 = ({ children: children })=>{
105
1074
  const client = (0, $172f489fc5d91d99$export$777fa8498be78705)();
106
1075
  const [isCamEnabled, setIsCamEnabled] = (0, $5Zyvw$react.useState)(false);
107
1076
  const [isMicEnabled, setIsMicEnabled] = (0, $5Zyvw$react.useState)(false);
1077
+ const [isScreenShareEnabled, setIsScreenShareEnabled] = (0, $5Zyvw$react.useState)(false);
108
1078
  const [transportState, setTransportState] = (0, $5Zyvw$react.useState)("disconnected");
109
1079
  (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).TransportStateChanged, (state)=>{
110
1080
  setTransportState(state);
111
1081
  if (state === "initialized" && client) {
112
1082
  setIsCamEnabled(client.isCamEnabled ?? false);
113
1083
  setIsMicEnabled(client.isMicEnabled ?? false);
1084
+ setIsScreenShareEnabled(client.isSharingScreen ?? false);
114
1085
  }
115
1086
  });
116
1087
  const enableCam = (0, $5Zyvw$react.useCallback)((enabled)=>{
@@ -125,6 +1096,17 @@ const $44c6278d186dd94f$export$4777554fda61c378 = ({ children: children })=>{
125
1096
  }, [
126
1097
  client
127
1098
  ]);
1099
+ const enableScreenShare = (0, $5Zyvw$react.useCallback)((enabled)=>{
1100
+ client?.enableScreenShare?.(enabled);
1101
+ }, [
1102
+ client
1103
+ ]);
1104
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).ScreenTrackStarted, (_track, participant)=>{
1105
+ if (participant?.local) setIsScreenShareEnabled(true);
1106
+ });
1107
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).ScreenTrackStopped, (_track, participant)=>{
1108
+ if (participant?.local) setIsScreenShareEnabled(false);
1109
+ });
128
1110
  return (0, $5Zyvw$reactjsxruntime.jsx)($44c6278d186dd94f$export$db79fdf85ddd6b65.Provider, {
129
1111
  value: transportState,
130
1112
  children: (0, $5Zyvw$reactjsxruntime.jsx)($44c6278d186dd94f$export$d6bdcccacef16204.Provider, {
@@ -137,7 +1119,13 @@ const $44c6278d186dd94f$export$4777554fda61c378 = ({ children: children })=>{
137
1119
  enableMic: enableMic,
138
1120
  isMicEnabled: isMicEnabled
139
1121
  },
140
- children: children
1122
+ children: (0, $5Zyvw$reactjsxruntime.jsx)($44c6278d186dd94f$export$8e633e67c760098b.Provider, {
1123
+ value: {
1124
+ enableScreenShare: enableScreenShare,
1125
+ isScreenShareEnabled: isScreenShareEnabled
1126
+ },
1127
+ children: children
1128
+ })
141
1129
  })
142
1130
  })
143
1131
  });
@@ -197,7 +1185,9 @@ const $8df0e777e4d7dd49$export$bb43666ced7a20d0 = ({ children: children, client:
197
1185
  off: off
198
1186
  },
199
1187
  children: (0, $5Zyvw$reactjsxruntime.jsx)((0, $44c6278d186dd94f$export$4777554fda61c378), {
200
- children: children
1188
+ children: (0, $5Zyvw$reactjsxruntime.jsx)((0, $984246ab4e966dee$export$e372a2172e7d18e8), {
1189
+ children: children
1190
+ })
201
1191
  })
202
1192
  })
203
1193
  })
@@ -378,6 +1368,44 @@ var $1a9931980e271aa3$export$2e2bcd8739ae039 = $1a9931980e271aa3$export$bc8133b6
378
1368
 
379
1369
 
380
1370
 
1371
+ /**
1372
+ * Copyright (c) 2025, Daily.
1373
+ *
1374
+ * SPDX-License-Identifier: BSD-2-Clause
1375
+ */
1376
+
1377
+ const $5fc67d7ca05dec34$export$be63b19bd7f7d4f5 = ()=>(0, $5Zyvw$react.useContext)((0, $44c6278d186dd94f$export$8e633e67c760098b));
1378
+
1379
+
1380
+ const $0a9d1dc5be2e1cc2$export$93764714dfab3a46 = ({ onScreenShareEnabledChanged: onScreenShareEnabledChanged, disabled: disabled = false, children: children })=>{
1381
+ const { enableScreenShare: enableScreenShare, isScreenShareEnabled: isScreenShareEnabled } = (0, $5fc67d7ca05dec34$export$be63b19bd7f7d4f5)();
1382
+ const handleToggleScreenShare = (0, $5Zyvw$react.useCallback)(()=>{
1383
+ if (disabled) return;
1384
+ enableScreenShare(!isScreenShareEnabled);
1385
+ }, [
1386
+ disabled,
1387
+ enableScreenShare,
1388
+ isScreenShareEnabled
1389
+ ]);
1390
+ (0, $5Zyvw$react.useEffect)(()=>{
1391
+ onScreenShareEnabledChanged?.(isScreenShareEnabled);
1392
+ }, [
1393
+ isScreenShareEnabled,
1394
+ onScreenShareEnabledChanged
1395
+ ]);
1396
+ return (0, $5Zyvw$reactjsxruntime.jsx)((0, $5Zyvw$reactjsxruntime.Fragment), {
1397
+ children: children({
1398
+ isScreenShareEnabled: isScreenShareEnabled,
1399
+ onClick: handleToggleScreenShare,
1400
+ disabled: disabled
1401
+ })
1402
+ });
1403
+ };
1404
+ var $0a9d1dc5be2e1cc2$export$2e2bcd8739ae039 = $0a9d1dc5be2e1cc2$export$93764714dfab3a46;
1405
+
1406
+
1407
+
1408
+
381
1409
  /**
382
1410
  * Copyright (c) 2024, Daily.
383
1411
  *
@@ -612,6 +1640,7 @@ const $9bd3e7d3a9d7acd1$export$642bc4d2d2a376f1 = ()=>{
612
1640
 
613
1641
 
614
1642
 
1643
+
615
1644
  /**
616
1645
  * Copyright (c) 2024, Daily.
617
1646
  *
@@ -804,4 +1833,125 @@ $a1dfa75b13e6bb9b$export$59bf27bd43679db6.displayName = "VoiceVisualizer";
804
1833
 
805
1834
 
806
1835
 
1836
+ /**
1837
+ * Copyright (c) 2024, Daily.
1838
+ *
1839
+ * SPDX-License-Identifier: BSD-2-Clause
1840
+ */
1841
+
1842
+
1843
+
1844
+
1845
+
1846
+ const $fe6dfdd0b8560c78$export$acb832b064cb6c09 = ({ onMessageCreated: onMessageCreated, onMessageUpdated: onMessageUpdated, onMessageAdded: onMessageAdded, aggregationMetadata: aggregationMetadata } = {})=>{
1847
+ const { injectMessage: injectMessage } = (0, $984246ab4e966dee$export$8eec679bf8249822)();
1848
+ // Generate a unique ID for this hook instance
1849
+ const callbackId = (0, $5Zyvw$react.useId)();
1850
+ // Resolve deprecated onMessageAdded → onMessageCreated
1851
+ const resolvedCreated = onMessageCreated ?? onMessageAdded;
1852
+ // Register and unregister the callbacks
1853
+ const doRegister = (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
1854
+ (0, $cbb62fb16c5d45ff$export$f2820c5e040afe17)(get, set, callbackId, {
1855
+ onMessageCreated: resolvedCreated,
1856
+ onMessageUpdated: onMessageUpdated
1857
+ });
1858
+ }, [
1859
+ callbackId,
1860
+ resolvedCreated,
1861
+ onMessageUpdated
1862
+ ]));
1863
+ const doUnregister = (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
1864
+ (0, $cbb62fb16c5d45ff$export$46ae1ba121fdc956)(get, set, callbackId);
1865
+ }, [
1866
+ callbackId
1867
+ ]));
1868
+ (0, $5Zyvw$react.useEffect)(()=>{
1869
+ doRegister();
1870
+ return ()=>{
1871
+ doUnregister();
1872
+ };
1873
+ }, [
1874
+ doRegister,
1875
+ doUnregister
1876
+ ]);
1877
+ // Get the raw state from atoms
1878
+ const messages = (0, $5Zyvw$jotai.useAtomValue)((0, $28e6b30207a0392e$export$92a4076839a978d0));
1879
+ const botOutputMessageState = (0, $5Zyvw$jotai.useAtomValue)((0, $28e6b30207a0392e$export$bd01e11bc95333a1));
1880
+ // Memoize the filtered messages to prevent infinite loops
1881
+ const filteredMessages = (0, $5Zyvw$react.useMemo)(()=>{
1882
+ const getMetadata = (part)=>{
1883
+ return part.aggregatedBy ? aggregationMetadata?.[part.aggregatedBy] : undefined;
1884
+ };
1885
+ // Process messages: convert string parts to BotOutputText based on position state
1886
+ const processedMessages = messages.map((message)=>{
1887
+ if (message.role === "assistant") {
1888
+ const messageId = message.createdAt;
1889
+ const messageState = botOutputMessageState.get(messageId);
1890
+ if (!messageState) // No state yet, return message as-is
1891
+ return message;
1892
+ const parts = message.parts || [];
1893
+ // Find the actual current part index (skip parts that aren't meant to be spoken)
1894
+ let actualCurrentPartIndex = messageState.currentPartIndex;
1895
+ while(actualCurrentPartIndex < parts.length){
1896
+ const part = parts[actualCurrentPartIndex];
1897
+ if (typeof part?.text !== "string") break;
1898
+ const isSpoken = getMetadata(part)?.isSpoken !== false;
1899
+ if (isSpoken) break;
1900
+ actualCurrentPartIndex++;
1901
+ }
1902
+ if (parts.length > 0 && actualCurrentPartIndex >= parts.length) actualCurrentPartIndex = parts.length - 1;
1903
+ // Convert parts to BotOutputText format based on position state
1904
+ const processedParts = parts.map((part, partIndex)=>{
1905
+ // If part text is not a string, it's already processed (e.g., ReactNode)
1906
+ if (typeof part.text !== "string") return part;
1907
+ const metadata = getMetadata(part);
1908
+ const displayMode = part.displayMode ?? metadata?.displayMode ?? "inline";
1909
+ const isSpoken = metadata?.isSpoken !== false;
1910
+ const partText = displayMode === "block" && !isSpoken ? part.text.trim() : part.text;
1911
+ if (!isSpoken) return {
1912
+ ...part,
1913
+ displayMode: displayMode,
1914
+ text: {
1915
+ spoken: "",
1916
+ unspoken: partText
1917
+ }
1918
+ };
1919
+ // Use cursor split for the part at actualCurrentPartIndex for every message,
1920
+ // so previous (e.g. interrupted) messages keep partially spoken state.
1921
+ const isPartAtCursor = partIndex === actualCurrentPartIndex;
1922
+ const currentCharIndex = messageState.currentCharIndex;
1923
+ const spokenText = isPartAtCursor ? partText.slice(0, currentCharIndex) : partIndex < actualCurrentPartIndex ? partText : "";
1924
+ const unspokenText = isPartAtCursor ? partText.slice(currentCharIndex) : partIndex < actualCurrentPartIndex ? "" : partText;
1925
+ return {
1926
+ ...part,
1927
+ displayMode: displayMode,
1928
+ text: {
1929
+ spoken: spokenText,
1930
+ unspoken: unspokenText
1931
+ }
1932
+ };
1933
+ });
1934
+ return {
1935
+ ...message,
1936
+ parts: processedParts
1937
+ };
1938
+ }
1939
+ return message;
1940
+ });
1941
+ // Messages are already normalized (sorted, filtered, deduped, merged) on write.
1942
+ return processedMessages;
1943
+ }, [
1944
+ messages,
1945
+ botOutputMessageState,
1946
+ aggregationMetadata
1947
+ ]);
1948
+ return {
1949
+ messages: filteredMessages,
1950
+ injectMessage: injectMessage
1951
+ };
1952
+ };
1953
+
1954
+
1955
+
1956
+
807
1957
  //# sourceMappingURL=index.js.map