perso-interactive-sdk-web 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -84
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.ts +457 -68
- package/dist/client/index.iife.js +1 -1
- package/dist/client/index.js +1 -1
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.d.ts +183 -79
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,21 +51,25 @@ const { createSessionId } = require("perso-interactive-sdk-web/server");
|
|
|
51
51
|
|
|
52
52
|
const app = express();
|
|
53
53
|
|
|
54
|
-
const API_SERVER = "https://platform.perso.ai";
|
|
55
54
|
const API_KEY = process.env.PERSO_INTERACTIVE_API_KEY;
|
|
56
55
|
|
|
57
56
|
app.post("/api/session", async (req, res) => {
|
|
58
57
|
try {
|
|
59
|
-
const sessionId = await createSessionId(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
const sessionId = await createSessionId({
|
|
59
|
+
apiKey: API_KEY,
|
|
60
|
+
params: {
|
|
61
|
+
using_stf_webrtc: true,
|
|
62
|
+
model_style: "<model_style_name>",
|
|
63
|
+
prompt: "<prompt_id>",
|
|
64
|
+
llm_type: "<llm_name>",
|
|
65
|
+
tts_type: "<tts_name>",
|
|
66
|
+
stt_type: "<stt_name>",
|
|
67
|
+
// text_normalization_config: "<textnormalizationconfig_id>", // optional
|
|
68
|
+
// stt_text_normalization_config: "<textnormalizationconfig_id>", // optional
|
|
69
|
+
// stt_text_normalization_locale: "ko", // optional
|
|
70
|
+
},
|
|
71
|
+
// apiServer defaults to "https://platform.perso.ai".
|
|
72
|
+
// Pass it explicitly to point at another environment (e.g., stage).
|
|
69
73
|
});
|
|
70
74
|
res.json({ sessionId });
|
|
71
75
|
} catch (error) {
|
|
@@ -77,12 +81,21 @@ app.post("/api/session", async (req, res) => {
|
|
|
77
81
|
app.listen(3000, () => console.log("Server running on port 3000"));
|
|
78
82
|
```
|
|
79
83
|
|
|
84
|
+
> **Two call styles supported.** Every SDK function shown above also accepts the
|
|
85
|
+
> classic positional form `createSessionId(apiServer, apiKey, params)` for
|
|
86
|
+
> backward compatibility. New code should prefer the **object form** — it lets
|
|
87
|
+
> you omit `apiServer` (defaults to `https://platform.perso.ai`) and is easier
|
|
88
|
+
> to read at call sites.
|
|
89
|
+
|
|
80
90
|
#### Using a SessionTemplate
|
|
81
91
|
|
|
82
92
|
If you have pre-configured session templates, pass the template ID directly instead of assembling params manually:
|
|
83
93
|
|
|
84
94
|
```javascript
|
|
85
|
-
const sessionId = await createSessionId(
|
|
95
|
+
const sessionId = await createSessionId({
|
|
96
|
+
apiKey: API_KEY,
|
|
97
|
+
sessionTemplateId: "<sessiontemplate_id>",
|
|
98
|
+
});
|
|
86
99
|
```
|
|
87
100
|
|
|
88
101
|
#### Listing available resources from the server
|
|
@@ -93,11 +106,33 @@ Use `getAllSettings` (or any individual `getXxx` helper) on the server to discov
|
|
|
93
106
|
const { getAllSettings } = require("perso-interactive-sdk-web/server");
|
|
94
107
|
|
|
95
108
|
app.get("/api/settings", async (req, res) => {
|
|
96
|
-
const settings = await getAllSettings(
|
|
109
|
+
const settings = await getAllSettings({ apiKey: API_KEY });
|
|
97
110
|
res.json(settings); // { llms, ttsTypes, sttTypes, modelStyles, ... }
|
|
98
111
|
});
|
|
99
112
|
```
|
|
100
113
|
|
|
114
|
+
#### Stage / custom API server
|
|
115
|
+
|
|
116
|
+
Every object-form call accepts an optional `apiServer`. Omit it for production
|
|
117
|
+
(defaults to `https://platform.perso.ai`), or pass an explicit URL to point at
|
|
118
|
+
a non-production environment. The SDK trims trailing slashes for you.
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
const { DEFAULT_API_SERVER, getAllSettings } = require(
|
|
122
|
+
"perso-interactive-sdk-web/server"
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
DEFAULT_API_SERVER; // "https://platform.perso.ai"
|
|
126
|
+
|
|
127
|
+
const stageSettings = await getAllSettings({
|
|
128
|
+
apiKey: API_KEY,
|
|
129
|
+
apiServer: "https://stage-platform.perso.ai",
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The same `DEFAULT_API_SERVER` constant is also re-exported from
|
|
134
|
+
`perso-interactive-sdk-web/client`.
|
|
135
|
+
|
|
101
136
|
> ⚠️ **Security Warning**: Never use `createSessionId` on the client-side in production. Exposing your API key in browser code can lead to unauthorized access and quota abuse. Always create sessions on the server and pass only the `sessionId` to the client.
|
|
102
137
|
|
|
103
138
|
#### Client-side Testing Only
|
|
@@ -110,22 +145,29 @@ import {
|
|
|
110
145
|
createSession,
|
|
111
146
|
} from "perso-interactive-sdk-web/client";
|
|
112
147
|
|
|
113
|
-
const apiServer = "https://platform.perso.ai";
|
|
114
148
|
const apiKey = "YOUR_API_KEY"; // ⚠️ NEVER commit or expose this in production
|
|
115
149
|
|
|
116
|
-
const sessionId = await createSessionId(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
150
|
+
const sessionId = await createSessionId({
|
|
151
|
+
apiKey,
|
|
152
|
+
params: {
|
|
153
|
+
using_stf_webrtc: true,
|
|
154
|
+
model_style: "<model_style_name>",
|
|
155
|
+
prompt: "<prompt_id>",
|
|
156
|
+
llm_type: "<llm_name>",
|
|
157
|
+
tts_type: "<tts_name>",
|
|
158
|
+
stt_type: "<stt_name>",
|
|
159
|
+
// text_normalization_config: "<textnormalizationconfig_id>", // optional
|
|
160
|
+
// stt_text_normalization_config: "<textnormalizationconfig_id>", // optional
|
|
161
|
+
// stt_text_normalization_locale: "ko", // optional
|
|
162
|
+
},
|
|
126
163
|
});
|
|
127
164
|
|
|
128
|
-
const session = await createSession(
|
|
165
|
+
const session = await createSession({
|
|
166
|
+
sessionId,
|
|
167
|
+
width: 1920,
|
|
168
|
+
height: 1080,
|
|
169
|
+
clientTools: [],
|
|
170
|
+
});
|
|
129
171
|
|
|
130
172
|
const videoEl = document.getElementById("video");
|
|
131
173
|
if (videoEl instanceof HTMLVideoElement) {
|
|
@@ -144,15 +186,18 @@ import {
|
|
|
144
186
|
ChatState,
|
|
145
187
|
} from "perso-interactive-sdk-web/client";
|
|
146
188
|
|
|
147
|
-
const apiServer = "https://platform.perso.ai";
|
|
148
|
-
|
|
149
189
|
// Obtain sessionId from your server (see Express.js example above)
|
|
150
190
|
const sessionId = await fetch("/api/session", { method: "POST" })
|
|
151
191
|
.then((res) => res.json())
|
|
152
192
|
.then((data) => data.sessionId);
|
|
153
193
|
|
|
154
|
-
// Create a session
|
|
155
|
-
const session = await createSession(
|
|
194
|
+
// Create a session (apiServer defaults to https://platform.perso.ai)
|
|
195
|
+
const session = await createSession({
|
|
196
|
+
sessionId,
|
|
197
|
+
width: 1920,
|
|
198
|
+
height: 1080,
|
|
199
|
+
clientTools: [],
|
|
200
|
+
});
|
|
156
201
|
|
|
157
202
|
// Bind to video element
|
|
158
203
|
const videoEl = document.getElementById("video");
|
|
@@ -247,13 +292,12 @@ const weatherTool = new ChatTool(
|
|
|
247
292
|
false, // executeOnly: if true, no follow-up LLM response
|
|
248
293
|
);
|
|
249
294
|
|
|
250
|
-
const session = await createSession(
|
|
251
|
-
apiServer,
|
|
295
|
+
const session = await createSession({
|
|
252
296
|
sessionId,
|
|
253
297
|
width,
|
|
254
298
|
height,
|
|
255
|
-
[weatherTool]
|
|
256
|
-
);
|
|
299
|
+
clientTools: [weatherTool],
|
|
300
|
+
});
|
|
257
301
|
```
|
|
258
302
|
|
|
259
303
|
### Browser (IIFE)
|
|
@@ -264,20 +308,18 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
264
308
|
<script src="https://cdn.jsdelivr.net/npm/perso-interactive-sdk-web@latest/dist/client/index.iife.js"></script>
|
|
265
309
|
<script>
|
|
266
310
|
async function start() {
|
|
267
|
-
const apiServer = "https://platform.perso.ai";
|
|
268
|
-
|
|
269
311
|
// Obtain sessionId from your server (see Express.js example above)
|
|
270
312
|
const sessionId = await fetch("/api/session", { method: "POST" })
|
|
271
313
|
.then((res) => res.json())
|
|
272
314
|
.then((data) => data.sessionId);
|
|
273
315
|
|
|
274
|
-
|
|
275
|
-
|
|
316
|
+
// apiServer defaults to https://platform.perso.ai
|
|
317
|
+
const session = await PersoInteractive.createSession({
|
|
276
318
|
sessionId,
|
|
277
|
-
1920,
|
|
278
|
-
1080,
|
|
279
|
-
[]
|
|
280
|
-
);
|
|
319
|
+
width: 1920,
|
|
320
|
+
height: 1080,
|
|
321
|
+
clientTools: [],
|
|
322
|
+
});
|
|
281
323
|
|
|
282
324
|
const videoEl = document.getElementById("video");
|
|
283
325
|
if (videoEl instanceof HTMLVideoElement) {
|
|
@@ -297,58 +339,65 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
297
339
|
|
|
298
340
|
## API Reference
|
|
299
341
|
|
|
342
|
+
> Every function below has two call styles: the **object form** shown in the
|
|
343
|
+
> table (recommended for new code; `apiServer` is optional and defaults to
|
|
344
|
+
> `https://platform.perso.ai`) and the equivalent **positional form**
|
|
345
|
+
> (`fn(apiServer, apiKey, …)`) which remains fully supported.
|
|
346
|
+
|
|
300
347
|
### Server Exports
|
|
301
348
|
|
|
302
|
-
| Export
|
|
303
|
-
|
|
|
304
|
-
| `createSessionId(
|
|
305
|
-
| `createSessionId(
|
|
306
|
-
| `getIntroMessage(
|
|
307
|
-
| `getLLMs(
|
|
308
|
-
| `getTTSs(
|
|
309
|
-
| `getSTTs(
|
|
310
|
-
| `getModelStyles(
|
|
311
|
-
| `getBackgroundImages(
|
|
312
|
-
| `getPrompts(
|
|
313
|
-
| `getDocuments(
|
|
314
|
-
| `getMcpServers(
|
|
315
|
-
| `getTextNormalizations(
|
|
316
|
-
| `getTextNormalization(
|
|
317
|
-
| `getAllSettings(
|
|
318
|
-
| `getSessionTemplates(
|
|
319
|
-
| `getSessionTemplate(
|
|
320
|
-
| `getSessionInfo(
|
|
321
|
-
| `makeTTS(
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
325
|
-
| `
|
|
326
|
-
| `
|
|
349
|
+
| Export | Description |
|
|
350
|
+
| ------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
|
351
|
+
| `createSessionId({ apiKey, sessionTemplateId, apiServer? })` | Create a session ID from a SessionTemplate |
|
|
352
|
+
| `createSessionId({ apiKey, params, apiServer? })` | Create a new session ID |
|
|
353
|
+
| `getIntroMessage({ apiKey, promptId, apiServer? })` | Get intro message for a prompt |
|
|
354
|
+
| `getLLMs({ apiKey, apiServer? })` | Get available LLM providers |
|
|
355
|
+
| `getTTSs({ apiKey, apiServer? })` | Get available TTS providers |
|
|
356
|
+
| `getSTTs({ apiKey, apiServer? })` | Get available STT providers |
|
|
357
|
+
| `getModelStyles({ apiKey, apiServer? })` | Get available avatar styles |
|
|
358
|
+
| `getBackgroundImages({ apiKey, apiServer? })` | Get available backgrounds |
|
|
359
|
+
| `getPrompts({ apiKey, apiServer? })` | Get available prompts |
|
|
360
|
+
| `getDocuments({ apiKey, apiServer? })` | Get available documents |
|
|
361
|
+
| `getMcpServers({ apiKey, apiServer? })` | Get available MCP servers |
|
|
362
|
+
| `getTextNormalizations({ apiKey, apiServer? })` | Get available text normalization configs |
|
|
363
|
+
| `getTextNormalization({ apiKey, configId, apiServer? })` | Download text normalization ruleset (pre-signed URL) |
|
|
364
|
+
| `getAllSettings({ apiKey, apiServer? })` | Get all settings at once |
|
|
365
|
+
| `getSessionTemplates({ apiKey, apiServer? })` | Get available session templates |
|
|
366
|
+
| `getSessionTemplate({ apiKey, sessionTemplateId, apiServer? })` | Get a single session template by ID |
|
|
367
|
+
| `getSessionInfo({ sessionId, apiServer? })` | Get session metadata |
|
|
368
|
+
| `makeTTS({ sessionId, text, locale?, output_format?, apiServer? })` | Generate TTS audio from text (standalone) |
|
|
369
|
+
| `DEFAULT_API_SERVER` | The default API server URL (`https://platform.perso.ai`) |
|
|
370
|
+
| `PersoUtilServer` | Low-level API utilities |
|
|
371
|
+
| `ApiError` | Error class for API errors |
|
|
372
|
+
| `SessionCreationError` | Error class for session creation failures (extends `ApiError`) |
|
|
373
|
+
| `DoesNotExistError` | Session creation referenced a non-existent resource (extends `SessionCreationError`) |
|
|
374
|
+
| `NotInOrganizationError` | Session creation referenced a resource not assigned to the org (extends `SessionCreationError`) |
|
|
327
375
|
|
|
328
376
|
### Client Exports
|
|
329
377
|
|
|
330
378
|
| Export | Description |
|
|
331
379
|
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
332
|
-
| `createSession(
|
|
380
|
+
| `createSession({ sessionId, width, height, clientTools, apiServer? })` | Create a session |
|
|
333
381
|
| `Session` | Session class |
|
|
334
382
|
| `ChatTool` | Client tool class |
|
|
335
383
|
| `ChatState` | Enum for chat states (RECORDING, LLM, ANALYZING, SPEAKING, TTS) |
|
|
336
|
-
| `getLLMs(
|
|
337
|
-
| `getTTSs(
|
|
338
|
-
| `getSTTs(
|
|
339
|
-
| `getModelStyles(
|
|
340
|
-
| `getBackgroundImages(
|
|
341
|
-
| `getPrompts(
|
|
342
|
-
| `getDocuments(
|
|
343
|
-
| `getMcpServers(
|
|
344
|
-
| `getTextNormalizations(
|
|
345
|
-
| `getTextNormalization(
|
|
346
|
-
| `getAllSettings(
|
|
347
|
-
| `getSessionInfo(
|
|
348
|
-
| `makeTTS(
|
|
349
|
-
| `createSessionId(
|
|
350
|
-
| `createSessionId(
|
|
351
|
-
| `getSessionTemplates(
|
|
384
|
+
| `getLLMs({ apiKey, apiServer? })` | Get available LLM providers |
|
|
385
|
+
| `getTTSs({ apiKey, apiServer? })` | Get available TTS providers |
|
|
386
|
+
| `getSTTs({ apiKey, apiServer? })` | Get available STT providers |
|
|
387
|
+
| `getModelStyles({ apiKey, apiServer? })` | Get available avatar styles |
|
|
388
|
+
| `getBackgroundImages({ apiKey, apiServer? })` | Get available backgrounds |
|
|
389
|
+
| `getPrompts({ apiKey, apiServer? })` | Get available prompts |
|
|
390
|
+
| `getDocuments({ apiKey, apiServer? })` | Get available documents |
|
|
391
|
+
| `getMcpServers({ apiKey, apiServer? })` | Get available MCP servers |
|
|
392
|
+
| `getTextNormalizations({ apiKey, apiServer? })` | Get available text normalization configs |
|
|
393
|
+
| `getTextNormalization({ apiKey, configId, apiServer? })` | Download text normalization ruleset (pre-signed URL) |
|
|
394
|
+
| `getAllSettings({ apiKey, apiServer? })` | Get all settings at once |
|
|
395
|
+
| `getSessionInfo({ sessionId, apiServer? })` | Get session metadata |
|
|
396
|
+
| `makeTTS({ sessionId, text, locale?, output_format?, apiServer? })` | Generate TTS audio from text (standalone) |
|
|
397
|
+
| `createSessionId({ apiKey, sessionTemplateId, apiServer? })` | Create session ID from a SessionTemplate (exposes API key) |
|
|
398
|
+
| `createSessionId({ apiKey, params, apiServer? })` | Create session ID (exposes API key in browser) |
|
|
399
|
+
| `getSessionTemplates({ apiKey, apiServer? })` | Get available session templates |
|
|
400
|
+
| `DEFAULT_API_SERVER` | The default API server URL (`https://platform.perso.ai`) |
|
|
352
401
|
| `ApiError` | Error class for API errors |
|
|
353
402
|
| `LLMError` | Error class for LLM errors |
|
|
354
403
|
| `LLMStreamingResponseError` | Error class for streaming errors |
|
|
@@ -378,7 +427,7 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
378
427
|
| `stopProcessSTT(language?)` | Stop recording and get text |
|
|
379
428
|
| `isSTTRecording()` | Check if STT recording is in progress |
|
|
380
429
|
| `transcribeAudio(audio, language?)` | Transcribe audio Blob/File to text |
|
|
381
|
-
| `transcribeAudioDetailed(audio, language?)` | Transcribe audio Blob/File and return
|
|
430
|
+
| `transcribeAudioDetailed(audio, language?)` | Transcribe audio Blob/File and return `STTResponse` (currently `{ text }`) |
|
|
382
431
|
| `getMessageHistory()` | Get LLM conversation history |
|
|
383
432
|
| `getRemoteStream()` | Get AI human's media stream |
|
|
384
433
|
| `getLocalStream()` | ~~Get user's audio stream~~ (Deprecated) |
|
package/dist/client/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t,e,s,a=require("emoji-regex");exports.ChatState=void 0,(t=exports.ChatState||(exports.ChatState={})).RECORDING="RECORDING",t.LLM="LLM",t.ANALYZING="ANALYZING",t.SPEAKING="SPEAKING",t.TTS="TTS";class r extends Error{constructor(){super("WebRTC connection timeout")}}class n extends Error{errorCode;code;detail;attr;constructor(t,e,s,a){let r;r=null!=a?`${t}:${a}_${s}`:`${t}:${s}`,super(r),this.errorCode=t,this.code=e,this.detail=s,this.attr=a}}class o extends Error{underlyingError;constructor(t){super(),this.underlyingError=t}}class i extends Error{description;constructor(t){super(),this.description=t}}class l extends Error{underlyingError;constructor(t){super(`STT Error: ${t.detail}`),this.underlyingError=t}}class c extends Error{underlyingError;constructor(t){super(t.message),this.underlyingError=t}}class h extends Error{description;constructor(t){super(`TTS decode error: ${t}`),this.description=t}}class d extends n{constructor(t){super(t.errorCode,t.code,t.detail,t.attr),this.name="SessionCreationError"}}class u extends d{constructor(t){super(t),this.name="DoesNotExistError"}}class p extends d{constructor(t){super(t),this.name="NotInOrganizationError"}}!function(t){t.LLM="LLM",t.TTS="TTS",t.STT="STT",t.STF_ONPREMISE="STF_ONPREMISE",t.STF_WEBRTC="STF_WEBRTC"}(e||(e={})),function(t){t.SESSION_START="SESSION_START",t.SESSION_DURING="SESSION_DURING",t.SESSION_LOG="SESSION_LOG",t.SESSION_END="SESSION_END",t.SESSION_ERROR="SESSION_ERROR",t.SESSION_TTS="SESSION_TTS",t.SESSION_STT="SESSION_STT",t.SESSION_LLM="SESSION_LLM"}(s||(s={}));class g{static async getLLMs(t,e){const s=fetch(`${t}/api/v1/settings/llm_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getModelStyles(t,e){const s=fetch(`${t}/api/v1/settings/modelstyle/?platform_type=webrtc`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getBackgroundImages(t,e){const s=fetch(`${t}/api/v1/background_image/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getTTSs(t,e){const s=fetch(`${t}/api/v1/settings/tts_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getSTTs(t,e){const s=fetch(`${t}/api/v1/settings/stt_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async makeTTS(t,{sessionId:e,text:s,locale:a,output_format:r}){const n={text:s};a&&(n.locale=a),r&&(n.output_format=r);const o=await fetch(`${t}/api/v1/session/${e}/tts/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});return await this.parseJson(o)}static async getPrompts(t,e){const s=fetch(`${t}/api/v1/prompt/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getDocuments(t,e){const s=fetch(`${t}/api/v1/document/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getTextNormalizations(t,e){const s=fetch(`${t}/api/v1/settings/text_normalization_config/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async downloadTextNormalization(t,e,s){const a=await fetch(`${t}/api/v1/settings/text_normalization_config/${s}/download/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(a)}static async getSessionTemplates(t,e){const s=await fetch(`${t}/api/v1/session_template/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(s)}static async getSessionTemplate(t,e,s){const a=await fetch(`${t}/api/v1/session_template/${s}/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(a)}static async getMcpServers(t,e){const s=fetch(`${t}/api/v1/settings/mcp_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getSessionInfo(t,e){const s=fetch(`${t}/api/v1/session/${e}/`,{method:"GET"}),a=await s;return await this.parseJson(a)}static async sessionEvent(t,e,s,a=""){const r=await fetch(`${t}/api/v1/session/${e}/event/create/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detail:a,event:s})});await this.parseJson(r)}static async makeSTT(t,e,s,a){const r=new FormData;r.append("audio",s),a&&r.append("language",a);const n=await fetch(`${t}/api/v1/session/${e}/stt/`,{method:"POST",body:r});return await this.parseJson(n)}static async makeLLM(t,e,s,a){const r=await fetch(`${t}/api/v1/session/${e}/llm/v2/`,{body:JSON.stringify(s),headers:{"Content-Type":"application/json"},method:"POST",signal:a});if(!r.ok){const t=await r.json(),e=t.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${r.status} with no error details`,attr:null};throw new n(r.status,e.code,e.detail,e.attr)}return r.body.getReader()}static async getIceServers(t,e){const s=await fetch(`${t}/api/v1/session/${e}/ice-servers/`,{method:"GET"});return(await this.parseJson(s)).ice_servers}static async exchangeSDP(t,e,s){const a=await fetch(`${t}/api/v1/session/${e}/exchange/`,{body:JSON.stringify({client_sdp:s}),headers:{"Content-Type":"application/json"},method:"POST"});return(await this.parseJson(a)).server_sdp}static async parseJson(t){const e=await t.json();if(t.ok)return e;{const s=e.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${t.status} with no error details`,attr:null};throw new n(t.status,s.code,s.detail,s.attr)}}}class m extends Error{constructor(t){super(`WAV parse error: ${t}`),this.name="WavParseError"}}function S(t,e,s=1){const a=2*t.length,r=new ArrayBuffer(44+a),n=new DataView(r);w(n,0,"RIFF"),n.setUint32(4,36+a,!0),w(n,8,"WAVE"),w(n,12,"fmt "),n.setUint32(16,16,!0),n.setUint16(20,1,!0),n.setUint16(22,s,!0),n.setUint32(24,e,!0),n.setUint32(28,e*s*2,!0),n.setUint16(32,2*s,!0),n.setUint16(34,16,!0),w(n,36,"data"),n.setUint32(40,a,!0);let o=44;for(let e=0;e<t.length;e++){const s=Math.max(-1,Math.min(1,t[e]));n.setInt16(o,s<0?32768*s:32767*s,!0),o+=2}return r}function f(t,e,s){let a="";for(let r=0;r<s;r++)a+=String.fromCharCode(t.getUint8(e+r));return a}function w(t,e,s){for(let a=0;a<s.length;a++)t.setUint8(e+a,s.charCodeAt(a))}function y(t,e,s){switch(s){case 8:return(t.getUint8(e)-128)/128;case 16:return t.getInt16(e,!0)/32768;case 24:{const s=t.getUint8(e),a=t.getUint8(e+1),r=t.getUint8(e+2)<<16|a<<8|s;return(r>8388607?r-16777216:r)/8388608}case 32:return t.getInt32(e,!0)/2147483648;default:return 0}}class T extends Error{constructor(t){super(`Audio resample error: ${t}`),this.name="AudioResampleError"}}async function C(t,e,s,a=1){if(0===t.length)throw new T("Cannot resample empty audio data");if(e<=0||s<=0)throw new T(`Invalid sample rate: original=${e}, target=${s}`);if(e===s)return t;try{const r=t.length/e,n=Math.ceil(r*s),o=new OfflineAudioContext(a,t.length,e).createBuffer(a,t.length,e);o.getChannelData(0).set(t);const i=new OfflineAudioContext(a,n,s),l=i.createBufferSource();l.buffer=o,l.connect(i.destination),l.start(0);return(await i.startRendering()).getChannelData(0)}catch(t){const a=t instanceof Error?t.message:String(t);throw new T(`Failed to resample audio from ${e}Hz to ${s}Hz: ${a}`)}}const E=16e3;async function v(t,e=!0){let s;try{const e=atob(t),a=new Array(e.length);for(let t=0;t<e.length;t++)a[t]=e.charCodeAt(t);s=new Uint8Array(a).buffer}catch{throw new h("Invalid Base64 audio data")}const a=function(t){const e=new Uint8Array(t);if(e.length>=4){const t=String.fromCharCode(e[0],e[1],e[2],e[3]);if("RIFF"===t)return"audio/wav";if(t.startsWith("ID3"))return"audio/mpeg";if(255===e[0]&&!(224&~e[1]))return"audio/mpeg"}return"audio/wav"}(s);if(!e)return new Blob([s],{type:a});try{const t=await async function(t,e){if("audio/wav"===e){const e=function(t){const e=new DataView(t);if(t.byteLength<44)throw new m("File too small to be a valid WAV");if("RIFF"!==f(e,0,4))throw new m("Missing RIFF header");if("WAVE"!==f(e,8,4))throw new m("Missing WAVE format identifier");let s=12,a=!1,r=0,n=0,o=0,i=0;for(;s<t.byteLength-8;){const l=f(e,s,4),c=e.getUint32(s+4,!0);if("fmt "===l){if(s+24>t.byteLength)throw new m("fmt chunk extends beyond file");r=e.getUint16(s+8,!0),n=e.getUint16(s+10,!0),o=e.getUint32(s+12,!0),i=e.getUint16(s+22,!0),a=!0,s+=8+c;break}const h=s+8+c;if(h<=s)break;s=h}if(!a)throw new m("Missing fmt chunk");if(1!==r)throw new m(`Unsupported audio format: ${r} (only PCM format 1 is supported)`);if(0===n||n>8)throw new m(`Invalid channel count: ${n}`);if(0===o||o>192e3)throw new m(`Invalid sample rate: ${o}`);if(![8,16,24,32].includes(i))throw new m(`Unsupported bits per sample: ${i}`);let l=-1,c=0;for(;s<t.byteLength-8;){const t=f(e,s,4),a=e.getUint32(s+4,!0);if("data"===t){l=s+8,c=a;break}const r=s+8+a;if(r<=s)break;s=r}if(-1===l)throw new m("Missing data chunk");const h=t.byteLength-l,d=Math.min(c,h),u=i/8,p=Math.floor(d/(u*n)),g=new Float32Array(p*n);let S=0;for(let s=0;s<p*n;s++){const a=l+s*u;if(a+u>t.byteLength)break;g[S++]=Math.max(-1,Math.min(1,y(e,a,i)))}if(2===n){const t=new Float32Array(p);for(let e=0;e<p;e++)t[e]=(g[2*e]+g[2*e+1])/2;return{sampleRate:o,channels:1,bitsPerSample:i,samples:t}}return{sampleRate:o,channels:n,bitsPerSample:i,samples:g.slice(0,S)}}(t);if(e.sampleRate===E)return{samples:e.samples,sampleRate:e.sampleRate};return{samples:await C(e.samples,e.sampleRate,E,e.channels),sampleRate:E}}const s=new AudioContext({sampleRate:E});try{const e=await s.decodeAudioData(t.slice(0));if(e.sampleRate===E)return{samples:e.getChannelData(0),sampleRate:E};return{samples:await C(e.getChannelData(0),e.sampleRate,E,1),sampleRate:E}}finally{await s.close()}}(s,a),e=S(t.samples,E,1);return new Blob([e],{type:"audio/wav"})}catch{return new Blob([s],{type:a})}}const _=a();function x(t){return t.replace(_,"")}class I{config;messageHistory=[];constructor(t){this.config=t}async*processLLM(t){if(0===t.message.length)throw new Error("Message cannot be empty");const e=t.tools??this.config.clientTools,s=e.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),a={newMessageHistory:[{role:"user",content:t.message}],allChunks:[],message:"",lastYieldedChunkCount:0,pendingToolCallsMessage:null,aborted:!1,streamingError:null};let r=0,l=[...this.messageHistory,...a.newMessageHistory];this.config.callbacks.onChatStateChange(exports.ChatState.LLM,null);try{for(;;){if(t.signal?.aborted)return void(a.allChunks.length>0&&(yield{type:"message",chunks:[...a.allChunks],message:a.message,finish:!0}));let c;try{c=await g.makeLLM(this.config.apiServer,this.config.sessionId,{messages:l,tools:s},t.signal)}catch(t){if(t instanceof n)return void(yield{type:"error",error:new o(t)});throw t}if(a.streamingError=null,yield*this.parseSSEStream(c,a,t),a.streamingError)return;if(a.aborted)return void(a.allChunks.length>0&&(yield{type:"message",chunks:[...a.allChunks],message:a.message,finish:!0}));if(null!=a.pendingToolCallsMessage){yield*this.executeToolCalls(a,e);const t=a.lastToolCallResults,s=t.length>0&&a.pendingToolCallsMessage.tool_calls.length!==t.length,n=t.some(t=>!t.chatTool.executeOnly);if(s||n){if(r++,r>=10)return void(yield{type:"error",error:new o(new i("Tool follow-up loop exceeded maximum rounds (10)"))});l=[...this.messageHistory,...a.newMessageHistory],a.pendingToolCallsMessage=null;continue}}return this.messageHistory.push(...a.newMessageHistory),void(yield{type:"message",chunks:[...a.allChunks],message:a.message,finish:!0})}}finally{this.config.callbacks.onChatStateChange(null,exports.ChatState.LLM)}}async*parseSSEStream(t,e,s){const a=new TextDecoder("utf-8");let r="",n="";e.pendingToolCallsMessage=null;const l=()=>e.allChunks.length>e.lastYieldedChunkCount?(e.lastYieldedChunkCount=e.allChunks.length,{type:"message",chunks:[...e.allChunks],message:e.message,finish:!1}):null;for(;;){const{done:c,value:h}=await t.read();if(c)break;let d;for(r+=a.decode(h,{stream:!0});-1!==(d=r.indexOf("\n"));){if(s.signal?.aborted)return void(e.aborted=!0);const t=r.slice(0,d).trim();if(r=r.slice(d+1),!t.startsWith("data: {"))return e.streamingError=new o(new i("Failed to parse SSE response")),void(yield{type:"error",error:e.streamingError});let a;try{a=JSON.parse(t.slice(6).trim())}catch{return e.streamingError=new o(new i("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==a.status)return e.streamingError=new o(new i(a.reason)),void(yield{type:"error",error:e.streamingError});if(n.length>0&&"message"!=a.type){e.newMessageHistory.push({role:"assistant",type:"message",content:n}),n="";const t=l();t&&(yield t)}if("message"===a.type){const t=x(a.content);n+=t,e.message+=t,e.allChunks.push(t);continue}"tool_call"!==a.type||null==a.tool_calls?"tool"!==a.role||"tool_call"===a.type&&e.newMessageHistory.push({role:a.role,type:a.type,content:a.content,tool_call_id:a.tool_call_id}):(e.newMessageHistory.push({role:"assistant",type:a.type,content:a.content,tool_calls:a.tool_calls}),e.pendingToolCallsMessage=a,yield{type:"tool_call",tool_calls:a.tool_calls})}const u=l();u&&(yield u)}const c=r.trim();if(c.length>0){if(!c.startsWith("data: {"))return e.streamingError=new o(new i("Failed to parse SSE response")),void(yield{type:"error",error:e.streamingError});let t;try{t=JSON.parse(c.slice(6).trim())}catch{return e.streamingError=new o(new i("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==t.status)return e.streamingError=new o(new i(t.reason)),void(yield{type:"error",error:e.streamingError});if("message"===t.type){const s=x(t.content);n+=s,e.message+=s,e.allChunks.push(s)}else if("tool_call"===t.type&&null!=t.tool_calls){if(n.length>0){e.newMessageHistory.push({role:"assistant",type:"message",content:n}),n="";const t=l();t&&(yield t)}e.newMessageHistory.push({role:"assistant",type:t.type,content:t.content,tool_calls:t.tool_calls}),e.pendingToolCallsMessage=t,yield{type:"tool_call",tool_calls:t.tool_calls}}}n.length>0&&e.newMessageHistory.push({role:"assistant",type:"message",content:n})}async*executeToolCalls(t,e){const s=t=>{for(const s of e)if(s.name===t)return s;return null},a=[];for(const e of t.pendingToolCallsMessage.tool_calls){const t=s(e.function.name);null!=t&&a.push((async()=>{try{const s=await t.call(JSON.parse(e.function.arguments));return{toolCallId:e.id,chatTool:t,chatToolResult:s}}catch(s){return{toolCallId:e.id,chatTool:t,chatToolResult:{error:s.message}}}})())}const r=await Promise.all(a);t.lastToolCallResults=r;for(const e of r)t.newMessageHistory.push({role:"tool",content:JSON.stringify(e.chatToolResult),tool_call_id:e.toolCallId}),yield{type:"tool_result",tool_call_id:e.toolCallId,result:e.chatToolResult}}addToHistory(t){this.messageHistory.push(t)}getHistory(){return this.messageHistory}}const R=`data:application/javascript,${encodeURIComponent("\nclass RecorderProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.isRecording = true;\n \n // Listen for stop message from main thread\n this.port.onmessage = (event) => {\n if (event.data.type === 'stop') {\n this.isRecording = false;\n // Send confirmation back to main thread\n this.port.postMessage({ type: 'stopped' });\n }\n };\n }\n\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n if (input && input.length > 0 && this.isRecording) {\n // Clone the audio data and send to main thread\n const channelData = new Float32Array(input[0]);\n this.port.postMessage({ type: 'audio', data: channelData });\n }\n // Return true to keep the processor alive until stopped\n return this.isRecording;\n }\n}\n\nregisterProcessor('recorder-processor', RecorderProcessor);\n")}`;class L{audioContext=null;mediaStream=null;workletNode=null;sourceNode=null;audioChunks=[];isRecordingState=!1;channels;targetSampleRate;constructor(t={}){this.channels=t.channels||1,this.targetSampleRate=t.targetSampleRate}async start(){if(this.isRecordingState)throw new Error("WavRecorder is already recording");if(this.mediaStream=await navigator.mediaDevices.getUserMedia({audio:!0}),this.audioContext=new AudioContext,"running"!==this.audioContext.state)try{await this.audioContext.resume()}catch(t){console.warn("WavRecorder: Failed to resume AudioContext:",t)}await this.audioContext.audioWorklet.addModule(R),this.sourceNode=this.audioContext.createMediaStreamSource(this.mediaStream),this.workletNode=new AudioWorkletNode(this.audioContext,"recorder-processor"),this.audioChunks=[],this.workletNode.port.onmessage=t=>{"audio"===t.data.type&&this.audioChunks.push(t.data.data)},this.sourceNode.connect(this.workletNode),this.workletNode.connect(this.audioContext.destination),this.isRecordingState=!0}async stop(){if(!this.isRecordingState)throw new Error("WavRecorder is not recording");this.isRecordingState=!1,await new Promise(t=>{this.workletNode.port.onmessage=e=>{"stopped"===e.data.type?t():"audio"===e.data.type&&this.audioChunks.push(e.data.data)},this.workletNode.port.postMessage({type:"stop"})}),this.workletNode?.disconnect(),this.sourceNode?.disconnect(),this.mediaStream?.getTracks().forEach(t=>t.stop());const t=this.audioChunks,e=this.audioContext.sampleRate;try{const s=t.reduce((t,e)=>t+e.length,0),a=new Float32Array(s);let r,n,o=0;for(const e of t)a.set(e,o),o+=e.length;this.targetSampleRate&&this.targetSampleRate!==e?(r=await C(a,e,this.targetSampleRate,this.channels),n=this.targetSampleRate):(r=a,n=e);const i=S(r,n,this.channels),l=new Blob([i],{type:"audio/wav"});return new File([l],"recording.wav",{type:"audio/wav"})}finally{await(this.audioContext?.close()),this.audioContext=null,this.mediaStream=null,this.workletNode=null,this.sourceNode=null,this.audioChunks=[]}}isRecording(){return this.isRecordingState}}class b extends EventTarget{pc;dc;streams=[];pingTime;pingIntervalId=null;constructor(t,e){super(),this.pc=t,this.dc=e,this.pingTime=Date.now()+3e3,this.pc.addEventListener("track",t=>{this.streams=this.streams.concat(t.streams)}),this.pc.addEventListener("connectionstatechange",()=>{"disconnected"!==this.pc.connectionState&&"failed"!==this.pc.connectionState||this.close()}),this.dc.onopen=()=>{this.pingIntervalId=setInterval(()=>{this.ping(),Date.now()-this.pingTime>3e4&&this.close()},1e3)},this.dc.onclose=()=>{null!=this.pingIntervalId&&clearInterval(this.pingIntervalId)},this.#t({live:!0,code:200,reason:"OK"}),this.setMessageCallback("ping",()=>{this.pingTime=Date.now()})}static async create(t,a,r,n,o){const i=await g.getSessionInfo(t,a);if(!(Array.isArray(i.capability)&&i.capability.some(t=>t.name===e.STF_ONPREMISE||t.name===e.STF_WEBRTC)))return await g.sessionEvent(t,a,s.SESSION_START),null;const l=await g.getIceServers(t,a);let c=await b.createPeerConnection(l),h=c.createDataChannel("message",{protocol:"message"}),d=new b(c,h);o?o.getTracks().forEach(function(t){c.addTrack(t,o)}):c.addTransceiver("audio",{direction:"recvonly"});const u=c.addTransceiver("video",{direction:"recvonly"}),p=RTCRtpReceiver.getCapabilities("video");null!=p&&u.setCodecPreferences(p.codecs);const m=await c.createOffer();await c.setLocalDescription(m);const S=await g.exchangeSDP(t,a,m);return await c.setRemoteDescription(S),await b.waitFor(()=>d.isReady(),100,50),d.changeSize(r,n),d}static async createPeerConnection(t){return new RTCPeerConnection({sdpSemantics:"unified-plan",iceServers:t})}static async waitFor(t,e,s){let a=0;if(await new Promise(r=>{const n=setInterval(()=>{a+=1,a>=s&&(clearInterval(n),r("bad")),t()&&(clearInterval(n),r("good"))},e)}),a>=s)throw new r}isReady(){return this.streams.length>0&&"open"===this.dc.readyState}#t(t){this.dispatchEvent(new CustomEvent("status",{detail:t}))}subscribeStatus(t){return this.addEventListener("status",t),()=>{this.removeEventListener("status",t)}}getStream(){return this.streams[0]}sendMessage(t,e){this.dc.send(JSON.stringify({type:t,data:e}))}ttstf(t){this.sendMessage("ttstf",{message:t})}static BACKPRESSURE_THRESHOLD=524288;static FILE_TRANSFER_TIMEOUT=3e4;sendFile(t,e=65536){return new Promise((s,a)=>{let r=!1;const n=t=>{r||(r=!0,clearTimeout(i),t())},o=this.pc.createDataChannel("file",{protocol:"file"}),i=setTimeout(()=>{n(()=>{o.close(),a(new Error("File transfer timed out"))})},b.FILE_TRANSFER_TIMEOUT);o.onclose=()=>{n(()=>{a(new Error("File channel closed before transfer completed"))})},o.onerror=t=>{n(()=>{o.close(),a(new Error(`File channel error: ${t}`))})},o.addEventListener("message",async r=>{try{if(0===r.data.length){const s=new Uint8Array(await t.arrayBuffer());let r=0;const i=()=>{for(;r<s.length;){if(o.bufferedAmount>b.BACKPRESSURE_THRESHOLD)return o.bufferedAmountLowThreshold=b.BACKPRESSURE_THRESHOLD/2,o.onbufferedamountlow=()=>{o.onbufferedamountlow=null,o.onclose=null,i()},void(o.onclose=()=>{o.onbufferedamountlow=null,n(()=>{a(new Error("File channel closed during transfer"))})});o.send(s.slice(r,r+e)),r+=e}o.send(new Uint8Array(0))};i()}else n(()=>{o.close(),s(r.data)})}catch(t){n(()=>{o.close(),a(t instanceof Error?t:new Error(String(t)))})}})})}async stf(t,e,s){const a=await this.sendFile(t);return this.sendMessage("stf",{message:s,file_ref:a,format:e}),a}recordStart(){this.sendMessage("record-start",{})}recordEndStt(t){this.sendMessage("record-end-stt",{language:t})}recordEndTranslate(t,e){this.sendMessage("record-end-translate",{src_lang:t,dst_lang:e})}changeSize(t,e){this.sendMessage("change-size",{width:t,height:e})}setTemplate(t,e){this.sendMessage("set-template",{model:t,dress:e})}clearBuffer(){this.sendMessage("clear-buffer",{})}ping(){this.sendMessage("ping",{})}setMessageCallback(t,e){const s=s=>{const a=JSON.parse(s.data);a.type===t&&e(a.data)};return this.dc.addEventListener("message",s),()=>{this.dc.removeEventListener("message",s)}}async tts(t,e=!0){return v(t,e)}close(){this.dc.close(),this.pc.close(),this.#t({live:!1,code:408,reason:"Request Timeout"})}closeSelf(){this.dc.close(),this.pc.close(),this.#t({live:!1,code:200,reason:"OK"})}}class M{apiServer;sessionId;perso;clientTools;chatStatesHandler=new EventTarget;chatLogHandler=new EventTarget;sttEventHandler=null;errorHandler=new EventTarget;lastStfTimeoutHandle=null;stfTotalDuration=0;stfTimeoutStartTime=0;messageHistory=[];chatLog=[];llmProcessor;chatStateMap=new Map([[exports.ChatState.RECORDING,0],[exports.ChatState.LLM,0],[exports.ChatState.ANALYZING,0],[exports.ChatState.SPEAKING,0],[exports.ChatState.TTS,0]]);sttRecorder=null;sttTimeoutHandle=null;sttTimeoutAudioFile=null;heartbeatIntervalId=null;legacyVoiceChatMode;stream;constructor(t,e,s,a,r){this.apiServer=t,this.sessionId=e,this.perso=s,this.clientTools=a,this.legacyVoiceChatMode=r?.legacyVoiceChatMode??!1,this.stream=r?.stream??null,this.resetChatState(),this.llmProcessor=new I({apiServer:t,sessionId:e,clientTools:a,callbacks:{onChatStateChange:(t,e)=>this.setChatState(t,e),onError:t=>this.setError(t),onChatLog:(t,e)=>this.addMessageToChatLog(t,e),onTTSTF:t=>this.processTTSTFInternal(t)}}),s?(s.subscribeStatus(t=>{!1===t.detail?.live&&this.stopHeartbeat()}),s.setMessageCallback("stf",t=>{if(this.chatStateMap.get(exports.ChatState.ANALYZING)||this.chatStateMap.get(exports.ChatState.SPEAKING))if(this.setChatState(exports.ChatState.SPEAKING,exports.ChatState.ANALYZING),null!==this.lastStfTimeoutHandle){clearTimeout(this.lastStfTimeoutHandle);let e=Date.now();this.stfTotalDuration+=t.duration+1e3-(e-this.stfTimeoutStartTime),this.stfTimeoutStartTime=e,this.lastStfTimeoutHandle=setTimeout(()=>{this.lastStfTimeoutHandle=null,this.stfTimeoutStartTime=0,this.stfTotalDuration=0,this.setChatState(null,exports.ChatState.SPEAKING)},this.stfTotalDuration)}else this.stfTimeoutStartTime=Date.now(),this.stfTotalDuration=t.duration+2e3,this.lastStfTimeoutHandle=setTimeout(()=>{this.lastStfTimeoutHandle=null,this.stfTimeoutStartTime=0,this.stfTotalDuration=0,this.setChatState(null,exports.ChatState.SPEAKING)},this.stfTotalDuration)}),s.setMessageCallback("stt",t=>{if(this.setChatState(null,exports.ChatState.ANALYZING),null!=this.sttEventHandler)this.sttEventHandler.dispatchEvent(new CustomEvent("stt",{detail:t.text}));else{if(""===t.text)return;this.processChat(t.text)}}),s.setMessageCallback("stt-error",t=>{this.setChatState(null,exports.ChatState.ANALYZING)})):this.startHeartbeat()}llmJob=null;async processChat(t){0!==t.trim().length&&(this.pipelineSuppressed=!1,this.addMessageToChatLog(t,!0),this.llmJob=this.processChatInternal(t))}processLLM(t){return this.pipelineSuppressed=!1,this.llmProcessor.processLLM(t)}getMessageHistory(){return this.llmProcessor.getHistory()}processCustomChat(t){0!==t.trim().length&&this.processTTSTFInternal(t)}processTTSTF(t){0!==t.trim().length&&(this.pipelineSuppressed=!1,this.messageHistory.push({role:"assistant",type:"message",content:t}),this.addMessageToChatLog(t,!1),this.processTTSTFInternal(t))}async transcribeAudio(t,e){return(await this.transcribeAudioDetailed(t,e)).text}async transcribeAudioDetailed(t,e){const s=t instanceof File?t:new File([t],"audio.wav",{type:t.type});try{return await g.makeSTT(this.apiServer,this.sessionId,s,e)}catch(t){if(t instanceof n)throw new l(t);throw t}}async processSTF(t,e,s=""){if(!this.perso)throw new Error("processSTF requires WebRTC (STF mode)");this.pipelineSuppressed=!1,this.setChatState(exports.ChatState.ANALYZING);try{const a=function(t,e){const s=t=>{if(!t)return null;const e=t.toLowerCase().split(";")[0].trim();return"mp3"===e||"audio/mpeg"===e||"audio/mp3"===e?"mp3":"wav"===e||"audio/wav"===e||"audio/x-wav"===e||"audio/wave"===e?"wav":null};return s(t)??s(e.type)??"wav"}(e,t),r=await this.perso.stf(t,a,s);return this.pipelineSuppressed?(this.setChatState(null,exports.ChatState.ANALYZING),r):r}catch(t){throw this.setChatState(null,exports.ChatState.ANALYZING),t}}async processTTS(t,e={}){const{resample:s=!1,locale:a,output_format:r}=e,o=x(t).trim();if(0===o.length)return;this.pipelineSuppressed=!1;const i=/[.?!]$/.test(o)?o:o+".";this.setChatState(exports.ChatState.TTS,null);try{const t={sessionId:this.sessionId,text:i,...a&&{locale:a},...r&&{output_format:r}},{audio:e}=await g.makeTTS(this.apiServer,t);if(this.pipelineSuppressed)return;return await v(e,s)}catch(t){t instanceof n||t instanceof h?this.setError(new c(t)):this.setError(t instanceof Error?t:new Error(String(t)))}finally{this.setChatState(null,exports.ChatState.TTS)}}startVoiceChat(){if(!this.perso)throw new Error("startVoiceChat requires WebRTC (STF mode)");return this.pipelineSuppressed=!1,this.setChatState(exports.ChatState.RECORDING),this.perso.recordStart()}stopVoiceChat(){if(!this.perso)throw new Error("stopVoiceChat requires WebRTC (STF mode)");this.setChatState(exports.ChatState.ANALYZING,exports.ChatState.RECORDING),this.perso.recordEndStt()}async startProcessSTT(t){if(this.sttRecorder?.isRecording())throw new Error("STT recording is already in progress");this.pipelineSuppressed=!1,this.setChatState(exports.ChatState.RECORDING);try{this.sttRecorder=new L({targetSampleRate:16e3}),await this.sttRecorder.start(),t&&t>0&&(this.sttTimeoutHandle=setTimeout(async()=>{if(this.sttTimeoutHandle=null,this.sttRecorder?.isRecording()){try{this.sttTimeoutAudioFile=await this.sttRecorder.stop()}catch{this.sttTimeoutAudioFile=null,this.setChatState(null,exports.ChatState.RECORDING)}this.sttRecorder=null}},t))}catch(t){throw this.setChatState(null,exports.ChatState.RECORDING),this.sttRecorder=null,t}}lastRecordedAudioFile=null;async stopProcessSTT(t){let e;if(this.sttTimeoutHandle&&(clearTimeout(this.sttTimeoutHandle),this.sttTimeoutHandle=null),this.setChatState(null,exports.ChatState.RECORDING),this.sttTimeoutAudioFile)e=this.sttTimeoutAudioFile,this.sttTimeoutAudioFile=null;else{if(!this.sttRecorder?.isRecording())throw this.sttRecorder?(this.sttRecorder=null,new Error("STT recording is not in progress")):new Error("STT recording has not been started");e=await this.sttRecorder.stop(),this.sttRecorder=null}this.lastRecordedAudioFile=e;try{return(await g.makeSTT(this.apiServer,this.sessionId,e,t)).text}catch(t){if(t instanceof n)throw new l(t);throw t}}isSTTRecording(){return(this.sttRecorder?.isRecording()??!1)||null!==this.sttTimeoutAudioFile}changeSize(t,e){this.perso?.changeSize(t,e)}async clearBuffer(){this.perso?.clearBuffer(),await this.clearLLMJob(),null!==this.lastStfTimeoutHandle&&(clearTimeout(this.lastStfTimeoutHandle),this.lastStfTimeoutHandle=null),this.pipelineSuppressed=!0,this.resetChatState()}setSrc(t){t.srcObject=this.getRemoteStream()??null}getRemoteStream(){return this.perso?.getStream()}getLocalStream(){return this.stream}stopSession(){this.close()}onClose(t){return this.perso?this.perso.subscribeStatus(e=>{null!=e.detail&&!1===e.detail.live&&t(200===e.detail.code)}):()=>{}}subscribeChatStates(t){const e=e=>{t(e.detail.status)};return this.chatStatesHandler.addEventListener("status",e),()=>{this.chatStatesHandler.removeEventListener("status",e)}}subscribeChatLog(t){const e=e=>{t(e.detail.chatLog)};return this.chatLogHandler.addEventListener("chatLog",e),()=>{this.chatLogHandler.removeEventListener("chatLog",e)}}setSttResultCallback(t){const e=e=>{t(e.detail)};return this.sttEventHandler=new EventTarget,this.sttEventHandler.addEventListener("stt",e),()=>{this.sttEventHandler?.removeEventListener("stt",e),this.sttEventHandler=null}}setErrorHandler(t){const e=e=>{t(e.detail.error)};return this.errorHandler.addEventListener("error",e),()=>{this.errorHandler.removeEventListener("error",e)}}getSessionId(){return this.sessionId}async processChatInternal(t){this.setChatState(exports.ChatState.LLM);const e=this.clientTools.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),s=new Array;null===t||(t instanceof Array?s.push(...t):"string"==typeof t&&s.push({role:"user",content:t}));const a=await fetch(`${this.apiServer}/api/v1/session/${this.sessionId}/llm/v2/`,{body:JSON.stringify({messages:[...this.messageHistory,...s],tools:e}),headers:{"Content-Type":"application/json"},method:"POST"});if(!a.ok){const t=await a.json(),e=new o(new n(a.status,t.errors[0].code,t.errors[0].detail,t.errors[0].attr));return this.setError(e),void this.setChatState(null,exports.ChatState.LLM)}const r=a.body?.getReader(),l=new TextDecoder("utf-8");let c="",h=null,d="";for(;;){const{done:t,value:e}=await r.read();if(t)break;let a;for(d+=l.decode(e,{stream:!0});-1!==(a=d.indexOf("\n"));){if(this.llmCancel)return c.length>0&&this.addMessageToChatLog(c,!1),void this.setChatState(null,exports.ChatState.LLM);const t=d.slice(0,a).trim();if(d=d.slice(a+1),!t.startsWith("data: {")){const t=new o(new i("Failed to parse SSE response"));return this.setError(t),void this.setChatState(null,exports.ChatState.LLM)}const e=JSON.parse(t.slice(6).trim());if("success"!==e.status){const t=new o(new i(e.reason));return this.setError(t),void this.setChatState(null,exports.ChatState.LLM)}c.length>0&&"message"!=e.type&&(s.push({role:"assistant",type:"message",content:c}),this.addMessageToChatLog(c,!1),c=""),"message"!==e.type?"tool_call"!==e.type||null==e.tool_calls?"tool"!==e.role||"tool_call"===e.type&&s.push({role:e.role,type:e.type,content:e.content,tool_call_id:e.tool_call_id}):(s.push({role:"assistant",type:e.type,content:e.content,tool_calls:e.tool_calls}),h=e):(c+=x(e.content),this.processTTSTFInternal(e.content))}}if(this.llmCancel)this.setChatState(null,exports.ChatState.LLM);else{if(null!=h){const t=[];for(const e of h.tool_calls){const s=this.getChatTool(this.clientTools,e.function.name);null!=s&&t.push(new Promise(async t=>{try{const a=await s.call(JSON.parse(e.function.arguments));t({toolCallId:e.id,chatTool:s,chatToolResult:a})}catch(a){t({toolCallId:e.id,chatTool:s,chatToolResult:{result:"error!"}})}}))}const e=await Promise.all(t);for(const t of e)s.push({role:"tool",content:JSON.stringify(t.chatToolResult),tool_call_id:t.toolCallId});const a=e.length>0&&h.tool_calls.length!==e.length,r=e.some(t=>!t.chatTool.executeOnly);a||r?await this.processChatInternal(s):this.messageHistory.push(...s)}else this.messageHistory.push(...s);this.setChatState(null,exports.ChatState.LLM)}}getChatTool(t,e){for(const s of t)if(s.name===e)return s;return null}llmCancel=!1;pipelineSuppressed=!1;async clearLLMJob(){null!=this.llmJob&&(this.llmCancel=!0,await this.llmJob,this.llmCancel=!1)}processTTSTFInternal(t){const e=x(t).trim();0!==e.length&&this.perso&&(this.setChatState(exports.ChatState.ANALYZING),this.perso.ttstf(e))}addMessageToChatLog(t,e){this.chatLog=[{text:t,isUser:e,timestamp:new Date},...this.chatLog],this.chatLogHandler.dispatchEvent(new CustomEvent("chatLog",{detail:{chatLog:this.chatLog}}))}setChatState(t=null,e=null){const s=new Map(this.chatStateMap);function a(t){t===exports.ChatState.ANALYZING?s.set(t,(s.get(t)||0)+1):s.set(t,1)}function r(t){t===exports.ChatState.ANALYZING?s.set(t,Math.max((s.get(t)||0)-1,0)):s.set(t,0)}if(null!=t)if(t instanceof Array)for(let e of t)a(e);else a(t);if(null!=e)if(e instanceof Array)for(let t of e)r(t);else r(e);const n=this.exchangeChatStateMapToSet(this.chatStateMap),o=this.exchangeChatStateMapToSet(s);this.chatStateMap=s,this.isEqualChatStateMap(n,o)||this.dispatchChatState(o)}resetChatState(){this.chatStateMap=new Map([[exports.ChatState.RECORDING,0],[exports.ChatState.LLM,0],[exports.ChatState.ANALYZING,0],[exports.ChatState.SPEAKING,0],[exports.ChatState.TTS,0]]),this.dispatchChatState(this.exchangeChatStateMapToSet(this.chatStateMap))}exchangeChatStateMapToSet(t){const e=new Set;for(const s of t)s[1]>0&&e.add(s[0]);return e}dispatchChatState(t){this.chatStatesHandler.dispatchEvent(new CustomEvent("status",{detail:{status:t}}))}isEqualChatStateMap(t,e){if(t.size!==e.size)return!1;for(const s of t)if(t.has(s)!==e.has(s))return!1;return!0}async logSessionEvent(t){const e="object"==typeof t?JSON.stringify(t):t;await g.sessionEvent(this.apiServer,this.sessionId,s.SESSION_LOG,e)}setError(t){this.errorHandler.dispatchEvent(new CustomEvent("error",{detail:{error:t}}))}close(){this.stopHeartbeat(),this.perso?.closeSelf()}startHeartbeat(){const t=async()=>{try{await g.sessionEvent(this.apiServer,this.sessionId,s.SESSION_DURING),null!==this.heartbeatIntervalId&&(this.heartbeatIntervalId=setTimeout(t,1e4))}catch(t){t instanceof n?this.setError(t):this.setError(t instanceof Error?t:new Error(String(t))),this.close()}};this.heartbeatIntervalId=setTimeout(t,1e4)}stopHeartbeat(){null!==this.heartbeatIntervalId&&(clearTimeout(this.heartbeatIntervalId),this.heartbeatIntervalId=null)}}async function N(t,e,s,a,r,n){if("boolean"!=typeof r){const n=await b.create(t,e,s,a);return new M(t,e,n,r)}const o=n??[];let i,l;if(r)i=await navigator.mediaDevices.getUserMedia({audio:!0,video:!1}),l=()=>{};else{const t=new AudioContext,e=t.createOscillator();e.frequency.value=0;const s=t.createMediaStreamDestination();e.connect(s),e.start(),i=s.stream,l=()=>{e.stop(),e.disconnect(s),t.close()}}const c=await b.create(t,e,s,a,i);if(!c)return l(),new M(t,e,null,o);const h=new M(t,e,c,o,{stream:i,legacyVoiceChatMode:!0});return h.onClose(()=>{l()}),h}async function A(t,e){return await g.getLLMs(t,e)}async function k(t,e){return await g.getTTSs(t,e)}async function O(t,e){return await g.getSTTs(t,e)}async function P(t,e){return await g.getModelStyles(t,e)}async function H(t,e){return await g.getBackgroundImages(t,e)}async function F(t,e){return await g.getPrompts(t,e)}async function D(t,e){return await g.getDocuments(t,e)}async function $(t,e){return await g.getMcpServers(t,e)}async function G(t,e){return await g.getTextNormalizations(t,e)}exports.ApiError=n,exports.ChatTool=class{name;description;parameters;call;executeOnly;constructor(t,e,s,a,r=!1){this.name=t,this.description=e,this.parameters=s,this.call=a,this.executeOnly=r}},exports.DoesNotExistError=u,exports.LLMError=o,exports.LLMStreamingResponseError=i,exports.LlmProcessor=I,exports.NotInOrganizationError=p,exports.STTError=l,exports.Session=M,exports.SessionCreationError=d,exports.TTSDecodeError=h,exports.TTSError=c,exports.TTS_TARGET_SAMPLE_RATE=E,exports.WavRecorder=L,exports.createSession=async function(t,e,s,a,r,n){return"boolean"==typeof r?await N(t,e,s,a,r,n??[]):await N(t,e,s,a,r)},exports.createSessionId=async function(t,s,a){"undefined"!=typeof window&&console.warn("[perso-interactive-sdk-web] WARNING: createSessionId is being called from the browser. This exposes your API key and is not recommended for production. Use server-side session creation with 'perso-interactive-sdk-web/server' instead. See: https://github.com/perso-ai/perso-interactive-sdk-web#server-side");try{let r;if("string"==typeof a){const n=await g.getSessionTemplate(t,s,a);if("webrtc"!==n.model_style.platform_type)throw new Error(`SessionTemplate "${a}" uses platform_type "${n.model_style.platform_type}", but only "webrtc" is supported`);r=function(t){const s=e=>t.capability.some(t=>t.name===e);return{using_stf_webrtc:s(e.STF_WEBRTC),model_style:t.model_style.name,prompt:t.prompt.prompt_id,document:t.document?.document_id,background_image:t.background_image?.backgroundimage_id,mcp_servers:t.mcp_servers?.length?t.mcp_servers.map(t=>t.mcpserver_id):[],llm_type:s(e.LLM)?t.llm_type.name:void 0,tts_type:s(e.TTS)?t.tts_type.name:void 0,stt_type:s(e.STT)?t.stt_type.name:void 0,text_normalization_config:t.text_normalization_config?.textnormalizationconfig_id,text_normalization_locale:t.text_normalization_locale,stt_text_normalization_config:t.stt_text_normalization_config?.textnormalizationconfig_id,stt_text_normalization_locale:t.stt_text_normalization_locale,padding_left:t.padding_left??void 0,padding_top:t.padding_top??void 0,padding_height:t.padding_height??void 0}}(n)}else r=a;const n={capability:[],...r};r.using_stf_webrtc&&n.capability.push(e.STF_WEBRTC),r?.llm_type&&(n.capability.push(e.LLM),n.llm_type=r.llm_type),r?.tts_type&&(n.capability.push(e.TTS),n.tts_type=r.tts_type),r?.stt_type&&(n.capability.push(e.STT),n.stt_type=r.stt_type);const o=await fetch(`${t}/api/v1/session/`,{body:JSON.stringify(n),headers:{"PersoLive-APIKey":s,"Content-Type":"application/json"},method:"POST"});return(await g.parseJson(o)).session_id}catch(t){throw function(t){if(t instanceof d)return t;if(t instanceof n)switch(t.code){case"does_not_exist":return new u(t);case"not_in_organization":return new p(t);default:return new d(t)}return t}(t)}},exports.createWavRecorder=function(t){return new L(t)},exports.getAllSettings=async function(t,e){const[s,a,r,n,o,i,l,c,h]=await Promise.all([A(t,e),k(t,e),O(t,e),P(t,e),H(t,e),F(t,e),D(t,e),$(t,e),G(t,e).catch(()=>[])]);return{llms:s,ttsTypes:a,sttTypes:r,modelStyles:n,backgroundImages:o,prompts:i,documents:l,mcpServers:c,textNormalizations:h}},exports.getBackgroundImages=H,exports.getDocuments=D,exports.getLLMs=A,exports.getMcpServers=$,exports.getModelStyles=P,exports.getPrompts=F,exports.getSTTs=O,exports.getSessionInfo=async function(t,e){return await g.getSessionInfo(t,e)},exports.getSessionTemplates=async function(t,e){return await g.getSessionTemplates(t,e)},exports.getTTSs=k,exports.getTextNormalization=async function(t,e,s){return await g.downloadTextNormalization(t,e,s)},exports.getTextNormalizations=G,exports.getWavSampleRate=function(t){const e=new DataView(t);if(t.byteLength<28)throw new m("File too small to be a valid WAV");if("RIFF"!==f(e,0,4))throw new m("Missing RIFF header");let s=12;for(;s<t.byteLength-8;){const a=f(e,s,4),r=e.getUint32(s+4,!0);if("fmt "===a){if(s+16>t.byteLength)throw new m("fmt chunk extends beyond file");return e.getUint32(s+12,!0)}const n=s+8+r;if(n<=s)break;s=n}throw new m("Missing fmt chunk")},exports.makeTTS=async function(t,e){return await g.makeTTS(t,e)};
|
|
1
|
+
"use strict";var t,e,s,a=require("emoji-regex");exports.ChatState=void 0,(t=exports.ChatState||(exports.ChatState={})).RECORDING="RECORDING",t.LLM="LLM",t.ANALYZING="ANALYZING",t.SPEAKING="SPEAKING",t.TTS="TTS";class r extends Error{constructor(){super("WebRTC connection timeout")}}class o extends Error{errorCode;code;detail;attr;constructor(t,e,s,a){let r;r=null!=a?`${t}:${a}_${s}`:`${t}:${s}`,super(r),this.errorCode=t,this.code=e,this.detail=s,this.attr=a}}class n extends Error{underlyingError;constructor(t){super(),this.underlyingError=t}}class i extends Error{description;constructor(t){super(),this.description=t}}class l extends Error{underlyingError;constructor(t){super(`STT Error: ${t.detail}`),this.underlyingError=t}}class c extends Error{underlyingError;constructor(t){super(t.message),this.underlyingError=t}}class h extends Error{description;constructor(t){super(`TTS decode error: ${t}`),this.description=t}}class d extends o{constructor(t){super(t.errorCode,t.code,t.detail,t.attr),this.name="SessionCreationError"}}class p extends d{constructor(t){super(t),this.name="DoesNotExistError"}}class u extends d{constructor(t){super(t),this.name="NotInOrganizationError"}}!function(t){t.LLM="LLM",t.TTS="TTS",t.STT="STT",t.STF_ONPREMISE="STF_ONPREMISE",t.STF_WEBRTC="STF_WEBRTC"}(e||(e={})),function(t){t.SESSION_START="SESSION_START",t.SESSION_DURING="SESSION_DURING",t.SESSION_LOG="SESSION_LOG",t.SESSION_END="SESSION_END",t.SESSION_ERROR="SESSION_ERROR",t.SESSION_TTS="SESSION_TTS",t.SESSION_STT="SESSION_STT",t.SESSION_LLM="SESSION_LLM"}(s||(s={}));class g{static async getLLMs(t,e){const s=fetch(`${t}/api/v1/settings/llm_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getModelStyles(t,e){const s=fetch(`${t}/api/v1/settings/modelstyle/?platform_type=webrtc`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getBackgroundImages(t,e){const s=fetch(`${t}/api/v1/background_image/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getTTSs(t,e){const s=fetch(`${t}/api/v1/settings/tts_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getSTTs(t,e){const s=fetch(`${t}/api/v1/settings/stt_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async makeTTS(t,{sessionId:e,text:s,locale:a,output_format:r}){const o={text:s};a&&(o.locale=a),r&&(o.output_format=r);const n=await fetch(`${t}/api/v1/session/${e}/tts/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)});return await this.parseJson(n)}static async getPrompts(t,e){const s=fetch(`${t}/api/v1/prompt/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getDocuments(t,e){const s=fetch(`${t}/api/v1/document/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getTextNormalizations(t,e){const s=fetch(`${t}/api/v1/settings/text_normalization_config/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async downloadTextNormalization(t,e,s){const a=await fetch(`${t}/api/v1/settings/text_normalization_config/${s}/download/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(a)}static async getSessionTemplates(t,e){const s=await fetch(`${t}/api/v1/session_template/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(s)}static async getSessionTemplate(t,e,s){const a=await fetch(`${t}/api/v1/session_template/${s}/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(a)}static async getMcpServers(t,e){const s=fetch(`${t}/api/v1/settings/mcp_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),a=await s;return await this.parseJson(a)}static async getSessionInfo(t,e){const s=fetch(`${t}/api/v1/session/${e}/`,{method:"GET"}),a=await s;return await this.parseJson(a)}static async sessionEvent(t,e,s,a=""){const r=await fetch(`${t}/api/v1/session/${e}/event/create/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detail:a,event:s})});await this.parseJson(r)}static async makeSTT(t,e,s,a){const r=new FormData;r.append("audio",s),a&&r.append("language",a);const o=await fetch(`${t}/api/v1/session/${e}/stt/`,{method:"POST",body:r});return{text:(await this.parseJson(o)).text}}static async makeLLM(t,e,s,a){const r=await fetch(`${t}/api/v1/session/${e}/llm/v2/`,{body:JSON.stringify(s),headers:{"Content-Type":"application/json"},method:"POST",signal:a});if(!r.ok){const t=await r.json(),e=t.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${r.status} with no error details`,attr:null};throw new o(r.status,e.code,e.detail,e.attr)}return r.body.getReader()}static async getIceServers(t,e){const s=await fetch(`${t}/api/v1/session/${e}/ice-servers/`,{method:"GET"});return(await this.parseJson(s)).ice_servers}static async exchangeSDP(t,e,s){const a=await fetch(`${t}/api/v1/session/${e}/exchange/`,{body:JSON.stringify({client_sdp:s}),headers:{"Content-Type":"application/json"},method:"POST"});return(await this.parseJson(a)).server_sdp}static async parseJson(t){const e=await t.json();if(t.ok)return e;{const s=e.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${t.status} with no error details`,attr:null};throw new o(t.status,s.code,s.detail,s.attr)}}}class m extends Error{constructor(t){super(`WAV parse error: ${t}`),this.name="WavParseError"}}function S(t,e,s=1){const a=2*t.length,r=new ArrayBuffer(44+a),o=new DataView(r);y(o,0,"RIFF"),o.setUint32(4,36+a,!0),y(o,8,"WAVE"),y(o,12,"fmt "),o.setUint32(16,16,!0),o.setUint16(20,1,!0),o.setUint16(22,s,!0),o.setUint32(24,e,!0),o.setUint32(28,e*s*2,!0),o.setUint16(32,2*s,!0),o.setUint16(34,16,!0),y(o,36,"data"),o.setUint32(40,a,!0);let n=44;for(let e=0;e<t.length;e++){const s=Math.max(-1,Math.min(1,t[e]));o.setInt16(n,s<0?32768*s:32767*s,!0),n+=2}return r}function f(t,e,s){let a="";for(let r=0;r<s;r++)a+=String.fromCharCode(t.getUint8(e+r));return a}function y(t,e,s){for(let a=0;a<s.length;a++)t.setUint8(e+a,s.charCodeAt(a))}function w(t,e,s){switch(s){case 8:return(t.getUint8(e)-128)/128;case 16:return t.getInt16(e,!0)/32768;case 24:{const s=t.getUint8(e),a=t.getUint8(e+1),r=t.getUint8(e+2)<<16|a<<8|s;return(r>8388607?r-16777216:r)/8388608}case 32:return t.getInt32(e,!0)/2147483648;default:return 0}}class T extends Error{constructor(t){super(`Audio resample error: ${t}`),this.name="AudioResampleError"}}async function C(t,e,s,a=1){if(0===t.length)throw new T("Cannot resample empty audio data");if(e<=0||s<=0)throw new T(`Invalid sample rate: original=${e}, target=${s}`);if(e===s)return t;try{const r=t.length/e,o=Math.ceil(r*s),n=new OfflineAudioContext(a,t.length,e).createBuffer(a,t.length,e);n.getChannelData(0).set(t);const i=new OfflineAudioContext(a,o,s),l=i.createBufferSource();l.buffer=n,l.connect(i.destination),l.start(0);return(await i.startRendering()).getChannelData(0)}catch(t){const a=t instanceof Error?t.message:String(t);throw new T(`Failed to resample audio from ${e}Hz to ${s}Hz: ${a}`)}}const v=16e3;async function E(t,e=!0){let s;try{const e=atob(t),a=new Array(e.length);for(let t=0;t<e.length;t++)a[t]=e.charCodeAt(t);s=new Uint8Array(a).buffer}catch{throw new h("Invalid Base64 audio data")}const a=function(t){const e=new Uint8Array(t);if(e.length>=4){const t=String.fromCharCode(e[0],e[1],e[2],e[3]);if("RIFF"===t)return"audio/wav";if(t.startsWith("ID3"))return"audio/mpeg";if(255===e[0]&&!(224&~e[1]))return"audio/mpeg"}return"audio/wav"}(s);if(!e)return new Blob([s],{type:a});try{const t=await async function(t,e){if("audio/wav"===e){const e=function(t){const e=new DataView(t);if(t.byteLength<44)throw new m("File too small to be a valid WAV");if("RIFF"!==f(e,0,4))throw new m("Missing RIFF header");if("WAVE"!==f(e,8,4))throw new m("Missing WAVE format identifier");let s=12,a=!1,r=0,o=0,n=0,i=0;for(;s<t.byteLength-8;){const l=f(e,s,4),c=e.getUint32(s+4,!0);if("fmt "===l){if(s+24>t.byteLength)throw new m("fmt chunk extends beyond file");r=e.getUint16(s+8,!0),o=e.getUint16(s+10,!0),n=e.getUint32(s+12,!0),i=e.getUint16(s+22,!0),a=!0,s+=8+c;break}const h=s+8+c;if(h<=s)break;s=h}if(!a)throw new m("Missing fmt chunk");if(1!==r)throw new m(`Unsupported audio format: ${r} (only PCM format 1 is supported)`);if(0===o||o>8)throw new m(`Invalid channel count: ${o}`);if(0===n||n>192e3)throw new m(`Invalid sample rate: ${n}`);if(![8,16,24,32].includes(i))throw new m(`Unsupported bits per sample: ${i}`);let l=-1,c=0;for(;s<t.byteLength-8;){const t=f(e,s,4),a=e.getUint32(s+4,!0);if("data"===t){l=s+8,c=a;break}const r=s+8+a;if(r<=s)break;s=r}if(-1===l)throw new m("Missing data chunk");const h=t.byteLength-l,d=Math.min(c,h),p=i/8,u=Math.floor(d/(p*o)),g=new Float32Array(u*o);let S=0;for(let s=0;s<u*o;s++){const a=l+s*p;if(a+p>t.byteLength)break;g[S++]=Math.max(-1,Math.min(1,w(e,a,i)))}if(2===o){const t=new Float32Array(u);for(let e=0;e<u;e++)t[e]=(g[2*e]+g[2*e+1])/2;return{sampleRate:n,channels:1,bitsPerSample:i,samples:t}}return{sampleRate:n,channels:o,bitsPerSample:i,samples:g.slice(0,S)}}(t);if(e.sampleRate===v)return{samples:e.samples,sampleRate:e.sampleRate};return{samples:await C(e.samples,e.sampleRate,v,e.channels),sampleRate:v}}const s=new AudioContext({sampleRate:v});try{const e=await s.decodeAudioData(t.slice(0));if(e.sampleRate===v)return{samples:e.getChannelData(0),sampleRate:v};return{samples:await C(e.getChannelData(0),e.sampleRate,v,1),sampleRate:v}}finally{await s.close()}}(s,a),e=S(t.samples,v,1);return new Blob([e],{type:"audio/wav"})}catch{return new Blob([s],{type:a})}}const x=a();function I(t){return t.replace(x,"")}class _{config;messageHistory=[];constructor(t){this.config=t}async*processLLM(t){if(0===t.message.length)throw new Error("Message cannot be empty");const e=t.tools??this.config.clientTools,s=e.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),a={newMessageHistory:[{role:"user",content:t.message}],allChunks:[],message:"",lastYieldedChunkCount:0,pendingToolCallsMessage:null,aborted:!1,streamingError:null};let r=0,l=[...this.messageHistory,...a.newMessageHistory];this.config.callbacks.onChatStateChange(exports.ChatState.LLM,null);try{for(;;){if(t.signal?.aborted)return void(a.allChunks.length>0&&(yield{type:"message",chunks:[...a.allChunks],message:a.message,finish:!0}));let c;try{c=await g.makeLLM(this.config.apiServer,this.config.sessionId,{messages:l,tools:s},t.signal)}catch(t){if(t instanceof o)return void(yield{type:"error",error:new n(t)});throw t}if(a.streamingError=null,yield*this.parseSSEStream(c,a,t),a.streamingError)return;if(a.aborted)return void(a.allChunks.length>0&&(yield{type:"message",chunks:[...a.allChunks],message:a.message,finish:!0}));if(null!=a.pendingToolCallsMessage){yield*this.executeToolCalls(a,e);const t=a.lastToolCallResults,s=t.length>0&&a.pendingToolCallsMessage.tool_calls.length!==t.length,o=t.some(t=>!t.chatTool.executeOnly);if(s||o){if(r++,r>=10)return void(yield{type:"error",error:new n(new i("Tool follow-up loop exceeded maximum rounds (10)"))});l=[...this.messageHistory,...a.newMessageHistory],a.pendingToolCallsMessage=null;continue}}return this.messageHistory.push(...a.newMessageHistory),void(yield{type:"message",chunks:[...a.allChunks],message:a.message,finish:!0})}}finally{this.config.callbacks.onChatStateChange(null,exports.ChatState.LLM)}}async*parseSSEStream(t,e,s){const a=new TextDecoder("utf-8");let r="",o="";e.pendingToolCallsMessage=null;const l=()=>e.allChunks.length>e.lastYieldedChunkCount?(e.lastYieldedChunkCount=e.allChunks.length,{type:"message",chunks:[...e.allChunks],message:e.message,finish:!1}):null;for(;;){const{done:c,value:h}=await t.read();if(c)break;let d;for(r+=a.decode(h,{stream:!0});-1!==(d=r.indexOf("\n"));){if(s.signal?.aborted)return void(e.aborted=!0);const t=r.slice(0,d).trim();if(r=r.slice(d+1),!t.startsWith("data: {"))return e.streamingError=new n(new i("Failed to parse SSE response")),void(yield{type:"error",error:e.streamingError});let a;try{a=JSON.parse(t.slice(6).trim())}catch{return e.streamingError=new n(new i("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==a.status)return e.streamingError=new n(new i(a.reason)),void(yield{type:"error",error:e.streamingError});if(o.length>0&&"message"!=a.type){e.newMessageHistory.push({role:"assistant",type:"message",content:o}),o="";const t=l();t&&(yield t)}if("message"===a.type){const t=I(a.content);o+=t,e.message+=t,e.allChunks.push(t);continue}"tool_call"!==a.type||null==a.tool_calls?"tool"!==a.role||"tool_call"===a.type&&e.newMessageHistory.push({role:a.role,type:a.type,content:a.content,tool_call_id:a.tool_call_id}):(e.newMessageHistory.push({role:"assistant",type:a.type,content:a.content,tool_calls:a.tool_calls}),e.pendingToolCallsMessage=a,yield{type:"tool_call",tool_calls:a.tool_calls})}const p=l();p&&(yield p)}const c=r.trim();if(c.length>0){if(!c.startsWith("data: {"))return e.streamingError=new n(new i("Failed to parse SSE response")),void(yield{type:"error",error:e.streamingError});let t;try{t=JSON.parse(c.slice(6).trim())}catch{return e.streamingError=new n(new i("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==t.status)return e.streamingError=new n(new i(t.reason)),void(yield{type:"error",error:e.streamingError});if("message"===t.type){const s=I(t.content);o+=s,e.message+=s,e.allChunks.push(s)}else if("tool_call"===t.type&&null!=t.tool_calls){if(o.length>0){e.newMessageHistory.push({role:"assistant",type:"message",content:o}),o="";const t=l();t&&(yield t)}e.newMessageHistory.push({role:"assistant",type:t.type,content:t.content,tool_calls:t.tool_calls}),e.pendingToolCallsMessage=t,yield{type:"tool_call",tool_calls:t.tool_calls}}}o.length>0&&e.newMessageHistory.push({role:"assistant",type:"message",content:o})}async*executeToolCalls(t,e){const s=t=>{for(const s of e)if(s.name===t)return s;return null},a=[];for(const e of t.pendingToolCallsMessage.tool_calls){const t=s(e.function.name);null!=t&&a.push((async()=>{try{const s=await t.call(JSON.parse(e.function.arguments));return{toolCallId:e.id,chatTool:t,chatToolResult:s}}catch(s){return{toolCallId:e.id,chatTool:t,chatToolResult:{error:s.message}}}})())}const r=await Promise.all(a);t.lastToolCallResults=r;for(const e of r)t.newMessageHistory.push({role:"tool",content:JSON.stringify(e.chatToolResult),tool_call_id:e.toolCallId}),yield{type:"tool_result",tool_call_id:e.toolCallId,result:e.chatToolResult}}addToHistory(t){this.messageHistory.push(t)}getHistory(){return this.messageHistory}}const R=`data:application/javascript,${encodeURIComponent("\nclass RecorderProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.isRecording = true;\n \n // Listen for stop message from main thread\n this.port.onmessage = (event) => {\n if (event.data.type === 'stop') {\n this.isRecording = false;\n // Send confirmation back to main thread\n this.port.postMessage({ type: 'stopped' });\n }\n };\n }\n\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n if (input && input.length > 0 && this.isRecording) {\n // Clone the audio data and send to main thread\n const channelData = new Float32Array(input[0]);\n this.port.postMessage({ type: 'audio', data: channelData });\n }\n // Return true to keep the processor alive until stopped\n return this.isRecording;\n }\n}\n\nregisterProcessor('recorder-processor', RecorderProcessor);\n")}`;class L{audioContext=null;mediaStream=null;workletNode=null;sourceNode=null;audioChunks=[];isRecordingState=!1;channels;targetSampleRate;constructor(t={}){this.channels=t.channels||1,this.targetSampleRate=t.targetSampleRate}async start(){if(this.isRecordingState)throw new Error("WavRecorder is already recording");if(this.mediaStream=await navigator.mediaDevices.getUserMedia({audio:!0}),this.audioContext=new AudioContext,"running"!==this.audioContext.state)try{await this.audioContext.resume()}catch(t){console.warn("WavRecorder: Failed to resume AudioContext:",t)}await this.audioContext.audioWorklet.addModule(R),this.sourceNode=this.audioContext.createMediaStreamSource(this.mediaStream),this.workletNode=new AudioWorkletNode(this.audioContext,"recorder-processor"),this.audioChunks=[],this.workletNode.port.onmessage=t=>{"audio"===t.data.type&&this.audioChunks.push(t.data.data)},this.sourceNode.connect(this.workletNode),this.workletNode.connect(this.audioContext.destination),this.isRecordingState=!0}async stop(){if(!this.isRecordingState)throw new Error("WavRecorder is not recording");this.isRecordingState=!1,await new Promise(t=>{this.workletNode.port.onmessage=e=>{"stopped"===e.data.type?t():"audio"===e.data.type&&this.audioChunks.push(e.data.data)},this.workletNode.port.postMessage({type:"stop"})}),this.workletNode?.disconnect(),this.sourceNode?.disconnect(),this.mediaStream?.getTracks().forEach(t=>t.stop());const t=this.audioChunks,e=this.audioContext.sampleRate;try{const s=t.reduce((t,e)=>t+e.length,0),a=new Float32Array(s);let r,o,n=0;for(const e of t)a.set(e,n),n+=e.length;this.targetSampleRate&&this.targetSampleRate!==e?(r=await C(a,e,this.targetSampleRate,this.channels),o=this.targetSampleRate):(r=a,o=e);const i=S(r,o,this.channels),l=new Blob([i],{type:"audio/wav"});return new File([l],"recording.wav",{type:"audio/wav"})}finally{await(this.audioContext?.close()),this.audioContext=null,this.mediaStream=null,this.workletNode=null,this.sourceNode=null,this.audioChunks=[]}}isRecording(){return this.isRecordingState}}class b extends EventTarget{pc;dc;streams=[];pingTime;pingIntervalId=null;constructor(t,e){super(),this.pc=t,this.dc=e,this.pingTime=Date.now()+3e3,this.pc.addEventListener("track",t=>{this.streams=this.streams.concat(t.streams)}),this.pc.addEventListener("connectionstatechange",()=>{"disconnected"!==this.pc.connectionState&&"failed"!==this.pc.connectionState||this.close()}),this.dc.onopen=()=>{this.pingIntervalId=setInterval(()=>{this.ping(),Date.now()-this.pingTime>3e4&&this.close()},1e3)},this.dc.onclose=()=>{null!=this.pingIntervalId&&clearInterval(this.pingIntervalId)},this.#t({live:!0,code:200,reason:"OK"}),this.setMessageCallback("ping",()=>{this.pingTime=Date.now()})}static async create(t,a,r,o,n){const i=await g.getSessionInfo(t,a);if(!(Array.isArray(i.capability)&&i.capability.some(t=>t.name===e.STF_ONPREMISE||t.name===e.STF_WEBRTC)))return await g.sessionEvent(t,a,s.SESSION_START),null;const l=await g.getIceServers(t,a);let c=await b.createPeerConnection(l),h=c.createDataChannel("message",{protocol:"message"}),d=new b(c,h);n?n.getTracks().forEach(function(t){c.addTrack(t,n)}):c.addTransceiver("audio",{direction:"recvonly"});const p=c.addTransceiver("video",{direction:"recvonly"}),u=RTCRtpReceiver.getCapabilities("video");null!=u&&p.setCodecPreferences(u.codecs);const m=await c.createOffer();await c.setLocalDescription(m);const S=await g.exchangeSDP(t,a,m);return await c.setRemoteDescription(S),await b.waitFor(()=>d.isReady(),100,50),d.changeSize(r,o),d}static async createPeerConnection(t){return new RTCPeerConnection({sdpSemantics:"unified-plan",iceServers:t})}static async waitFor(t,e,s){let a=0;if(await new Promise(r=>{const o=setInterval(()=>{a+=1,a>=s&&(clearInterval(o),r("bad")),t()&&(clearInterval(o),r("good"))},e)}),a>=s)throw new r}isReady(){return this.streams.length>0&&"open"===this.dc.readyState}#t(t){this.dispatchEvent(new CustomEvent("status",{detail:t}))}subscribeStatus(t){return this.addEventListener("status",t),()=>{this.removeEventListener("status",t)}}getStream(){return this.streams[0]}sendMessage(t,e){this.dc.send(JSON.stringify({type:t,data:e}))}ttstf(t){this.sendMessage("ttstf",{message:t})}static BACKPRESSURE_THRESHOLD=524288;static FILE_TRANSFER_TIMEOUT=3e4;sendFile(t,e=65536){return new Promise((s,a)=>{let r=!1;const o=t=>{r||(r=!0,clearTimeout(i),t())},n=this.pc.createDataChannel("file",{protocol:"file"}),i=setTimeout(()=>{o(()=>{n.close(),a(new Error("File transfer timed out"))})},b.FILE_TRANSFER_TIMEOUT);n.onclose=()=>{o(()=>{a(new Error("File channel closed before transfer completed"))})},n.onerror=t=>{o(()=>{n.close(),a(new Error(`File channel error: ${t}`))})},n.addEventListener("message",async r=>{try{if(0===r.data.length){const s=new Uint8Array(await t.arrayBuffer());let r=0;const i=()=>{for(;r<s.length;){if(n.bufferedAmount>b.BACKPRESSURE_THRESHOLD)return n.bufferedAmountLowThreshold=b.BACKPRESSURE_THRESHOLD/2,n.onbufferedamountlow=()=>{n.onbufferedamountlow=null,n.onclose=null,i()},void(n.onclose=()=>{n.onbufferedamountlow=null,o(()=>{a(new Error("File channel closed during transfer"))})});n.send(s.slice(r,r+e)),r+=e}n.send(new Uint8Array(0))};i()}else o(()=>{n.close(),s(r.data)})}catch(t){o(()=>{n.close(),a(t instanceof Error?t:new Error(String(t)))})}})})}async stf(t,e,s){const a=await this.sendFile(t);return this.sendMessage("stf",{message:s,file_ref:a,format:e}),a}recordStart(){this.sendMessage("record-start",{})}recordEndStt(t){this.sendMessage("record-end-stt",{language:t})}recordEndTranslate(t,e){this.sendMessage("record-end-translate",{src_lang:t,dst_lang:e})}changeSize(t,e){this.sendMessage("change-size",{width:t,height:e})}setTemplate(t,e){this.sendMessage("set-template",{model:t,dress:e})}clearBuffer(){this.sendMessage("clear-buffer",{})}ping(){this.sendMessage("ping",{})}setMessageCallback(t,e){const s=s=>{const a=JSON.parse(s.data);a.type===t&&e(a.data)};return this.dc.addEventListener("message",s),()=>{this.dc.removeEventListener("message",s)}}async tts(t,e=!0){return E(t,e)}close(){this.dc.close(),this.pc.close(),this.#t({live:!1,code:408,reason:"Request Timeout"})}closeSelf(){this.dc.close(),this.pc.close(),this.#t({live:!1,code:200,reason:"OK"})}}class M{apiServer;sessionId;perso;clientTools;chatStatesHandler=new EventTarget;chatLogHandler=new EventTarget;sttEventHandler=null;errorHandler=new EventTarget;lastStfTimeoutHandle=null;stfTotalDuration=0;stfTimeoutStartTime=0;messageHistory=[];chatLog=[];llmProcessor;chatStateMap=new Map([[exports.ChatState.RECORDING,0],[exports.ChatState.LLM,0],[exports.ChatState.ANALYZING,0],[exports.ChatState.SPEAKING,0],[exports.ChatState.TTS,0]]);sttRecorder=null;sttTimeoutHandle=null;sttTimeoutAudioFile=null;heartbeatIntervalId=null;legacyVoiceChatMode;stream;constructor(t,e,s,a,r){this.apiServer=t,this.sessionId=e,this.perso=s,this.clientTools=a,this.legacyVoiceChatMode=r?.legacyVoiceChatMode??!1,this.stream=r?.stream??null,this.resetChatState(),this.llmProcessor=new _({apiServer:t,sessionId:e,clientTools:a,callbacks:{onChatStateChange:(t,e)=>this.setChatState(t,e),onError:t=>this.setError(t),onChatLog:(t,e)=>this.addMessageToChatLog(t,e),onTTSTF:t=>this.processTTSTFInternal(t)}}),s?(s.subscribeStatus(t=>{!1===t.detail?.live&&this.stopHeartbeat()}),s.setMessageCallback("stf",t=>{if(this.chatStateMap.get(exports.ChatState.ANALYZING)||this.chatStateMap.get(exports.ChatState.SPEAKING))if(this.setChatState(exports.ChatState.SPEAKING,exports.ChatState.ANALYZING),null!==this.lastStfTimeoutHandle){clearTimeout(this.lastStfTimeoutHandle);let e=Date.now();this.stfTotalDuration+=t.duration+1e3-(e-this.stfTimeoutStartTime),this.stfTimeoutStartTime=e,this.lastStfTimeoutHandle=setTimeout(()=>{this.lastStfTimeoutHandle=null,this.stfTimeoutStartTime=0,this.stfTotalDuration=0,this.setChatState(null,exports.ChatState.SPEAKING)},this.stfTotalDuration)}else this.stfTimeoutStartTime=Date.now(),this.stfTotalDuration=t.duration+2e3,this.lastStfTimeoutHandle=setTimeout(()=>{this.lastStfTimeoutHandle=null,this.stfTimeoutStartTime=0,this.stfTotalDuration=0,this.setChatState(null,exports.ChatState.SPEAKING)},this.stfTotalDuration)}),s.setMessageCallback("stt",t=>{if(this.setChatState(null,exports.ChatState.ANALYZING),null!=this.sttEventHandler)this.sttEventHandler.dispatchEvent(new CustomEvent("stt",{detail:t.text}));else{if(""===t.text)return;this.processChat(t.text)}}),s.setMessageCallback("stt-error",t=>{this.setChatState(null,exports.ChatState.ANALYZING)})):this.startHeartbeat()}llmJob=null;async processChat(t){0!==t.trim().length&&(this.pipelineSuppressed=!1,this.addMessageToChatLog(t,!0),this.llmJob=this.processChatInternal(t))}processLLM(t){return this.pipelineSuppressed=!1,this.llmProcessor.processLLM(t)}getMessageHistory(){return this.llmProcessor.getHistory()}processCustomChat(t){0!==t.trim().length&&this.processTTSTFInternal(t)}processTTSTF(t){0!==t.trim().length&&(this.pipelineSuppressed=!1,this.messageHistory.push({role:"assistant",type:"message",content:t}),this.addMessageToChatLog(t,!1),this.processTTSTFInternal(t))}async transcribeAudio(t,e){return(await this.transcribeAudioDetailed(t,e)).text}async transcribeAudioDetailed(t,e){const s=t instanceof File?t:new File([t],"audio.wav",{type:t.type});try{return await g.makeSTT(this.apiServer,this.sessionId,s,e)}catch(t){if(t instanceof o)throw new l(t);throw t}}async processSTF(t,e,s=""){if(!this.perso)throw new Error("processSTF requires WebRTC (STF mode)");this.pipelineSuppressed=!1,this.setChatState(exports.ChatState.ANALYZING);try{const a=function(t,e){const s=t=>{if(!t)return null;const e=t.toLowerCase().split(";")[0].trim();return"mp3"===e||"audio/mpeg"===e||"audio/mp3"===e?"mp3":"wav"===e||"audio/wav"===e||"audio/x-wav"===e||"audio/wave"===e?"wav":null};return s(t)??s(e.type)??"wav"}(e,t),r=await this.perso.stf(t,a,s);return this.pipelineSuppressed?(this.setChatState(null,exports.ChatState.ANALYZING),r):r}catch(t){throw this.setChatState(null,exports.ChatState.ANALYZING),t}}async processTTS(t,e={}){const{resample:s=!1,locale:a,output_format:r}=e,n=I(t).trim();if(0===n.length)return;this.pipelineSuppressed=!1;const i=/[.?!]$/.test(n)?n:n+".";this.setChatState(exports.ChatState.TTS,null);try{const t={sessionId:this.sessionId,text:i,...a&&{locale:a},...r&&{output_format:r}},{audio:e}=await g.makeTTS(this.apiServer,t);if(this.pipelineSuppressed)return;return await E(e,s)}catch(t){t instanceof o||t instanceof h?this.setError(new c(t)):this.setError(t instanceof Error?t:new Error(String(t)))}finally{this.setChatState(null,exports.ChatState.TTS)}}startVoiceChat(){if(!this.perso)throw new Error("startVoiceChat requires WebRTC (STF mode)");return this.pipelineSuppressed=!1,this.setChatState(exports.ChatState.RECORDING),this.perso.recordStart()}stopVoiceChat(){if(!this.perso)throw new Error("stopVoiceChat requires WebRTC (STF mode)");this.setChatState(exports.ChatState.ANALYZING,exports.ChatState.RECORDING),this.perso.recordEndStt()}async startProcessSTT(t){if(this.sttRecorder?.isRecording())throw new Error("STT recording is already in progress");this.pipelineSuppressed=!1,this.setChatState(exports.ChatState.RECORDING);try{this.sttRecorder=new L({targetSampleRate:16e3}),await this.sttRecorder.start(),t&&t>0&&(this.sttTimeoutHandle=setTimeout(async()=>{if(this.sttTimeoutHandle=null,this.sttRecorder?.isRecording()){try{this.sttTimeoutAudioFile=await this.sttRecorder.stop()}catch{this.sttTimeoutAudioFile=null,this.setChatState(null,exports.ChatState.RECORDING)}this.sttRecorder=null}},t))}catch(t){throw this.setChatState(null,exports.ChatState.RECORDING),this.sttRecorder=null,t}}lastRecordedAudioFile=null;async stopProcessSTT(t){let e;if(this.sttTimeoutHandle&&(clearTimeout(this.sttTimeoutHandle),this.sttTimeoutHandle=null),this.setChatState(null,exports.ChatState.RECORDING),this.sttTimeoutAudioFile)e=this.sttTimeoutAudioFile,this.sttTimeoutAudioFile=null;else{if(!this.sttRecorder?.isRecording())throw this.sttRecorder?(this.sttRecorder=null,new Error("STT recording is not in progress")):new Error("STT recording has not been started");e=await this.sttRecorder.stop(),this.sttRecorder=null}this.lastRecordedAudioFile=e;try{return(await g.makeSTT(this.apiServer,this.sessionId,e,t)).text}catch(t){if(t instanceof o)throw new l(t);throw t}}isSTTRecording(){return(this.sttRecorder?.isRecording()??!1)||null!==this.sttTimeoutAudioFile}changeSize(t,e){this.perso?.changeSize(t,e)}async clearBuffer(){this.perso?.clearBuffer(),await this.clearLLMJob(),null!==this.lastStfTimeoutHandle&&(clearTimeout(this.lastStfTimeoutHandle),this.lastStfTimeoutHandle=null),this.pipelineSuppressed=!0,this.resetChatState()}setSrc(t){t.srcObject=this.getRemoteStream()??null}getRemoteStream(){return this.perso?.getStream()}getLocalStream(){return this.stream}stopSession(){this.close()}onClose(t){return this.perso?this.perso.subscribeStatus(e=>{null!=e.detail&&!1===e.detail.live&&t(200===e.detail.code)}):()=>{}}subscribeChatStates(t){const e=e=>{t(e.detail.status)};return this.chatStatesHandler.addEventListener("status",e),()=>{this.chatStatesHandler.removeEventListener("status",e)}}subscribeChatLog(t){const e=e=>{t(e.detail.chatLog)};return this.chatLogHandler.addEventListener("chatLog",e),()=>{this.chatLogHandler.removeEventListener("chatLog",e)}}setSttResultCallback(t){const e=e=>{t(e.detail)};return this.sttEventHandler=new EventTarget,this.sttEventHandler.addEventListener("stt",e),()=>{this.sttEventHandler?.removeEventListener("stt",e),this.sttEventHandler=null}}setErrorHandler(t){const e=e=>{t(e.detail.error)};return this.errorHandler.addEventListener("error",e),()=>{this.errorHandler.removeEventListener("error",e)}}getSessionId(){return this.sessionId}async processChatInternal(t){this.setChatState(exports.ChatState.LLM);const e=this.clientTools.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),s=new Array;null===t||(t instanceof Array?s.push(...t):"string"==typeof t&&s.push({role:"user",content:t}));const a=await fetch(`${this.apiServer}/api/v1/session/${this.sessionId}/llm/v2/`,{body:JSON.stringify({messages:[...this.messageHistory,...s],tools:e}),headers:{"Content-Type":"application/json"},method:"POST"});if(!a.ok){const t=await a.json(),e=new n(new o(a.status,t.errors[0].code,t.errors[0].detail,t.errors[0].attr));return this.setError(e),void this.setChatState(null,exports.ChatState.LLM)}const r=a.body?.getReader(),l=new TextDecoder("utf-8");let c="",h=null,d="";for(;;){const{done:t,value:e}=await r.read();if(t)break;let a;for(d+=l.decode(e,{stream:!0});-1!==(a=d.indexOf("\n"));){if(this.llmCancel)return c.length>0&&this.addMessageToChatLog(c,!1),void this.setChatState(null,exports.ChatState.LLM);const t=d.slice(0,a).trim();if(d=d.slice(a+1),!t.startsWith("data: {")){const t=new n(new i("Failed to parse SSE response"));return this.setError(t),void this.setChatState(null,exports.ChatState.LLM)}const e=JSON.parse(t.slice(6).trim());if("success"!==e.status){const t=new n(new i(e.reason));return this.setError(t),void this.setChatState(null,exports.ChatState.LLM)}c.length>0&&"message"!=e.type&&(s.push({role:"assistant",type:"message",content:c}),this.addMessageToChatLog(c,!1),c=""),"message"!==e.type?"tool_call"!==e.type||null==e.tool_calls?"tool"!==e.role||"tool_call"===e.type&&s.push({role:e.role,type:e.type,content:e.content,tool_call_id:e.tool_call_id}):(s.push({role:"assistant",type:e.type,content:e.content,tool_calls:e.tool_calls}),h=e):(c+=I(e.content),this.processTTSTFInternal(e.content))}}if(this.llmCancel)this.setChatState(null,exports.ChatState.LLM);else{if(null!=h){const t=[];for(const e of h.tool_calls){const s=this.getChatTool(this.clientTools,e.function.name);null!=s&&t.push(new Promise(async t=>{try{const a=await s.call(JSON.parse(e.function.arguments));t({toolCallId:e.id,chatTool:s,chatToolResult:a})}catch(a){t({toolCallId:e.id,chatTool:s,chatToolResult:{result:"error!"}})}}))}const e=await Promise.all(t);for(const t of e)s.push({role:"tool",content:JSON.stringify(t.chatToolResult),tool_call_id:t.toolCallId});const a=e.length>0&&h.tool_calls.length!==e.length,r=e.some(t=>!t.chatTool.executeOnly);a||r?await this.processChatInternal(s):this.messageHistory.push(...s)}else this.messageHistory.push(...s);this.setChatState(null,exports.ChatState.LLM)}}getChatTool(t,e){for(const s of t)if(s.name===e)return s;return null}llmCancel=!1;pipelineSuppressed=!1;async clearLLMJob(){null!=this.llmJob&&(this.llmCancel=!0,await this.llmJob,this.llmCancel=!1)}processTTSTFInternal(t){const e=I(t).trim();0!==e.length&&this.perso&&(this.setChatState(exports.ChatState.ANALYZING),this.perso.ttstf(e))}addMessageToChatLog(t,e){this.chatLog=[{text:t,isUser:e,timestamp:new Date},...this.chatLog],this.chatLogHandler.dispatchEvent(new CustomEvent("chatLog",{detail:{chatLog:this.chatLog}}))}setChatState(t=null,e=null){const s=new Map(this.chatStateMap);function a(t){t===exports.ChatState.ANALYZING?s.set(t,(s.get(t)||0)+1):s.set(t,1)}function r(t){t===exports.ChatState.ANALYZING?s.set(t,Math.max((s.get(t)||0)-1,0)):s.set(t,0)}if(null!=t)if(t instanceof Array)for(let e of t)a(e);else a(t);if(null!=e)if(e instanceof Array)for(let t of e)r(t);else r(e);const o=this.exchangeChatStateMapToSet(this.chatStateMap),n=this.exchangeChatStateMapToSet(s);this.chatStateMap=s,this.isEqualChatStateMap(o,n)||this.dispatchChatState(n)}resetChatState(){this.chatStateMap=new Map([[exports.ChatState.RECORDING,0],[exports.ChatState.LLM,0],[exports.ChatState.ANALYZING,0],[exports.ChatState.SPEAKING,0],[exports.ChatState.TTS,0]]),this.dispatchChatState(this.exchangeChatStateMapToSet(this.chatStateMap))}exchangeChatStateMapToSet(t){const e=new Set;for(const s of t)s[1]>0&&e.add(s[0]);return e}dispatchChatState(t){this.chatStatesHandler.dispatchEvent(new CustomEvent("status",{detail:{status:t}}))}isEqualChatStateMap(t,e){if(t.size!==e.size)return!1;for(const s of t)if(t.has(s)!==e.has(s))return!1;return!0}async logSessionEvent(t){const e="object"==typeof t?JSON.stringify(t):t;await g.sessionEvent(this.apiServer,this.sessionId,s.SESSION_LOG,e)}setError(t){this.errorHandler.dispatchEvent(new CustomEvent("error",{detail:{error:t}}))}close(){this.stopHeartbeat(),this.perso?.closeSelf()}startHeartbeat(){const t=async()=>{try{await g.sessionEvent(this.apiServer,this.sessionId,s.SESSION_DURING),null!==this.heartbeatIntervalId&&(this.heartbeatIntervalId=setTimeout(t,1e4))}catch(t){t instanceof o?this.setError(t):this.setError(t instanceof Error?t:new Error(String(t))),this.close()}};this.heartbeatIntervalId=setTimeout(t,1e4)}stopHeartbeat(){null!==this.heartbeatIntervalId&&(clearTimeout(this.heartbeatIntervalId),this.heartbeatIntervalId=null)}}async function N(t,e,s,a,r,o){if("boolean"!=typeof r){const o=await b.create(t,e,s,a);return new M(t,e,o,r)}const n=o??[];let i,l;if(r)i=await navigator.mediaDevices.getUserMedia({audio:!0,video:!1}),l=()=>{};else{const t=new AudioContext,e=t.createOscillator();e.frequency.value=0;const s=t.createMediaStreamDestination();e.connect(s),e.start(),i=s.stream,l=()=>{e.stop(),e.disconnect(s),t.close()}}const c=await b.create(t,e,s,a,i);if(!c)return l(),new M(t,e,null,n);const h=new M(t,e,c,n,{stream:i,legacyVoiceChatMode:!0});return h.onClose(()=>{l()}),h}const A="https://platform.perso.ai";function k(t){const e=t?.trim();return(e||A).replace(/\/+$/,"")}function O(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)&&"apiKey"in t}exports.ApiError=o,exports.ChatTool=class{name;description;parameters;call;executeOnly;constructor(t,e,s,a,r=!1){this.name=t,this.description=e,this.parameters=s,this.call=a,this.executeOnly=r}},exports.DEFAULT_API_SERVER=A,exports.DoesNotExistError=p,exports.LLMError=n,exports.LLMStreamingResponseError=i,exports.LlmProcessor=_,exports.NotInOrganizationError=u,exports.STTError=l,exports.Session=M,exports.SessionCreationError=d,exports.TTSDecodeError=h,exports.TTSError=c,exports.TTS_TARGET_SAMPLE_RATE=v,exports.WavRecorder=L,exports.createSession=async function(t,e,s,a,r,o){if("object"==typeof t){const e=t,s=k(e.apiServer);return await N(s,e.sessionId,e.width,e.height,e.clientTools)}const n=k(t);return"boolean"==typeof r?await N(n,e,s,a,r,o??[]):await N(n,e,s,a,r)},exports.createSessionId=async function(t,s,a){let r,n,i;if("undefined"!=typeof window&&console.warn("[perso-interactive-sdk-web] WARNING: createSessionId is being called from the browser. This exposes your API key and is not recommended for production. Use server-side session creation with 'perso-interactive-sdk-web/server' instead. See: https://github.com/perso-ai/perso-interactive-sdk-web#server-side"),"object"==typeof t){const e=t;r=k(e.apiServer),n=e.apiKey,i="sessionTemplateId"in e?e.sessionTemplateId:e.params}else r=k(t),n=s,i=a;return await async function(t,s,a){try{let r;if("string"==typeof a){const o=await g.getSessionTemplate(t,s,a);if("webrtc"!==o.model_style.platform_type)throw new Error(`SessionTemplate "${a}" uses platform_type "${o.model_style.platform_type}", but only "webrtc" is supported`);r=function(t){const s=e=>t.capability.some(t=>t.name===e);return{using_stf_webrtc:s(e.STF_WEBRTC),model_style:t.model_style.name,prompt:t.prompt.prompt_id,document:t.document?.document_id,background_image:t.background_image?.backgroundimage_id,mcp_servers:t.mcp_servers?.length?t.mcp_servers.map(t=>t.mcpserver_id):[],llm_type:s(e.LLM)?t.llm_type.name:void 0,tts_type:s(e.TTS)?t.tts_type.name:void 0,stt_type:s(e.STT)?t.stt_type.name:void 0,text_normalization_config:t.text_normalization_config?.textnormalizationconfig_id,text_normalization_locale:t.text_normalization_locale,stt_text_normalization_config:t.stt_text_normalization_config?.textnormalizationconfig_id,stt_text_normalization_locale:t.stt_text_normalization_locale,padding_left:t.padding_left??void 0,padding_top:t.padding_top??void 0,padding_height:t.padding_height??void 0}}(o)}else r=a;const o={capability:[],...r};r.using_stf_webrtc&&o.capability.push(e.STF_WEBRTC),r?.llm_type&&(o.capability.push(e.LLM),o.llm_type=r.llm_type),r?.tts_type&&(o.capability.push(e.TTS),o.tts_type=r.tts_type),r?.stt_type&&(o.capability.push(e.STT),o.stt_type=r.stt_type);const n=await fetch(`${t}/api/v1/session/`,{body:JSON.stringify(o),headers:{"PersoLive-APIKey":s,"Content-Type":"application/json"},method:"POST"});return(await g.parseJson(n)).session_id}catch(t){throw function(t){if(t instanceof d)return t;if(t instanceof o)switch(t.code){case"does_not_exist":return new p(t);case"not_in_organization":return new u(t);default:return new d(t)}return t}(t)}}(r,n,i)},exports.createWavRecorder=function(t){return new L(t)},exports.getAllSettings=async function(t,e){const s=O(t)?{apiServer:k(t.apiServer),apiKey:t.apiKey}:{apiServer:k(t),apiKey:e},[a,r,o,n,i,l,c,h,d]=await Promise.all([g.getLLMs(s.apiServer,s.apiKey),g.getTTSs(s.apiServer,s.apiKey),g.getSTTs(s.apiServer,s.apiKey),g.getModelStyles(s.apiServer,s.apiKey),g.getBackgroundImages(s.apiServer,s.apiKey),g.getPrompts(s.apiServer,s.apiKey),g.getDocuments(s.apiServer,s.apiKey),g.getMcpServers(s.apiServer,s.apiKey),g.getTextNormalizations(s.apiServer,s.apiKey).catch(()=>[])]);return{llms:a,ttsTypes:r,sttTypes:o,modelStyles:n,backgroundImages:i,prompts:l,documents:c,mcpServers:h,textNormalizations:d}},exports.getBackgroundImages=async function(t,e){return O(t)?await g.getBackgroundImages(k(t.apiServer),t.apiKey):await g.getBackgroundImages(k(t),e)},exports.getDocuments=async function(t,e){return O(t)?await g.getDocuments(k(t.apiServer),t.apiKey):await g.getDocuments(k(t),e)},exports.getLLMs=async function(t,e){return O(t)?await g.getLLMs(k(t.apiServer),t.apiKey):await g.getLLMs(k(t),e)},exports.getMcpServers=async function(t,e){return O(t)?await g.getMcpServers(k(t.apiServer),t.apiKey):await g.getMcpServers(k(t),e)},exports.getModelStyles=async function(t,e){return O(t)?await g.getModelStyles(k(t.apiServer),t.apiKey):await g.getModelStyles(k(t),e)},exports.getPrompts=async function(t,e){return O(t)?await g.getPrompts(k(t.apiServer),t.apiKey):await g.getPrompts(k(t),e)},exports.getSTTs=async function(t,e){return O(t)?await g.getSTTs(k(t.apiServer),t.apiKey):await g.getSTTs(k(t),e)},exports.getSessionInfo=async function(t,e){return"object"==typeof t?await g.getSessionInfo(k(t.apiServer),t.sessionId):await g.getSessionInfo(k(t),e)},exports.getSessionTemplates=async function(t,e){return O(t)?await g.getSessionTemplates(k(t.apiServer),t.apiKey):await g.getSessionTemplates(k(t),e)},exports.getTTSs=async function(t,e){return O(t)?await g.getTTSs(k(t.apiServer),t.apiKey):await g.getTTSs(k(t),e)},exports.getTextNormalization=async function(t,e,s){return"object"==typeof t?await g.downloadTextNormalization(k(t.apiServer),t.apiKey,t.configId):await g.downloadTextNormalization(k(t),e,s)},exports.getTextNormalizations=async function(t,e){return O(t)?await g.getTextNormalizations(k(t.apiServer),t.apiKey):await g.getTextNormalizations(k(t),e)},exports.getWavSampleRate=function(t){const e=new DataView(t);if(t.byteLength<28)throw new m("File too small to be a valid WAV");if("RIFF"!==f(e,0,4))throw new m("Missing RIFF header");let s=12;for(;s<t.byteLength-8;){const a=f(e,s,4),r=e.getUint32(s+4,!0);if("fmt "===a){if(s+16>t.byteLength)throw new m("fmt chunk extends beyond file");return e.getUint32(s+12,!0)}const o=s+8+r;if(o<=s)break;s=o}throw new m("Missing fmt chunk")},exports.makeTTS=async function(t,e){if("object"==typeof t){const{apiServer:e,...s}=t;return await g.makeTTS(k(e),s)}return await g.makeTTS(k(t),e)};
|