perso-interactive-sdk-web 1.3.4 → 1.5.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 +57 -16
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.ts +91 -20
- 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 +304 -150
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
WebRTC-based real-time interactive AI avatar SDK for web applications.
|
|
4
4
|
|
|
5
|
+
> **API server:** Use `https://platform.perso.ai` as the Perso Interactive API server URL.
|
|
6
|
+
>
|
|
7
|
+
> Legacy `https://live-api.perso.ai` remains backward-compatible.
|
|
8
|
+
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
```bash
|
|
@@ -47,7 +51,7 @@ const { createSessionId } = require("perso-interactive-sdk-web/server");
|
|
|
47
51
|
|
|
48
52
|
const app = express();
|
|
49
53
|
|
|
50
|
-
const API_SERVER = "https://
|
|
54
|
+
const API_SERVER = "https://platform.perso.ai";
|
|
51
55
|
const API_KEY = process.env.PERSO_INTERACTIVE_API_KEY;
|
|
52
56
|
|
|
53
57
|
app.post("/api/session", async (req, res) => {
|
|
@@ -60,6 +64,8 @@ app.post("/api/session", async (req, res) => {
|
|
|
60
64
|
tts_type: "<tts_name>",
|
|
61
65
|
stt_type: "<stt_name>",
|
|
62
66
|
// text_normalization_config: "<textnormalizationconfig_id>", // optional
|
|
67
|
+
// stt_text_normalization_config: "<textnormalizationconfig_id>", // optional
|
|
68
|
+
// stt_text_normalization_locale: "ko", // optional
|
|
63
69
|
});
|
|
64
70
|
res.json({ sessionId });
|
|
65
71
|
} catch (error) {
|
|
@@ -79,6 +85,19 @@ If you have pre-configured session templates, pass the template ID directly inst
|
|
|
79
85
|
const sessionId = await createSessionId(API_SERVER, API_KEY, "<sessiontemplate_id>");
|
|
80
86
|
```
|
|
81
87
|
|
|
88
|
+
#### Listing available resources from the server
|
|
89
|
+
|
|
90
|
+
Use `getAllSettings` (or any individual `getXxx` helper) on the server to discover the LLM/TTS/STT/model-style options that your tenant has access to without exposing the API key in the browser:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
const { getAllSettings } = require("perso-interactive-sdk-web/server");
|
|
94
|
+
|
|
95
|
+
app.get("/api/settings", async (req, res) => {
|
|
96
|
+
const settings = await getAllSettings(API_SERVER, API_KEY);
|
|
97
|
+
res.json(settings); // { llms, ttsTypes, sttTypes, modelStyles, ... }
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
82
101
|
> ⚠️ **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.
|
|
83
102
|
|
|
84
103
|
#### Client-side Testing Only
|
|
@@ -91,7 +110,7 @@ import {
|
|
|
91
110
|
createSession,
|
|
92
111
|
} from "perso-interactive-sdk-web/client";
|
|
93
112
|
|
|
94
|
-
const apiServer = "https://
|
|
113
|
+
const apiServer = "https://platform.perso.ai";
|
|
95
114
|
const apiKey = "YOUR_API_KEY"; // ⚠️ NEVER commit or expose this in production
|
|
96
115
|
|
|
97
116
|
const sessionId = await createSessionId(apiServer, apiKey, {
|
|
@@ -102,6 +121,8 @@ const sessionId = await createSessionId(apiServer, apiKey, {
|
|
|
102
121
|
tts_type: "<tts_name>",
|
|
103
122
|
stt_type: "<stt_name>",
|
|
104
123
|
// text_normalization_config: "<textnormalizationconfig_id>", // optional
|
|
124
|
+
// stt_text_normalization_config: "<textnormalizationconfig_id>", // optional
|
|
125
|
+
// stt_text_normalization_locale: "ko", // optional
|
|
105
126
|
});
|
|
106
127
|
|
|
107
128
|
const session = await createSession(apiServer, sessionId, 1920, 1080, []);
|
|
@@ -123,7 +144,7 @@ import {
|
|
|
123
144
|
ChatState,
|
|
124
145
|
} from "perso-interactive-sdk-web/client";
|
|
125
146
|
|
|
126
|
-
const apiServer = "https://
|
|
147
|
+
const apiServer = "https://platform.perso.ai";
|
|
127
148
|
|
|
128
149
|
// Obtain sessionId from your server (see Express.js example above)
|
|
129
150
|
const sessionId = await fetch("/api/session", { method: "POST" })
|
|
@@ -169,7 +190,7 @@ const audioBlob = await session.processTTS(llmResponse);
|
|
|
169
190
|
|
|
170
191
|
// 3. Animate avatar with audio
|
|
171
192
|
if (audioBlob) {
|
|
172
|
-
await session.processSTF(audioBlob,
|
|
193
|
+
await session.processSTF(audioBlob, audioBlob.type, llmResponse);
|
|
173
194
|
}
|
|
174
195
|
```
|
|
175
196
|
|
|
@@ -243,7 +264,7 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
243
264
|
<script src="https://cdn.jsdelivr.net/npm/perso-interactive-sdk-web@latest/dist/client/index.iife.js"></script>
|
|
244
265
|
<script>
|
|
245
266
|
async function start() {
|
|
246
|
-
const apiServer = "https://
|
|
267
|
+
const apiServer = "https://platform.perso.ai";
|
|
247
268
|
|
|
248
269
|
// Obtain sessionId from your server (see Express.js example above)
|
|
249
270
|
const sessionId = await fetch("/api/session", { method: "POST" })
|
|
@@ -278,15 +299,31 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
278
299
|
|
|
279
300
|
### Server Exports
|
|
280
301
|
|
|
281
|
-
| Export
|
|
282
|
-
|
|
|
283
|
-
| `createSessionId(apiServer, apiKey, sessionTemplateId)`
|
|
284
|
-
| `createSessionId(apiServer, apiKey, params)`
|
|
285
|
-
| `getIntroMessage(apiServer, apiKey, promptId)`
|
|
286
|
-
| `
|
|
287
|
-
| `
|
|
288
|
-
| `
|
|
289
|
-
| `
|
|
302
|
+
| Export | Description |
|
|
303
|
+
| ---------------------------------------------------------- | ---------------------------------------------------- |
|
|
304
|
+
| `createSessionId(apiServer, apiKey, sessionTemplateId)` | Create a session ID from a SessionTemplate |
|
|
305
|
+
| `createSessionId(apiServer, apiKey, params)` | Create a new session ID |
|
|
306
|
+
| `getIntroMessage(apiServer, apiKey, promptId)` | Get intro message for a prompt |
|
|
307
|
+
| `getLLMs(apiServer, apiKey)` | Get available LLM providers |
|
|
308
|
+
| `getTTSs(apiServer, apiKey)` | Get available TTS providers |
|
|
309
|
+
| `getSTTs(apiServer, apiKey)` | Get available STT providers |
|
|
310
|
+
| `getModelStyles(apiServer, apiKey)` | Get available avatar styles |
|
|
311
|
+
| `getBackgroundImages(apiServer, apiKey)` | Get available backgrounds |
|
|
312
|
+
| `getPrompts(apiServer, apiKey)` | Get available prompts |
|
|
313
|
+
| `getDocuments(apiServer, apiKey)` | Get available documents |
|
|
314
|
+
| `getMcpServers(apiServer, apiKey)` | Get available MCP servers |
|
|
315
|
+
| `getTextNormalizations(apiServer, apiKey)` | Get available text normalization configs |
|
|
316
|
+
| `getTextNormalization(apiServer, apiKey, configId)` | Download text normalization ruleset (pre-signed URL) |
|
|
317
|
+
| `getAllSettings(apiServer, apiKey)` | Get all settings at once |
|
|
318
|
+
| `getSessionTemplates(apiServer, apiKey)` | Get available session templates |
|
|
319
|
+
| `getSessionTemplate(apiServer, apiKey, sessionTemplateId)` | Get a single session template by ID |
|
|
320
|
+
| `getSessionInfo(apiServer, sessionId)` | Get session metadata |
|
|
321
|
+
| `makeTTS(apiServer, params)` | Generate TTS audio from text (standalone) |
|
|
322
|
+
| `PersoUtilServer` | Low-level API utilities |
|
|
323
|
+
| `ApiError` | Error class for API errors |
|
|
324
|
+
| `SessionCreationError` | Error class for session creation failures (extends `ApiError`) |
|
|
325
|
+
| `DoesNotExistError` | Session creation referenced a non-existent resource (extends `SessionCreationError`) |
|
|
326
|
+
| `NotInOrganizationError` | Session creation referenced a resource not assigned to the org (extends `SessionCreationError`) |
|
|
290
327
|
|
|
291
328
|
### Client Exports
|
|
292
329
|
|
|
@@ -318,6 +355,9 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
318
355
|
| `STTError` | Error class for STT errors |
|
|
319
356
|
| `TTSError` | Error class for TTS errors |
|
|
320
357
|
| `TTSDecodeError` | Error class for TTS decode errors |
|
|
358
|
+
| `SessionCreationError` | Error class for session creation failures (extends `ApiError`) |
|
|
359
|
+
| `DoesNotExistError` | Session creation referenced a non-existent resource (extends `SessionCreationError`) |
|
|
360
|
+
| `NotInOrganizationError` | Session creation referenced a resource not assigned to the org (extends `SessionCreationError`) |
|
|
321
361
|
| `LlmProcessor` | Standalone LLM streaming processor |
|
|
322
362
|
| `WavRecorder` | Audio recorder producing WAV files |
|
|
323
363
|
| `createWavRecorder(options?)` | Factory function for WavRecorder |
|
|
@@ -333,11 +373,12 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
333
373
|
| `processLLM(options)` | Stream LLM responses with full control |
|
|
334
374
|
| `processTTSTF(message)` | Speak a message without LLM |
|
|
335
375
|
| `processTTS(message, options?)` | Generate TTS audio from text (returns Blob). Options: `resample`, `locale`, `output_format` |
|
|
336
|
-
| `processSTF(file, format
|
|
376
|
+
| `processSTF(file, format?, message?)` | Send audio to the STF pipeline. `format` accepts canonical (`'wav'` / `'mp3'`) or MIME (`'audio/wav'`, `'audio/mpeg'`, …); when omitted, derived from `file.type` |
|
|
337
377
|
| `startProcessSTT(timeout?)` | Start recording voice for STT |
|
|
338
378
|
| `stopProcessSTT(language?)` | Stop recording and get text |
|
|
339
379
|
| `isSTTRecording()` | Check if STT recording is in progress |
|
|
340
380
|
| `transcribeAudio(audio, language?)` | Transcribe audio Blob/File to text |
|
|
381
|
+
| `transcribeAudioDetailed(audio, language?)` | Transcribe audio Blob/File and return full `STTResponse` (text + locale + normalized_text) |
|
|
341
382
|
| `getMessageHistory()` | Get LLM conversation history |
|
|
342
383
|
| `getRemoteStream()` | Get AI human's media stream |
|
|
343
384
|
| `getLocalStream()` | ~~Get user's audio stream~~ (Deprecated) |
|
|
@@ -358,7 +399,7 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
358
399
|
| ------------------------ | -------------- | ---------------------------------------------- |
|
|
359
400
|
| `lastRecordedAudioFile` | `File \| null` | Last recorded WAV audio file from STT |
|
|
360
401
|
|
|
361
|
-
For detailed API documentation, see [
|
|
402
|
+
For detailed API documentation, see the [API Reference site](https://perso-ai.github.io/perso-interactive-sdk-web/docs/api/).
|
|
362
403
|
|
|
363
404
|
## License
|
|
364
405
|
|
package/dist/client/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t,e,s=require("emoji-regex");class a extends Error{constructor(){super("WebRTC connection timeout")}}class r 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 o extends Error{description;constructor(t){super(),this.description=t}}class i extends Error{underlyingError;constructor(t){super(`STT Error: ${t.detail}`),this.underlyingError=t}}class l extends Error{underlyingError;constructor(t){super(t.message),this.underlyingError=t}}class c extends Error{description;constructor(t){super(`TTS decode error: ${t}`),this.description=t}}!function(t){t.LLM="LLM",t.TTS="TTS",t.STT="STT",t.STF_ONPREMISE="STF_ONPREMISE",t.STF_WEBRTC="STF_WEBRTC"}(t||(t={})),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"}(e||(e={}));class h{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 n=await fetch(`${t}/api/v1/session/${e}/llm/v2/`,{body:JSON.stringify(s),headers:{"Content-Type":"application/json"},method:"POST",signal:a});if(!n.ok){const t=await n.json(),e=t.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${n.status} with no error details`,attr:null};throw new r(n.status,e.code,e.detail,e.attr)}return n.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 r(t.status,s.code,s.detail,s.attr)}}}class d extends Error{constructor(t){super(`WAV parse error: ${t}`),this.name="WavParseError"}}function u(t,e,s=1){const a=2*t.length,r=new ArrayBuffer(44+a),n=new DataView(r);g(n,0,"RIFF"),n.setUint32(4,36+a,!0),g(n,8,"WAVE"),g(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),g(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 p(t,e,s){let a="";for(let r=0;r<s;r++)a+=String.fromCharCode(t.getUint8(e+r));return a}function g(t,e,s){for(let a=0;a<s.length;a++)t.setUint8(e+a,s.charCodeAt(a))}function m(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 S extends Error{constructor(t){super(`Audio resample error: ${t}`),this.name="AudioResampleError"}}async function f(t,e,s,a=1){if(0===t.length)throw new S("Cannot resample empty audio data");if(e<=0||s<=0)throw new S(`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 S(`Failed to resample audio from ${e}Hz to ${s}Hz: ${a}`)}}const y=16e3;async function w(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 c("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 d("File too small to be a valid WAV");if("RIFF"!==p(e,0,4))throw new d("Missing RIFF header");if("WAVE"!==p(e,8,4))throw new d("Missing WAVE format identifier");let s=12,a=!1,r=0,n=0,o=0,i=0;for(;s<t.byteLength-8;){const l=p(e,s,4),c=e.getUint32(s+4,!0);if("fmt "===l){if(s+24>t.byteLength)throw new d("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 d("Missing fmt chunk");if(1!==r)throw new d(`Unsupported audio format: ${r} (only PCM format 1 is supported)`);if(0===n||n>8)throw new d(`Invalid channel count: ${n}`);if(0===o||o>192e3)throw new d(`Invalid sample rate: ${o}`);if(![8,16,24,32].includes(i))throw new d(`Unsupported bits per sample: ${i}`);let l=-1,c=0;for(;s<t.byteLength-8;){const t=p(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 d("Missing data chunk");const h=t.byteLength-l,u=Math.min(c,h),g=i/8,S=Math.floor(u/(g*n)),f=new Float32Array(S*n);let y=0;for(let s=0;s<S*n;s++){const a=l+s*g;if(a+g>t.byteLength)break;f[y++]=Math.max(-1,Math.min(1,m(e,a,i)))}if(2===n){const t=new Float32Array(S);for(let e=0;e<S;e++)t[e]=(f[2*e]+f[2*e+1])/2;return{sampleRate:o,channels:1,bitsPerSample:i,samples:t}}return{sampleRate:o,channels:n,bitsPerSample:i,samples:f.slice(0,y)}}(t);if(e.sampleRate===y)return{samples:e.samples,sampleRate:e.sampleRate};return{samples:await f(e.samples,e.sampleRate,y,e.channels),sampleRate:y}}const s=new AudioContext({sampleRate:y});try{const e=await s.decodeAudioData(t.slice(0));if(e.sampleRate===y)return{samples:e.getChannelData(0),sampleRate:y};return{samples:await f(e.getChannelData(0),e.sampleRate,y,1),sampleRate:y}}finally{await s.close()}}(s,a),e=u(t.samples,y,1);return new Blob([e],{type:"audio/wav"})}catch{return new Blob([s],{type:a})}}const T=s();function C(t){return t.replace(T,"")}var E;exports.ChatState=void 0,(E=exports.ChatState||(exports.ChatState={})).RECORDING="RECORDING",E.LLM="LLM",E.ANALYZING="ANALYZING",E.SPEAKING="SPEAKING",E.TTS="TTS";class v{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 i=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 h.makeLLM(this.config.apiServer,this.config.sessionId,{messages:l,tools:s},t.signal)}catch(t){if(t instanceof r)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,r=t.some(t=>!t.chatTool.executeOnly);if(s||r){if(i++,i>=10)return void(yield{type:"error",error:new n(new o("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="",i="";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 o("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 o("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==a.status)return e.streamingError=new n(new o(a.reason)),void(yield{type:"error",error:e.streamingError});if(i.length>0&&"message"!=a.type){e.newMessageHistory.push({role:"assistant",type:"message",content:i}),i="";const t=l();t&&(yield t)}if("message"===a.type){const t=C(a.content);i+=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 n(new o("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 o("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==t.status)return e.streamingError=new n(new o(t.reason)),void(yield{type:"error",error:e.streamingError});if("message"===t.type){const s=C(t.content);i+=s,e.message+=s,e.allChunks.push(s)}else if("tool_call"===t.type&&null!=t.tool_calls){if(i.length>0){e.newMessageHistory.push({role:"assistant",type:"message",content:i}),i="";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}}}i.length>0&&e.newMessageHistory.push({role:"assistant",type:"message",content:i})}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 I=`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 R{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(I),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 f(a,e,this.targetSampleRate,this.channels),n=this.targetSampleRate):(r=a,n=e);const i=u(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 _ 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>5e3&&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(s,a,r,n,o){const i=await h.getSessionInfo(s,a);if(!(Array.isArray(i.capability)&&i.capability.some(e=>e.name===t.STF_ONPREMISE||e.name===t.STF_WEBRTC)))return await h.sessionEvent(s,a,e.SESSION_START),null;const l=await h.getIceServers(s,a);let c=await _.createPeerConnection(l),d=c.createDataChannel("message",{protocol:"message"}),u=new _(c,d);o?o.getTracks().forEach(function(t){c.addTrack(t,o)}):c.addTransceiver("audio",{direction:"recvonly"});const p=c.addTransceiver("video",{direction:"recvonly"}),g=RTCRtpReceiver.getCapabilities("video");null!=g&&p.setCodecPreferences(g.codecs);const m=await c.createOffer();await c.setLocalDescription(m);const S=await h.exchangeSDP(s,a,m);return await c.setRemoteDescription(S),await _.waitFor(()=>u.isReady(),100,50),u.changeSize(r,n),u}static async createPeerConnection(t){return new RTCPeerConnection({sdpSemantics:"unified-plan",iceServers:t})}static async waitFor(t,e,s){let r=0;if(await new Promise(a=>{const n=setInterval(()=>{r+=1,r>=s&&(clearInterval(n),a("bad")),t()&&(clearInterval(n),a("good"))},e)}),r>=s)throw new a}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"))})},_.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>_.BACKPRESSURE_THRESHOLD)return o.bufferedAmountLowThreshold=_.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 w(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 x{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 v({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){const s=t instanceof File?t:new File([t],"audio.wav",{type:t.type});try{return(await h.makeSTT(this.apiServer,this.sessionId,s,e)).text}catch(t){if(t instanceof r)throw new i(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=await this.perso.stf(t,e,s);return this.pipelineSuppressed?(this.setChatState(null,exports.ChatState.ANALYZING),a):a}catch(t){throw this.setChatState(null,exports.ChatState.ANALYZING),t}}async processTTS(t,e={}){const{resample:s=!1,locale:a,output_format:n}=e,o=C(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},...n&&{output_format:n}},{audio:e}=await h.makeTTS(this.apiServer,t);if(this.pipelineSuppressed)return;return await w(e,s)}catch(t){t instanceof r||t instanceof c?this.setError(new l(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 R({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 h.makeSTT(this.apiServer,this.sessionId,e,t)).text}catch(t){if(t instanceof r)throw new i(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 r(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 i=a.body?.getReader(),l=new TextDecoder("utf-8");let c="",h=null,d="";for(;;){const{done:t,value:e}=await i.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 o("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 o(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+=C(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=C(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 s="object"==typeof t?JSON.stringify(t):t;await h.sessionEvent(this.apiServer,this.sessionId,e.SESSION_LOG,s)}setError(t){this.errorHandler.dispatchEvent(new CustomEvent("error",{detail:{error:t}}))}close(){this.stopHeartbeat(),this.perso?.closeSelf()}startHeartbeat(){const t=async()=>{try{await h.sessionEvent(this.apiServer,this.sessionId,e.SESSION_DURING),null!==this.heartbeatIntervalId&&(this.heartbeatIntervalId=setTimeout(t,1e4))}catch(t){t instanceof r?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 L(t,e,s,a,r,n){if("boolean"!=typeof r){const n=await _.create(t,e,s,a);return new x(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 _.create(t,e,s,a,i);if(!c)return l(),new x(t,e,null,o);const h=new x(t,e,c,o,{stream:i,legacyVoiceChatMode:!0});return h.onClose(()=>{l()}),h}async function M(t,e){return await h.getLLMs(t,e)}async function b(t,e){return await h.getTTSs(t,e)}async function N(t,e){return await h.getSTTs(t,e)}async function A(t,e){return await h.getModelStyles(t,e)}async function k(t,e){return await h.getBackgroundImages(t,e)}async function O(t,e){return await h.getPrompts(t,e)}async function P(t,e){return await h.getDocuments(t,e)}async function H(t,e){return await h.getTextNormalizations(t,e)}async function F(t,e){return await h.getMcpServers(t,e)}exports.ApiError=r,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.LLMError=n,exports.LLMStreamingResponseError=o,exports.LlmProcessor=v,exports.STTError=i,exports.Session=x,exports.TTSDecodeError=c,exports.TTSError=l,exports.TTS_TARGET_SAMPLE_RATE=y,exports.WavRecorder=R,exports.createSession=async function(t,e,s,a,r,n){return"boolean"==typeof r?await L(t,e,s,a,r,n??[]):await L(t,e,s,a,r)},exports.createSessionId=async function(e,s,a){let r;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"),"string"==typeof a){const n=await h.getSessionTemplate(e,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(e){const s=t=>e.capability.some(e=>e.name===t);return{using_stf_webrtc:s(t.STF_WEBRTC),model_style:e.model_style.name,prompt:e.prompt.prompt_id,document:e.document?.document_id,background_image:e.background_image?.backgroundimage_id,mcp_servers:e.mcp_servers?.length?e.mcp_servers.map(t=>t.mcpserver_id):[],llm_type:s(t.LLM)?e.llm_type.name:void 0,tts_type:s(t.TTS)?e.tts_type.name:void 0,stt_type:s(t.STT)?e.stt_type.name:void 0,text_normalization_config:e.text_normalization_config?.textnormalizationconfig_id,text_normalization_locale:e.text_normalization_locale,padding_left:e.padding_left??void 0,padding_top:e.padding_top??void 0,padding_height:e.padding_height??void 0}}(n)}else r=a;const n={capability:[],...r};r.using_stf_webrtc&&n.capability.push(t.STF_WEBRTC),r?.llm_type&&(n.capability.push(t.LLM),n.llm_type=r.llm_type),r?.tts_type&&(n.capability.push(t.TTS),n.tts_type=r.tts_type),r?.stt_type&&(n.capability.push(t.STT),n.stt_type=r.stt_type);const o=await fetch(`${e}/api/v1/session/`,{body:JSON.stringify(n),headers:{"PersoLive-APIKey":s,"Content-Type":"application/json"},method:"POST"});return(await h.parseJson(o)).session_id},exports.createWavRecorder=function(t){return new R(t)},exports.getAllSettings=async function(t,e){const[s,a,r,n,o,i,l,c,h]=await Promise.all([M(t,e),b(t,e),N(t,e),A(t,e),k(t,e),O(t,e),P(t,e),F(t,e),H(t,e).catch(()=>[])]);return{llms:s,ttsTypes:a,sttTypes:r,modelStyles:n,backgroundImages:o,prompts:i,documents:l,mcpServers:c,textNormalizations:h}},exports.getBackgroundImages=k,exports.getDocuments=P,exports.getLLMs=M,exports.getMcpServers=F,exports.getModelStyles=A,exports.getPrompts=O,exports.getSTTs=N,exports.getSessionInfo=async function(t,e){return await h.getSessionInfo(t,e)},exports.getSessionTemplates=async function(t,e){return await h.getSessionTemplates(t,e)},exports.getTTSs=b,exports.getTextNormalization=async function(t,e,s){return await h.downloadTextNormalization(t,e,s)},exports.getTextNormalizations=H,exports.getWavSampleRate=function(t){const e=new DataView(t);if(t.byteLength<28)throw new d("File too small to be a valid WAV");if("RIFF"!==p(e,0,4))throw new d("Missing RIFF header");let s=12;for(;s<t.byteLength-8;){const a=p(e,s,4),r=e.getUint32(s+4,!0);if("fmt "===a){if(s+16>t.byteLength)throw new d("fmt chunk extends beyond file");return e.getUint32(s+12,!0)}const n=s+8+r;if(n<=s)break;s=n}throw new d("Missing fmt chunk")},exports.makeTTS=async function(t,e){return await h.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 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)};
|
package/dist/client/index.d.ts
CHANGED
|
@@ -41,6 +41,17 @@ interface STTType {
|
|
|
41
41
|
service: string;
|
|
42
42
|
options?: unknown | null;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Response from POST /api/v1/session/{session_id}/stt/.
|
|
46
|
+
* Mirrors STTResponse in the OpenAPI schema. All three fields are required;
|
|
47
|
+
* `normalized_text` may be empty or equal to `text` when the session has no
|
|
48
|
+
* STT text-normalization config configured.
|
|
49
|
+
*/
|
|
50
|
+
interface STTResponse {
|
|
51
|
+
text: string;
|
|
52
|
+
locale: string;
|
|
53
|
+
normalized_text: string;
|
|
54
|
+
}
|
|
44
55
|
interface TextNormalizationConfig {
|
|
45
56
|
textnormalizationconfig_id: string;
|
|
46
57
|
name: string;
|
|
@@ -92,6 +103,8 @@ interface SessionTemplate {
|
|
|
92
103
|
stt_type: STTType;
|
|
93
104
|
text_normalization_config?: TextNormalizationConfig | null;
|
|
94
105
|
text_normalization_locale?: string | null;
|
|
106
|
+
stt_text_normalization_config?: TextNormalizationConfig | null;
|
|
107
|
+
stt_text_normalization_locale?: string | null;
|
|
95
108
|
model_style: ModelStyle;
|
|
96
109
|
background_image: BackgroundImage | null;
|
|
97
110
|
agent: string | null;
|
|
@@ -380,7 +393,25 @@ declare class Session {
|
|
|
380
393
|
*/
|
|
381
394
|
processTTSTF(message: string): void;
|
|
382
395
|
transcribeAudio(audio: Blob | File, language?: string): Promise<string>;
|
|
383
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Same as transcribeAudio but returns the full STTResponse including
|
|
398
|
+
* locale and normalized_text from the OpenAPI contract.
|
|
399
|
+
*/
|
|
400
|
+
transcribeAudioDetailed(audio: Blob | File, language?: string): Promise<STTResponse>;
|
|
401
|
+
/**
|
|
402
|
+
* Sends an audio file for Speech-to-Face processing.
|
|
403
|
+
*
|
|
404
|
+
* `format` is optional — when omitted, undefined, or supplied as a MIME type
|
|
405
|
+
* (`audio/wav` / `audio/mpeg`), it's normalized via `normalizeAudioFormat`,
|
|
406
|
+
* which falls back to `file.type`. Pass `'wav'` or `'mp3'` directly when the
|
|
407
|
+
* source format is already known and authoritative.
|
|
408
|
+
*
|
|
409
|
+
* @param file Audio blob to lip-sync.
|
|
410
|
+
* @param format Optional format hint. Canonical (`'wav'`/`'mp3'`), MIME, or omitted.
|
|
411
|
+
* @param message Optional text caption.
|
|
412
|
+
* @returns File reference returned by the server.
|
|
413
|
+
*/
|
|
414
|
+
processSTF(file: Blob, format?: string, message?: string): Promise<string>;
|
|
384
415
|
processTTS(message: string, options?: {
|
|
385
416
|
resample?: boolean;
|
|
386
417
|
locale?: string;
|
|
@@ -727,6 +758,7 @@ declare function getDocuments(apiServer: string, apiKey: string): Promise<any>;
|
|
|
727
758
|
* @param apiServer Perso API server URL.
|
|
728
759
|
* @param apiKey API key used for authentication.
|
|
729
760
|
*/
|
|
761
|
+
declare function getMcpServers(apiServer: string, apiKey: string): Promise<any>;
|
|
730
762
|
/**
|
|
731
763
|
* Retrieves available text normalization options.
|
|
732
764
|
* @param apiServer Perso API server URL.
|
|
@@ -741,7 +773,6 @@ declare function getTextNormalizations(apiServer: string, apiKey: string): Promi
|
|
|
741
773
|
* @param configId Text Normalization Config ID.
|
|
742
774
|
*/
|
|
743
775
|
declare function getTextNormalization(apiServer: string, apiKey: string, configId: string): Promise<TextNormalizationDownload>;
|
|
744
|
-
declare function getMcpServers(apiServer: string, apiKey: string): Promise<any>;
|
|
745
776
|
/**
|
|
746
777
|
* Retrieves the list of session templates.
|
|
747
778
|
* @param apiServer Perso API server URL.
|
|
@@ -766,21 +797,6 @@ declare function getAllSettings(apiServer: string, apiKey: string): Promise<{
|
|
|
766
797
|
mcpServers: any;
|
|
767
798
|
textNormalizations: any;
|
|
768
799
|
}>;
|
|
769
|
-
/**
|
|
770
|
-
* Creates a Session with REST-based STT/TTS (current mode).
|
|
771
|
-
*/
|
|
772
|
-
declare function createSession(apiServer: string, sessionId: string, width: number, height: number, clientTools: Array<ChatTool>): Promise<Session>;
|
|
773
|
-
/**
|
|
774
|
-
* Creates a Session with bidirectional WebRTC audio (legacy mode).
|
|
775
|
-
* @deprecated Legacy voice chat mode will be removed in a future version.
|
|
776
|
-
* Use the 5-argument overload with REST-based STT/TTS instead.
|
|
777
|
-
*/
|
|
778
|
-
declare function createSession(apiServer: string, sessionId: string, width: number, height: number, enableVoiceChat: boolean, clientTools: Array<ChatTool>): Promise<Session>;
|
|
779
|
-
/**
|
|
780
|
-
* Retrieves metadata for an existing session.
|
|
781
|
-
* @param apiServer Perso API server URL.
|
|
782
|
-
* @param sessionId Session id to inspect.
|
|
783
|
-
*/
|
|
784
800
|
/**
|
|
785
801
|
* Sends text to the TTS API and returns Base64-encoded audio.
|
|
786
802
|
* @param apiServer Perso API server URL.
|
|
@@ -795,8 +811,24 @@ declare function makeTTS(apiServer: string, params: {
|
|
|
795
811
|
}): Promise<{
|
|
796
812
|
audio: string;
|
|
797
813
|
}>;
|
|
814
|
+
/**
|
|
815
|
+
* Retrieves metadata for an existing session.
|
|
816
|
+
* @param apiServer Perso API server URL.
|
|
817
|
+
* @param sessionId Session id to inspect.
|
|
818
|
+
*/
|
|
798
819
|
declare function getSessionInfo(apiServer: string, sessionId: string): Promise<any>;
|
|
799
820
|
|
|
821
|
+
/**
|
|
822
|
+
* Creates a Session with REST-based STT/TTS (current mode).
|
|
823
|
+
*/
|
|
824
|
+
declare function createSession(apiServer: string, sessionId: string, width: number, height: number, clientTools: Array<ChatTool>): Promise<Session>;
|
|
825
|
+
/**
|
|
826
|
+
* Creates a Session with bidirectional WebRTC audio (legacy mode).
|
|
827
|
+
* @deprecated Legacy voice chat mode will be removed in a future version.
|
|
828
|
+
* Use the 5-argument overload with REST-based STT/TTS instead.
|
|
829
|
+
*/
|
|
830
|
+
declare function createSession(apiServer: string, sessionId: string, width: number, height: number, enableVoiceChat: boolean, clientTools: Array<ChatTool>): Promise<Session>;
|
|
831
|
+
|
|
800
832
|
type CreateSessionIdBody = {
|
|
801
833
|
using_stf_webrtc: boolean;
|
|
802
834
|
model_style: string;
|
|
@@ -812,6 +844,8 @@ type CreateSessionIdBody = {
|
|
|
812
844
|
stt_type?: string;
|
|
813
845
|
text_normalization_config?: string;
|
|
814
846
|
text_normalization_locale?: string | null;
|
|
847
|
+
stt_text_normalization_config?: string;
|
|
848
|
+
stt_text_normalization_locale?: string | null;
|
|
815
849
|
};
|
|
816
850
|
/**
|
|
817
851
|
* Requests a new session creation ID by POSTing the desired runtime options to
|
|
@@ -828,7 +862,9 @@ type CreateSessionIdBody = {
|
|
|
828
862
|
* @param sessionTemplateId SessionTemplate ID to resolve configuration from.
|
|
829
863
|
* @returns Session ID returned by the server.
|
|
830
864
|
* @throws {Error} If the template's `model_style.platform_type` is not `"webrtc"`.
|
|
831
|
-
* @throws {
|
|
865
|
+
* @throws {SessionCreationError} When the API returns an error during session creation.
|
|
866
|
+
* @throws {DoesNotExistError} When the server response `code` is `'does_not_exist'` (subclass of `SessionCreationError`).
|
|
867
|
+
* @throws {NotInOrganizationError} When the server response `code` is `'not_in_organization'` (subclass of `SessionCreationError`).
|
|
832
868
|
*/
|
|
833
869
|
declare function createSessionId(apiServer: string, apiKey: string, sessionTemplateId: string): Promise<string>;
|
|
834
870
|
/**
|
|
@@ -867,10 +903,45 @@ declare class TTSDecodeError extends Error {
|
|
|
867
903
|
description: string;
|
|
868
904
|
constructor(description: string);
|
|
869
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* Domain error thrown by `createSessionId()` (and the `getSessionTemplate`
|
|
908
|
+
* path) when the underlying API returns an `ApiError`. The raw
|
|
909
|
+
* `errorCode`, `code`, `detail`, and `attr` fields are preserved as-is —
|
|
910
|
+
* callers inspect them to decide how to react (e.g. treat
|
|
911
|
+
* `code === 'invalid'` with a "not found" detail as feature-unavailable
|
|
912
|
+
* per LIV-1681).
|
|
913
|
+
*
|
|
914
|
+
* Extends `ApiError`, so existing `instanceof ApiError` branches keep
|
|
915
|
+
* working.
|
|
916
|
+
*/
|
|
917
|
+
declare class SessionCreationError extends ApiError {
|
|
918
|
+
constructor(source: ApiError);
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Session creation failed because a referenced resource does not exist.
|
|
922
|
+
* Triggered when the server returns `code === 'does_not_exist'` — for
|
|
923
|
+
* example, a `prompt_id` that has been deleted or never existed. The
|
|
924
|
+
* `attr` field, when present, identifies which input field referenced
|
|
925
|
+
* the missing resource (e.g. `'prompt'`).
|
|
926
|
+
*/
|
|
927
|
+
declare class DoesNotExistError extends SessionCreationError {
|
|
928
|
+
constructor(source: ApiError);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Session creation failed because a referenced resource is not assigned
|
|
932
|
+
* to the caller's organization. Triggered when the server returns
|
|
933
|
+
* `code === 'not_in_organization'` — for example, an LLM/TTS/STT type
|
|
934
|
+
* that exists in the platform catalog but is not enabled for this
|
|
935
|
+
* organization. The `attr` field, when present, identifies which input
|
|
936
|
+
* field referenced the unavailable resource.
|
|
937
|
+
*/
|
|
938
|
+
declare class NotInOrganizationError extends SessionCreationError {
|
|
939
|
+
constructor(source: ApiError);
|
|
940
|
+
}
|
|
870
941
|
|
|
871
942
|
declare function getWavSampleRate(arrayBuffer: ArrayBuffer): number;
|
|
872
943
|
|
|
873
944
|
declare const TTS_TARGET_SAMPLE_RATE = 16000;
|
|
874
945
|
|
|
875
|
-
export { ApiError, ChatState, ChatTool, LLMError, LLMStreamingResponseError, LlmProcessor, STTError, Session, TTSDecodeError, TTSError, TTS_TARGET_SAMPLE_RATE, WavRecorder, createSession, createSessionId, createWavRecorder, getAllSettings, getBackgroundImages, getDocuments, getLLMs, getMcpServers, getModelStyles, getPrompts, getSTTs, getSessionInfo, getSessionTemplates, getTTSs, getTextNormalization, getTextNormalizations, getWavSampleRate, makeTTS };
|
|
876
|
-
export type { AIHumanModelFile, BackgroundImage, Chat, Document, LLMStreamChunk, LLMType, LlmProcessorCallbacks, LlmProcessorConfig, MCPServer, ModelStyle, ModelStyleConfig, ProcessLLMOptions, Prompt, STTType, SessionCapability, SessionTemplate, TTSType, TextNormalizationConfig, TextNormalizationDownload, WavRecorderOptions };
|
|
946
|
+
export { ApiError, ChatState, ChatTool, DoesNotExistError, LLMError, LLMStreamingResponseError, LlmProcessor, NotInOrganizationError, STTError, Session, SessionCreationError, TTSDecodeError, TTSError, TTS_TARGET_SAMPLE_RATE, WavRecorder, createSession, createSessionId, createWavRecorder, getAllSettings, getBackgroundImages, getDocuments, getLLMs, getMcpServers, getModelStyles, getPrompts, getSTTs, getSessionInfo, getSessionTemplates, getTTSs, getTextNormalization, getTextNormalizations, getWavSampleRate, makeTTS };
|
|
947
|
+
export type { AIHumanModelFile, BackgroundImage, Chat, Document, LLMStreamChunk, LLMType, LlmProcessorCallbacks, LlmProcessorConfig, MCPServer, ModelStyle, ModelStyleConfig, ProcessLLMOptions, Prompt, STTResponse, STTType, SessionCapability, SessionTemplate, TTSType, TextNormalizationConfig, TextNormalizationDownload, WavRecorderOptions };
|