@shogo-ai/voice 1.9.13 → 2.4.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.
Files changed (68) hide show
  1. package/README.md +89 -0
  2. package/dist/{chunk-S3S2RNO6.js → chunk-BI6SL35K.js} +1 -184
  3. package/dist/chunk-CLF25CZC.js +987 -0
  4. package/dist/chunk-KMSL5PWK.js +540 -0
  5. package/dist/chunk-ME3SIZZU.js +581 -0
  6. package/dist/chunk-TQOBJMEK.js +17 -0
  7. package/dist/{chunk-T3T4EBWY.js → chunk-UH75UVM2.js} +6 -2
  8. package/dist/chunk-UW7VQ3S6.js +148 -0
  9. package/dist/chunk-XE4LLC7Z.js +1067 -0
  10. package/dist/chunk-ZAW6F3YT.js +172 -0
  11. package/dist/index.cjs +735 -25
  12. package/dist/index.d.cts +373 -3
  13. package/dist/index.d.ts +373 -3
  14. package/dist/index.js +59 -3
  15. package/dist/live/client.cjs +173 -0
  16. package/dist/live/client.d.cts +132 -0
  17. package/dist/live/client.d.ts +132 -0
  18. package/dist/live/client.js +9 -0
  19. package/dist/native/index.d.cts +5 -2
  20. package/dist/native/index.d.ts +5 -2
  21. package/dist/native/index.js +15 -11
  22. package/dist/{organicParticlesShaders-DIva0vBO.d.ts → organicParticlesShaders-seZq7jYX.d.cts} +1 -421
  23. package/dist/{organicParticlesShaders-DIva0vBO.d.cts → organicParticlesShaders-seZq7jYX.d.ts} +1 -421
  24. package/dist/particlesConfig-0YbdPOdY.d.cts +223 -0
  25. package/dist/particlesConfig-0YbdPOdY.d.ts +223 -0
  26. package/dist/react/conversation.cjs +789 -0
  27. package/dist/react/conversation.d.cts +134 -0
  28. package/dist/react/conversation.d.ts +134 -0
  29. package/dist/react/conversation.js +13 -0
  30. package/dist/react/index.cjs +374 -36
  31. package/dist/react/index.d.cts +10 -141
  32. package/dist/react/index.d.ts +10 -141
  33. package/dist/react/index.js +23 -770
  34. package/dist/react/visuals.cjs +1048 -0
  35. package/dist/react/visuals.d.cts +70 -0
  36. package/dist/react/visuals.d.ts +70 -0
  37. package/dist/react/visuals.js +10 -0
  38. package/dist/route/agent.cjs +1038 -85
  39. package/dist/route/agent.js +3 -3
  40. package/dist/route/audio-tags.cjs +1038 -85
  41. package/dist/route/audio-tags.js +3 -3
  42. package/dist/route/index.cjs +1042 -85
  43. package/dist/route/index.d.cts +19 -3
  44. package/dist/route/index.d.ts +19 -3
  45. package/dist/route/index.js +3 -3
  46. package/dist/route/music.cjs +1038 -85
  47. package/dist/route/music.js +3 -3
  48. package/dist/route/op.cjs +2118 -0
  49. package/dist/route/op.d.cts +43 -0
  50. package/dist/route/op.d.ts +43 -0
  51. package/dist/route/op.js +27 -0
  52. package/dist/route/signed-url.cjs +1038 -85
  53. package/dist/route/signed-url.js +3 -3
  54. package/dist/route/tts-preview.cjs +1038 -85
  55. package/dist/route/tts-preview.d.cts +6 -4
  56. package/dist/route/tts-preview.d.ts +6 -4
  57. package/dist/route/tts-preview.js +3 -3
  58. package/dist/server.cjs +1038 -85
  59. package/dist/server.d.cts +40 -8
  60. package/dist/server.d.ts +40 -8
  61. package/dist/server.js +2 -2
  62. package/dist/types-Bfhu-aQH.d.cts +199 -0
  63. package/dist/types-Bfhu-aQH.d.ts +199 -0
  64. package/dist/{types-W9y6eMj3.d.ts → types-CadjTXNH.d.cts} +193 -12
  65. package/dist/{types-W9y6eMj3.d.cts → types-CadjTXNH.d.ts} +193 -12
  66. package/package.json +21 -1
  67. package/dist/chunk-B4JC3DK2.js +0 -529
  68. package/dist/chunk-O362EJQR.js +0 -524
