@pipecat-ai/client-react 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -29,6 +29,13 @@ $parcel$export(module.exports, "usePipecatClientScreenShareControl", () => $5fc6
29
29
  $parcel$export(module.exports, "usePipecatClientTransportState", () => $810478f6ae107062$export$30aee278309a867b);
30
30
  $parcel$export(module.exports, "useRTVIClientEvent", () => $8a6b68ebf0332682$export$33a6ac53b8f02625);
31
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);
32
39
  /**
33
40
  * Copyright (c) 2024, Daily.
34
41
  *
@@ -55,13 +62,784 @@ $parcel$export(module.exports, "VoiceVisualizer", () => $a1dfa75b13e6bb9b$export
55
62
 
56
63
 
57
64
  var $d1f89db81f967f39$exports = {};
58
- $d1f89db81f967f39$exports = JSON.parse("{\"name\":\"@pipecat-ai/client-react\",\"version\":\"1.1.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\"},\"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.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\"},\"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\"}}");
59
66
 
60
67
 
61
68
 
62
69
 
63
70
 
64
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$export$7d7fdcf7579a70b2 = (text)=>{
81
+ return text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, "");
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$export$7d7fdcf7579a70b2(spokenForMatching).trim() === $ad0c8ea1ee293a79$export$7d7fdcf7579a70b2(remainder).trim()) return unspoken.length;
106
+ const spokenWords = $ad0c8ea1ee293a79$export$7d7fdcf7579a70b2(spokenForMatching).split(/\s+/).filter(Boolean);
107
+ if (spokenWords.length === 0) return actualStart;
108
+ const unspokenWords = $ad0c8ea1ee293a79$export$7d7fdcf7579a70b2(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)=>/[\p{L}\p{N}]/u.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
+ // If the spoken text is pure punctuation (e.g. an em dash "—"), normalization
179
+ // strips it to empty and word-matching would fail. Treat it as successfully
180
+ // consumed so the cursor stays put and subsequent words continue matching.
181
+ if ($ad0c8ea1ee293a79$export$7d7fdcf7579a70b2(spokenText).trim().length === 0) return true;
182
+ const newPosition = $ad0c8ea1ee293a79$var$findSpokenPositionInUnspoken(spokenText, partText, startChar);
183
+ const whitespaceEnd = $ad0c8ea1ee293a79$var$skipWhitespace(partText, startChar);
184
+ if (newPosition > whitespaceEnd) {
185
+ cursor.currentCharIndex = newPosition;
186
+ if (newPosition >= partText.length) {
187
+ cursor.partFinalFlags[cursor.currentPartIndex] = true;
188
+ if (cursor.currentPartIndex < parts.length - 1) {
189
+ cursor.currentPartIndex++;
190
+ cursor.currentCharIndex = 0;
191
+ }
192
+ }
193
+ return true;
194
+ }
195
+ // Intra-part scan-ahead recovery: if matching failed at the current position,
196
+ // scan forward word-by-word within the same part. This prevents the cursor
197
+ // from getting permanently stuck mid-part when a single word mismatch occurs
198
+ // (e.g. TTS variation, punctuation boundary like `apexes."Sometimes`).
199
+ if (startChar > 0) {
200
+ const MAX_SCAN_WORDS = 8;
201
+ let scanPos = startChar;
202
+ for(let scan = 0; scan < MAX_SCAN_WORDS; scan++){
203
+ // Advance past current word
204
+ while(scanPos < partText.length && !/\s/.test(partText[scanPos]))scanPos++;
205
+ // Advance past whitespace to next word
206
+ while(scanPos < partText.length && /\s/.test(partText[scanPos]))scanPos++;
207
+ if (scanPos >= partText.length) break;
208
+ const retryPos = $ad0c8ea1ee293a79$var$findSpokenPositionInUnspoken(spokenText, partText, scanPos);
209
+ const scanWsEnd = $ad0c8ea1ee293a79$var$skipWhitespace(partText, scanPos);
210
+ if (retryPos > scanWsEnd) {
211
+ cursor.currentCharIndex = retryPos;
212
+ if (retryPos >= partText.length) {
213
+ cursor.partFinalFlags[cursor.currentPartIndex] = true;
214
+ if (cursor.currentPartIndex < parts.length - 1) {
215
+ cursor.currentPartIndex++;
216
+ cursor.currentCharIndex = 0;
217
+ }
218
+ }
219
+ return true;
220
+ }
221
+ }
222
+ }
223
+ // Mismatch recovery: try to find the spoken text in a later part.
224
+ for(let nextPartIdx = cursor.currentPartIndex + 1; nextPartIdx < parts.length; nextPartIdx++){
225
+ const nextPart = parts[nextPartIdx];
226
+ if (typeof nextPart.text !== "string") continue;
227
+ const match = $ad0c8ea1ee293a79$var$findSpokenPositionInUnspoken(spokenText, nextPart.text, 0);
228
+ const nextWhitespaceEnd = $ad0c8ea1ee293a79$var$skipWhitespace(nextPart.text, 0);
229
+ if (match > nextWhitespaceEnd) {
230
+ // Mark skipped parts as final and jump to the matched part
231
+ for(let i = cursor.currentPartIndex; i < nextPartIdx; i++)cursor.partFinalFlags[i] = true;
232
+ cursor.currentPartIndex = nextPartIdx;
233
+ cursor.currentCharIndex = match;
234
+ return true;
235
+ }
236
+ }
237
+ // If we're stuck at the start, mark the current part as skipped to avoid deadlock.
238
+ if (startChar === 0 && cursor.currentPartIndex < parts.length - 1) {
239
+ cursor.partFinalFlags[cursor.currentPartIndex] = true;
240
+ cursor.currentPartIndex++;
241
+ cursor.currentCharIndex = 0;
242
+ return true;
243
+ }
244
+ return false;
245
+ }
246
+
247
+
248
+ /**
249
+ * Copyright (c) 2024, Daily.
250
+ *
251
+ * SPDX-License-Identifier: BSD-2-Clause
252
+ */
253
+ const $28e6b30207a0392e$export$92a4076839a978d0 = (0, $5Zyvw$jotai.atom)([]);
254
+ const $28e6b30207a0392e$export$bd01e11bc95333a1 = (0, $5Zyvw$jotai.atom)(new Map());
255
+ const $28e6b30207a0392e$export$b0cd27059c88d0ab = (0, $5Zyvw$jotai.atom)(new Map());
256
+ const $28e6b30207a0392e$export$57b1a1df06a7e728 = (0, $5Zyvw$jotai.atom)(null);
257
+
258
+
259
+ /**
260
+ * Copyright (c) 2024, Daily.
261
+ *
262
+ * SPDX-License-Identifier: BSD-2-Clause
263
+ */ // ES2020-compatible polyfills for findLast / findLastIndex
264
+ function $f4fa178c5adc67bb$export$8855a8be7bd3e9f8(arr, predicate) {
265
+ for(let i = arr.length - 1; i >= 0; i--){
266
+ if (predicate(arr[i], i, arr)) return i;
267
+ }
268
+ return -1;
269
+ }
270
+ function $f4fa178c5adc67bb$export$296de88ccac4bedb(arr, predicate) {
271
+ const idx = $f4fa178c5adc67bb$export$8855a8be7bd3e9f8(arr, predicate);
272
+ return idx === -1 ? undefined : arr[idx];
273
+ }
274
+
275
+
276
+ /** Max time gap (ms) between consecutive same-role messages for merging. */ const $cbb62fb16c5d45ff$var$MERGE_WINDOW_MS = 30000;
277
+ /**
278
+ * Unicode characters used by `FilterIncompleteTurns` on the server to mark
279
+ * turn completion status. The LLM emits one of these as the very first
280
+ * character of every response:
281
+ *
282
+ * ✓ (U+2713) — complete turn
283
+ * ○ (U+25CB) — incomplete short
284
+ * ◐ (U+25D0) — incomplete long
285
+ *
286
+ * They must be stripped before the text reaches conversation state.
287
+ */ const $cbb62fb16c5d45ff$var$TURN_COMPLETION_MARKERS = new Set([
288
+ "\u2713",
289
+ "\u25CB",
290
+ "\u25D0"
291
+ ]);
292
+ function $cbb62fb16c5d45ff$export$146b18879fa8cfd7(text) {
293
+ if (text.length === 0) return text;
294
+ if ($cbb62fb16c5d45ff$var$TURN_COMPLETION_MARKERS.has(text[0])) // Remove marker and optional single trailing space
295
+ return text[1] === " " ? text.slice(2) : text.slice(1);
296
+ return text;
297
+ }
298
+ const $cbb62fb16c5d45ff$export$cbefc5865f8cbd89 = (a, b)=>{
299
+ return a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : 0;
300
+ };
301
+ const $cbb62fb16c5d45ff$export$f4610a88b89838b = (message)=>{
302
+ if (message.role === "function_call") return false;
303
+ const parts = message.parts || [];
304
+ if (parts.length === 0) return true;
305
+ return parts.every((p)=>{
306
+ if (typeof p.text === "string") return p.text.trim().length === 0;
307
+ // Check BotOutputText objects
308
+ if (typeof p.text === "object" && p.text !== null && "spoken" in p.text && "unspoken" in p.text) {
309
+ const botText = p.text;
310
+ return botText.spoken.trim().length === 0 && botText.unspoken.trim().length === 0;
311
+ }
312
+ // For ReactNode, consider it non-empty
313
+ return false;
314
+ });
315
+ };
316
+ const $cbb62fb16c5d45ff$export$3d3951a55577516 = (messages)=>{
317
+ return messages.filter((message, index, array)=>{
318
+ if (!$cbb62fb16c5d45ff$export$f4610a88b89838b(message)) return true;
319
+ // For empty messages, keep only if no following non-empty message with same role
320
+ const nextMessageWithSameRole = array.slice(index + 1).find((m)=>m.role === message.role && !$cbb62fb16c5d45ff$export$f4610a88b89838b(m));
321
+ return !nextMessageWithSameRole;
322
+ });
323
+ };
324
+ const $cbb62fb16c5d45ff$export$23a96e868837e158 = (messages)=>{
325
+ const mergedMessages = [];
326
+ for(let i = 0; i < messages.length; i++){
327
+ const currentMessage = messages[i];
328
+ const lastMerged = mergedMessages[mergedMessages.length - 1];
329
+ const timeDiff = lastMerged ? Math.abs(new Date(currentMessage.createdAt).getTime() - new Date(lastMerged.createdAt).getTime()) : Infinity;
330
+ const shouldMerge = lastMerged && lastMerged.role === currentMessage.role && currentMessage.role !== "system" && currentMessage.role !== "function_call" && timeDiff < $cbb62fb16c5d45ff$var$MERGE_WINDOW_MS;
331
+ if (shouldMerge) mergedMessages[mergedMessages.length - 1] = {
332
+ ...lastMerged,
333
+ parts: [
334
+ ...lastMerged.parts || [],
335
+ ...currentMessage.parts || []
336
+ ],
337
+ updatedAt: currentMessage.updatedAt || currentMessage.createdAt,
338
+ final: currentMessage.final !== false
339
+ };
340
+ else mergedMessages.push(currentMessage);
341
+ }
342
+ return mergedMessages;
343
+ };
344
+ const $cbb62fb16c5d45ff$var$statusPriority = {
345
+ started: 0,
346
+ in_progress: 1,
347
+ completed: 2
348
+ };
349
+ const $cbb62fb16c5d45ff$export$dfa183f99573a822 = (messages)=>{
350
+ const bestByToolCallId = new Map();
351
+ const toRemove = new Set();
352
+ for(let i = 0; i < messages.length; i++){
353
+ const msg = messages[i];
354
+ const tcid = msg.functionCall?.tool_call_id;
355
+ if (msg.role !== "function_call" || !tcid) continue;
356
+ const existingIdx = bestByToolCallId.get(tcid);
357
+ if (existingIdx !== undefined) {
358
+ const existingPriority = $cbb62fb16c5d45ff$var$statusPriority[messages[existingIdx].functionCall.status] ?? 0;
359
+ const currentPriority = $cbb62fb16c5d45ff$var$statusPriority[msg.functionCall.status] ?? 0;
360
+ if (currentPriority >= existingPriority) {
361
+ toRemove.add(existingIdx);
362
+ bestByToolCallId.set(tcid, i);
363
+ } else toRemove.add(i);
364
+ } else bestByToolCallId.set(tcid, i);
365
+ }
366
+ if (toRemove.size === 0) return messages;
367
+ return messages.filter((_, i)=>!toRemove.has(i));
368
+ };
369
+ const $cbb62fb16c5d45ff$var$normalizeMessagesForUI = (messages)=>{
370
+ return $cbb62fb16c5d45ff$export$23a96e868837e158($cbb62fb16c5d45ff$export$dfa183f99573a822($cbb62fb16c5d45ff$export$3d3951a55577516([
371
+ ...messages
372
+ ].sort($cbb62fb16c5d45ff$export$cbefc5865f8cbd89))));
373
+ };
374
+ // ---------------------------------------------------------------------------
375
+ // Internal helpers
376
+ // ---------------------------------------------------------------------------
377
+ const $cbb62fb16c5d45ff$var$callCallbacks = (callbacksMap, type, message)=>{
378
+ callbacksMap.forEach((callbacks)=>{
379
+ try {
380
+ callbacks[type]?.(message);
381
+ } catch (error) {
382
+ console.error(`Error in ${type} callback:`, error);
383
+ }
384
+ });
385
+ };
386
+ function $cbb62fb16c5d45ff$export$f2820c5e040afe17(get, set, id, callbacks) {
387
+ const map = new Map(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)));
388
+ map.set(id, callbacks);
389
+ set((0, $28e6b30207a0392e$export$b0cd27059c88d0ab), map);
390
+ }
391
+ function $cbb62fb16c5d45ff$export$46ae1ba121fdc956(get, set, id) {
392
+ const map = new Map(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)));
393
+ map.delete(id);
394
+ set((0, $28e6b30207a0392e$export$b0cd27059c88d0ab), map);
395
+ }
396
+ function $cbb62fb16c5d45ff$export$f2434643f2abff11(_get, set) {
397
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), []);
398
+ set((0, $28e6b30207a0392e$export$bd01e11bc95333a1), new Map());
399
+ }
400
+ function $cbb62fb16c5d45ff$export$16fdb433d434f08(get, set, messageData) {
401
+ const now = new Date();
402
+ const message = {
403
+ ...messageData,
404
+ createdAt: now.toISOString(),
405
+ updatedAt: now.toISOString()
406
+ };
407
+ const current = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
408
+ const updatedMessages = [
409
+ ...current,
410
+ message
411
+ ];
412
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(updatedMessages);
413
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageCreated", message);
414
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
415
+ }
416
+ function $cbb62fb16c5d45ff$export$a05f96a2aa40873e(get, set, role, updates) {
417
+ const messages = [
418
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
419
+ ];
420
+ const lastMessageIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === role);
421
+ if (lastMessageIndex === -1) return;
422
+ const existing = messages[lastMessageIndex];
423
+ const updatedMessage = {
424
+ ...existing,
425
+ ...updates,
426
+ updatedAt: new Date().toISOString()
427
+ };
428
+ messages[lastMessageIndex] = updatedMessage;
429
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
430
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageUpdated", updatedMessage);
431
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
432
+ }
433
+ function $cbb62fb16c5d45ff$export$78bd074993ab081f(get, set, role) {
434
+ const messages = [
435
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
436
+ ];
437
+ const lastMessageIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === role);
438
+ if (lastMessageIndex === -1) return;
439
+ const lastMessage = messages[lastMessageIndex];
440
+ // Check if message is empty
441
+ if ($cbb62fb16c5d45ff$export$f4610a88b89838b(lastMessage)) // Remove empty message only if it has no text in streams either
442
+ messages.splice(lastMessageIndex, 1);
443
+ else {
444
+ // Finalize message and its last part
445
+ const parts = [
446
+ ...lastMessage.parts || []
447
+ ];
448
+ if (parts.length > 0) parts[parts.length - 1] = {
449
+ ...parts[parts.length - 1],
450
+ final: true
451
+ };
452
+ messages[lastMessageIndex] = {
453
+ ...lastMessage,
454
+ parts: parts,
455
+ final: true,
456
+ updatedAt: new Date().toISOString()
457
+ };
458
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageUpdated", messages[lastMessageIndex]);
459
+ }
460
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
461
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
462
+ }
463
+ function $cbb62fb16c5d45ff$export$cd74f809c89ea10c(get, set, role) {
464
+ const messages = [
465
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
466
+ ];
467
+ const lastMessageIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === role);
468
+ if (lastMessageIndex === -1) return;
469
+ const lastMessage = messages[lastMessageIndex];
470
+ if ($cbb62fb16c5d45ff$export$f4610a88b89838b(lastMessage)) {
471
+ messages.splice(lastMessageIndex, 1);
472
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
473
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
474
+ }
475
+ }
476
+ function $cbb62fb16c5d45ff$export$c3a007efc2f315a0(get, set, messageData) {
477
+ const now = new Date();
478
+ const current = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
479
+ // If the most recent message is an active (non-final) assistant message,
480
+ // backdate the injected message so it sorts before the assistant's createdAt.
481
+ // This prevents the injected message from splitting the bot's response across
482
+ // two bubbles and breaking the karaoke cursor.
483
+ const lastMessage = current[current.length - 1];
484
+ const lastAssistant = lastMessage?.role === "assistant" ? lastMessage : undefined;
485
+ let timestamp;
486
+ if (lastAssistant && lastAssistant.final === false && messageData.role === "system") {
487
+ const assistantTime = new Date(lastAssistant.createdAt);
488
+ timestamp = new Date(assistantTime.getTime() - 1).toISOString();
489
+ } else timestamp = now.toISOString();
490
+ const message = {
491
+ role: messageData.role,
492
+ final: true,
493
+ parts: [
494
+ ...messageData.parts
495
+ ],
496
+ createdAt: timestamp,
497
+ updatedAt: now.toISOString()
498
+ };
499
+ const updatedMessages = [
500
+ ...current,
501
+ message
502
+ ];
503
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(updatedMessages);
504
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageCreated", message);
505
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
506
+ }
507
+ function $cbb62fb16c5d45ff$export$57de01211e3df548(get, set, text, final) {
508
+ const now = new Date();
509
+ const messages = [
510
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
511
+ ];
512
+ // Find last user message
513
+ const lastUserIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (m)=>m.role === "user");
514
+ if (lastUserIndex !== -1 && !messages[lastUserIndex].final) {
515
+ // Update existing user message
516
+ const target = {
517
+ ...messages[lastUserIndex]
518
+ };
519
+ const parts = Array.isArray(target.parts) ? [
520
+ ...target.parts
521
+ ] : [];
522
+ const lastPart = parts[parts.length - 1];
523
+ if (!lastPart || lastPart.final) // Start a new part
524
+ parts.push({
525
+ text: text,
526
+ final: final,
527
+ createdAt: now.toISOString()
528
+ });
529
+ else // Update in-progress part
530
+ parts[parts.length - 1] = {
531
+ ...lastPart,
532
+ text: text,
533
+ final: final
534
+ };
535
+ const updatedMessage = {
536
+ ...target,
537
+ parts: parts,
538
+ updatedAt: now.toISOString()
539
+ };
540
+ messages[lastUserIndex] = updatedMessage;
541
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
542
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageUpdated", updatedMessage);
543
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
544
+ return;
545
+ }
546
+ // Create a new user message initialized with this transcript
547
+ const newMessage = {
548
+ role: "user",
549
+ final: false,
550
+ parts: [
551
+ {
552
+ text: text,
553
+ final: final,
554
+ createdAt: now.toISOString()
555
+ }
556
+ ],
557
+ createdAt: now.toISOString(),
558
+ updatedAt: now.toISOString()
559
+ };
560
+ const updatedMessages = [
561
+ ...messages,
562
+ newMessage
563
+ ];
564
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(updatedMessages);
565
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageCreated", newMessage);
566
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
567
+ }
568
+ function $cbb62fb16c5d45ff$export$d78810e35f742a16(get, set, text, final, spoken, aggregatedBy) {
569
+ // Strip turn-completion markers emitted by FilterIncompleteTurns before
570
+ // the text enters conversation state.
571
+ text = $cbb62fb16c5d45ff$export$146b18879fa8cfd7(text);
572
+ if (text.length === 0) return;
573
+ const now = new Date();
574
+ const messages = [
575
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
576
+ ];
577
+ const botOutputMessageState = new Map(get((0, $28e6b30207a0392e$export$bd01e11bc95333a1)));
578
+ const lastAssistantIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "assistant");
579
+ let messageId;
580
+ let isNewMessage = false;
581
+ if (lastAssistantIndex === -1) {
582
+ // Create new assistant message
583
+ isNewMessage = true;
584
+ messageId = now.toISOString();
585
+ const newMessage = {
586
+ role: "assistant",
587
+ final: final,
588
+ parts: [],
589
+ createdAt: messageId,
590
+ updatedAt: messageId
591
+ };
592
+ messages.push(newMessage);
593
+ // Initialize message state
594
+ botOutputMessageState.set(messageId, {
595
+ currentPartIndex: 0,
596
+ currentCharIndex: 0,
597
+ partFinalFlags: []
598
+ });
599
+ } else {
600
+ // Update existing assistant message
601
+ const lastMessage = messages[lastAssistantIndex];
602
+ messageId = lastMessage.createdAt;
603
+ messages[lastAssistantIndex] = {
604
+ ...lastMessage,
605
+ final: final ? true : lastMessage.final,
606
+ updatedAt: now.toISOString()
607
+ };
608
+ // Ensure message state exists
609
+ if (!botOutputMessageState.has(messageId)) botOutputMessageState.set(messageId, {
610
+ currentPartIndex: 0,
611
+ currentCharIndex: 0,
612
+ partFinalFlags: []
613
+ });
614
+ }
615
+ const messageState = botOutputMessageState.get(messageId);
616
+ const message = messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex];
617
+ const parts = [
618
+ ...message.parts || []
619
+ ];
620
+ if (!spoken) {
621
+ // UNSPOKEN EVENT: Create/update message parts immediately
622
+ // Only append when both current and last part are word-level; sentence-level
623
+ // and other types each get their own part so spoken events can match 1:1.
624
+ const isDefaultType = aggregatedBy === "sentence" || aggregatedBy === "word" || !aggregatedBy;
625
+ const lastPart = parts[parts.length - 1];
626
+ const shouldAppend = lastPart && aggregatedBy === "word" && lastPart.aggregatedBy === "word" && typeof lastPart.text === "string";
627
+ if (shouldAppend) {
628
+ // Append to last part (word-level only)
629
+ const lastPartText = lastPart.text;
630
+ const separator = lastPartText && !lastPartText.endsWith(" ") && !text.startsWith(" ") ? " " : "";
631
+ parts[parts.length - 1] = {
632
+ ...lastPart,
633
+ text: lastPartText + separator + text
634
+ };
635
+ } else {
636
+ // Create new part (sentence-level, custom types, or first word chunk)
637
+ // Default to inline; custom types get displayMode from metadata in the hook
638
+ const defaultDisplayMode = isDefaultType ? "inline" : undefined;
639
+ const newPart = {
640
+ text: text,
641
+ final: false,
642
+ createdAt: now.toISOString(),
643
+ aggregatedBy: aggregatedBy,
644
+ displayMode: defaultDisplayMode
645
+ };
646
+ parts.push(newPart);
647
+ // Extend partFinalFlags array
648
+ messageState.partFinalFlags.push(false);
649
+ }
650
+ // Update message with new parts
651
+ messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex] = {
652
+ ...message,
653
+ parts: parts
654
+ };
655
+ } else {
656
+ // SPOKEN EVENT: advance cursor into existing text, or add as new part if
657
+ // there is none (bots that only send spoken: true, never unspoken).
658
+ const advanced = parts.length > 0 && (0, $ad0c8ea1ee293a79$export$9b39fd8438081d6d)(messageState, parts, text);
659
+ if (!advanced) {
660
+ // No unspoken content to advance: add this text as a part already fully spoken
661
+ const isDefaultType = aggregatedBy === "sentence" || aggregatedBy === "word" || !aggregatedBy;
662
+ const defaultDisplayMode = isDefaultType ? "inline" : undefined;
663
+ const newPart = {
664
+ text: text,
665
+ final: false,
666
+ createdAt: now.toISOString(),
667
+ aggregatedBy: aggregatedBy,
668
+ displayMode: defaultDisplayMode
669
+ };
670
+ parts.push(newPart);
671
+ messageState.partFinalFlags.push(true);
672
+ messageState.currentPartIndex = parts.length - 1;
673
+ messageState.currentCharIndex = text.length;
674
+ messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex] = {
675
+ ...message,
676
+ parts: parts
677
+ };
678
+ }
679
+ }
680
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
681
+ const cbs = get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab));
682
+ const updatedMsg = messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex];
683
+ if (isNewMessage) $cbb62fb16c5d45ff$var$callCallbacks(cbs, "onMessageCreated", updatedMsg);
684
+ else $cbb62fb16c5d45ff$var$callCallbacks(cbs, "onMessageUpdated", updatedMsg);
685
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
686
+ set((0, $28e6b30207a0392e$export$bd01e11bc95333a1), botOutputMessageState);
687
+ }
688
+ function $cbb62fb16c5d45ff$export$e450805545c7dd86(get, set, data) {
689
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
690
+ // If a tool_call_id is provided, check for an existing entry to avoid duplicates
691
+ if (data.tool_call_id) {
692
+ const existingIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "function_call" && msg.functionCall?.tool_call_id === data.tool_call_id);
693
+ if (existingIndex !== -1) return;
694
+ }
695
+ const now = new Date();
696
+ // If the most recent message is an active (non-final) assistant message,
697
+ // backdate the function call so it sorts before the assistant's createdAt.
698
+ // This prevents the function call from splitting the bot's response across
699
+ // two bubbles and breaking the karaoke cursor.
700
+ const lastMessage = messages[messages.length - 1];
701
+ let timestamp;
702
+ if (lastMessage?.role === "assistant" && lastMessage.final === false) {
703
+ const assistantTime = new Date(lastMessage.createdAt);
704
+ timestamp = new Date(assistantTime.getTime() - 1).toISOString();
705
+ } else timestamp = now.toISOString();
706
+ const message = {
707
+ role: "function_call",
708
+ final: false,
709
+ parts: [],
710
+ createdAt: timestamp,
711
+ updatedAt: now.toISOString(),
712
+ functionCall: {
713
+ function_name: data.function_name,
714
+ tool_call_id: data.tool_call_id,
715
+ args: data.args,
716
+ status: "started"
717
+ }
718
+ };
719
+ const updatedMessages = [
720
+ ...messages,
721
+ message
722
+ ];
723
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(updatedMessages);
724
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageCreated", message);
725
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
726
+ }
727
+ function $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, tool_call_id, updates) {
728
+ const messages = [
729
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
730
+ ];
731
+ const index = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "function_call" && msg.functionCall?.tool_call_id === tool_call_id);
732
+ if (index === -1) return false;
733
+ const existing = messages[index];
734
+ const updated = {
735
+ ...existing,
736
+ updatedAt: new Date().toISOString(),
737
+ final: updates.status === "completed" ? true : existing.final,
738
+ functionCall: {
739
+ ...existing.functionCall,
740
+ ...updates
741
+ }
742
+ };
743
+ messages[index] = updated;
744
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
745
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageUpdated", updated);
746
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
747
+ return true;
748
+ }
749
+ function $cbb62fb16c5d45ff$export$54b3765e641b1813(get, set, updates) {
750
+ const messages = [
751
+ ...get((0, $28e6b30207a0392e$export$92a4076839a978d0))
752
+ ];
753
+ const index = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "function_call" && msg.functionCall?.status === "started" && !msg.functionCall?.tool_call_id);
754
+ if (index === -1) return false;
755
+ const existing = messages[index];
756
+ const updated = {
757
+ ...existing,
758
+ updatedAt: new Date().toISOString(),
759
+ functionCall: {
760
+ ...existing.functionCall,
761
+ ...updates
762
+ }
763
+ };
764
+ messages[index] = updated;
765
+ const processedMessages = $cbb62fb16c5d45ff$var$normalizeMessagesForUI(messages);
766
+ $cbb62fb16c5d45ff$var$callCallbacks(get((0, $28e6b30207a0392e$export$b0cd27059c88d0ab)), "onMessageUpdated", updated);
767
+ set((0, $28e6b30207a0392e$export$92a4076839a978d0), processedMessages);
768
+ return true;
769
+ }
770
+ function $cbb62fb16c5d45ff$export$809efdca1a10761a(get, set, data) {
771
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
772
+ const lastFc = (0, $f4fa178c5adc67bb$export$296de88ccac4bedb)(messages, (m)=>m.role === "function_call");
773
+ // Check if InProgress already created an entry (events arrived out of order).
774
+ if (lastFc?.functionCall && lastFc.functionCall.status !== "started" && Date.now() - new Date(lastFc.updatedAt ?? lastFc.createdAt).getTime() < 2000) {
775
+ if (data.function_name && !lastFc.functionCall.function_name && lastFc.functionCall.tool_call_id) $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, lastFc.functionCall.tool_call_id, {
776
+ function_name: data.function_name
777
+ });
778
+ return;
779
+ }
780
+ $cbb62fb16c5d45ff$export$e450805545c7dd86(get, set, {
781
+ function_name: data.function_name
782
+ });
783
+ }
784
+ function $cbb62fb16c5d45ff$export$568cde5f4aa4dcba(get, set, data) {
785
+ // Tier 1: Try to update the last "started" entry (from LLMFunctionCallStarted)
786
+ const updated = $cbb62fb16c5d45ff$export$54b3765e641b1813(get, set, {
787
+ function_name: data.function_name,
788
+ tool_call_id: data.tool_call_id,
789
+ args: data.args,
790
+ status: "in_progress"
791
+ });
792
+ if (!updated) {
793
+ // Tier 2: Try updating an existing entry by tool_call_id
794
+ const found = $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, data.tool_call_id, {
795
+ function_name: data.function_name,
796
+ args: data.args,
797
+ status: "in_progress"
798
+ });
799
+ if (!found) {
800
+ // Tier 3: No existing entry at all; create a new one as in_progress
801
+ $cbb62fb16c5d45ff$export$e450805545c7dd86(get, set, {
802
+ function_name: data.function_name,
803
+ tool_call_id: data.tool_call_id,
804
+ args: data.args
805
+ });
806
+ $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, data.tool_call_id, {
807
+ status: "in_progress"
808
+ });
809
+ }
810
+ }
811
+ }
812
+ function $cbb62fb16c5d45ff$export$a3cea3f7508e820b(get, set, data) {
813
+ // Tier 1: Try updating by tool_call_id
814
+ const found = $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, data.tool_call_id, {
815
+ function_name: data.function_name,
816
+ status: "completed",
817
+ result: data.result,
818
+ cancelled: data.cancelled
819
+ });
820
+ if (!found) {
821
+ // Tier 2: No match by tool_call_id (e.g. InProgress was skipped).
822
+ const matched = $cbb62fb16c5d45ff$export$54b3765e641b1813(get, set, {
823
+ function_name: data.function_name,
824
+ tool_call_id: data.tool_call_id
825
+ });
826
+ if (matched) $cbb62fb16c5d45ff$export$1908264de3a8afe4(get, set, data.tool_call_id, {
827
+ status: "completed",
828
+ result: data.result,
829
+ cancelled: data.cancelled
830
+ });
831
+ }
832
+ }
833
+
834
+
835
+
836
+ /**
837
+ * Copyright (c) 2024, Daily.
838
+ *
839
+ * SPDX-License-Identifier: BSD-2-Clause
840
+ */
841
+
842
+
65
843
  /**
66
844
  * Copyright (c) 2024, Daily.
67
845
  *
@@ -90,6 +868,239 @@ const $8a6b68ebf0332682$export$33a6ac53b8f02625 = (event, handler)=>{
90
868
  };
91
869
 
92
870
 
871
+
872
+
873
+
874
+
875
+ /**
876
+ * Checks if a version meets a minimum version requirement.
877
+ * Inlined to avoid adding a `semver` dependency.
878
+ */ function $40515932ecd2dc41$var$isMinVersion(currentVersion, minVersion) {
879
+ // Strip pre-release suffix (e.g. "1.1.0-beta.1" -> "1.1.0")
880
+ const parts = currentVersion.split("-")[0].split(".").map(Number);
881
+ for(let i = 0; i < 3; i++){
882
+ if ((parts[i] || 0) > minVersion[i]) return true;
883
+ if ((parts[i] || 0) < minVersion[i]) return false;
884
+ }
885
+ return true; // equal
886
+ }
887
+ /** Delay (ms) before finalizing the assistant message after bot stops speaking. */ const $40515932ecd2dc41$var$BOT_STOPPED_FINALIZE_DELAY_MS = 2500;
888
+ function $40515932ecd2dc41$export$52680e26621bab39() {
889
+ const userStoppedTimeout = (0, $5Zyvw$react.useRef)(undefined);
890
+ const botStoppedSpeakingTimeoutRef = (0, $5Zyvw$react.useRef)(undefined);
891
+ const assistantStreamResetRef = (0, $5Zyvw$react.useRef)(0);
892
+ const botOutputLastChunkRef = (0, $5Zyvw$react.useRef)({
893
+ spoken: "",
894
+ unspoken: ""
895
+ });
896
+ // Clean up pending timeouts on unmount
897
+ (0, $5Zyvw$react.useEffect)(()=>{
898
+ return ()=>{
899
+ clearTimeout(userStoppedTimeout.current);
900
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
901
+ };
902
+ }, []);
903
+ // -- helpers ---------------------------------------------------------------
904
+ const finalizeLastAssistantMessageIfPending = (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
905
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
906
+ botStoppedSpeakingTimeoutRef.current = undefined;
907
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
908
+ const lastAssistant = (0, $f4fa178c5adc67bb$export$296de88ccac4bedb)(messages, (m)=>m.role === "assistant");
909
+ if (lastAssistant && !lastAssistant.final) (0, $cbb62fb16c5d45ff$export$78bd074993ab081f)(get, set, "assistant");
910
+ }, []));
911
+ const ensureAssistantMessage = (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
912
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
913
+ const lastAssistantIndex = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "assistant");
914
+ const lastAssistant = lastAssistantIndex !== -1 ? messages[lastAssistantIndex] : undefined;
915
+ if (!lastAssistant || lastAssistant.final) {
916
+ // If the message was finalized but still has unspoken content, it was
917
+ // finalized prematurely (e.g. BotStoppedSpeaking timer fired during a
918
+ // TTS pause mid-response). Un-finalize it instead of creating a new
919
+ // message bubble — but only when no user message followed.
920
+ if (lastAssistant?.final && lastAssistantIndex === messages.length - 1) {
921
+ const messageId = lastAssistant.createdAt;
922
+ const botOutputState = get((0, $28e6b30207a0392e$export$bd01e11bc95333a1));
923
+ const cursor = botOutputState.get(messageId);
924
+ if (cursor && (0, $ad0c8ea1ee293a79$export$802e9b947136afbf)(cursor, lastAssistant.parts || [])) {
925
+ (0, $cbb62fb16c5d45ff$export$a05f96a2aa40873e)(get, set, "assistant", {
926
+ final: false
927
+ });
928
+ return false;
929
+ }
930
+ }
931
+ (0, $cbb62fb16c5d45ff$export$16fdb433d434f08)(get, set, {
932
+ role: "assistant",
933
+ final: false,
934
+ parts: []
935
+ });
936
+ assistantStreamResetRef.current += 1;
937
+ return true;
938
+ }
939
+ return false;
940
+ }, []));
941
+ // -- event handlers --------------------------------------------------------
942
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).Connected, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
943
+ (0, $cbb62fb16c5d45ff$export$f2434643f2abff11)(get, set);
944
+ set((0, $28e6b30207a0392e$export$57b1a1df06a7e728), null);
945
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
946
+ botStoppedSpeakingTimeoutRef.current = undefined;
947
+ botOutputLastChunkRef.current = {
948
+ spoken: "",
949
+ unspoken: ""
950
+ };
951
+ }, [])));
952
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).BotReady, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((_get, set, botData)=>{
953
+ const rtviVersion = botData.version;
954
+ const supportsBotOutput = $40515932ecd2dc41$var$isMinVersion(rtviVersion, [
955
+ 1,
956
+ 1,
957
+ 0
958
+ ]);
959
+ set((0, $28e6b30207a0392e$export$57b1a1df06a7e728), supportsBotOutput);
960
+ }, [])));
961
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).BotOutput, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, data)=>{
962
+ // A BotOutput event means the response is still active; cancel any
963
+ // pending finalize timer from BotStoppedSpeaking.
964
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
965
+ botStoppedSpeakingTimeoutRef.current = undefined;
966
+ ensureAssistantMessage();
967
+ // Handle spacing for BotOutput chunks
968
+ let textToAdd = data.text;
969
+ const lastChunk = data.spoken ? botOutputLastChunkRef.current.spoken : botOutputLastChunkRef.current.unspoken;
970
+ // Add space separator if needed between BotOutput chunks
971
+ if (lastChunk) textToAdd = " " + textToAdd;
972
+ // Update the appropriate last chunk tracker
973
+ if (data.spoken) botOutputLastChunkRef.current.spoken = textToAdd;
974
+ else botOutputLastChunkRef.current.unspoken = textToAdd;
975
+ // Update both spoken and unspoken text streams
976
+ const isFinal = data.aggregated_by === "sentence";
977
+ (0, $cbb62fb16c5d45ff$export$d78810e35f742a16)(get, set, textToAdd, isFinal, data.spoken, data.aggregated_by);
978
+ }, [
979
+ ensureAssistantMessage
980
+ ])));
981
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).BotStoppedSpeaking, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
982
+ // Don't finalize immediately; start a timer. Bot may start speaking again (pause).
983
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
984
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
985
+ const lastAssistant = (0, $f4fa178c5adc67bb$export$296de88ccac4bedb)(messages, (m)=>m.role === "assistant");
986
+ if (!lastAssistant || lastAssistant.final) return;
987
+ botStoppedSpeakingTimeoutRef.current = setTimeout(()=>{
988
+ botStoppedSpeakingTimeoutRef.current = undefined;
989
+ // Snap the speech-progress cursor to the end of all parts.
990
+ // The bot finished speaking normally (not interrupted), so all
991
+ // text should render as "spoken". Without this, text from the
992
+ // last sentence can remain grey if the spoken BotOutput event
993
+ // didn't match the unspoken text exactly.
994
+ const msgs = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
995
+ const cursorMap = new Map(get((0, $28e6b30207a0392e$export$bd01e11bc95333a1)));
996
+ const last = (0, $f4fa178c5adc67bb$export$296de88ccac4bedb)(msgs, (m)=>m.role === "assistant");
997
+ if (last) {
998
+ const cursor = cursorMap.get(last.createdAt);
999
+ if (cursor && last.parts && last.parts.length > 0) {
1000
+ const lastPartIdx = last.parts.length - 1;
1001
+ const lastPartText = last.parts[lastPartIdx]?.text;
1002
+ cursor.currentPartIndex = lastPartIdx;
1003
+ cursor.currentCharIndex = typeof lastPartText === "string" ? lastPartText.length : 0;
1004
+ for(let i = 0; i <= lastPartIdx; i++)cursor.partFinalFlags[i] = true;
1005
+ set((0, $28e6b30207a0392e$export$bd01e11bc95333a1), cursorMap);
1006
+ }
1007
+ }
1008
+ (0, $cbb62fb16c5d45ff$export$78bd074993ab081f)(get, set, "assistant");
1009
+ }, $40515932ecd2dc41$var$BOT_STOPPED_FINALIZE_DELAY_MS);
1010
+ }, [])));
1011
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).BotStartedSpeaking, (0, $5Zyvw$react.useCallback)(()=>{
1012
+ // Bot is speaking again; reset the finalize timer (bot was just pausing).
1013
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
1014
+ botStoppedSpeakingTimeoutRef.current = undefined;
1015
+ }, []));
1016
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).UserStartedSpeaking, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
1017
+ // User started a new turn; bot's turn is done. Fast-forward: finalize immediately.
1018
+ finalizeLastAssistantMessageIfPending();
1019
+ clearTimeout(userStoppedTimeout.current);
1020
+ // Only finalize the previous user message if the bot has responded since
1021
+ // the user last spoke. This prevents finalizing during VAD gaps (brief
1022
+ // breathing pauses within the same user turn where UserStoppedSpeaking/
1023
+ // UserStartedSpeaking fire without an actual turn change).
1024
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
1025
+ const lastUserIdx = (0, $f4fa178c5adc67bb$export$8855a8be7bd3e9f8)(messages, (m)=>m.role === "user");
1026
+ if (lastUserIdx !== -1 && !messages[lastUserIdx].final) {
1027
+ const hasBotActivityAfterUser = messages.slice(lastUserIdx + 1).some((m)=>m.role === "assistant");
1028
+ if (hasBotActivityAfterUser) (0, $cbb62fb16c5d45ff$export$78bd074993ab081f)(get, set, "user");
1029
+ }
1030
+ }, [
1031
+ finalizeLastAssistantMessageIfPending
1032
+ ])));
1033
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).UserTranscript, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, data)=>{
1034
+ const text = data.text ?? "";
1035
+ const final = Boolean(data.final);
1036
+ (0, $cbb62fb16c5d45ff$export$57de01211e3df548)(get, set, text, final);
1037
+ // If we got any transcript, cancel pending cleanup
1038
+ clearTimeout(userStoppedTimeout.current);
1039
+ }, [])));
1040
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).UserStoppedSpeaking, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
1041
+ clearTimeout(userStoppedTimeout.current);
1042
+ // If no transcript ends up arriving, ensure any accidental empty placeholder is removed.
1043
+ userStoppedTimeout.current = setTimeout(()=>{
1044
+ // Re-read state at timeout time
1045
+ const messages = get((0, $28e6b30207a0392e$export$92a4076839a978d0));
1046
+ const lastUser = (0, $f4fa178c5adc67bb$export$296de88ccac4bedb)(messages, (m)=>m.role === "user");
1047
+ const hasParts = Array.isArray(lastUser?.parts) && lastUser.parts.length > 0;
1048
+ if (!lastUser || !hasParts) (0, $cbb62fb16c5d45ff$export$cd74f809c89ea10c)(get, set, "user");
1049
+ else if (!lastUser.final) (0, $cbb62fb16c5d45ff$export$78bd074993ab081f)(get, set, "user");
1050
+ }, 3000);
1051
+ }, [])));
1052
+ // LLM Function Call lifecycle events
1053
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).LLMFunctionCallStarted, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, data)=>{
1054
+ (0, $cbb62fb16c5d45ff$export$809efdca1a10761a)(get, set, {
1055
+ function_name: data.function_name
1056
+ });
1057
+ }, [])));
1058
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).LLMFunctionCallInProgress, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, data)=>{
1059
+ (0, $cbb62fb16c5d45ff$export$568cde5f4aa4dcba)(get, set, {
1060
+ function_name: data.function_name,
1061
+ tool_call_id: data.tool_call_id,
1062
+ args: data.arguments
1063
+ });
1064
+ }, [])));
1065
+ (0, $8a6b68ebf0332682$export$33a6ac53b8f02625)((0, $5Zyvw$pipecataiclientjs.RTVIEvent).LLMFunctionCallStopped, (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, data)=>{
1066
+ (0, $cbb62fb16c5d45ff$export$a3cea3f7508e820b)(get, set, {
1067
+ function_name: data.function_name,
1068
+ tool_call_id: data.tool_call_id,
1069
+ result: data.result,
1070
+ cancelled: data.cancelled
1071
+ });
1072
+ }, [])));
1073
+ }
1074
+
1075
+
1076
+ const $984246ab4e966dee$export$4d68d6035e4164b1 = /*#__PURE__*/ (0, $5Zyvw$react.createContext)(null);
1077
+ const $984246ab4e966dee$export$e372a2172e7d18e8 = ({ children: children })=>{
1078
+ (0, $40515932ecd2dc41$export$52680e26621bab39)();
1079
+ const injectMessage = (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set, message)=>{
1080
+ (0, $cbb62fb16c5d45ff$export$c3a007efc2f315a0)(get, set, message);
1081
+ }, []));
1082
+ const botOutputSupported = (0, $5Zyvw$jotai.useAtomValue)((0, $28e6b30207a0392e$export$57b1a1df06a7e728));
1083
+ return (0, $5Zyvw$reactjsxruntime.jsx)($984246ab4e966dee$export$4d68d6035e4164b1.Provider, {
1084
+ value: {
1085
+ injectMessage: injectMessage,
1086
+ botOutputSupported: botOutputSupported
1087
+ },
1088
+ children: children
1089
+ });
1090
+ };
1091
+ $984246ab4e966dee$export$e372a2172e7d18e8.displayName = "PipecatConversationProvider";
1092
+ const $984246ab4e966dee$export$8eec679bf8249822 = ()=>{
1093
+ const context = (0, $5Zyvw$react.useContext)($984246ab4e966dee$export$4d68d6035e4164b1);
1094
+ if (!context) throw new Error("useConversationContext must be used within a PipecatClientProvider");
1095
+ return context;
1096
+ };
1097
+
1098
+
1099
+
1100
+
1101
+
1102
+
1103
+
93
1104
  const $44c6278d186dd94f$export$d6bdcccacef16204 = /*#__PURE__*/ (0, $5Zyvw$react.createContext)({
94
1105
  enableCam: ()=>{
95
1106
  throw new Error("PipecatClientCamStateContext: enableCam() called outside of provider");
@@ -224,7 +1235,9 @@ const $8df0e777e4d7dd49$export$bb43666ced7a20d0 = ({ children: children, client:
224
1235
  off: off
225
1236
  },
226
1237
  children: (0, $5Zyvw$reactjsxruntime.jsx)((0, $44c6278d186dd94f$export$4777554fda61c378), {
227
- children: children
1238
+ children: (0, $5Zyvw$reactjsxruntime.jsx)((0, $984246ab4e966dee$export$e372a2172e7d18e8), {
1239
+ children: children
1240
+ })
228
1241
  })
229
1242
  })
230
1243
  })
