@pipecat-ai/client-react 1.1.0 → 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.
@@ -1,6 +1,6 @@
1
1
  import {jsx as $h9lXz$jsx, Fragment as $h9lXz$Fragment} from "react/jsx-runtime";
2
2
  import {RTVIEvent as $h9lXz$RTVIEvent, setAboutClient as $h9lXz$setAboutClient} from "@pipecat-ai/client-js";
3
- import $h9lXz$react, {useRef as $h9lXz$useRef, useEffect as $h9lXz$useEffect, useCallback as $h9lXz$useCallback, useContext as $h9lXz$useContext, createContext as $h9lXz$createContext, useState as $h9lXz$useState, forwardRef as $h9lXz$forwardRef} from "react";
3
+ import $h9lXz$react, {useRef as $h9lXz$useRef, useEffect as $h9lXz$useEffect, useCallback as $h9lXz$useCallback, useContext as $h9lXz$useContext, createContext as $h9lXz$createContext, useState as $h9lXz$useState, forwardRef as $h9lXz$forwardRef, useId as $h9lXz$useId, useMemo as $h9lXz$useMemo} from "react";
4
4
  import {atom as $h9lXz$atom, useAtomValue as $h9lXz$useAtomValue, createStore as $h9lXz$createStore} from "jotai";
5
5
  import {atomFamily as $h9lXz$atomFamily, useAtomCallback as $h9lXz$useAtomCallback} from "jotai/utils";
6
6
  import {Provider as $h9lXz$Provider} from "jotai/react";
@@ -31,13 +31,734 @@ import {Provider as $h9lXz$Provider} from "jotai/react";
31
31
 
32
32
 
33
33
  var $ad20387e24e513d4$exports = {};
34
- $ad20387e24e513d4$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\"}}");
34
+ $ad20387e24e513d4$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\"}}");
35
35
 
36
36
 
37
37
 
38
38
 
39
39
 
40
40
 
