@shogo-ai/voice 1.10.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -0
- package/dist/{chunk-S3S2RNO6.js → chunk-BI6SL35K.js} +1 -184
- package/dist/chunk-IOFWML5L.js +1078 -0
- package/dist/chunk-KMSL5PWK.js +540 -0
- package/dist/chunk-ME3SIZZU.js +581 -0
- package/dist/{chunk-T3T4EBWY.js → chunk-RALZRMFL.js} +6 -2
- package/dist/chunk-TQOBJMEK.js +17 -0
- package/dist/chunk-U5UC7ZVS.js +988 -0
- package/dist/chunk-UW7VQ3S6.js +148 -0
- package/dist/chunk-ZAW6F3YT.js +172 -0
- package/dist/index.cjs +748 -25
- package/dist/index.d.cts +396 -3
- package/dist/index.d.ts +396 -3
- package/dist/index.js +63 -3
- package/dist/live/client.cjs +173 -0
- package/dist/live/client.d.cts +132 -0
- package/dist/live/client.d.ts +132 -0
- package/dist/live/client.js +9 -0
- package/dist/native/index.d.cts +5 -2
- package/dist/native/index.d.ts +5 -2
- package/dist/native/index.js +15 -11
- package/dist/{organicParticlesShaders-DIva0vBO.d.ts → organicParticlesShaders-seZq7jYX.d.cts} +1 -421
- package/dist/{organicParticlesShaders-DIva0vBO.d.cts → organicParticlesShaders-seZq7jYX.d.ts} +1 -421
- package/dist/particlesConfig-0YbdPOdY.d.cts +223 -0
- package/dist/particlesConfig-0YbdPOdY.d.ts +223 -0
- package/dist/react/conversation.cjs +789 -0
- package/dist/react/conversation.d.cts +134 -0
- package/dist/react/conversation.d.ts +134 -0
- package/dist/react/conversation.js +13 -0
- package/dist/react/index.cjs +374 -36
- package/dist/react/index.d.cts +10 -141
- package/dist/react/index.d.ts +10 -141
- package/dist/react/index.js +23 -770
- package/dist/react/visuals.cjs +1048 -0
- package/dist/react/visuals.d.cts +70 -0
- package/dist/react/visuals.d.ts +70 -0
- package/dist/react/visuals.js +10 -0
- package/dist/route/agent.cjs +1047 -85
- package/dist/route/agent.js +3 -3
- package/dist/route/audio-tags.cjs +1047 -85
- package/dist/route/audio-tags.js +3 -3
- package/dist/route/index.cjs +1051 -85
- package/dist/route/index.d.cts +19 -3
- package/dist/route/index.d.ts +19 -3
- package/dist/route/index.js +3 -3
- package/dist/route/music.cjs +1047 -85
- package/dist/route/music.js +3 -3
- package/dist/route/op.cjs +2127 -0
- package/dist/route/op.d.cts +43 -0
- package/dist/route/op.d.ts +43 -0
- package/dist/route/op.js +27 -0
- package/dist/route/signed-url.cjs +1047 -85
- package/dist/route/signed-url.js +3 -3
- package/dist/route/tts-preview.cjs +1047 -85
- package/dist/route/tts-preview.d.cts +6 -4
- package/dist/route/tts-preview.d.ts +6 -4
- package/dist/route/tts-preview.js +3 -3
- package/dist/server.cjs +1047 -85
- package/dist/server.d.cts +40 -8
- package/dist/server.d.ts +40 -8
- package/dist/server.js +2 -2
- package/dist/types-Bfhu-aQH.d.cts +199 -0
- package/dist/types-Bfhu-aQH.d.ts +199 -0
- package/dist/{types-W9y6eMj3.d.ts → types-CadjTXNH.d.cts} +193 -12
- package/dist/{types-W9y6eMj3.d.cts → types-CadjTXNH.d.ts} +193 -12
- package/package.json +21 -1
- package/dist/chunk-B4JC3DK2.js +0 -529
- package/dist/chunk-O362EJQR.js +0 -524
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendTranscriptLine,
|
|
3
|
+
buildSessionPayload,
|
|
4
|
+
createMemoryAddTool,
|
|
5
|
+
createMemoryContextInjector,
|
|
6
|
+
createPostJson,
|
|
7
|
+
createTranscriptDisconnectHandler,
|
|
8
|
+
fetchSignedUrl,
|
|
9
|
+
withProjectQuery
|
|
10
|
+
} from "./chunk-ZAW6F3YT.js";
|
|
11
|
+
|
|
12
|
+
// src/react/useVoiceConversation.ts
|
|
13
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
14
|
+
import { useConversation } from "@elevenlabs/react";
|
|
15
|
+
|
|
16
|
+
// src/react/mic-access.ts
|
|
17
|
+
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.";
|
|
18
|
+
var MIC_NOT_FOUND_MESSAGE = "No microphone was found. Connect one and try again.";
|
|
19
|
+
async function requestMicStream(constraints = { audio: true }) {
|
|
20
|
+
const desktop = globalThis.shogoDesktop;
|
|
21
|
+
if (desktop?.isDesktop && desktop.ensureMicAccess) {
|
|
22
|
+
const access = await desktop.ensureMicAccess().catch(() => "granted");
|
|
23
|
+
if (access !== "granted") throw new Error(MIC_BLOCKED_MESSAGE);
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return await navigator.mediaDevices.getUserMedia(constraints);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
const name = err?.name;
|
|
29
|
+
if (name === "NotAllowedError" || name === "SecurityError") throw new Error(MIC_BLOCKED_MESSAGE);
|
|
30
|
+
if (name === "NotFoundError" || name === "OverconstrainedError") throw new Error(MIC_NOT_FOUND_MESSAGE);
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/react/useVoiceConversation.ts
|
|
36
|
+
function useVoiceConversation(options) {
|
|
37
|
+
const {
|
|
38
|
+
characterName,
|
|
39
|
+
signedUrlPath = "/api/voice/signed-url",
|
|
40
|
+
memoryAddPath = "/api/memory/add",
|
|
41
|
+
memoryRetrievePath = "/api/memory/retrieve",
|
|
42
|
+
transcriptIngestPath = "/api/memory/ingest",
|
|
43
|
+
autoInjectMemory = true,
|
|
44
|
+
includeMemoryTool = true,
|
|
45
|
+
clientTools = {},
|
|
46
|
+
onTranscript,
|
|
47
|
+
shogoApiKey,
|
|
48
|
+
projectId,
|
|
49
|
+
agentName,
|
|
50
|
+
fetchCredentials = shogoApiKey ? "omit" : "same-origin",
|
|
51
|
+
onError,
|
|
52
|
+
onMessage,
|
|
53
|
+
conversationId: optionConversationId,
|
|
54
|
+
dynamicVariables
|
|
55
|
+
} = options;
|
|
56
|
+
const authHeaders = useCallback(() => {
|
|
57
|
+
return shogoApiKey ? { authorization: `Bearer ${shogoApiKey}` } : {};
|
|
58
|
+
}, [shogoApiKey]);
|
|
59
|
+
const resolvedSignedUrlPath = withProjectQuery(signedUrlPath, { projectId, agentName });
|
|
60
|
+
const transcriptRef = useRef([]);
|
|
61
|
+
const lastInjectedRef = useRef("");
|
|
62
|
+
const weStartedSessionRef = useRef(false);
|
|
63
|
+
const [convaiConversationId, setConvaiConversationId] = useState(null);
|
|
64
|
+
const isRestartingRef = useRef(false);
|
|
65
|
+
const conversationRef = useRef(null);
|
|
66
|
+
const postJson = useCallback(
|
|
67
|
+
createPostJson({ authHeaders, fetchCredentials }),
|
|
68
|
+
[fetchCredentials, authHeaders]
|
|
69
|
+
);
|
|
70
|
+
const injectMemoryContext = useCallback(
|
|
71
|
+
createMemoryContextInjector({
|
|
72
|
+
postJson,
|
|
73
|
+
memoryRetrievePath,
|
|
74
|
+
conversationRef,
|
|
75
|
+
enabled: autoInjectMemory,
|
|
76
|
+
lastInjectedRef
|
|
77
|
+
}),
|
|
78
|
+
[autoInjectMemory, memoryRetrievePath, postJson]
|
|
79
|
+
);
|
|
80
|
+
const handleTranscriptOnDisconnect = useCallback(
|
|
81
|
+
createTranscriptDisconnectHandler({
|
|
82
|
+
isRestartingRef,
|
|
83
|
+
weStartedSessionRef,
|
|
84
|
+
transcriptRef,
|
|
85
|
+
onTranscript,
|
|
86
|
+
transcriptIngestPath,
|
|
87
|
+
postJson
|
|
88
|
+
}),
|
|
89
|
+
[onTranscript, postJson, transcriptIngestPath]
|
|
90
|
+
);
|
|
91
|
+
const builtInTools = includeMemoryTool ? { add_memory: createMemoryAddTool({ postJson, memoryAddPath }) } : {};
|
|
92
|
+
const mergedClientTools = { ...builtInTools, ...clientTools };
|
|
93
|
+
const conversation = useConversation({
|
|
94
|
+
clientTools: mergedClientTools,
|
|
95
|
+
onConnect: (info) => {
|
|
96
|
+
const id = info?.conversationId;
|
|
97
|
+
if (typeof id === "string" && id.length > 0) {
|
|
98
|
+
setConvaiConversationId(id);
|
|
99
|
+
}
|
|
100
|
+
if (!isRestartingRef.current) {
|
|
101
|
+
transcriptRef.current = [];
|
|
102
|
+
lastInjectedRef.current = "";
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
onDisconnect: () => {
|
|
106
|
+
handleTranscriptOnDisconnect();
|
|
107
|
+
if (!isRestartingRef.current) {
|
|
108
|
+
setConvaiConversationId(null);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
onError: (e) => {
|
|
112
|
+
onError?.(e);
|
|
113
|
+
},
|
|
114
|
+
onMessage: (m) => {
|
|
115
|
+
const msg = m;
|
|
116
|
+
if (!msg?.message || !msg?.source) return;
|
|
117
|
+
appendTranscriptLine(msg, { transcriptRef, weStartedSessionRef });
|
|
118
|
+
onMessage?.({ source: msg.source, message: msg.message });
|
|
119
|
+
if (autoInjectMemory && msg.source === "user" && msg.message.trim().length >= 3 && weStartedSessionRef.current) {
|
|
120
|
+
void injectMemoryContext(msg.message);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
const { status, isSpeaking, isListening, isMuted, setMuted, startSession, endSession } = conversation;
|
|
125
|
+
conversationRef.current = conversation;
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
const onHide = () => {
|
|
128
|
+
if (!weStartedSessionRef.current || transcriptRef.current.length === 0) return;
|
|
129
|
+
const transcript = transcriptRef.current.join("\n").trim();
|
|
130
|
+
transcriptRef.current = [];
|
|
131
|
+
if (transcript.length < 20) return;
|
|
132
|
+
try {
|
|
133
|
+
const blob = new Blob([JSON.stringify({ transcript })], { type: "application/json" });
|
|
134
|
+
navigator.sendBeacon(transcriptIngestPath, blob);
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
window.addEventListener("pagehide", onHide);
|
|
139
|
+
return () => window.removeEventListener("pagehide", onHide);
|
|
140
|
+
}, [transcriptIngestPath]);
|
|
141
|
+
const startInternal = useCallback(
|
|
142
|
+
async (opts) => {
|
|
143
|
+
if (status === "connected" || status === "connecting") {
|
|
144
|
+
try {
|
|
145
|
+
endSession();
|
|
146
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const micStream = await requestMicStream({ audio: true });
|
|
151
|
+
micStream.getTracks().forEach((track) => track.stop());
|
|
152
|
+
const data = await fetchSignedUrl({
|
|
153
|
+
path: resolvedSignedUrlPath,
|
|
154
|
+
fetchCredentials,
|
|
155
|
+
authHeaders
|
|
156
|
+
});
|
|
157
|
+
const ctx = data.userContext || "No prior memories yet.";
|
|
158
|
+
weStartedSessionRef.current = true;
|
|
159
|
+
const sessionPayload = buildSessionPayload({
|
|
160
|
+
signedUrl: data.signedUrl,
|
|
161
|
+
characterName,
|
|
162
|
+
userContext: ctx,
|
|
163
|
+
agentPromptOverride: data.agentPromptOverride,
|
|
164
|
+
suppressFirstMessage: opts?.suppressFirstMessage,
|
|
165
|
+
conversationId: optionConversationId,
|
|
166
|
+
dynamicVariables
|
|
167
|
+
});
|
|
168
|
+
await startSession(sessionPayload);
|
|
169
|
+
},
|
|
170
|
+
[
|
|
171
|
+
status,
|
|
172
|
+
endSession,
|
|
173
|
+
resolvedSignedUrlPath,
|
|
174
|
+
fetchCredentials,
|
|
175
|
+
dynamicVariables,
|
|
176
|
+
startSession,
|
|
177
|
+
characterName,
|
|
178
|
+
authHeaders,
|
|
179
|
+
optionConversationId
|
|
180
|
+
]
|
|
181
|
+
);
|
|
182
|
+
const start = useCallback(
|
|
183
|
+
async (options2) => {
|
|
184
|
+
await startInternal(options2);
|
|
185
|
+
},
|
|
186
|
+
[startInternal]
|
|
187
|
+
);
|
|
188
|
+
const end = useCallback(() => {
|
|
189
|
+
endSession();
|
|
190
|
+
}, [endSession]);
|
|
191
|
+
const restart = useCallback(
|
|
192
|
+
async (options2) => {
|
|
193
|
+
const suppress = options2?.suppressFirstMessage ?? true;
|
|
194
|
+
isRestartingRef.current = true;
|
|
195
|
+
try {
|
|
196
|
+
try {
|
|
197
|
+
endSession();
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
201
|
+
await startInternal({ suppressFirstMessage: suppress });
|
|
202
|
+
} finally {
|
|
203
|
+
isRestartingRef.current = false;
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
[endSession, startInternal]
|
|
207
|
+
);
|
|
208
|
+
const sendContextualUpdate = useCallback((text) => {
|
|
209
|
+
conversationRef.current?.sendContextualUpdate(text);
|
|
210
|
+
}, []);
|
|
211
|
+
const sendUserMessage = useCallback((text) => {
|
|
212
|
+
const ref = conversationRef.current;
|
|
213
|
+
if (!ref) return;
|
|
214
|
+
if (typeof ref.sendUserMessage === "function") {
|
|
215
|
+
ref.sendUserMessage(text);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
ref.sendContextualUpdate(text);
|
|
219
|
+
try {
|
|
220
|
+
ref.sendUserActivity?.();
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
}, []);
|
|
224
|
+
const sendUserActivity = useCallback(() => {
|
|
225
|
+
conversationRef.current?.sendUserActivity?.();
|
|
226
|
+
}, []);
|
|
227
|
+
const getOutputByteFrequencyData = useCallback(() => {
|
|
228
|
+
try {
|
|
229
|
+
const fn = conversation.getOutputByteFrequencyData;
|
|
230
|
+
return fn ? fn() : null;
|
|
231
|
+
} catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}, [conversation]);
|
|
235
|
+
return {
|
|
236
|
+
start,
|
|
237
|
+
end,
|
|
238
|
+
restart,
|
|
239
|
+
status,
|
|
240
|
+
isSpeaking,
|
|
241
|
+
isListening,
|
|
242
|
+
isMuted,
|
|
243
|
+
setMuted,
|
|
244
|
+
getOutputByteFrequencyData,
|
|
245
|
+
sendContextualUpdate,
|
|
246
|
+
sendUserMessage,
|
|
247
|
+
sendUserActivity,
|
|
248
|
+
conversationId: optionConversationId ?? convaiConversationId,
|
|
249
|
+
convaiConversationId
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/react/useLiveVoiceConversation.ts
|
|
254
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
255
|
+
function parseEvent(data) {
|
|
256
|
+
try {
|
|
257
|
+
const text = typeof data === "string" ? data : String(data);
|
|
258
|
+
const event = JSON.parse(text);
|
|
259
|
+
return event && typeof event === "object" ? event : null;
|
|
260
|
+
} catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function waitForIceGathering(pc) {
|
|
265
|
+
if (pc.iceGatheringState === "complete") return Promise.resolve();
|
|
266
|
+
return new Promise((resolve) => {
|
|
267
|
+
const onStateChange = () => {
|
|
268
|
+
if (pc.iceGatheringState === "complete") {
|
|
269
|
+
pc.removeEventListener("icegatheringstatechange", onStateChange);
|
|
270
|
+
resolve();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
pc.addEventListener("icegatheringstatechange", onStateChange);
|
|
274
|
+
window.setTimeout(() => {
|
|
275
|
+
pc.removeEventListener("icegatheringstatechange", onStateChange);
|
|
276
|
+
resolve();
|
|
277
|
+
}, 2e3);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function splitContext(text) {
|
|
281
|
+
const chunks = [];
|
|
282
|
+
for (let offset = 0; offset < text.length; offset += 1800) {
|
|
283
|
+
chunks.push(text.slice(offset, offset + 1800));
|
|
284
|
+
}
|
|
285
|
+
return chunks.length ? chunks : [""];
|
|
286
|
+
}
|
|
287
|
+
var TRANSCRIPT_HISTORY_MAX = 30;
|
|
288
|
+
function useLiveVoiceConversation(options) {
|
|
289
|
+
const { mintSession } = options;
|
|
290
|
+
const [status, setStatus] = useState2("disconnected");
|
|
291
|
+
const [isSpeaking, setIsSpeaking] = useState2(false);
|
|
292
|
+
const [isListening, setIsListening] = useState2(false);
|
|
293
|
+
const [isMuted, setIsMuted] = useState2(false);
|
|
294
|
+
const [sessionId, setSessionId] = useState2(null);
|
|
295
|
+
const optionsRef = useRef2(options);
|
|
296
|
+
optionsRef.current = options;
|
|
297
|
+
const pcRef = useRef2(null);
|
|
298
|
+
const dataChannelRef = useRef2(null);
|
|
299
|
+
const mediaStreamRef = useRef2(null);
|
|
300
|
+
const audioRef = useRef2(null);
|
|
301
|
+
const analyserRef = useRef2(null);
|
|
302
|
+
const frequencyRef = useRef2(null);
|
|
303
|
+
const audioContextRef = useRef2(null);
|
|
304
|
+
const speakingFrameRef = useRef2(null);
|
|
305
|
+
const inputTranscriptRef = useRef2("");
|
|
306
|
+
const outputTranscriptRef = useRef2("");
|
|
307
|
+
const historyRef = useRef2([]);
|
|
308
|
+
const closingRef = useRef2(false);
|
|
309
|
+
const mutedRef = useRef2(false);
|
|
310
|
+
const emit = useCallback2((source, message) => {
|
|
311
|
+
const text = message.trim();
|
|
312
|
+
if (!text) return;
|
|
313
|
+
const history = historyRef.current;
|
|
314
|
+
history.push({ role: source === "user" ? "user" : "assistant", text });
|
|
315
|
+
if (history.length > TRANSCRIPT_HISTORY_MAX) history.splice(0, history.length - TRANSCRIPT_HISTORY_MAX);
|
|
316
|
+
optionsRef.current.onMessage?.({ source, message: text });
|
|
317
|
+
}, []);
|
|
318
|
+
const flushTranscripts = useCallback2(() => {
|
|
319
|
+
const input = inputTranscriptRef.current.trim();
|
|
320
|
+
const output = outputTranscriptRef.current.trim();
|
|
321
|
+
inputTranscriptRef.current = "";
|
|
322
|
+
outputTranscriptRef.current = "";
|
|
323
|
+
if (input) emit("user", input);
|
|
324
|
+
if (output) emit("agent", output);
|
|
325
|
+
}, [emit]);
|
|
326
|
+
const sendEvent = useCallback2((event) => {
|
|
327
|
+
const channel = dataChannelRef.current;
|
|
328
|
+
if (channel?.readyState === "open") channel.send(JSON.stringify(event));
|
|
329
|
+
}, []);
|
|
330
|
+
const updateSpeaking = useCallback2(() => {
|
|
331
|
+
const analyser = analyserRef.current;
|
|
332
|
+
if (analyser) {
|
|
333
|
+
const data = frequencyRef.current ?? new Uint8Array(analyser.frequencyBinCount);
|
|
334
|
+
frequencyRef.current = data;
|
|
335
|
+
analyser.getByteFrequencyData(data);
|
|
336
|
+
let sum = 0;
|
|
337
|
+
for (const value of data) sum += value;
|
|
338
|
+
setIsSpeaking(data.length > 0 && sum / data.length > 8);
|
|
339
|
+
}
|
|
340
|
+
if (pcRef.current) speakingFrameRef.current = requestAnimationFrame(updateSpeaking);
|
|
341
|
+
}, []);
|
|
342
|
+
const handleEvent = useCallback2(async (event) => {
|
|
343
|
+
const nested = event.type === "response.event" && event.event ? event.event : event;
|
|
344
|
+
const type = nested.type ?? "";
|
|
345
|
+
if (type === "session.started" || type === "session.created") {
|
|
346
|
+
setStatus("connected");
|
|
347
|
+
setIsListening(!mutedRef.current);
|
|
348
|
+
const id = typeof nested.session?.id === "string" ? nested.session.id : null;
|
|
349
|
+
if (id) setSessionId(id);
|
|
350
|
+
}
|
|
351
|
+
if (type === "session.closed") {
|
|
352
|
+
flushTranscripts();
|
|
353
|
+
setStatus("disconnected");
|
|
354
|
+
setIsListening(false);
|
|
355
|
+
}
|
|
356
|
+
if (type === "error") {
|
|
357
|
+
optionsRef.current.onError?.(new Error(event.error?.message || "GPT-Live error"));
|
|
358
|
+
}
|
|
359
|
+
if (type === "session.input_transcript.delta" || type === "input_audio_transcription.delta") {
|
|
360
|
+
inputTranscriptRef.current += event.delta ?? "";
|
|
361
|
+
}
|
|
362
|
+
if (type === "session.output_transcript.delta" || type === "response.audio_transcript.delta") {
|
|
363
|
+
outputTranscriptRef.current += event.delta ?? "";
|
|
364
|
+
}
|
|
365
|
+
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") {
|
|
366
|
+
flushTranscripts();
|
|
367
|
+
}
|
|
368
|
+
if (type === "session.delegation.created" && nested.delegation?.target !== "responses" && typeof nested.delegation?.id === "string") {
|
|
369
|
+
const onDelegation = optionsRef.current.onDelegation;
|
|
370
|
+
if (!onDelegation) return;
|
|
371
|
+
const delegationId = nested.delegation.id;
|
|
372
|
+
flushTranscripts();
|
|
373
|
+
let result;
|
|
374
|
+
try {
|
|
375
|
+
result = await onDelegation({ delegationId, transcript: [...historyRef.current] });
|
|
376
|
+
} catch (error) {
|
|
377
|
+
optionsRef.current.onError?.(error);
|
|
378
|
+
result = "That request failed before it could finish. Nothing was confirmed.";
|
|
379
|
+
}
|
|
380
|
+
const text = typeof result === "string" ? result.trim() : "";
|
|
381
|
+
if (!text) return;
|
|
382
|
+
for (const content of splitContext(text)) {
|
|
383
|
+
sendEvent({
|
|
384
|
+
type: "session.commentary.append",
|
|
385
|
+
delegation_id: delegationId,
|
|
386
|
+
content
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const item = nested.item;
|
|
392
|
+
if (type === "response.output_item.done" && item?.type === "function_call") {
|
|
393
|
+
const tool = optionsRef.current.clientTools?.[item.name ?? ""];
|
|
394
|
+
if (!tool || !item.call_id) return;
|
|
395
|
+
let args = {};
|
|
396
|
+
try {
|
|
397
|
+
const parsed = JSON.parse(item.arguments || "{}");
|
|
398
|
+
if (parsed && typeof parsed === "object") args = parsed;
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
let output;
|
|
402
|
+
try {
|
|
403
|
+
output = String(await tool(args));
|
|
404
|
+
} catch (error) {
|
|
405
|
+
output = `Tool failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
406
|
+
}
|
|
407
|
+
sendEvent({
|
|
408
|
+
type: "response.item.create",
|
|
409
|
+
item: {
|
|
410
|
+
type: "function_call_output",
|
|
411
|
+
call_id: item.call_id,
|
|
412
|
+
output
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
sendEvent({ type: "response.create" });
|
|
416
|
+
}
|
|
417
|
+
}, [flushTranscripts, sendEvent]);
|
|
418
|
+
const end = useCallback2(() => {
|
|
419
|
+
closingRef.current = true;
|
|
420
|
+
flushTranscripts();
|
|
421
|
+
sendEvent({ type: "session.close" });
|
|
422
|
+
dataChannelRef.current?.close();
|
|
423
|
+
pcRef.current?.close();
|
|
424
|
+
mediaStreamRef.current?.getTracks().forEach((track) => track.stop());
|
|
425
|
+
audioRef.current?.pause();
|
|
426
|
+
if (speakingFrameRef.current !== null) cancelAnimationFrame(speakingFrameRef.current);
|
|
427
|
+
speakingFrameRef.current = null;
|
|
428
|
+
pcRef.current = null;
|
|
429
|
+
dataChannelRef.current = null;
|
|
430
|
+
mediaStreamRef.current = null;
|
|
431
|
+
analyserRef.current = null;
|
|
432
|
+
frequencyRef.current = null;
|
|
433
|
+
audioContextRef.current?.close().catch(() => {
|
|
434
|
+
});
|
|
435
|
+
audioContextRef.current = null;
|
|
436
|
+
setIsSpeaking(false);
|
|
437
|
+
setIsListening(false);
|
|
438
|
+
setSessionId(null);
|
|
439
|
+
setStatus("disconnected");
|
|
440
|
+
}, [flushTranscripts, sendEvent]);
|
|
441
|
+
const startInternal = useCallback2(async () => {
|
|
442
|
+
if (status === "connected" || status === "connecting") end();
|
|
443
|
+
closingRef.current = false;
|
|
444
|
+
setStatus("connecting");
|
|
445
|
+
try {
|
|
446
|
+
const stream = await requestMicStream({ audio: true });
|
|
447
|
+
mediaStreamRef.current = stream;
|
|
448
|
+
const pc = new RTCPeerConnection();
|
|
449
|
+
pcRef.current = pc;
|
|
450
|
+
stream.getTracks().forEach((track) => pc.addTrack(track, stream));
|
|
451
|
+
const channel = pc.createDataChannel("oai-events");
|
|
452
|
+
dataChannelRef.current = channel;
|
|
453
|
+
channel.onopen = () => {
|
|
454
|
+
if (!closingRef.current) setIsListening(!mutedRef.current);
|
|
455
|
+
};
|
|
456
|
+
channel.onmessage = (message) => {
|
|
457
|
+
const event = parseEvent(message.data);
|
|
458
|
+
if (event) void handleEvent(event);
|
|
459
|
+
};
|
|
460
|
+
channel.onerror = (event) => optionsRef.current.onError?.(event);
|
|
461
|
+
pc.ontrack = (event) => {
|
|
462
|
+
const remoteStream = event.streams[0];
|
|
463
|
+
if (!remoteStream) return;
|
|
464
|
+
const audio = new Audio();
|
|
465
|
+
audio.autoplay = true;
|
|
466
|
+
audio.srcObject = remoteStream;
|
|
467
|
+
audioRef.current = audio;
|
|
468
|
+
void audio.play().catch(() => {
|
|
469
|
+
});
|
|
470
|
+
try {
|
|
471
|
+
const AudioContextImpl = window.AudioContext || window.webkitAudioContext;
|
|
472
|
+
if (AudioContextImpl) {
|
|
473
|
+
const context = new AudioContextImpl();
|
|
474
|
+
const source = context.createMediaStreamSource(remoteStream);
|
|
475
|
+
const analyser = context.createAnalyser();
|
|
476
|
+
analyser.fftSize = 256;
|
|
477
|
+
source.connect(analyser);
|
|
478
|
+
audioContextRef.current = context;
|
|
479
|
+
analyserRef.current = analyser;
|
|
480
|
+
frequencyRef.current = new Uint8Array(analyser.frequencyBinCount);
|
|
481
|
+
if (speakingFrameRef.current === null) {
|
|
482
|
+
speakingFrameRef.current = requestAnimationFrame(updateSpeaking);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
pc.onconnectionstatechange = () => {
|
|
489
|
+
if (["failed", "disconnected", "closed"].includes(pc.connectionState) && !closingRef.current) {
|
|
490
|
+
optionsRef.current.onError?.(new Error(`GPT-Live connection ${pc.connectionState}`));
|
|
491
|
+
end();
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
const offer = await pc.createOffer();
|
|
495
|
+
await pc.setLocalDescription(offer);
|
|
496
|
+
await waitForIceGathering(pc);
|
|
497
|
+
const localSdp = pc.localDescription?.sdp;
|
|
498
|
+
if (!localSdp) throw new Error("Failed to create a WebRTC offer");
|
|
499
|
+
const answer = await mintSession(localSdp);
|
|
500
|
+
await pc.setRemoteDescription({ type: "answer", sdp: answer.sdp });
|
|
501
|
+
setSessionId(answer.sessionId);
|
|
502
|
+
setStatus("connected");
|
|
503
|
+
} catch (error) {
|
|
504
|
+
end();
|
|
505
|
+
optionsRef.current.onError?.(error);
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
}, [end, handleEvent, mintSession, status, updateSpeaking]);
|
|
509
|
+
const start = useCallback2(async () => {
|
|
510
|
+
await startInternal();
|
|
511
|
+
}, [startInternal]);
|
|
512
|
+
const restart = useCallback2(async () => {
|
|
513
|
+
end();
|
|
514
|
+
await new Promise((resolve) => window.setTimeout(resolve, 120));
|
|
515
|
+
await startInternal();
|
|
516
|
+
}, [end, startInternal]);
|
|
517
|
+
const setMuted = useCallback2((muted) => {
|
|
518
|
+
mutedRef.current = muted;
|
|
519
|
+
mediaStreamRef.current?.getAudioTracks().forEach((track) => {
|
|
520
|
+
track.enabled = !muted;
|
|
521
|
+
});
|
|
522
|
+
sendEvent({ type: muted ? "session.input_audio.mute" : "session.input_audio.unmute" });
|
|
523
|
+
setIsMuted(muted);
|
|
524
|
+
setIsListening(!muted && status === "connected");
|
|
525
|
+
}, [sendEvent, status]);
|
|
526
|
+
const sendContextualUpdate = useCallback2((text) => {
|
|
527
|
+
for (const content of splitContext(text)) {
|
|
528
|
+
sendEvent({
|
|
529
|
+
type: "session.thinking.append",
|
|
530
|
+
delegation_id: null,
|
|
531
|
+
content
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}, [sendEvent]);
|
|
535
|
+
const sendUserMessage = useCallback2((text) => {
|
|
536
|
+
sendEvent({
|
|
537
|
+
type: "session.commentary.append",
|
|
538
|
+
delegation_id: null,
|
|
539
|
+
content: text
|
|
540
|
+
});
|
|
541
|
+
}, [sendEvent]);
|
|
542
|
+
const sendUserActivity = useCallback2(() => {
|
|
543
|
+
sendEvent({ type: "session.input_audio.commit" });
|
|
544
|
+
}, [sendEvent]);
|
|
545
|
+
const getOutputByteFrequencyData = useCallback2(() => {
|
|
546
|
+
return frequencyRef.current;
|
|
547
|
+
}, []);
|
|
548
|
+
const getTranscript = useCallback2(() => [...historyRef.current], []);
|
|
549
|
+
useEffect2(() => () => end(), [end]);
|
|
550
|
+
return {
|
|
551
|
+
start,
|
|
552
|
+
end,
|
|
553
|
+
restart,
|
|
554
|
+
status,
|
|
555
|
+
isSpeaking,
|
|
556
|
+
isListening,
|
|
557
|
+
isMuted,
|
|
558
|
+
setMuted,
|
|
559
|
+
getOutputByteFrequencyData,
|
|
560
|
+
sendContextualUpdate,
|
|
561
|
+
sendUserMessage,
|
|
562
|
+
sendUserActivity,
|
|
563
|
+
getTranscript,
|
|
564
|
+
conversationId: sessionId,
|
|
565
|
+
convaiConversationId: sessionId
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/react/ShogoVoiceProvider.tsx
|
|
570
|
+
import { ConversationProvider as ElevenLabsConversationProvider } from "@elevenlabs/react";
|
|
571
|
+
import { jsx } from "react/jsx-runtime";
|
|
572
|
+
function ShogoVoiceProvider(props) {
|
|
573
|
+
return /* @__PURE__ */ jsx(ElevenLabsConversationProvider, { children: props.children });
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export {
|
|
577
|
+
useVoiceConversation,
|
|
578
|
+
useLiveVoiceConversation,
|
|
579
|
+
ShogoVoiceProvider
|
|
580
|
+
};
|
|
581
|
+
//# sourceMappingURL=chunk-ME3SIZZU.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createVoiceHandlers
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-U5UC7ZVS.js";
|
|
4
4
|
|
|
5
5
|
// src/route/index.ts
|
|
6
6
|
function defaultHandlers() {
|
|
@@ -12,6 +12,10 @@ function createVoiceRoute(config) {
|
|
|
12
12
|
signedUrl: { GET: (req) => v.signedUrl(req) },
|
|
13
13
|
ttsPreview: { POST: (req) => v.tts(req) },
|
|
14
14
|
music: { POST: (req) => v.music(req) },
|
|
15
|
+
op: {
|
|
16
|
+
GET: (req, opId) => v.op(req, opId),
|
|
17
|
+
POST: (req, opId) => v.op(req, opId)
|
|
18
|
+
},
|
|
15
19
|
agent: {
|
|
16
20
|
POST: (req) => v.agent.create(req),
|
|
17
21
|
PATCH: (req) => v.agent.patch(req),
|
|
@@ -25,4 +29,4 @@ export {
|
|
|
25
29
|
defaultHandlers,
|
|
26
30
|
createVoiceRoute
|
|
27
31
|
};
|
|
28
|
-
//# sourceMappingURL=chunk-
|
|
32
|
+
//# sourceMappingURL=chunk-RALZRMFL.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/shared/chatUrl.ts
|
|
2
|
+
function appendChatQuery(basePath, params) {
|
|
3
|
+
const tail = [];
|
|
4
|
+
for (const [k, v] of Object.entries(params)) {
|
|
5
|
+
if (typeof v === "string" && v.length > 0) {
|
|
6
|
+
tail.push(`${k}=${encodeURIComponent(v)}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
if (tail.length === 0) return basePath;
|
|
10
|
+
const sep = basePath.includes("?") ? "&" : "?";
|
|
11
|
+
return `${basePath}${sep}${tail.join("&")}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
appendChatQuery
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=chunk-TQOBJMEK.js.map
|