@@ -0,0 +1,789 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react/conversation.ts
21
+ var conversation_exports = {};
22
+ __export(conversation_exports, {
23
+ ShogoVoiceProvider: () => ShogoVoiceProvider,
24
+ useLiveVoiceConversation: () => useLiveVoiceConversation,
25
+ useVoiceConversation: () => useVoiceConversation
26
+ });
27
+ module.exports = __toCommonJS(conversation_exports);
28
+
29
+ // src/react/useVoiceConversation.ts
30
+ var import_react = require("react");
31
+ var import_react2 = require("@elevenlabs/react");
32
+
33
+ // src/shared/postJson.ts
34
+ function createPostJson({
35
+ authHeaders,
36
+ fetchCredentials
37
+ }) {
38
+ return async function postJson(path, body) {
39
+ return fetch(path, {
40
+ method: "POST",
41
+ headers: { "content-type": "application/json", ...authHeaders() },
42
+ credentials: fetchCredentials,
43
+ body: JSON.stringify(body)
44
+ });
45
+ };
46
+ }
47
+
48
+ // src/shared/memory.ts
49
+ function createMemoryAddTool({
50
+ postJson,
51
+ memoryAddPath
52
+ }) {
53
+ return async (params) => {
54
+ const fact = typeof params.fact === "string" ? params.fact : "";
55
+ if (!fact.trim()) return "Memory save failed: empty fact.";
56
+ try {
57
+ const res = await postJson(memoryAddPath, { fact });
58
+ if (!res.ok) return "Failed to save memory.";
59
+ return "Memory saved.";
60
+ } catch {
61
+ return "Memory save failed.";
62
+ }
63
+ };
64
+ }
65
+ function createMemoryContextInjector({
66
+ postJson,
67
+ memoryRetrievePath,
68
+ conversationRef,
69
+ enabled,
70
+ lastInjectedRef
71
+ }) {
72
+ return async function inject(userText) {
73
+ if (!enabled) return;
74
+ try {
75
+ const res = await postJson(memoryRetrievePath, { query: userText, limit: 4 });
76
+ if (!res.ok) return;
77
+ const data = await res.json();
78
+ const results = data.results ?? [];
79
+ if (!results.length) return;
80
+ const lines = results.map((r) => `- ${r.chunk.trim().replace(/\s+/g, " ").slice(0, 200)}`).join("\n");
81
+ if (lines === lastInjectedRef.current) return;
82
+ lastInjectedRef.current = lines;
83
+ const payload = `Relevant memory about this user:
84
+ ${lines}`;
85
+ conversationRef.current?.sendContextualUpdate(payload);
86
+ } catch {
87
+ }
88
+ };
89
+ }
90
+
91
+ // src/audioTags.ts
92
+ var AUDIO_TAGS = [
93
+ // Emotions
94
+ { tag: "happy", group: "emotion", label: "Happy", description: "Warm, upbeat delivery.", example: "[happy] Oh, I\u2019m so glad you stopped by." },
95
+ { tag: "sad", group: "emotion", label: "Sad", description: "Subdued, heavy-hearted tone.", example: "[sad] I wish things had gone differently." },
96
+ { tag: "excited", group: "emotion", label: "Excited", description: "Energized, eager delivery.", example: "[excited] Wait until you hear this!" },
97
+ { tag: "curious", group: "emotion", label: "Curious", description: "Inquisitive, rising inflection.", example: "[curious] Hmm, what did they look like?" },
98
+ { tag: "sarcastic", group: "emotion", label: "Sarcastic", description: "Dry, ironic tone.", example: "[sarcastic] Oh, fantastic. Another Monday." },
99
+ { tag: "angry", group: "emotion", label: "Angry", description: "Sharp, irritated edge.", example: "[angry] I told you not to touch that." },
100
+ { tag: "nervous", group: "emotion", label: "Nervous", description: "Uneasy, faltering delivery.", example: "[nervous] I\u2014 I didn\u2019t think you\u2019d actually come." },
101
+ { tag: "confident", group: "emotion", label: "Confident", description: "Grounded, assured delivery.", example: "[confident] Trust me. I\u2019ve done this before." },
102
+ // Delivery
103
+ { tag: "whispers", group: "delivery", label: "Whispers", description: "Hushed, close-mic delivery.", example: "[whispers] Don\u2019t say it too loud." },
104
+ { tag: "shouts", group: "delivery", label: "Shouts", description: "Loud, projected delivery.", example: "[shouts] Look out behind you!" },
105
+ { tag: "softly", group: "delivery", label: "Softly", description: "Gentle, quiet tone.", example: "[softly] It\u2019s okay. I\u2019m here." },
106
+ { tag: "slowly", group: "delivery", label: "Slowly", description: "Deliberate pacing.", example: "[slowly] Tell me\u2026 exactly\u2026 what happened." },
107
+ { tag: "quickly", group: "delivery", label: "Quickly", description: "Rapid, urgent pacing.", example: "[quickly] We need to go, right now." },
108
+ // Reactions (non-verbal sounds)
109
+ { tag: "laughs", group: "reaction", label: "Laughs", description: "A genuine laugh.", example: "That\u2019s ridiculous [laughs]." },
110
+ { tag: "chuckles", group: "reaction", label: "Chuckles", description: "A small, warm chuckle.", example: "[chuckles] You always say that." },
111
+ { tag: "sighs", group: "reaction", label: "Sighs", description: "An audible sigh.", example: "[sighs] Alright, fine. Let\u2019s do it." },
112
+ { tag: "gasps", group: "reaction", label: "Gasps", description: "A sharp inhale of surprise.", example: "[gasps] You didn\u2019t!" },
113
+ { tag: "hesitates", group: "reaction", label: "Hesitates", description: "A pause with uncertainty.", example: "[hesitates] I\u2019m not sure I should say." },
114
+ { tag: "clears throat", group: "reaction", label: "Clears throat", description: "Throat-clearing beat.", example: "[clears throat] As I was saying\u2026" }
115
+ ];
116
+ var KNOWN_TAG_SET = new Set(AUDIO_TAGS.map((t) => t.tag.toLowerCase()));
117
+ function stripAudioTags(text) {
118
+ if (!text) return "";
119
+ return text.replace(/\[([a-z][a-z ]{0,24})\]/gi, (match, inner) => {
120
+ const key = inner.trim().toLowerCase();
121
+ return KNOWN_TAG_SET.has(key) ? "" : match;
122
+ }).replace(/[ \t]{2,}/g, " ").replace(/\s+([,.!?;:])/g, "$1").trim();
123
+ }
124
+
125
+ // src/shared/transcript.ts
126
+ function createTranscriptDisconnectHandler({
127
+ isRestartingRef,
128
+ weStartedSessionRef,
129
+ transcriptRef,
130
+ onTranscript,
131
+ transcriptIngestPath,
132
+ postJson
133
+ }) {
134
+ return function handleTranscriptOnDisconnect() {
135
+ if (isRestartingRef.current) {
136
+ return;
137
+ }
138
+ if (!weStartedSessionRef.current) {
139
+ transcriptRef.current = [];
140
+ return;
141
+ }
142
+ weStartedSessionRef.current = false;
143
+ const transcript = transcriptRef.current.join("\n");
144
+ transcriptRef.current = [];
145
+ if (onTranscript) {
146
+ void onTranscript(transcript);
147
+ return;
148
+ }
149
+ const trimmed = transcript.trim();
150
+ if (trimmed.length < 20) return;
151
+ void postJson(transcriptIngestPath, { transcript: trimmed }).catch(() => {
152
+ });
153
+ };
154
+ }
155
+ function appendTranscriptLine(msg, { transcriptRef, weStartedSessionRef }) {
156
+ if (!msg?.message || !msg?.source) return;
157
+ if (!weStartedSessionRef.current) return;
158
+ const speaker = msg.source === "user" ? "User" : "Agent";
159
+ const cleaned = msg.source === "user" ? msg.message : stripAudioTags(msg.message);
160
+ if (!cleaned) return;
161
+ transcriptRef.current.push(`${speaker}: ${cleaned}`);
162
+ }
163
+
164
+ // src/shared/sessionPayload.ts
165
+ function sanitizeDynamicVariables(raw) {
166
+ if (raw == null) return {};
167
+ const out = {};
168
+ for (const [k, v] of Object.entries(raw)) {
169
+ if (v == null) continue;
170
+ out[k] = typeof v === "string" ? v : String(v);
171
+ }
172
+ return out;
173
+ }
174
+ function buildSessionPayload(opts) {
175
+ const dynamicVariables = {
176
+ ...sanitizeDynamicVariables(opts.dynamicVariables),
177
+ character_name: opts.characterName,
178
+ user_context: opts.userContext
179
+ };
180
+ if (typeof opts.conversationId === "string" && opts.conversationId.length > 0) {
181
+ dynamicVariables.conversation_id = opts.conversationId;
182
+ }
183
+ const payload = {
184
+ signedUrl: opts.signedUrl,
185
+ dynamicVariables
186
+ };
187
+ const agentOverrides = {};
188
+ if (opts.suppressFirstMessage) {
189
+ agentOverrides.firstMessage = "";
190
+ }
191
+ if (typeof opts.agentPromptOverride === "string" && opts.agentPromptOverride.length > 0) {
192
+ agentOverrides.prompt = { prompt: opts.agentPromptOverride };
193
+ }
194
+ if (Object.keys(agentOverrides).length > 0) {
195
+ payload.overrides = { agent: agentOverrides };
196
+ }
197
+ return payload;
198
+ }
199
+ async function fetchSignedUrl({
200
+ path,
201
+ fetchCredentials,
202
+ authHeaders
203
+ }) {
204
+ const res = await fetch(path, {
205
+ credentials: fetchCredentials,
206
+ headers: authHeaders()
207
+ });
208
+ if (!res.ok) throw new Error(`Signed URL request failed: ${res.status}`);
209
+ return await res.json();
210
+ }
211
+ function withProjectQuery(signedUrlPath, params) {
212
+ const tail = [];
213
+ if (typeof params.projectId === "string" && params.projectId.length > 0) {
214
+ tail.push(`projectId=${encodeURIComponent(params.projectId)}`);
215
+ }
216
+ if (typeof params.agentName === "string" && params.agentName.length > 0) {
217
+ tail.push(`agentName=${encodeURIComponent(params.agentName)}`);
218
+ }
219
+ if (tail.length === 0) return signedUrlPath;
220
+ const sep = signedUrlPath.includes("?") ? "&" : "?";
221
+ return `${signedUrlPath}${sep}${tail.join("&")}`;
222
+ }
223
+
224
+ // src/react/mic-access.ts
225
+ var MIC_BLOCKED_MESSAGE = "Microphone access is blocked. Allow it in your system or browser settings (on macOS: System Settings > Privacy & Security > Microphone), then try again.";
226
+ var MIC_NOT_FOUND_MESSAGE = "No microphone was found. Connect one and try again.";
227
+ async function requestMicStream(constraints = { audio: true }) {
228
+ const desktop = globalThis.shogoDesktop;
229
+ if (desktop?.isDesktop && desktop.ensureMicAccess) {
230
+ const access = await desktop.ensureMicAccess().catch(() => "granted");
231
+ if (access !== "granted") throw new Error(MIC_BLOCKED_MESSAGE);
232
+ }
233
+ try {
234
+ return await navigator.mediaDevices.getUserMedia(constraints);
235
+ } catch (err) {
236
+ const name = err?.name;
237
+ if (name === "NotAllowedError" || name === "SecurityError") throw new Error(MIC_BLOCKED_MESSAGE);
238
+ if (name === "NotFoundError" || name === "OverconstrainedError") throw new Error(MIC_NOT_FOUND_MESSAGE);
239
+ throw err;
240
+ }
241
+ }
242
+
243
+ // src/react/useVoiceConversation.ts
244
+ function useVoiceConversation(options) {
245
+ const {
246
+ characterName,
247
+ signedUrlPath = "/api/voice/signed-url",
248
+ memoryAddPath = "/api/memory/add",
249
+ memoryRetrievePath = "/api/memory/retrieve",
250
+ transcriptIngestPath = "/api/memory/ingest",
251
+ autoInjectMemory = true,
252
+ includeMemoryTool = true,
253
+ clientTools = {},
254
+ onTranscript,
255
+ shogoApiKey,
256
+ projectId,
257
+ agentName,
258
+ fetchCredentials = shogoApiKey ? "omit" : "same-origin",
259
+ onError,
260
+ onMessage,
261
+ conversationId: optionConversationId,
262
+ dynamicVariables
263
+ } = options;
264
+ const authHeaders = (0, import_react.useCallback)(() => {
265
+ return shogoApiKey ? { authorization: `Bearer ${shogoApiKey}` } : {};
266
+ }, [shogoApiKey]);
267
+ const resolvedSignedUrlPath = withProjectQuery(signedUrlPath, { projectId, agentName });
268
+ const transcriptRef = (0, import_react.useRef)([]);
269
+ const lastInjectedRef = (0, import_react.useRef)("");
270
+ const weStartedSessionRef = (0, import_react.useRef)(false);
271
+ const [convaiConversationId, setConvaiConversationId] = (0, import_react.useState)(null);
272
+ const isRestartingRef = (0, import_react.useRef)(false);
273
+ const conversationRef = (0, import_react.useRef)(null);
274
+ const postJson = (0, import_react.useCallback)(
275
+ createPostJson({ authHeaders, fetchCredentials }),
276
+ [fetchCredentials, authHeaders]
277
+ );
278
+ const injectMemoryContext = (0, import_react.useCallback)(
279
+ createMemoryContextInjector({
280
+ postJson,
281
+ memoryRetrievePath,
282
+ conversationRef,
283
+ enabled: autoInjectMemory,
284
+ lastInjectedRef
285
+ }),
286
+ [autoInjectMemory, memoryRetrievePath, postJson]
287
+ );
288
+ const handleTranscriptOnDisconnect = (0, import_react.useCallback)(
289
+ createTranscriptDisconnectHandler({
290
+ isRestartingRef,
291
+ weStartedSessionRef,
292
+ transcriptRef,
293
+ onTranscript,
294
+ transcriptIngestPath,
295
+ postJson
296
+ }),
297
+ [onTranscript, postJson, transcriptIngestPath]
298
+ );
299
+ const builtInTools = includeMemoryTool ? { add_memory: createMemoryAddTool({ postJson, memoryAddPath }) } : {};
300
+ const mergedClientTools = { ...builtInTools, ...clientTools };
301
+ const conversation = (0, import_react2.useConversation)({
302
+ clientTools: mergedClientTools,
303
+ onConnect: (info) => {
304
+ const id = info?.conversationId;
305
+ if (typeof id === "string" && id.length > 0) {
306
+ setConvaiConversationId(id);
307
+ }
308
+ if (!isRestartingRef.current) {
309
+ transcriptRef.current = [];
310
+ lastInjectedRef.current = "";
311
+ }
312
+ },
313
+ onDisconnect: () => {
314
+ handleTranscriptOnDisconnect();
315
+ if (!isRestartingRef.current) {
316
+ setConvaiConversationId(null);
317
+ }
318
+ },
319
+ onError: (e) => {
320
+ onError?.(e);
321
+ },
322
+ onMessage: (m) => {
323
+ const msg = m;
324
+ if (!msg?.message || !msg?.source) return;
325
+ appendTranscriptLine(msg, { transcriptRef, weStartedSessionRef });
326
+ onMessage?.({ source: msg.source, message: msg.message });
327
+ if (autoInjectMemory && msg.source === "user" && msg.message.trim().length >= 3 && weStartedSessionRef.current) {
328
+ void injectMemoryContext(msg.message);
329
+ }
330
+ }
331
+ });
332
+ const { status, isSpeaking, isListening, isMuted, setMuted, startSession, endSession } = conversation;
333
+ conversationRef.current = conversation;
334
+ (0, import_react.useEffect)(() => {
335
+ const onHide = () => {
336
+ if (!weStartedSessionRef.current || transcriptRef.current.length === 0) return;
337
+ const transcript = transcriptRef.current.join("\n").trim();
338
+ transcriptRef.current = [];
339
+ if (transcript.length < 20) return;
340
+ try {
341
+ const blob = new Blob([JSON.stringify({ transcript })], { type: "application/json" });
342
+ navigator.sendBeacon(transcriptIngestPath, blob);
343
+ } catch {
344
+ }
345
+ };
346
+ window.addEventListener("pagehide", onHide);
347
+ return () => window.removeEventListener("pagehide", onHide);
348
+ }, [transcriptIngestPath]);
349
+ const startInternal = (0, import_react.useCallback)(
350
+ async (opts) => {
351
+ if (status === "connected" || status === "connecting") {
352
+ try {
353
+ endSession();
354
+ await new Promise((r) => setTimeout(r, 120));
355
+ } catch {
356
+ }
357
+ }
358
+ const micStream = await requestMicStream({ audio: true });
359
+ micStream.getTracks().forEach((track) => track.stop());
360
+ const data = await fetchSignedUrl({
361
+ path: resolvedSignedUrlPath,
362
+ fetchCredentials,
363
+ authHeaders
364
+ });
365
+ const ctx = data.userContext || "No prior memories yet.";
366
+ weStartedSessionRef.current = true;
367
+ const sessionPayload = buildSessionPayload({
368
+ signedUrl: data.signedUrl,
369
+ characterName,
370
+ userContext: ctx,
371
+ agentPromptOverride: data.agentPromptOverride,
372
+ suppressFirstMessage: opts?.suppressFirstMessage,
373
+ conversationId: optionConversationId,
374
+ dynamicVariables
375
+ });
376
+ await startSession(sessionPayload);
377
+ },
378
+ [
379
+ status,
380
+ endSession,
381
+ resolvedSignedUrlPath,
382
+ fetchCredentials,
383
+ dynamicVariables,
384
+ startSession,
385
+ characterName,
386
+ authHeaders,
387
+ optionConversationId
388
+ ]
389
+ );
390
+ const start = (0, import_react.useCallback)(
391
+ async (options2) => {
392
+ await startInternal(options2);
393
+ },
394
+ [startInternal]
395
+ );
396
+ const end = (0, import_react.useCallback)(() => {
397
+ endSession();
398
+ }, [endSession]);
399
+ const restart = (0, import_react.useCallback)(
400
+ async (options2) => {
401
+ const suppress = options2?.suppressFirstMessage ?? true;
402
+ isRestartingRef.current = true;
403
+ try {
404
+ try {
405
+ endSession();
406
+ } catch {
407
+ }
408
+ await new Promise((r) => setTimeout(r, 120));
409
+ await startInternal({ suppressFirstMessage: suppress });
410
+ } finally {
411
+ isRestartingRef.current = false;
412
+ }
413
+ },
414
+ [endSession, startInternal]
415
+ );
416
+ const sendContextualUpdate = (0, import_react.useCallback)((text) => {
417
+ conversationRef.current?.sendContextualUpdate(text);
418
+ }, []);
419
+ const sendUserMessage = (0, import_react.useCallback)((text) => {
420
+ const ref = conversationRef.current;
421
+ if (!ref) return;
422
+ if (typeof ref.sendUserMessage === "function") {
423
+ ref.sendUserMessage(text);
424
+ return;
425
+ }
426
+ ref.sendContextualUpdate(text);
427
+ try {
428
+ ref.sendUserActivity?.();
429
+ } catch {
430
+ }
431
+ }, []);
432
+ const sendUserActivity = (0, import_react.useCallback)(() => {
433
+ conversationRef.current?.sendUserActivity?.();
434
+ }, []);
435
+ const getOutputByteFrequencyData = (0, import_react.useCallback)(() => {
436
+ try {
437
+ const fn = conversation.getOutputByteFrequencyData;
438
+ return fn ? fn() : null;
439
+ } catch {
440
+ return null;
441
+ }
442
+ }, [conversation]);
443
+ return {
444
+ start,
445
+ end,
446
+ restart,
447
+ status,
448
+ isSpeaking,
449
+ isListening,
450
+ isMuted,
451
+ setMuted,
452
+ getOutputByteFrequencyData,
453
+ sendContextualUpdate,
454
+ sendUserMessage,
455
+ sendUserActivity,
456
+ conversationId: optionConversationId ?? convaiConversationId,
457
+ convaiConversationId
458
+ };
459
+ }
460
+
461
+ // src/react/useLiveVoiceConversation.ts
462
+ var import_react3 = require("react");
463
+ function parseEvent(data) {
464
+ try {
465
+ const text = typeof data === "string" ? data : String(data);
466
+ const event = JSON.parse(text);
467
+ return event && typeof event === "object" ? event : null;
468
+ } catch {
469
+ return null;
470
+ }
471
+ }
472
+ function waitForIceGathering(pc) {
473
+ if (pc.iceGatheringState === "complete") return Promise.resolve();
474
+ return new Promise((resolve) => {
475
+ const onStateChange = () => {
476
+ if (pc.iceGatheringState === "complete") {
477
+ pc.removeEventListener("icegatheringstatechange", onStateChange);
478
+ resolve();
479
+ }
480
+ };
481
+ pc.addEventListener("icegatheringstatechange", onStateChange);
482
+ window.setTimeout(() => {
483
+ pc.removeEventListener("icegatheringstatechange", onStateChange);
484
+ resolve();
485
+ }, 2e3);
486
+ });
487
+ }
488
+ function splitContext(text) {
489
+ const chunks = [];
490
+ for (let offset = 0; offset < text.length; offset += 1800) {
491
+ chunks.push(text.slice(offset, offset + 1800));
492
+ }
493
+ return chunks.length ? chunks : [""];
494
+ }
495
+ var TRANSCRIPT_HISTORY_MAX = 30;
496
+ function useLiveVoiceConversation(options) {
497
+ const { mintSession } = options;
498
+ const [status, setStatus] = (0, import_react3.useState)("disconnected");
499
+ const [isSpeaking, setIsSpeaking] = (0, import_react3.useState)(false);
500
+ const [isListening, setIsListening] = (0, import_react3.useState)(false);
501
+ const [isMuted, setIsMuted] = (0, import_react3.useState)(false);
502
+ const [sessionId, setSessionId] = (0, import_react3.useState)(null);
503
+ const optionsRef = (0, import_react3.useRef)(options);
504
+ optionsRef.current = options;
505
+ const pcRef = (0, import_react3.useRef)(null);
506
+ const dataChannelRef = (0, import_react3.useRef)(null);
507
+ const mediaStreamRef = (0, import_react3.useRef)(null);
508
+ const audioRef = (0, import_react3.useRef)(null);
509
+ const analyserRef = (0, import_react3.useRef)(null);
510
+ const frequencyRef = (0, import_react3.useRef)(null);
511
+ const audioContextRef = (0, import_react3.useRef)(null);
512
+ const speakingFrameRef = (0, import_react3.useRef)(null);
513
+ const inputTranscriptRef = (0, import_react3.useRef)("");
514
+ const outputTranscriptRef = (0, import_react3.useRef)("");
515
+ const historyRef = (0, import_react3.useRef)([]);
516
+ const closingRef = (0, import_react3.useRef)(false);
517
+ const mutedRef = (0, import_react3.useRef)(false);
518
+ const emit = (0, import_react3.useCallback)((source, message) => {
519
+ const text = message.trim();
520
+ if (!text) return;
521
+ const history = historyRef.current;
522
+ history.push({ role: source === "user" ? "user" : "assistant", text });
523
+ if (history.length > TRANSCRIPT_HISTORY_MAX) history.splice(0, history.length - TRANSCRIPT_HISTORY_MAX);
524
+ optionsRef.current.onMessage?.({ source, message: text });
525
+ }, []);
526
+ const flushTranscripts = (0, import_react3.useCallback)(() => {
527
+ const input = inputTranscriptRef.current.trim();
528
+ const output = outputTranscriptRef.current.trim();
529
+ inputTranscriptRef.current = "";
530
+ outputTranscriptRef.current = "";
531
+ if (input) emit("user", input);
532
+ if (output) emit("agent", output);
533
+ }, [emit]);
534
+ const sendEvent = (0, import_react3.useCallback)((event) => {
535
+ const channel = dataChannelRef.current;
536
+ if (channel?.readyState === "open") channel.send(JSON.stringify(event));
537
+ }, []);
538
+ const updateSpeaking = (0, import_react3.useCallback)(() => {
539
+ const analyser = analyserRef.current;
540
+ if (analyser) {
541
+ const data = frequencyRef.current ?? new Uint8Array(analyser.frequencyBinCount);
542
+ frequencyRef.current = data;
543
+ analyser.getByteFrequencyData(data);
544
+ let sum = 0;
545
+ for (const value of data) sum += value;
546
+ setIsSpeaking(data.length > 0 && sum / data.length > 8);
547
+ }
548
+ if (pcRef.current) speakingFrameRef.current = requestAnimationFrame(updateSpeaking);
549
+ }, []);
550
+ const handleEvent = (0, import_react3.useCallback)(async (event) => {
551
+ const nested = event.type === "response.event" && event.event ? event.event : event;
552
+ const type = nested.type ?? "";
553
+ if (type === "session.started" || type === "session.created") {
554
+ setStatus("connected");
555
+ setIsListening(!mutedRef.current);
556
+ const id = typeof nested.session?.id === "string" ? nested.session.id : null;
557
+ if (id) setSessionId(id);
558
+ }
559
+ if (type === "session.closed") {
560
+ flushTranscripts();
561
+ setStatus("disconnected");
562
+ setIsListening(false);
563
+ }
564
+ if (type === "error") {
565
+ optionsRef.current.onError?.(new Error(event.error?.message || "GPT-Live error"));
566
+ }
567
+ if (type === "session.input_transcript.delta" || type === "input_audio_transcription.delta") {
568
+ inputTranscriptRef.current += event.delta ?? "";
569
+ }
570
+ if (type === "session.output_transcript.delta" || type === "response.audio_transcript.delta") {
571
+ outputTranscriptRef.current += event.delta ?? "";
572
+ }
573
+ if (type === "session.input_transcript.done" || type === "input_audio_transcription.completed" || type === "session.output_transcript.done" || type === "response.audio_transcript.done" || type === "response.done" || type === "response.completed") {
574
+ flushTranscripts();
575
+ }
576
+ if (type === "session.delegation.created" && nested.delegation?.target !== "responses" && typeof nested.delegation?.id === "string") {
577
+ const onDelegation = optionsRef.current.onDelegation;
578
+ if (!onDelegation) return;
579
+ const delegationId = nested.delegation.id;
580
+ flushTranscripts();
581
+ let result;
582
+ try {
583
+ result = await onDelegation({ delegationId, transcript: [...historyRef.current] });
584
+ } catch (error) {
585
+ optionsRef.current.onError?.(error);
586
+ result = "That request failed before it could finish. Nothing was confirmed.";
587
+ }
588
+ const text = typeof result === "string" ? result.trim() : "";
589
+ if (!text) return;
590
+ for (const content of splitContext(text)) {
591
+ sendEvent({
592
+ type: "session.commentary.append",
593
+ delegation_id: delegationId,
594
+ content
595
+ });
596
+ }
597
+ return;
598
+ }
599
+ const item = nested.item;
600
+ if (type === "response.output_item.done" && item?.type === "function_call") {
601
+ const tool = optionsRef.current.clientTools?.[item.name ?? ""];
602
+ if (!tool || !item.call_id) return;
603
+ let args = {};
604
+ try {
605
+ const parsed = JSON.parse(item.arguments || "{}");
606
+ if (parsed && typeof parsed === "object") args = parsed;
607
+ } catch {
608
+ }
609
+ let output;
610
+ try {
611
+ output = String(await tool(args));
612
+ } catch (error) {
613
+ output = `Tool failed: ${error instanceof Error ? error.message : String(error)}`;
614
+ }
615
+ sendEvent({
616
+ type: "response.item.create",
617
+ item: {
618
+ type: "function_call_output",
619
+ call_id: item.call_id,
620
+ output
621
+ }
622
+ });
623
+ sendEvent({ type: "response.create" });
624
+ }
625
+ }, [flushTranscripts, sendEvent]);
626
+ const end = (0, import_react3.useCallback)(() => {
627
+ closingRef.current = true;
628
+ flushTranscripts();
629
+ sendEvent({ type: "session.close" });
630
+ dataChannelRef.current?.close();
631
+ pcRef.current?.close();
632
+ mediaStreamRef.current?.getTracks().forEach((track) => track.stop());
633
+ audioRef.current?.pause();
634
+ if (speakingFrameRef.current !== null) cancelAnimationFrame(speakingFrameRef.current);
635
+ speakingFrameRef.current = null;
636
+ pcRef.current = null;
637
+ dataChannelRef.current = null;
638
+ mediaStreamRef.current = null;
639
+ analyserRef.current = null;
640
+ frequencyRef.current = null;
641
+ audioContextRef.current?.close().catch(() => {
642
+ });
643
+ audioContextRef.current = null;
644
+ setIsSpeaking(false);
645
+ setIsListening(false);
646
+ setSessionId(null);
647
+ setStatus("disconnected");
648
+ }, [flushTranscripts, sendEvent]);
649
+ const startInternal = (0, import_react3.useCallback)(async () => {
650
+ if (status === "connected" || status === "connecting") end();
651
+ closingRef.current = false;
652
+ setStatus("connecting");
653
+ try {
654
+ const stream = await requestMicStream({ audio: true });
655
+ mediaStreamRef.current = stream;
656
+ const pc = new RTCPeerConnection();
657
+ pcRef.current = pc;
658
+ stream.getTracks().forEach((track) => pc.addTrack(track, stream));
659
+ const channel = pc.createDataChannel("oai-events");
660
+ dataChannelRef.current = channel;
661
+ channel.onopen = () => {
662
+ if (!closingRef.current) setIsListening(!mutedRef.current);
663
+ };
664
+ channel.onmessage = (message) => {
665
+ const event = parseEvent(message.data);
666
+ if (event) void handleEvent(event);
667
+ };
668
+ channel.onerror = (event) => optionsRef.current.onError?.(event);
669
+ pc.ontrack = (event) => {
670
+ const remoteStream = event.streams[0];
671
+ if (!remoteStream) return;
672
+ const audio = new Audio();
673
+ audio.autoplay = true;
674
+ audio.srcObject = remoteStream;
675
+ audioRef.current = audio;
676
+ void audio.play().catch(() => {
677
+ });
678
+ try {
679
+ const AudioContextImpl = window.AudioContext || window.webkitAudioContext;
680
+ if (AudioContextImpl) {
681
+ const context = new AudioContextImpl();
682
+ const source = context.createMediaStreamSource(remoteStream);
683
+ const analyser = context.createAnalyser();
684
+ analyser.fftSize = 256;
685
+ source.connect(analyser);
686
+ audioContextRef.current = context;
687
+ analyserRef.current = analyser;
688
+ frequencyRef.current = new Uint8Array(analyser.frequencyBinCount);
689
+ if (speakingFrameRef.current === null) {
690
+ speakingFrameRef.current = requestAnimationFrame(updateSpeaking);
691
+ }
692
+ }
693
+ } catch {
694
+ }
695
+ };
696
+ pc.onconnectionstatechange = () => {
697
+ if (["failed", "disconnected", "closed"].includes(pc.connectionState) && !closingRef.current) {
698
+ optionsRef.current.onError?.(new Error(`GPT-Live connection ${pc.connectionState}`));
699
+ end();
700
+ }
701
+ };
702
+ const offer = await pc.createOffer();
703
+ await pc.setLocalDescription(offer);
704
+ await waitForIceGathering(pc);
705
+ const localSdp = pc.localDescription?.sdp;
706
+ if (!localSdp) throw new Error("Failed to create a WebRTC offer");
707
+ const answer = await mintSession(localSdp);
708
+ await pc.setRemoteDescription({ type: "answer", sdp: answer.sdp });
709
+ setSessionId(answer.sessionId);
710
+ setStatus("connected");
711
+ } catch (error) {
712
+ end();
713
+ optionsRef.current.onError?.(error);
714
+ throw error;
715
+ }
716
+ }, [end, handleEvent, mintSession, status, updateSpeaking]);
717
+ const start = (0, import_react3.useCallback)(async () => {
718
+ await startInternal();
719
+ }, [startInternal]);
720
+ const restart = (0, import_react3.useCallback)(async () => {
721
+ end();
722
+ await new Promise((resolve) => window.setTimeout(resolve, 120));
723
+ await startInternal();
724
+ }, [end, startInternal]);
725
+ const setMuted = (0, import_react3.useCallback)((muted) => {
726
+ mutedRef.current = muted;
727
+ mediaStreamRef.current?.getAudioTracks().forEach((track) => {
728
+ track.enabled = !muted;
729
+ });
730
+ sendEvent({ type: muted ? "session.input_audio.mute" : "session.input_audio.unmute" });
731
+ setIsMuted(muted);
732
+ setIsListening(!muted && status === "connected");
733
+ }, [sendEvent, status]);
734
+ const sendContextualUpdate = (0, import_react3.useCallback)((text) => {
735
+ for (const content of splitContext(text)) {
736
+ sendEvent({
737
+ type: "session.thinking.append",
738
+ delegation_id: null,
739
+ content
740
+ });
741
+ }
742
+ }, [sendEvent]);
743
+ const sendUserMessage = (0, import_react3.useCallback)((text) => {
744
+ sendEvent({
745
+ type: "session.commentary.append",
746
+ delegation_id: null,
747
+ content: text
748
+ });
749
+ }, [sendEvent]);
750
+ const sendUserActivity = (0, import_react3.useCallback)(() => {
751
+ sendEvent({ type: "session.input_audio.commit" });
752
+ }, [sendEvent]);
753
+ const getOutputByteFrequencyData = (0, import_react3.useCallback)(() => {
754
+ return frequencyRef.current;
755
+ }, []);
756
+ const getTranscript = (0, import_react3.useCallback)(() => [...historyRef.current], []);
757
+ (0, import_react3.useEffect)(() => () => end(), [end]);
758
+ return {
759
+ start,
760
+ end,
761
+ restart,
762
+ status,
763
+ isSpeaking,
764
+ isListening,
765
+ isMuted,
766
+ setMuted,
767
+ getOutputByteFrequencyData,
768
+ sendContextualUpdate,
769
+ sendUserMessage,
770
+ sendUserActivity,
771
+ getTranscript,
772
+ conversationId: sessionId,
773
+ convaiConversationId: sessionId
774
+ };
775
+ }
776
+
777
+ // src/react/ShogoVoiceProvider.tsx
778
+ var import_react4 = require("@elevenlabs/react");
779
+ var import_jsx_runtime = require("react/jsx-runtime");
780
+ function ShogoVoiceProvider(props) {
781
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react4.ConversationProvider, { children: props.children });
782
+ }
783
+ // Annotate the CommonJS export names for ESM import in node:
784
+ 0 && (module.exports = {
785
+ ShogoVoiceProvider,
786
+ useLiveVoiceConversation,
787
+ useVoiceConversation
788
+ });
789
+ //# sourceMappingURL=conversation.cjs.map