41
+ /**
42
+ * Copyright (c) 2024, Daily.
43
+ *
44
+ * SPDX-License-Identifier: BSD-2-Clause
45
+ */ /**
46
+ * Copyright (c) 2024, Daily.
47
+ *
48
+ * SPDX-License-Identifier: BSD-2-Clause
49
+ */ const $5989c1f88db60f73$var$normalizeForMatching = (text)=>{
50
+ return text.toLowerCase().replace(/[^\w\s]/g, "");
51
+ };
52
+ const $5989c1f88db60f73$var$skipWhitespace = (text, start)=>{
53
+ let i = start;
54
+ while(i < text.length && /\s/.test(text[i]))i++;
55
+ return i;
56
+ };
57
+ /**
58
+ * Finds where `spoken` appears in `unspoken`, starting from `startPosition`.
59
+ * - Best-effort sequential word matching (normalized, punctuation-stripped)
60
+ * - Returns the original start position on mismatch (no advancement)
61
+ */ const $5989c1f88db60f73$var$findSpokenPositionInUnspoken = (spoken, unspoken, startPosition)=>{
62
+ if (!spoken || !unspoken || startPosition >= unspoken.length) return startPosition;
63
+ // If spoken text includes a leading separator space, skip leading whitespace in unspoken.
64
+ let actualStart = startPosition;
65
+ let spokenForMatching = spoken;
66
+ if (spoken.startsWith(" ") && startPosition < unspoken.length) {
67
+ actualStart = $5989c1f88db60f73$var$skipWhitespace(unspoken, startPosition);
68
+ spokenForMatching = spoken.trimStart();
69
+ } else if (startPosition === 0 && startPosition < unspoken.length) // If we're at the start, also skip leading whitespace (e.g. newlines)
70
+ actualStart = $5989c1f88db60f73$var$skipWhitespace(unspoken, 0);
71
+ const remainder = unspoken.slice(actualStart);
72
+ // Sentence-level: if spoken exactly matches the remainder (normalized), consume the whole part
73
+ // so we never leave a word unspoken due to word-matching edge cases.
74
+ if ($5989c1f88db60f73$var$normalizeForMatching(spokenForMatching).trim() === $5989c1f88db60f73$var$normalizeForMatching(remainder).trim()) return unspoken.length;
75
+ const spokenWords = $5989c1f88db60f73$var$normalizeForMatching(spokenForMatching).split(/\s+/).filter(Boolean);
76
+ if (spokenWords.length === 0) return actualStart;
77
+ const unspokenWords = $5989c1f88db60f73$var$normalizeForMatching(unspoken.slice(actualStart)).split(/\s+/).filter(Boolean);
78
+ // Sequential match, allowing prefix match for contractions (e.g. "I" vs "I'm")
79
+ // and limited skipping of mismatched unspoken words (e.g. punctuation artifacts).
80
+ let matchedWords = 0;
81
+ let consecutiveSkips = 0;
82
+ const MAX_CONSECUTIVE_SKIPS = 2;
83
+ for(let i = 0; i < unspokenWords.length && matchedWords < spokenWords.length; i++){
84
+ const target = spokenWords[matchedWords];
85
+ const candidate = unspokenWords[i];
86
+ if (candidate === target || candidate.startsWith(target)) {
87
+ matchedWords++;
88
+ consecutiveSkips = 0;
89
+ continue;
90
+ }
91
+ consecutiveSkips++;
92
+ if (consecutiveSkips > MAX_CONSECUTIVE_SKIPS) return actualStart;
93
+ // Skip this unspoken word and try matching the next one
94
+ }
95
+ if (matchedWords < spokenWords.length) return actualStart;
96
+ // Convert word matches back into a character position in the original unspoken string.
97
+ const isWordChar = (char)=>/[a-zA-Z0-9]/.test(char);
98
+ let wordCount = 0;
99
+ let i = actualStart;
100
+ let inWord = false;
101
+ while(i < unspoken.length){
102
+ const charIsWord = isWordChar(unspoken[i]);
103
+ if (charIsWord && !inWord) {
104
+ inWord = true;
105
+ wordCount++;
106
+ if (wordCount === matchedWords) {
107
+ // Consume the rest of this word
108
+ i++;
109
+ while(i < unspoken.length && isWordChar(unspoken[i]))i++;
110
+ // Include any punctuation after the word until the next space, then include the space
111
+ while(i < unspoken.length){
112
+ if (unspoken[i] === " ") {
113
+ i++;
114
+ break;
115
+ }
116
+ i++;
117
+ }
118
+ return i;
119
+ }
120
+ } else if (!charIsWord && inWord) inWord = false;
121
+ i++;
122
+ }
123
+ return unspoken.length;
124
+ };
125
+ function $5989c1f88db60f73$export$802e9b947136afbf(cursor, parts) {
126
+ if (parts.length === 0) return false;
127
+ for(let i = 0; i < parts.length; i++){
128
+ if (typeof parts[i]?.text !== "string") continue;
129
+ if (!cursor.partFinalFlags[i]) return true;
130
+ }
131
+ return false;
132
+ }
133
+ function $5989c1f88db60f73$export$9b39fd8438081d6d(cursor, parts, spokenText) {
134
+ if (parts.length === 0) return false;
135
+ // Find the next part that should be spoken (skip parts already marked final/skipped)
136
+ let partToMatch = cursor.currentPartIndex;
137
+ while(partToMatch < parts.length && cursor.partFinalFlags[partToMatch])partToMatch++;
138
+ if (partToMatch >= parts.length) return false;
139
+ if (partToMatch > cursor.currentPartIndex) {
140
+ cursor.currentPartIndex = partToMatch;
141
+ cursor.currentCharIndex = 0;
142
+ }
143
+ const currentPart = parts[cursor.currentPartIndex];
144
+ if (typeof currentPart.text !== "string") return false;
145
+ const partText = currentPart.text;
146
+ const startChar = cursor.currentCharIndex;
147
+ const newPosition = $5989c1f88db60f73$var$findSpokenPositionInUnspoken(spokenText, partText, startChar);
148
+ const whitespaceEnd = $5989c1f88db60f73$var$skipWhitespace(partText, startChar);
149
+ if (newPosition > whitespaceEnd) {
150
+ cursor.currentCharIndex = newPosition;
151
+ if (newPosition >= partText.length) {
152
+ cursor.partFinalFlags[cursor.currentPartIndex] = true;
153
+ if (cursor.currentPartIndex < parts.length - 1) {
154
+ cursor.currentPartIndex++;
155
+ cursor.currentCharIndex = 0;
156
+ }
157
+ }
158
+ return true;
159
+ }
160
+ // Intra-part scan-ahead recovery: if matching failed at the current position,
161
+ // scan forward word-by-word within the same part. This prevents the cursor
162
+ // from getting permanently stuck mid-part when a single word mismatch occurs
163
+ // (e.g. TTS variation, punctuation boundary like `apexes."Sometimes`).
164
+ if (startChar > 0) {
165
+ const MAX_SCAN_WORDS = 8;
166
+ let scanPos = startChar;
167
+ for(let scan = 0; scan < MAX_SCAN_WORDS; scan++){
168
+ // Advance past current word
169
+ while(scanPos < partText.length && !/\s/.test(partText[scanPos]))scanPos++;
170
+ // Advance past whitespace to next word
171
+ while(scanPos < partText.length && /\s/.test(partText[scanPos]))scanPos++;
172
+ if (scanPos >= partText.length) break;
173
+ const retryPos = $5989c1f88db60f73$var$findSpokenPositionInUnspoken(spokenText, partText, scanPos);
174
+ const scanWsEnd = $5989c1f88db60f73$var$skipWhitespace(partText, scanPos);
175
+ if (retryPos > scanWsEnd) {
176
+ cursor.currentCharIndex = retryPos;
177
+ if (retryPos >= partText.length) {
178
+ cursor.partFinalFlags[cursor.currentPartIndex] = true;
179
+ if (cursor.currentPartIndex < parts.length - 1) {
180
+ cursor.currentPartIndex++;
181
+ cursor.currentCharIndex = 0;
182
+ }
183
+ }
184
+ return true;
185
+ }
186
+ }
187
+ }
188
+ // Mismatch recovery: try to find the spoken text in a later part.
189
+ for(let nextPartIdx = cursor.currentPartIndex + 1; nextPartIdx < parts.length; nextPartIdx++){
190
+ const nextPart = parts[nextPartIdx];
191
+ if (typeof nextPart.text !== "string") continue;
192
+ const match = $5989c1f88db60f73$var$findSpokenPositionInUnspoken(spokenText, nextPart.text, 0);
193
+ const nextWhitespaceEnd = $5989c1f88db60f73$var$skipWhitespace(nextPart.text, 0);
194
+ if (match > nextWhitespaceEnd) {
195
+ // Mark skipped parts as final and jump to the matched part
196
+ for(let i = cursor.currentPartIndex; i < nextPartIdx; i++)cursor.partFinalFlags[i] = true;
197
+ cursor.currentPartIndex = nextPartIdx;
198
+ cursor.currentCharIndex = match;
199
+ return true;
200
+ }
201
+ }
202
+ // If we're stuck at the start, mark the current part as skipped to avoid deadlock.
203
+ if (startChar === 0 && cursor.currentPartIndex < parts.length - 1) {
204
+ cursor.partFinalFlags[cursor.currentPartIndex] = true;
205
+ cursor.currentPartIndex++;
206
+ cursor.currentCharIndex = 0;
207
+ return true;
208
+ }
209
+ return false;
210
+ }
211
+
212
+
213
+ /**
214
+ * Copyright (c) 2024, Daily.
215
+ *
216
+ * SPDX-License-Identifier: BSD-2-Clause
217
+ */
218
+ const $7b7579093c9adf9b$export$92a4076839a978d0 = (0, $h9lXz$atom)([]);
219
+ const $7b7579093c9adf9b$export$bd01e11bc95333a1 = (0, $h9lXz$atom)(new Map());
220
+ const $7b7579093c9adf9b$export$b0cd27059c88d0ab = (0, $h9lXz$atom)(new Map());
221
+ const $7b7579093c9adf9b$export$57b1a1df06a7e728 = (0, $h9lXz$atom)(null);
222
+
223
+
224
+ /**
225
+ * Copyright (c) 2024, Daily.
226
+ *
227
+ * SPDX-License-Identifier: BSD-2-Clause
228
+ */ // ES2020-compatible polyfills for findLast / findLastIndex
229
+ function $603acc766b2aa439$export$8855a8be7bd3e9f8(arr, predicate) {
230
+ for(let i = arr.length - 1; i >= 0; i--){
231
+ if (predicate(arr[i], i, arr)) return i;
232
+ }
233
+ return -1;
234
+ }
235
+ function $603acc766b2aa439$export$296de88ccac4bedb(arr, predicate) {
236
+ const idx = $603acc766b2aa439$export$8855a8be7bd3e9f8(arr, predicate);
237
+ return idx === -1 ? undefined : arr[idx];
238
+ }
239
+
240
+
241
+ /** Max time gap (ms) between consecutive same-role messages for merging. */ const $38ede2d92c123540$var$MERGE_WINDOW_MS = 30000;
242
+ const $38ede2d92c123540$export$cbefc5865f8cbd89 = (a, b)=>{
243
+ return a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : 0;
244
+ };
245
+ const $38ede2d92c123540$export$f4610a88b89838b = (message)=>{
246
+ if (message.role === "function_call") return false;
247
+ const parts = message.parts || [];
248
+ if (parts.length === 0) return true;
249
+ return parts.every((p)=>{
250
+ if (typeof p.text === "string") return p.text.trim().length === 0;
251
+ // Check BotOutputText objects
252
+ if (typeof p.text === "object" && p.text !== null && "spoken" in p.text && "unspoken" in p.text) {
253
+ const botText = p.text;
254
+ return botText.spoken.trim().length === 0 && botText.unspoken.trim().length === 0;
255
+ }
256
+ // For ReactNode, consider it non-empty
257
+ return false;
258
+ });
259
+ };
260
+ const $38ede2d92c123540$export$3d3951a55577516 = (messages)=>{
261
+ return messages.filter((message, index, array)=>{
262
+ if (!$38ede2d92c123540$export$f4610a88b89838b(message)) return true;
263
+ // For empty messages, keep only if no following non-empty message with same role
264
+ const nextMessageWithSameRole = array.slice(index + 1).find((m)=>m.role === message.role && !$38ede2d92c123540$export$f4610a88b89838b(m));
265
+ return !nextMessageWithSameRole;
266
+ });
267
+ };
268
+ const $38ede2d92c123540$export$23a96e868837e158 = (messages)=>{
269
+ const mergedMessages = [];
270
+ for(let i = 0; i < messages.length; i++){
271
+ const currentMessage = messages[i];
272
+ const lastMerged = mergedMessages[mergedMessages.length - 1];
273
+ const timeDiff = lastMerged ? Math.abs(new Date(currentMessage.createdAt).getTime() - new Date(lastMerged.createdAt).getTime()) : Infinity;
274
+ const shouldMerge = lastMerged && lastMerged.role === currentMessage.role && currentMessage.role !== "system" && currentMessage.role !== "function_call" && timeDiff < $38ede2d92c123540$var$MERGE_WINDOW_MS;
275
+ if (shouldMerge) mergedMessages[mergedMessages.length - 1] = {
276
+ ...lastMerged,
277
+ parts: [
278
+ ...lastMerged.parts || [],
279
+ ...currentMessage.parts || []
280
+ ],
281
+ updatedAt: currentMessage.updatedAt || currentMessage.createdAt,
282
+ final: currentMessage.final !== false
283
+ };
284
+ else mergedMessages.push(currentMessage);
285
+ }
286
+ return mergedMessages;
287
+ };
288
+ const $38ede2d92c123540$var$statusPriority = {
289
+ started: 0,
290
+ in_progress: 1,
291
+ completed: 2
292
+ };
293
+ const $38ede2d92c123540$export$dfa183f99573a822 = (messages)=>{
294
+ const bestByToolCallId = new Map();
295
+ const toRemove = new Set();
296
+ for(let i = 0; i < messages.length; i++){
297
+ const msg = messages[i];
298
+ const tcid = msg.functionCall?.tool_call_id;
299
+ if (msg.role !== "function_call" || !tcid) continue;
300
+ const existingIdx = bestByToolCallId.get(tcid);
301
+ if (existingIdx !== undefined) {
302
+ const existingPriority = $38ede2d92c123540$var$statusPriority[messages[existingIdx].functionCall.status] ?? 0;
303
+ const currentPriority = $38ede2d92c123540$var$statusPriority[msg.functionCall.status] ?? 0;
304
+ if (currentPriority >= existingPriority) {
305
+ toRemove.add(existingIdx);
306
+ bestByToolCallId.set(tcid, i);
307
+ } else toRemove.add(i);
308
+ } else bestByToolCallId.set(tcid, i);
309
+ }
310
+ if (toRemove.size === 0) return messages;
311
+ return messages.filter((_, i)=>!toRemove.has(i));
312
+ };
313
+ const $38ede2d92c123540$var$normalizeMessagesForUI = (messages)=>{
314
+ return $38ede2d92c123540$export$23a96e868837e158($38ede2d92c123540$export$dfa183f99573a822($38ede2d92c123540$export$3d3951a55577516([
315
+ ...messages
316
+ ].sort($38ede2d92c123540$export$cbefc5865f8cbd89))));
317
+ };
318
+ // ---------------------------------------------------------------------------
319
+ // Internal helpers
320
+ // ---------------------------------------------------------------------------
321
+ const $38ede2d92c123540$var$callCallbacks = (callbacksMap, type, message)=>{
322
+ callbacksMap.forEach((callbacks)=>{
323
+ try {
324
+ callbacks[type]?.(message);
325
+ } catch (error) {
326
+ console.error(`Error in ${type} callback:`, error);
327
+ }
328
+ });
329
+ };
330
+ function $38ede2d92c123540$export$f2820c5e040afe17(get, set, id, callbacks) {
331
+ const map = new Map(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)));
332
+ map.set(id, callbacks);
333
+ set((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab), map);
334
+ }
335
+ function $38ede2d92c123540$export$46ae1ba121fdc956(get, set, id) {
336
+ const map = new Map(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)));
337
+ map.delete(id);
338
+ set((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab), map);
339
+ }
340
+ function $38ede2d92c123540$export$f2434643f2abff11(_get, set) {
341
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), []);
342
+ set((0, $7b7579093c9adf9b$export$bd01e11bc95333a1), new Map());
343
+ }
344
+ function $38ede2d92c123540$export$16fdb433d434f08(get, set, messageData) {
345
+ const now = new Date();
346
+ const message = {
347
+ ...messageData,
348
+ createdAt: now.toISOString(),
349
+ updatedAt: now.toISOString()
350
+ };
351
+ const current = get((0, $7b7579093c9adf9b$export$92a4076839a978d0));
352
+ const updatedMessages = [
353
+ ...current,
354
+ message
355
+ ];
356
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(updatedMessages);
357
+ $38ede2d92c123540$var$callCallbacks(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)), "onMessageCreated", message);
358
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
359
+ }
360
+ function $38ede2d92c123540$export$a05f96a2aa40873e(get, set, role, updates) {
361
+ const messages = [
362
+ ...get((0, $7b7579093c9adf9b$export$92a4076839a978d0))
363
+ ];
364
+ const lastMessageIndex = (0, $603acc766b2aa439$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === role);
365
+ if (lastMessageIndex === -1) return;
366
+ const existing = messages[lastMessageIndex];
367
+ const updatedMessage = {
368
+ ...existing,
369
+ ...updates,
370
+ updatedAt: new Date().toISOString()
371
+ };
372
+ messages[lastMessageIndex] = updatedMessage;
373
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(messages);
374
+ $38ede2d92c123540$var$callCallbacks(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)), "onMessageUpdated", updatedMessage);
375
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
376
+ }
377
+ function $38ede2d92c123540$export$78bd074993ab081f(get, set, role) {
378
+ const messages = [
379
+ ...get((0, $7b7579093c9adf9b$export$92a4076839a978d0))
380
+ ];
381
+ const lastMessageIndex = (0, $603acc766b2aa439$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === role);
382
+ if (lastMessageIndex === -1) return;
383
+ const lastMessage = messages[lastMessageIndex];
384
+ // Check if message is empty
385
+ if ($38ede2d92c123540$export$f4610a88b89838b(lastMessage)) // Remove empty message only if it has no text in streams either
386
+ messages.splice(lastMessageIndex, 1);
387
+ else {
388
+ // Finalize message and its last part
389
+ const parts = [
390
+ ...lastMessage.parts || []
391
+ ];
392
+ if (parts.length > 0) parts[parts.length - 1] = {
393
+ ...parts[parts.length - 1],
394
+ final: true
395
+ };
396
+ messages[lastMessageIndex] = {
397
+ ...lastMessage,
398
+ parts: parts,
399
+ final: true,
400
+ updatedAt: new Date().toISOString()
401
+ };
402
+ $38ede2d92c123540$var$callCallbacks(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)), "onMessageUpdated", messages[lastMessageIndex]);
403
+ }
404
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(messages);
405
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
406
+ }
407
+ function $38ede2d92c123540$export$cd74f809c89ea10c(get, set, role) {
408
+ const messages = [
409
+ ...get((0, $7b7579093c9adf9b$export$92a4076839a978d0))
410
+ ];
411
+ const lastMessageIndex = (0, $603acc766b2aa439$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === role);
412
+ if (lastMessageIndex === -1) return;
413
+ const lastMessage = messages[lastMessageIndex];
414
+ if ($38ede2d92c123540$export$f4610a88b89838b(lastMessage)) {
415
+ messages.splice(lastMessageIndex, 1);
416
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(messages);
417
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
418
+ }
419
+ }
420
+ function $38ede2d92c123540$export$c3a007efc2f315a0(get, set, messageData) {
421
+ const now = new Date();
422
+ const message = {
423
+ role: messageData.role,
424
+ final: true,
425
+ parts: [
426
+ ...messageData.parts
427
+ ],
428
+ createdAt: now.toISOString(),
429
+ updatedAt: now.toISOString()
430
+ };
431
+ const current = get((0, $7b7579093c9adf9b$export$92a4076839a978d0));
432
+ const updatedMessages = [
433
+ ...current,
434
+ message
435
+ ];
436
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(updatedMessages);
437
+ $38ede2d92c123540$var$callCallbacks(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)), "onMessageCreated", message);
438
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
439
+ }
440
+ function $38ede2d92c123540$export$57de01211e3df548(get, set, text, final) {
441
+ const now = new Date();
442
+ const messages = [
443
+ ...get((0, $7b7579093c9adf9b$export$92a4076839a978d0))
444
+ ];
445
+ // Find last user message
446
+ const lastUserIndex = (0, $603acc766b2aa439$export$8855a8be7bd3e9f8)(messages, (m)=>m.role === "user");
447
+ if (lastUserIndex !== -1 && !messages[lastUserIndex].final) {
448
+ // Update existing user message
449
+ const target = {
450
+ ...messages[lastUserIndex]
451
+ };
452
+ const parts = Array.isArray(target.parts) ? [
453
+ ...target.parts
454
+ ] : [];
455
+ const lastPart = parts[parts.length - 1];
456
+ if (!lastPart || lastPart.final) // Start a new part
457
+ parts.push({
458
+ text: text,
459
+ final: final,
460
+ createdAt: now.toISOString()
461
+ });
462
+ else // Update in-progress part
463
+ parts[parts.length - 1] = {
464
+ ...lastPart,
465
+ text: text,
466
+ final: final
467
+ };
468
+ const updatedMessage = {
469
+ ...target,
470
+ parts: parts,
471
+ updatedAt: now.toISOString()
472
+ };
473
+ messages[lastUserIndex] = updatedMessage;
474
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(messages);
475
+ $38ede2d92c123540$var$callCallbacks(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)), "onMessageUpdated", updatedMessage);
476
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
477
+ return;
478
+ }
479
+ // Create a new user message initialized with this transcript
480
+ const newMessage = {
481
+ role: "user",
482
+ final: false,
483
+ parts: [
484
+ {
485
+ text: text,
486
+ final: final,
487
+ createdAt: now.toISOString()
488
+ }
489
+ ],
490
+ createdAt: now.toISOString(),
491
+ updatedAt: now.toISOString()
492
+ };
493
+ const updatedMessages = [
494
+ ...messages,
495
+ newMessage
496
+ ];
497
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(updatedMessages);
498
+ $38ede2d92c123540$var$callCallbacks(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)), "onMessageCreated", newMessage);
499
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
500
+ }
501
+ function $38ede2d92c123540$export$d78810e35f742a16(get, set, text, final, spoken, aggregatedBy) {
502
+ const now = new Date();
503
+ const messages = [
504
+ ...get((0, $7b7579093c9adf9b$export$92a4076839a978d0))
505
+ ];
506
+ const botOutputMessageState = new Map(get((0, $7b7579093c9adf9b$export$bd01e11bc95333a1)));
507
+ const lastAssistantIndex = (0, $603acc766b2aa439$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "assistant");
508
+ let messageId;
509
+ let isNewMessage = false;
510
+ if (lastAssistantIndex === -1) {
511
+ // Create new assistant message
512
+ isNewMessage = true;
513
+ messageId = now.toISOString();
514
+ const newMessage = {
515
+ role: "assistant",
516
+ final: final,
517
+ parts: [],
518
+ createdAt: messageId,
519
+ updatedAt: messageId
520
+ };
521
+ messages.push(newMessage);
522
+ // Initialize message state
523
+ botOutputMessageState.set(messageId, {
524
+ currentPartIndex: 0,
525
+ currentCharIndex: 0,
526
+ partFinalFlags: []
527
+ });
528
+ } else {
529
+ // Update existing assistant message
530
+ const lastMessage = messages[lastAssistantIndex];
531
+ messageId = lastMessage.createdAt;
532
+ messages[lastAssistantIndex] = {
533
+ ...lastMessage,
534
+ final: final ? true : lastMessage.final,
535
+ updatedAt: now.toISOString()
536
+ };
537
+ // Ensure message state exists
538
+ if (!botOutputMessageState.has(messageId)) botOutputMessageState.set(messageId, {
539
+ currentPartIndex: 0,
540
+ currentCharIndex: 0,
541
+ partFinalFlags: []
542
+ });
543
+ }
544
+ const messageState = botOutputMessageState.get(messageId);
545
+ const message = messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex];
546
+ const parts = [
547
+ ...message.parts || []
548
+ ];
549
+ if (!spoken) {
550
+ // UNSPOKEN EVENT: Create/update message parts immediately
551
+ // Only append when both current and last part are word-level; sentence-level
552
+ // and other types each get their own part so spoken events can match 1:1.
553
+ const isDefaultType = aggregatedBy === "sentence" || aggregatedBy === "word" || !aggregatedBy;
554
+ const lastPart = parts[parts.length - 1];
555
+ const shouldAppend = lastPart && aggregatedBy === "word" && lastPart.aggregatedBy === "word" && typeof lastPart.text === "string";
556
+ if (shouldAppend) {
557
+ // Append to last part (word-level only)
558
+ const lastPartText = lastPart.text;
559
+ const separator = lastPartText && !lastPartText.endsWith(" ") && !text.startsWith(" ") ? " " : "";
560
+ parts[parts.length - 1] = {
561
+ ...lastPart,
562
+ text: lastPartText + separator + text
563
+ };
564
+ } else {
565
+ // Create new part (sentence-level, custom types, or first word chunk)
566
+ // Default to inline; custom types get displayMode from metadata in the hook
567
+ const defaultDisplayMode = isDefaultType ? "inline" : undefined;
568
+ const newPart = {
569
+ text: text,
570
+ final: false,
571
+ createdAt: now.toISOString(),
572
+ aggregatedBy: aggregatedBy,
573
+ displayMode: defaultDisplayMode
574
+ };
575
+ parts.push(newPart);
576
+ // Extend partFinalFlags array
577
+ messageState.partFinalFlags.push(false);
578
+ }
579
+ // Update message with new parts
580
+ messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex] = {
581
+ ...message,
582
+ parts: parts
583
+ };
584
+ } else {
585
+ // SPOKEN EVENT: advance cursor into existing text, or add as new part if
586
+ // there is none (bots that only send spoken: true, never unspoken).
587
+ const advanced = parts.length > 0 && (0, $5989c1f88db60f73$export$9b39fd8438081d6d)(messageState, parts, text);
588
+ if (!advanced) {
589
+ // No unspoken content to advance: add this text as a part already fully spoken
590
+ const isDefaultType = aggregatedBy === "sentence" || aggregatedBy === "word" || !aggregatedBy;
591
+ const defaultDisplayMode = isDefaultType ? "inline" : undefined;
592
+ const newPart = {
593
+ text: text,
594
+ final: false,
595
+ createdAt: now.toISOString(),
596
+ aggregatedBy: aggregatedBy,
597
+ displayMode: defaultDisplayMode
598
+ };
599
+ parts.push(newPart);
600
+ messageState.partFinalFlags.push(true);
601
+ messageState.currentPartIndex = parts.length - 1;
602
+ messageState.currentCharIndex = text.length;
603
+ messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex] = {
604
+ ...message,
605
+ parts: parts
606
+ };
607
+ }
608
+ }
609
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(messages);
610
+ const cbs = get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab));
611
+ const updatedMsg = messages[lastAssistantIndex === -1 ? messages.length - 1 : lastAssistantIndex];
612
+ if (isNewMessage) $38ede2d92c123540$var$callCallbacks(cbs, "onMessageCreated", updatedMsg);
613
+ else $38ede2d92c123540$var$callCallbacks(cbs, "onMessageUpdated", updatedMsg);
614
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
615
+ set((0, $7b7579093c9adf9b$export$bd01e11bc95333a1), botOutputMessageState);
616
+ }
617
+ function $38ede2d92c123540$export$e450805545c7dd86(get, set, data) {
618
+ const messages = get((0, $7b7579093c9adf9b$export$92a4076839a978d0));
619
+ // If a tool_call_id is provided, check for an existing entry to avoid duplicates
620
+ if (data.tool_call_id) {
621
+ const existingIndex = (0, $603acc766b2aa439$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "function_call" && msg.functionCall?.tool_call_id === data.tool_call_id);
622
+ if (existingIndex !== -1) return;
623
+ }
624
+ const now = new Date();
625
+ const message = {
626
+ role: "function_call",
627
+ final: false,
628
+ parts: [],
629
+ createdAt: now.toISOString(),
630
+ updatedAt: now.toISOString(),
631
+ functionCall: {
632
+ function_name: data.function_name,
633
+ tool_call_id: data.tool_call_id,
634
+ args: data.args,
635
+ status: "started"
636
+ }
637
+ };
638
+ const updatedMessages = [
639
+ ...messages,
640
+ message
641
+ ];
642
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(updatedMessages);
643
+ $38ede2d92c123540$var$callCallbacks(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)), "onMessageCreated", message);
644
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
645
+ }
646
+ function $38ede2d92c123540$export$1908264de3a8afe4(get, set, tool_call_id, updates) {
647
+ const messages = [
648
+ ...get((0, $7b7579093c9adf9b$export$92a4076839a978d0))
649
+ ];
650
+ const index = (0, $603acc766b2aa439$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "function_call" && msg.functionCall?.tool_call_id === tool_call_id);
651
+ if (index === -1) return false;
652
+ const existing = messages[index];
653
+ const updated = {
654
+ ...existing,
655
+ updatedAt: new Date().toISOString(),
656
+ final: updates.status === "completed" ? true : existing.final,
657
+ functionCall: {
658
+ ...existing.functionCall,
659
+ ...updates
660
+ }
661
+ };
662
+ messages[index] = updated;
663
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(messages);
664
+ $38ede2d92c123540$var$callCallbacks(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)), "onMessageUpdated", updated);
665
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
666
+ return true;
667
+ }
668
+ function $38ede2d92c123540$export$54b3765e641b1813(get, set, updates) {
669
+ const messages = [
670
+ ...get((0, $7b7579093c9adf9b$export$92a4076839a978d0))
671
+ ];
672
+ const index = (0, $603acc766b2aa439$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "function_call" && msg.functionCall?.status === "started" && !msg.functionCall?.tool_call_id);
673
+ if (index === -1) return false;
674
+ const existing = messages[index];
675
+ const updated = {
676
+ ...existing,
677
+ updatedAt: new Date().toISOString(),
678
+ functionCall: {
679
+ ...existing.functionCall,
680
+ ...updates
681
+ }
682
+ };
683
+ messages[index] = updated;
684
+ const processedMessages = $38ede2d92c123540$var$normalizeMessagesForUI(messages);
685
+ $38ede2d92c123540$var$callCallbacks(get((0, $7b7579093c9adf9b$export$b0cd27059c88d0ab)), "onMessageUpdated", updated);
686
+ set((0, $7b7579093c9adf9b$export$92a4076839a978d0), processedMessages);
687
+ return true;
688
+ }
689
+ function $38ede2d92c123540$export$809efdca1a10761a(get, set, data) {
690
+ const messages = get((0, $7b7579093c9adf9b$export$92a4076839a978d0));
691
+ const lastFc = (0, $603acc766b2aa439$export$296de88ccac4bedb)(messages, (m)=>m.role === "function_call");
692
+ // Check if InProgress already created an entry (events arrived out of order).
693
+ if (lastFc?.functionCall && lastFc.functionCall.status !== "started" && Date.now() - new Date(lastFc.createdAt).getTime() < 2000) {
694
+ if (data.function_name && !lastFc.functionCall.function_name && lastFc.functionCall.tool_call_id) $38ede2d92c123540$export$1908264de3a8afe4(get, set, lastFc.functionCall.tool_call_id, {
695
+ function_name: data.function_name
696
+ });
697
+ return;
698
+ }
699
+ $38ede2d92c123540$export$e450805545c7dd86(get, set, {
700
+ function_name: data.function_name
701
+ });
702
+ }
703
+ function $38ede2d92c123540$export$568cde5f4aa4dcba(get, set, data) {
704
+ // Tier 1: Try to update the last "started" entry (from LLMFunctionCallStarted)
705
+ const updated = $38ede2d92c123540$export$54b3765e641b1813(get, set, {
706
+ function_name: data.function_name,
707
+ tool_call_id: data.tool_call_id,
708
+ args: data.args,
709
+ status: "in_progress"
710
+ });
711
+ if (!updated) {
712
+ // Tier 2: Try updating an existing entry by tool_call_id
713
+ const found = $38ede2d92c123540$export$1908264de3a8afe4(get, set, data.tool_call_id, {
714
+ function_name: data.function_name,
715
+ args: data.args,
716
+ status: "in_progress"
717
+ });
718
+ if (!found) {
719
+ // Tier 3: No existing entry at all; create a new one as in_progress
720
+ $38ede2d92c123540$export$e450805545c7dd86(get, set, {
721
+ function_name: data.function_name,
722
+ tool_call_id: data.tool_call_id,
723
+ args: data.args
724
+ });
725
+ $38ede2d92c123540$export$1908264de3a8afe4(get, set, data.tool_call_id, {
726
+ status: "in_progress"
727
+ });
728
+ }
729
+ }
730
+ }
731
+ function $38ede2d92c123540$export$a3cea3f7508e820b(get, set, data) {
732
+ // Tier 1: Try updating by tool_call_id
733
+ const found = $38ede2d92c123540$export$1908264de3a8afe4(get, set, data.tool_call_id, {
734
+ function_name: data.function_name,
735
+ status: "completed",
736
+ result: data.result,
737
+ cancelled: data.cancelled
738
+ });
739
+ if (!found) {
740
+ // Tier 2: No match by tool_call_id (e.g. InProgress was skipped).
741
+ const matched = $38ede2d92c123540$export$54b3765e641b1813(get, set, {
742
+ function_name: data.function_name,
743
+ tool_call_id: data.tool_call_id
744
+ });
745
+ if (matched) $38ede2d92c123540$export$1908264de3a8afe4(get, set, data.tool_call_id, {
746
+ status: "completed",
747
+ result: data.result,
748
+ cancelled: data.cancelled
749
+ });
750
+ }
751
+ }
752
+
753
+
754
+
755
+ /**
756
+ * Copyright (c) 2024, Daily.
757
+ *
758
+ * SPDX-License-Identifier: BSD-2-Clause
759
+ */
760
+
761
+
41
762
  /**
42
763
  * Copyright (c) 2024, Daily.
43
764
  *
@@ -66,6 +787,239 @@ const $824ea64b5f757259$export$33a6ac53b8f02625 = (event, handler)=>{
66
787
  };
67
788
 
68
789
 
790
+
791
+
792
+
793
+
794
+ /**
795
+ * Checks if a version meets a minimum version requirement.
796
+ * Inlined to avoid adding a `semver` dependency.
797
+ */ function $d1596d1cb01c59d1$var$isMinVersion(currentVersion, minVersion) {
798
+ // Strip pre-release suffix (e.g. "1.1.0-beta.1" -> "1.1.0")
799
+ const parts = currentVersion.split("-")[0].split(".").map(Number);
800
+ for(let i = 0; i < 3; i++){
801
+ if ((parts[i] || 0) > minVersion[i]) return true;
802
+ if ((parts[i] || 0) < minVersion[i]) return false;
803
+ }
804
+ return true; // equal
805
+ }
806
+ /** Delay (ms) before finalizing the assistant message after bot stops speaking. */ const $d1596d1cb01c59d1$var$BOT_STOPPED_FINALIZE_DELAY_MS = 2500;
807
+ function $d1596d1cb01c59d1$export$52680e26621bab39() {
808
+ const userStoppedTimeout = (0, $h9lXz$useRef)(undefined);
809
+ const botStoppedSpeakingTimeoutRef = (0, $h9lXz$useRef)(undefined);
810
+ const assistantStreamResetRef = (0, $h9lXz$useRef)(0);
811
+ const botOutputLastChunkRef = (0, $h9lXz$useRef)({
812
+ spoken: "",
813
+ unspoken: ""
814
+ });
815
+ // Clean up pending timeouts on unmount
816
+ (0, $h9lXz$useEffect)(()=>{
817
+ return ()=>{
818
+ clearTimeout(userStoppedTimeout.current);
819
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
820
+ };
821
+ }, []);
822
+ // -- helpers ---------------------------------------------------------------
823
+ const finalizeLastAssistantMessageIfPending = (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set)=>{
824
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
825
+ botStoppedSpeakingTimeoutRef.current = undefined;
826
+ const messages = get((0, $7b7579093c9adf9b$export$92a4076839a978d0));
827
+ const lastAssistant = (0, $603acc766b2aa439$export$296de88ccac4bedb)(messages, (m)=>m.role === "assistant");
828
+ if (lastAssistant && !lastAssistant.final) (0, $38ede2d92c123540$export$78bd074993ab081f)(get, set, "assistant");
829
+ }, []));
830
+ const ensureAssistantMessage = (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set)=>{
831
+ const messages = get((0, $7b7579093c9adf9b$export$92a4076839a978d0));
832
+ const lastAssistantIndex = (0, $603acc766b2aa439$export$8855a8be7bd3e9f8)(messages, (msg)=>msg.role === "assistant");
833
+ const lastAssistant = lastAssistantIndex !== -1 ? messages[lastAssistantIndex] : undefined;
834
+ if (!lastAssistant || lastAssistant.final) {
835
+ // If the message was finalized but still has unspoken content, it was
836
+ // finalized prematurely (e.g. BotStoppedSpeaking timer fired during a
837
+ // TTS pause mid-response). Un-finalize it instead of creating a new
838
+ // message bubble — but only when no user message followed.
839
+ if (lastAssistant?.final && lastAssistantIndex === messages.length - 1) {
840
+ const messageId = lastAssistant.createdAt;
841
+ const botOutputState = get((0, $7b7579093c9adf9b$export$bd01e11bc95333a1));
842
+ const cursor = botOutputState.get(messageId);
843
+ if (cursor && (0, $5989c1f88db60f73$export$802e9b947136afbf)(cursor, lastAssistant.parts || [])) {
844
+ (0, $38ede2d92c123540$export$a05f96a2aa40873e)(get, set, "assistant", {
845
+ final: false
846
+ });
847
+ return false;
848
+ }
849
+ }
850
+ (0, $38ede2d92c123540$export$16fdb433d434f08)(get, set, {
851
+ role: "assistant",
852
+ final: false,
853
+ parts: []
854
+ });
855
+ assistantStreamResetRef.current += 1;
856
+ return true;
857
+ }
858
+ return false;
859
+ }, []));
860
+ // -- event handlers --------------------------------------------------------
861
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).Connected, (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set)=>{
862
+ (0, $38ede2d92c123540$export$f2434643f2abff11)(get, set);
863
+ set((0, $7b7579093c9adf9b$export$57b1a1df06a7e728), null);
864
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
865
+ botStoppedSpeakingTimeoutRef.current = undefined;
866
+ botOutputLastChunkRef.current = {
867
+ spoken: "",
868
+ unspoken: ""
869
+ };
870
+ }, [])));
871
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).BotReady, (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((_get, set, botData)=>{
872
+ const rtviVersion = botData.version;
873
+ const supportsBotOutput = $d1596d1cb01c59d1$var$isMinVersion(rtviVersion, [
874
+ 1,
875
+ 1,
876
+ 0
877
+ ]);
878
+ set((0, $7b7579093c9adf9b$export$57b1a1df06a7e728), supportsBotOutput);
879
+ }, [])));
880
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).BotOutput, (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set, data)=>{
881
+ // A BotOutput event means the response is still active; cancel any
882
+ // pending finalize timer from BotStoppedSpeaking.
883
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
884
+ botStoppedSpeakingTimeoutRef.current = undefined;
885
+ ensureAssistantMessage();
886
+ // Handle spacing for BotOutput chunks
887
+ let textToAdd = data.text;
888
+ const lastChunk = data.spoken ? botOutputLastChunkRef.current.spoken : botOutputLastChunkRef.current.unspoken;
889
+ // Add space separator if needed between BotOutput chunks
890
+ if (lastChunk) textToAdd = " " + textToAdd;
891
+ // Update the appropriate last chunk tracker
892
+ if (data.spoken) botOutputLastChunkRef.current.spoken = textToAdd;
893
+ else botOutputLastChunkRef.current.unspoken = textToAdd;
894
+ // Update both spoken and unspoken text streams
895
+ const isFinal = data.aggregated_by === "sentence";
896
+ (0, $38ede2d92c123540$export$d78810e35f742a16)(get, set, textToAdd, isFinal, data.spoken, data.aggregated_by);
897
+ }, [
898
+ ensureAssistantMessage
899
+ ])));
900
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).BotStoppedSpeaking, (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set)=>{
901
+ // Don't finalize immediately; start a timer. Bot may start speaking again (pause).
902
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
903
+ const messages = get((0, $7b7579093c9adf9b$export$92a4076839a978d0));
904
+ const lastAssistant = (0, $603acc766b2aa439$export$296de88ccac4bedb)(messages, (m)=>m.role === "assistant");
905
+ if (!lastAssistant || lastAssistant.final) return;
906
+ botStoppedSpeakingTimeoutRef.current = setTimeout(()=>{
907
+ botStoppedSpeakingTimeoutRef.current = undefined;
908
+ // Snap the speech-progress cursor to the end of all parts.
909
+ // The bot finished speaking normally (not interrupted), so all
910
+ // text should render as "spoken". Without this, text from the
911
+ // last sentence can remain grey if the spoken BotOutput event
912
+ // didn't match the unspoken text exactly.
913
+ const msgs = get((0, $7b7579093c9adf9b$export$92a4076839a978d0));
914
+ const cursorMap = new Map(get((0, $7b7579093c9adf9b$export$bd01e11bc95333a1)));
915
+ const last = (0, $603acc766b2aa439$export$296de88ccac4bedb)(msgs, (m)=>m.role === "assistant");
916
+ if (last) {
917
+ const cursor = cursorMap.get(last.createdAt);
918
+ if (cursor && last.parts && last.parts.length > 0) {
919
+ const lastPartIdx = last.parts.length - 1;
920
+ const lastPartText = last.parts[lastPartIdx]?.text;
921
+ cursor.currentPartIndex = lastPartIdx;
922
+ cursor.currentCharIndex = typeof lastPartText === "string" ? lastPartText.length : 0;
923
+ for(let i = 0; i <= lastPartIdx; i++)cursor.partFinalFlags[i] = true;
924
+ set((0, $7b7579093c9adf9b$export$bd01e11bc95333a1), cursorMap);
925
+ }
926
+ }
927
+ (0, $38ede2d92c123540$export$78bd074993ab081f)(get, set, "assistant");
928
+ }, $d1596d1cb01c59d1$var$BOT_STOPPED_FINALIZE_DELAY_MS);
929
+ }, [])));
930
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).BotStartedSpeaking, (0, $h9lXz$useCallback)(()=>{
931
+ // Bot is speaking again; reset the finalize timer (bot was just pausing).
932
+ clearTimeout(botStoppedSpeakingTimeoutRef.current);
933
+ botStoppedSpeakingTimeoutRef.current = undefined;
934
+ }, []));
935
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).UserStartedSpeaking, (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set)=>{
936
+ // User started a new turn; bot's turn is done. Fast-forward: finalize immediately.
937
+ finalizeLastAssistantMessageIfPending();
938
+ clearTimeout(userStoppedTimeout.current);
939
+ // Only finalize the previous user message if the bot has responded since
940
+ // the user last spoke. This prevents finalizing during VAD gaps (brief
941
+ // breathing pauses within the same user turn where UserStoppedSpeaking/
942
+ // UserStartedSpeaking fire without an actual turn change).
943
+ const messages = get((0, $7b7579093c9adf9b$export$92a4076839a978d0));
944
+ const lastUserIdx = (0, $603acc766b2aa439$export$8855a8be7bd3e9f8)(messages, (m)=>m.role === "user");
945
+ if (lastUserIdx !== -1 && !messages[lastUserIdx].final) {
946
+ const hasBotActivityAfterUser = messages.slice(lastUserIdx + 1).some((m)=>m.role === "assistant");
947
+ if (hasBotActivityAfterUser) (0, $38ede2d92c123540$export$78bd074993ab081f)(get, set, "user");
948
+ }
949
+ }, [
950
+ finalizeLastAssistantMessageIfPending
951
+ ])));
952
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).UserTranscript, (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set, data)=>{
953
+ const text = data.text ?? "";
954
+ const final = Boolean(data.final);
955
+ (0, $38ede2d92c123540$export$57de01211e3df548)(get, set, text, final);
956
+ // If we got any transcript, cancel pending cleanup
957
+ clearTimeout(userStoppedTimeout.current);
958
+ }, [])));
959
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).UserStoppedSpeaking, (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set)=>{
960
+ clearTimeout(userStoppedTimeout.current);
961
+ // If no transcript ends up arriving, ensure any accidental empty placeholder is removed.
962
+ userStoppedTimeout.current = setTimeout(()=>{
963
+ // Re-read state at timeout time
964
+ const messages = get((0, $7b7579093c9adf9b$export$92a4076839a978d0));
965
+ const lastUser = (0, $603acc766b2aa439$export$296de88ccac4bedb)(messages, (m)=>m.role === "user");
966
+ const hasParts = Array.isArray(lastUser?.parts) && lastUser.parts.length > 0;
967
+ if (!lastUser || !hasParts) (0, $38ede2d92c123540$export$cd74f809c89ea10c)(get, set, "user");
968
+ else if (!lastUser.final) (0, $38ede2d92c123540$export$78bd074993ab081f)(get, set, "user");
969
+ }, 3000);
970
+ }, [])));
971
+ // LLM Function Call lifecycle events
972
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).LLMFunctionCallStarted, (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set, data)=>{
973
+ (0, $38ede2d92c123540$export$809efdca1a10761a)(get, set, {
974
+ function_name: data.function_name
975
+ });
976
+ }, [])));
977
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).LLMFunctionCallInProgress, (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set, data)=>{
978
+ (0, $38ede2d92c123540$export$568cde5f4aa4dcba)(get, set, {
979
+ function_name: data.function_name,
980
+ tool_call_id: data.tool_call_id,
981
+ args: data.arguments
982
+ });
983
+ }, [])));
984
+ (0, $824ea64b5f757259$export$33a6ac53b8f02625)((0, $h9lXz$RTVIEvent).LLMFunctionCallStopped, (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set, data)=>{
985
+ (0, $38ede2d92c123540$export$a3cea3f7508e820b)(get, set, {
986
+ function_name: data.function_name,
987
+ tool_call_id: data.tool_call_id,
988
+ result: data.result,
989
+ cancelled: data.cancelled
990
+ });
991
+ }, [])));
992
+ }
993
+
994
+
995
+ const $b5a460b32af71766$export$4d68d6035e4164b1 = /*#__PURE__*/ (0, $h9lXz$createContext)(null);
996
+ const $b5a460b32af71766$export$e372a2172e7d18e8 = ({ children: children })=>{
997
+ (0, $d1596d1cb01c59d1$export$52680e26621bab39)();
998
+ const injectMessage = (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set, message)=>{
999
+ (0, $38ede2d92c123540$export$c3a007efc2f315a0)(get, set, message);
1000
+ }, []));
1001
+ const botOutputSupported = (0, $h9lXz$useAtomValue)((0, $7b7579093c9adf9b$export$57b1a1df06a7e728));
1002
+ return (0, $h9lXz$jsx)($b5a460b32af71766$export$4d68d6035e4164b1.Provider, {
1003
+ value: {
1004
+ injectMessage: injectMessage,
1005
+ botOutputSupported: botOutputSupported
1006
+ },
1007
+ children: children
1008
+ });
1009
+ };
1010
+ $b5a460b32af71766$export$e372a2172e7d18e8.displayName = "PipecatConversationProvider";
1011
+ const $b5a460b32af71766$export$8eec679bf8249822 = ()=>{
1012
+ const context = (0, $h9lXz$useContext)($b5a460b32af71766$export$4d68d6035e4164b1);
1013
+ if (!context) throw new Error("useConversationContext must be used within a PipecatClientProvider");
1014
+ return context;
1015
+ };
1016
+
1017
+
1018
+
1019
+
1020
+
1021
+
1022
+
69
1023
  const $a90aa7250c094218$export$d6bdcccacef16204 = /*#__PURE__*/ (0, $h9lXz$createContext)({
70
1024
  enableCam: ()=>{
71
1025
  throw new Error("PipecatClientCamStateContext: enableCam() called outside of provider");
@@ -200,7 +1154,9 @@ const $d2e362c5a07ee3c5$export$bb43666ced7a20d0 = ({ children: children, client:
200
1154
  off: off
201
1155
  },
202
1156
  children: (0, $h9lXz$jsx)((0, $a90aa7250c094218$export$4777554fda61c378), {
203
- children: children
1157
+ children: (0, $h9lXz$jsx)((0, $b5a460b32af71766$export$e372a2172e7d18e8), {
1158
+ children: children
1159
+ })
204
1160
  })
205
1161
  })
206
1162
  })