@@ -870,4 +1883,125 @@ $a1dfa75b13e6bb9b$export$59bf27bd43679db6.displayName = "VoiceVisualizer";
870
1883
 
871
1884
 
872
1885
 
1886
+ /**
1887
+ * Copyright (c) 2024, Daily.
1888
+ *
1889
+ * SPDX-License-Identifier: BSD-2-Clause
1890
+ */
1891
+
1892
+
1893
+
1894
+
1895
+
1896
+ const $fe6dfdd0b8560c78$export$acb832b064cb6c09 = ({ onMessageCreated: onMessageCreated, onMessageUpdated: onMessageUpdated, onMessageAdded: onMessageAdded, aggregationMetadata: aggregationMetadata } = {})=>{
1897
+ const { injectMessage: injectMessage } = (0, $984246ab4e966dee$export$8eec679bf8249822)();
1898
+ // Generate a unique ID for this hook instance
1899
+ const callbackId = (0, $5Zyvw$react.useId)();
1900
+ // Resolve deprecated onMessageAdded → onMessageCreated
1901
+ const resolvedCreated = onMessageCreated ?? onMessageAdded;
1902
+ // Register and unregister the callbacks
1903
+ const doRegister = (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
1904
+ (0, $cbb62fb16c5d45ff$export$f2820c5e040afe17)(get, set, callbackId, {
1905
+ onMessageCreated: resolvedCreated,
1906
+ onMessageUpdated: onMessageUpdated
1907
+ });
1908
+ }, [
1909
+ callbackId,
1910
+ resolvedCreated,
1911
+ onMessageUpdated
1912
+ ]));
1913
+ const doUnregister = (0, $5Zyvw$jotaiutils.useAtomCallback)((0, $5Zyvw$react.useCallback)((get, set)=>{
1914
+ (0, $cbb62fb16c5d45ff$export$46ae1ba121fdc956)(get, set, callbackId);
1915
+ }, [
1916
+ callbackId
1917
+ ]));
1918
+ (0, $5Zyvw$react.useEffect)(()=>{
1919
+ doRegister();
1920
+ return ()=>{
1921
+ doUnregister();
1922
+ };
1923
+ }, [
1924
+ doRegister,
1925
+ doUnregister
1926
+ ]);
1927
+ // Get the raw state from atoms
1928
+ const messages = (0, $5Zyvw$jotai.useAtomValue)((0, $28e6b30207a0392e$export$92a4076839a978d0));
1929
+ const botOutputMessageState = (0, $5Zyvw$jotai.useAtomValue)((0, $28e6b30207a0392e$export$bd01e11bc95333a1));
1930
+ // Memoize the filtered messages to prevent infinite loops
1931
+ const filteredMessages = (0, $5Zyvw$react.useMemo)(()=>{
1932
+ const getMetadata = (part)=>{
1933
+ return part.aggregatedBy ? aggregationMetadata?.[part.aggregatedBy] : undefined;
1934
+ };
1935
+ // Process messages: convert string parts to BotOutputText based on position state
1936
+ const processedMessages = messages.map((message)=>{
1937
+ if (message.role === "assistant") {
1938
+ const messageId = message.createdAt;
1939
+ const messageState = botOutputMessageState.get(messageId);
1940
+ if (!messageState) // No state yet, return message as-is
1941
+ return message;
1942
+ const parts = message.parts || [];
1943
+ // Find the actual current part index (skip parts that aren't meant to be spoken)
1944
+ let actualCurrentPartIndex = messageState.currentPartIndex;
1945
+ while(actualCurrentPartIndex < parts.length){
1946
+ const part = parts[actualCurrentPartIndex];
1947
+ if (typeof part?.text !== "string") break;
1948
+ const isSpoken = getMetadata(part)?.isSpoken !== false;
1949
+ if (isSpoken) break;
1950
+ actualCurrentPartIndex++;
1951
+ }
1952
+ if (parts.length > 0 && actualCurrentPartIndex >= parts.length) actualCurrentPartIndex = parts.length - 1;
1953
+ // Convert parts to BotOutputText format based on position state
1954
+ const processedParts = parts.map((part, partIndex)=>{
1955
+ // If part text is not a string, it's already processed (e.g., ReactNode)
1956
+ if (typeof part.text !== "string") return part;
1957
+ const metadata = getMetadata(part);
1958
+ const displayMode = part.displayMode ?? metadata?.displayMode ?? "inline";
1959
+ const isSpoken = metadata?.isSpoken !== false;
1960
+ const partText = displayMode === "block" && !isSpoken ? part.text.trim() : part.text;
1961
+ if (!isSpoken) return {
1962
+ ...part,
1963
+ displayMode: displayMode,
1964
+ text: {
1965
+ spoken: "",
1966
+ unspoken: partText
1967
+ }
1968
+ };
1969
+ // Use cursor split for the part at actualCurrentPartIndex for every message,
1970
+ // so previous (e.g. interrupted) messages keep partially spoken state.
1971
+ const isPartAtCursor = partIndex === actualCurrentPartIndex;
1972
+ const currentCharIndex = messageState.currentCharIndex;
1973
+ const spokenText = isPartAtCursor ? partText.slice(0, currentCharIndex) : partIndex < actualCurrentPartIndex ? partText : "";
1974
+ const unspokenText = isPartAtCursor ? partText.slice(currentCharIndex) : partIndex < actualCurrentPartIndex ? "" : partText;
1975
+ return {
1976
+ ...part,
1977
+ displayMode: displayMode,
1978
+ text: {
1979
+ spoken: spokenText,
1980
+ unspoken: unspokenText
1981
+ }
1982
+ };
1983
+ });
1984
+ return {
1985
+ ...message,
1986
+ parts: processedParts
1987
+ };
1988
+ }
1989
+ return message;
1990
+ });
1991
+ // Messages are already normalized (sorted, filtered, deduped, merged) on write.
1992
+ return processedMessages;
1993
+ }, [
1994
+ messages,
1995
+ botOutputMessageState,
1996
+ aggregationMetadata
1997
+ ]);
1998
+ return {
1999
+ messages: filteredMessages,
2000
+ injectMessage: injectMessage
2001
+ };
2002
+ };
2003
+
2004
+
2005
+
2006
+
873
2007
  //# sourceMappingURL=index.js.map