@@ -846,5 +1802,126 @@ $993a744193844a95$export$59bf27bd43679db6.displayName = "VoiceVisualizer";
846
1802
 
847
1803
 
848
1804
 
849
- export {$f209aa7ddb77dcb2$export$b52250cb73ff4de1 as PipecatClientAudio, $7cb2ce2c4cbfb401$export$dc9a029eeca8213f as PipecatClientCamToggle, $2984fdfc31bad375$export$bc8133b69ff660a2 as PipecatClientMicToggle, $d2e362c5a07ee3c5$export$bb43666ced7a20d0 as PipecatClientProvider, $d6dc7e80e6e976a0$export$93764714dfab3a46 as PipecatClientScreenShareToggle, $6a65deb8615a2ad7$export$85974db6d0cc43b3 as PipecatClientVideo, $034a56e7ee1b7bed$export$777fa8498be78705 as usePipecatClient, $e76ee2f021b54325$export$3ea2601427f0430f as usePipecatClientCamControl, $f934f1f8b10aaf19$export$642bc4d2d2a376f1 as usePipecatClientMediaDevices, $4b4b9099cdb5b776$export$9813dcd2d0c26814 as usePipecatClientMediaTrack, $5905c001b0dc8d25$export$388e706586309ef0 as usePipecatClientMicControl, $d39c0bc3744ea030$export$be63b19bd7f7d4f5 as usePipecatClientScreenShareControl, $33f3729bbe9f09df$export$30aee278309a867b as usePipecatClientTransportState, $824ea64b5f757259$export$33a6ac53b8f02625 as useRTVIClientEvent, $993a744193844a95$export$59bf27bd43679db6 as VoiceVisualizer};
1805
+ /**
1806
+ * Copyright (c) 2024, Daily.
1807
+ *
1808
+ * SPDX-License-Identifier: BSD-2-Clause
1809
+ */
1810
+
1811
+
1812
+
1813
+
1814
+
1815
+ const $894023a10d0bd040$export$acb832b064cb6c09 = ({ onMessageCreated: onMessageCreated, onMessageUpdated: onMessageUpdated, onMessageAdded: onMessageAdded, aggregationMetadata: aggregationMetadata } = {})=>{
1816
+ const { injectMessage: injectMessage } = (0, $b5a460b32af71766$export$8eec679bf8249822)();
1817
+ // Generate a unique ID for this hook instance
1818
+ const callbackId = (0, $h9lXz$useId)();
1819
+ // Resolve deprecated onMessageAdded → onMessageCreated
1820
+ const resolvedCreated = onMessageCreated ?? onMessageAdded;
1821
+ // Register and unregister the callbacks
1822
+ const doRegister = (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set)=>{
1823
+ (0, $38ede2d92c123540$export$f2820c5e040afe17)(get, set, callbackId, {
1824
+ onMessageCreated: resolvedCreated,
1825
+ onMessageUpdated: onMessageUpdated
1826
+ });
1827
+ }, [
1828
+ callbackId,
1829
+ resolvedCreated,
1830
+ onMessageUpdated
1831
+ ]));
1832
+ const doUnregister = (0, $h9lXz$useAtomCallback)((0, $h9lXz$useCallback)((get, set)=>{
1833
+ (0, $38ede2d92c123540$export$46ae1ba121fdc956)(get, set, callbackId);
1834
+ }, [
1835
+ callbackId
1836
+ ]));
1837
+ (0, $h9lXz$useEffect)(()=>{
1838
+ doRegister();
1839
+ return ()=>{
1840
+ doUnregister();
1841
+ };
1842
+ }, [
1843
+ doRegister,
1844
+ doUnregister
1845
+ ]);
1846
+ // Get the raw state from atoms
1847
+ const messages = (0, $h9lXz$useAtomValue)((0, $7b7579093c9adf9b$export$92a4076839a978d0));
1848
+ const botOutputMessageState = (0, $h9lXz$useAtomValue)((0, $7b7579093c9adf9b$export$bd01e11bc95333a1));
1849
+ // Memoize the filtered messages to prevent infinite loops
1850
+ const filteredMessages = (0, $h9lXz$useMemo)(()=>{
1851
+ const getMetadata = (part)=>{
1852
+ return part.aggregatedBy ? aggregationMetadata?.[part.aggregatedBy] : undefined;
1853
+ };
1854
+ // Process messages: convert string parts to BotOutputText based on position state
1855
+ const processedMessages = messages.map((message)=>{
1856
+ if (message.role === "assistant") {
1857
+ const messageId = message.createdAt;
1858
+ const messageState = botOutputMessageState.get(messageId);
1859
+ if (!messageState) // No state yet, return message as-is
1860
+ return message;
1861
+ const parts = message.parts || [];
1862
+ // Find the actual current part index (skip parts that aren't meant to be spoken)
1863
+ let actualCurrentPartIndex = messageState.currentPartIndex;
1864
+ while(actualCurrentPartIndex < parts.length){
1865
+ const part = parts[actualCurrentPartIndex];
1866
+ if (typeof part?.text !== "string") break;
1867
+ const isSpoken = getMetadata(part)?.isSpoken !== false;
1868
+ if (isSpoken) break;
1869
+ actualCurrentPartIndex++;
1870
+ }
1871
+ if (parts.length > 0 && actualCurrentPartIndex >= parts.length) actualCurrentPartIndex = parts.length - 1;
1872
+ // Convert parts to BotOutputText format based on position state
1873
+ const processedParts = parts.map((part, partIndex)=>{
1874
+ // If part text is not a string, it's already processed (e.g., ReactNode)
1875
+ if (typeof part.text !== "string") return part;
1876
+ const metadata = getMetadata(part);
1877
+ const displayMode = part.displayMode ?? metadata?.displayMode ?? "inline";
1878
+ const isSpoken = metadata?.isSpoken !== false;
1879
+ const partText = displayMode === "block" && !isSpoken ? part.text.trim() : part.text;
1880
+ if (!isSpoken) return {
1881
+ ...part,
1882
+ displayMode: displayMode,
1883
+ text: {
1884
+ spoken: "",
1885
+ unspoken: partText
1886
+ }
1887
+ };
1888
+ // Use cursor split for the part at actualCurrentPartIndex for every message,
1889
+ // so previous (e.g. interrupted) messages keep partially spoken state.
1890
+ const isPartAtCursor = partIndex === actualCurrentPartIndex;
1891
+ const currentCharIndex = messageState.currentCharIndex;
1892
+ const spokenText = isPartAtCursor ? partText.slice(0, currentCharIndex) : partIndex < actualCurrentPartIndex ? partText : "";
1893
+ const unspokenText = isPartAtCursor ? partText.slice(currentCharIndex) : partIndex < actualCurrentPartIndex ? "" : partText;
1894
+ return {
1895
+ ...part,
1896
+ displayMode: displayMode,
1897
+ text: {
1898
+ spoken: spokenText,
1899
+ unspoken: unspokenText
1900
+ }
1901
+ };
1902
+ });
1903
+ return {
1904
+ ...message,
1905
+ parts: processedParts
1906
+ };
1907
+ }
1908
+ return message;
1909
+ });
1910
+ // Messages are already normalized (sorted, filtered, deduped, merged) on write.
1911
+ return processedMessages;
1912
+ }, [
1913
+ messages,
1914
+ botOutputMessageState,
1915
+ aggregationMetadata
1916
+ ]);
1917
+ return {
1918
+ messages: filteredMessages,
1919
+ injectMessage: injectMessage
1920
+ };
1921
+ };
1922
+
1923
+
1924
+
1925
+
1926
+ export {$f209aa7ddb77dcb2$export$b52250cb73ff4de1 as PipecatClientAudio, $7cb2ce2c4cbfb401$export$dc9a029eeca8213f as PipecatClientCamToggle, $2984fdfc31bad375$export$bc8133b69ff660a2 as PipecatClientMicToggle, $d2e362c5a07ee3c5$export$bb43666ced7a20d0 as PipecatClientProvider, $d6dc7e80e6e976a0$export$93764714dfab3a46 as PipecatClientScreenShareToggle, $6a65deb8615a2ad7$export$85974db6d0cc43b3 as PipecatClientVideo, $034a56e7ee1b7bed$export$777fa8498be78705 as usePipecatClient, $e76ee2f021b54325$export$3ea2601427f0430f as usePipecatClientCamControl, $f934f1f8b10aaf19$export$642bc4d2d2a376f1 as usePipecatClientMediaDevices, $4b4b9099cdb5b776$export$9813dcd2d0c26814 as usePipecatClientMediaTrack, $5905c001b0dc8d25$export$388e706586309ef0 as usePipecatClientMicControl, $d39c0bc3744ea030$export$be63b19bd7f7d4f5 as usePipecatClientScreenShareControl, $33f3729bbe9f09df$export$30aee278309a867b as usePipecatClientTransportState, $824ea64b5f757259$export$33a6ac53b8f02625 as useRTVIClientEvent, $993a744193844a95$export$59bf27bd43679db6 as VoiceVisualizer, $b5a460b32af71766$export$8eec679bf8249822 as useConversationContext, $894023a10d0bd040$export$acb832b064cb6c09 as usePipecatConversation, $38ede2d92c123540$export$dfa183f99573a822 as deduplicateFunctionCalls, $38ede2d92c123540$export$3d3951a55577516 as filterEmptyMessages, $38ede2d92c123540$export$f4610a88b89838b as isMessageEmpty, $38ede2d92c123540$export$23a96e868837e158 as mergeMessages, $38ede2d92c123540$export$cbefc5865f8cbd89 as sortByCreatedAt};
850
1927
  //# sourceMappingURL=index.module.js.map