perso-interactive-sdk-web 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -51
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.ts +280 -80
- 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 +73 -2
- package/dist/server/index.js +1 -1
- package/package.json +94 -93
package/README.md
CHANGED
|
@@ -17,16 +17,74 @@ pnpm add perso-interactive-sdk-web
|
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
20
|
+
> 📖 **Looking for step-by-step examples?** See the [Example Guide](https://github.com/perso-ai/perso-interactive-sdk-web/blob/master/packages/perso-interactive-sdk/example-guide/en/README.md) for annotated code snippets covering LLM, TTS, STT, STF, and full pipeline patterns.
|
|
21
|
+
|
|
20
22
|
The SDK provides two entry points:
|
|
21
23
|
|
|
22
24
|
### Server-side (`perso-interactive-sdk-web/server`)
|
|
23
25
|
|
|
24
|
-
Use this module in Node.js
|
|
26
|
+
Use this module in Node.js server environments to create sessions securely without exposing your API key. The client examples below (ES Module, TypeScript, IIFE) all call this server endpoint to obtain a `sessionId`.
|
|
27
|
+
|
|
28
|
+
#### Express.js Example
|
|
29
|
+
|
|
30
|
+
This example uses [Express](https://www.npmjs.com/package/express). Install the required packages:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# npm
|
|
34
|
+
npm install express perso-interactive-sdk-web
|
|
35
|
+
|
|
36
|
+
# yarn
|
|
37
|
+
yarn add express perso-interactive-sdk-web
|
|
38
|
+
|
|
39
|
+
# pnpm
|
|
40
|
+
pnpm add express perso-interactive-sdk-web
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// server.js
|
|
45
|
+
const express = require("express");
|
|
46
|
+
const { createSessionId } = require("perso-interactive-sdk-web/server");
|
|
47
|
+
|
|
48
|
+
const app = express();
|
|
49
|
+
|
|
50
|
+
const API_SERVER = "https://live-api.perso.ai";
|
|
51
|
+
const API_KEY = process.env.PERSO_INTERACTIVE_API_KEY;
|
|
52
|
+
|
|
53
|
+
app.post("/api/session", async (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const sessionId = await createSessionId(API_SERVER, API_KEY, {
|
|
56
|
+
using_stf_webrtc: true,
|
|
57
|
+
model_style: "<model_style_name>",
|
|
58
|
+
prompt: "<prompt_id>",
|
|
59
|
+
llm_type: "<llm_name>",
|
|
60
|
+
tts_type: "<tts_name>",
|
|
61
|
+
stt_type: "<stt_name>",
|
|
62
|
+
});
|
|
63
|
+
res.json({ sessionId });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("Session creation failed:", error);
|
|
66
|
+
res.status(500).json({ error: "Failed to create session" });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
app.listen(3000, () => console.log("Server running on port 3000"));
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
> ⚠️ **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.
|
|
74
|
+
|
|
75
|
+
#### Client-side Testing Only
|
|
76
|
+
|
|
77
|
+
> ⚠️ **Warning**: The following example exposes your API key in the browser. Use this **only for local testing**. Never deploy this to production. If your API key is compromised due to client-side usage, the SDK provider assumes no responsibility.
|
|
25
78
|
|
|
26
79
|
```typescript
|
|
27
|
-
import {
|
|
80
|
+
import {
|
|
81
|
+
createSessionId,
|
|
82
|
+
createSession,
|
|
83
|
+
} from "perso-interactive-sdk-web/client";
|
|
84
|
+
|
|
85
|
+
const apiServer = "https://live-api.perso.ai";
|
|
86
|
+
const apiKey = "YOUR_API_KEY"; // ⚠️ NEVER commit or expose this in production
|
|
28
87
|
|
|
29
|
-
// Create a session on the server
|
|
30
88
|
const sessionId = await createSessionId(apiServer, apiKey, {
|
|
31
89
|
using_stf_webrtc: true,
|
|
32
90
|
model_style: "<model_style_name>",
|
|
@@ -36,12 +94,10 @@ const sessionId = await createSessionId(apiServer, apiKey, {
|
|
|
36
94
|
stt_type: "<stt_name>",
|
|
37
95
|
});
|
|
38
96
|
|
|
39
|
-
|
|
40
|
-
|
|
97
|
+
const session = await createSession(apiServer, sessionId, 1920, 1080, []);
|
|
98
|
+
session.setSrc(document.getElementById("video"));
|
|
41
99
|
```
|
|
42
100
|
|
|
43
|
-
> ⚠️ **Security Warning**: Never use `createSessionId` on the client-side. 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.
|
|
44
|
-
|
|
45
101
|
### Client-side (`perso-interactive-sdk-web/client`)
|
|
46
102
|
|
|
47
103
|
Use this module in browser environments to create and manage interactive sessions.
|
|
@@ -51,18 +107,17 @@ import {
|
|
|
51
107
|
createSession,
|
|
52
108
|
ChatTool,
|
|
53
109
|
ChatState,
|
|
54
|
-
getAllSettings,
|
|
55
110
|
} from "perso-interactive-sdk-web/client";
|
|
56
111
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
);
|
|
112
|
+
const apiServer = "https://live-api.perso.ai";
|
|
113
|
+
|
|
114
|
+
// Obtain sessionId from your server (see Express.js example above)
|
|
115
|
+
const sessionId = await fetch("/api/session", { method: "POST" })
|
|
116
|
+
.then((res) => res.json())
|
|
117
|
+
.then((data) => data.sessionId);
|
|
118
|
+
|
|
119
|
+
// Create a session
|
|
120
|
+
const session = await createSession(apiServer, sessionId, 1920, 1080, []);
|
|
66
121
|
|
|
67
122
|
// Bind to video element
|
|
68
123
|
session.setSrc(videoElement);
|
|
@@ -80,8 +135,10 @@ session.subscribeChatLog((chatLog) => {
|
|
|
80
135
|
// Send a message
|
|
81
136
|
session.processChat("Hello!");
|
|
82
137
|
|
|
83
|
-
//
|
|
84
|
-
session.
|
|
138
|
+
// Voice chat using STT
|
|
139
|
+
await session.startProcessSTT();
|
|
140
|
+
const text = await session.stopProcessSTT();
|
|
141
|
+
session.processChat(text);
|
|
85
142
|
|
|
86
143
|
// Stop session
|
|
87
144
|
session.stopSession();
|
|
@@ -116,29 +173,46 @@ const session = await createSession(
|
|
|
116
173
|
sessionId,
|
|
117
174
|
width,
|
|
118
175
|
height,
|
|
119
|
-
|
|
120
|
-
[weatherTool],
|
|
176
|
+
[weatherTool]
|
|
121
177
|
);
|
|
122
178
|
```
|
|
123
179
|
|
|
124
180
|
### Browser (IIFE)
|
|
125
181
|
|
|
126
|
-
For direct browser usage via script tag:
|
|
182
|
+
For direct browser usage via `<script>` tag without a bundler. The SDK exposes a global `PersoInteractive` namespace:
|
|
127
183
|
|
|
128
184
|
```html
|
|
129
|
-
<script src="https://cdn.jsdelivr.net/npm/perso-interactive-sdk-web@
|
|
185
|
+
<script src="https://cdn.jsdelivr.net/npm/perso-interactive-sdk-web@latest/dist/client/index.iife.js"></script>
|
|
130
186
|
<script>
|
|
131
|
-
|
|
132
|
-
apiServer
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
187
|
+
async function start() {
|
|
188
|
+
const apiServer = "https://live-api.perso.ai";
|
|
189
|
+
|
|
190
|
+
// Obtain sessionId from your server (see Express.js example above)
|
|
191
|
+
const sessionId = await fetch("/api/session", { method: "POST" })
|
|
192
|
+
.then((res) => res.json())
|
|
193
|
+
.then((data) => data.sessionId);
|
|
194
|
+
|
|
195
|
+
const session = await PersoInteractive.createSession(
|
|
196
|
+
apiServer,
|
|
197
|
+
sessionId,
|
|
198
|
+
1920,
|
|
199
|
+
1080,
|
|
200
|
+
[]
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
session.setSrc(document.getElementById("video"));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
start();
|
|
139
207
|
</script>
|
|
140
208
|
```
|
|
141
209
|
|
|
210
|
+
> **Note**: The browser examples above call `POST /api/session` on your server. See the [Express.js example](#expressjs-example) for the server implementation. Never expose your API key in client-side code.
|
|
211
|
+
|
|
212
|
+
### Example Guide
|
|
213
|
+
|
|
214
|
+
> 📖 **Example Guide**: [English](https://github.com/perso-ai/perso-interactive-sdk-web/blob/master/packages/perso-interactive-sdk/example-guide/en/README.md)
|
|
215
|
+
|
|
142
216
|
## API Reference
|
|
143
217
|
|
|
144
218
|
### Server Exports
|
|
@@ -154,10 +228,10 @@ For direct browser usage via script tag:
|
|
|
154
228
|
|
|
155
229
|
| Export | Description |
|
|
156
230
|
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
157
|
-
| `createSession(apiServer, sessionId, width, height,
|
|
231
|
+
| `createSession(apiServer, sessionId, width, height, clientTools)` | Create a session |
|
|
158
232
|
| `Session` | Session class |
|
|
159
233
|
| `ChatTool` | Client tool class |
|
|
160
|
-
| `ChatState` | Enum for chat states (RECORDING, LLM, ANALYZING, SPEAKING) |
|
|
234
|
+
| `ChatState` | Enum for chat states (RECORDING, LLM, ANALYZING, SPEAKING, TTS) |
|
|
161
235
|
| `getLLMs(apiServer, apiKey)` | Get available LLM providers |
|
|
162
236
|
| `getTTSs(apiServer, apiKey)` | Get available TTS providers |
|
|
163
237
|
| `getSTTs(apiServer, apiKey)` | Get available STT providers |
|
|
@@ -168,30 +242,53 @@ For direct browser usage via script tag:
|
|
|
168
242
|
| `getMcpServers(apiServer, apiKey)` | Get available MCP servers |
|
|
169
243
|
| `getAllSettings(apiServer, apiKey)` | Get all settings at once |
|
|
170
244
|
| `getSessionInfo(apiServer, sessionId)` | Get session metadata |
|
|
245
|
+
| `createSessionId(apiServer, apiKey, params)` | Create session ID (exposes API key in browser) |
|
|
171
246
|
| `ApiError` | Error class for API errors |
|
|
172
247
|
| `LLMError` | Error class for LLM errors |
|
|
173
248
|
| `LLMStreamingResponseError` | Error class for streaming errors |
|
|
249
|
+
| `STTError` | Error class for STT errors |
|
|
250
|
+
| `TTSError` | Error class for TTS errors |
|
|
251
|
+
| `TTSDecodeError` | Error class for TTS decode errors |
|
|
252
|
+
| `LlmProcessor` | Standalone LLM streaming processor |
|
|
253
|
+
| `WavRecorder` | Audio recorder producing WAV files |
|
|
254
|
+
| `createWavRecorder(options?)` | Factory function for WavRecorder |
|
|
255
|
+
| `getWavSampleRate(wavData)` | Extract sample rate from WAV data |
|
|
256
|
+
| `TTS_TARGET_SAMPLE_RATE` | TTS target sample rate constant (16000) |
|
|
174
257
|
|
|
175
258
|
### Session Methods
|
|
176
259
|
|
|
177
|
-
| Method
|
|
178
|
-
|
|
|
179
|
-
| `setSrc(videoElement)`
|
|
180
|
-
| `processChat(message)`
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
190
|
-
| `
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
260
|
+
| Method | Description |
|
|
261
|
+
| ----------------------------------- | ---------------------------------------------- |
|
|
262
|
+
| `setSrc(videoElement)` | Bind session to video element |
|
|
263
|
+
| `processChat(message)` | Send a message to the LLM |
|
|
264
|
+
| `processLLM(options)` | Stream LLM responses with full control |
|
|
265
|
+
| `processTTSTF(message)` | Speak a message without LLM |
|
|
266
|
+
| `processTTS(message, options?)` | Generate TTS audio from text (returns Blob) |
|
|
267
|
+
| `processSTF(file, format, message)` | Send audio/video to STF pipeline |
|
|
268
|
+
| `startProcessSTT(timeout?)` | Start recording voice for STT |
|
|
269
|
+
| `stopProcessSTT(language?)` | Stop recording and get text |
|
|
270
|
+
| `isSTTRecording()` | Check if STT recording is in progress |
|
|
271
|
+
| `transcribeAudio(audio, language?)` | Transcribe audio Blob/File to text |
|
|
272
|
+
| `getMessageHistory()` | Get LLM conversation history |
|
|
273
|
+
| `getRemoteStream()` | Get AI human's media stream |
|
|
274
|
+
| `getLocalStream()` | ~~Get user's audio stream~~ (Deprecated) |
|
|
275
|
+
| `getSessionId()` | Get session ID |
|
|
276
|
+
| `clearBuffer()` | Stop AI human speaking |
|
|
277
|
+
| `changeSize(width, height)` | Resize the avatar canvas |
|
|
278
|
+
| `stopSession()` | Close the session |
|
|
279
|
+
| `subscribeChatStates(callback)` | Subscribe to state changes |
|
|
280
|
+
| `subscribeChatLog(callback)` | Subscribe to chat log updates |
|
|
281
|
+
| `setSttResultCallback(callback)` | Set STT result callback |
|
|
282
|
+
| `setErrorHandler(callback)` | Subscribe to errors |
|
|
283
|
+
| `onClose(callback)` | Subscribe to session close |
|
|
284
|
+
|
|
285
|
+
### Session Properties
|
|
286
|
+
|
|
287
|
+
| Property | Type | Description |
|
|
288
|
+
| ------------------------ | -------------- | ---------------------------------------------- |
|
|
289
|
+
| `lastRecordedAudioFile` | `File \| null` | Last recorded WAV audio file from STT |
|
|
290
|
+
|
|
291
|
+
For detailed API documentation, see [api-docs.md](https://github.com/perso-ai/perso-interactive-sdk-web/blob/master/core/api-docs.md).
|
|
195
292
|
|
|
196
293
|
## License
|
|
197
294
|
|
package/dist/client/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t,e=require("emoji-regex");class s extends Error{constructor(){super("WebRTC connection timeout")}}class a 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 r extends Error{underlyingError;constructor(t){super(),this.underlyingError=t}}class n extends Error{description;constructor(t){super(),this.description=t}}class i{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 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 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){const a=fetch(`${t}/api/v1/session/${e}/event/create/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detail:"",event:s})});await a}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 a(t.status,s.code,s.detail,s.attr)}}}class o 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(t,e,s,a,r){const n=await fetch(`${t}/api/v1/session/${e}/ice-servers/`,{method:"GET"}),c=(await i.parseJson(n)).ice_servers;let l=await o.createPeerConnection(c),h=l.createDataChannel("message",{protocol:"message"}),d=new o(l,h);s.getTracks().forEach(function(t){l.addTrack(t,s)});const p=l.addTransceiver("video",{direction:"recvonly"}),u=RTCRtpReceiver.getCapabilities("video");null!=u&&p.setCodecPreferences(u.codecs.filter(t=>"video/VP8"===t.mimeType));const S=await l.createOffer();await l.setLocalDescription(S);const g=await fetch(`${t}/api/v1/session/${e}/exchange/`,{body:JSON.stringify({client_sdp:S}),headers:{"Content-Type":"application/json"},method:"POST"}),m=await i.parseJson(g);return await l.setRemoteDescription(m.server_sdp),await o.waitFor(()=>d.isReady(),100,50),d.changeSize(a,r),d}static async createPeerConnection(t){return new RTCPeerConnection({sdpSemantics:"unified-plan",iceServers:t})}static async waitFor(t,e,a){let r=0;if(await new Promise(s=>{const n=setInterval(()=>{r+=1,r>=a&&(clearInterval(n),s("bad")),t()&&(clearInterval(n),s("good"))},e)}),r>=a)throw new s}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})}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)}}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"})}}exports.ChatState=void 0,(t=exports.ChatState||(exports.ChatState={})).RECORDING="RECORDING",t.LLM="LLM",t.ANALYZING="ANALYZING",t.SPEAKING="SPEAKING";class c{apiServer;sessionId;stream;perso;clientTools;chatStatesHandler=new EventTarget;chatLogHandler=new EventTarget;sttEventHandler=null;errorHandler=new EventTarget;lastStfTimeoutHandle=null;stfTotalDuration=0;stfTimeoutStartTime=0;messageHistory=[];chatLog=[];chatStateMap=new Map([[exports.ChatState.RECORDING,0],[exports.ChatState.LLM,0],[exports.ChatState.ANALYZING,0],[exports.ChatState.SPEAKING,0]]);emojiRegex=e();constructor(t,e,s,a,r){this.apiServer=t,this.sessionId=e,this.stream=s,this.perso=a,this.clientTools=r,this.resetChatState(),a.setMessageCallback("stf",t=>{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)}),a.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)}}),a.setMessageCallback("stt-error",t=>{this.setChatState(null,exports.ChatState.ANALYZING)})}llmJob=null;async processChat(t){0!==t.trim().length&&(this.addMessageToChatLog(t,!0),this.llmJob=this.processChatInternal(t))}processCustomChat(t){0!==t.trim().length&&this.processTTSTFInternal(t)}processTTSTF(t){0!==t.trim().length&&(this.messageHistory.push({role:"assistant",type:"message",content:t}),this.addMessageToChatLog(t,!1),this.processTTSTFInternal(t))}startVoiceChat(){return this.setChatState(exports.ChatState.RECORDING),this.perso.recordStart()}stopVoiceChat(){this.setChatState(exports.ChatState.ANALYZING,exports.ChatState.RECORDING),this.perso.recordEndStt()}changeSize(t,e){this.perso.changeSize(t,e)}async clearBuffer(){await this.clearLLMJob(),this.perso.clearBuffer(),null!==this.lastStfTimeoutHandle&&(clearTimeout(this.lastStfTimeoutHandle),this.lastStfTimeoutHandle=null),this.resetChatState()}setSrc(t){t.srcObject=this.getRemoteStream()}getLocalStream(){return this.stream}getRemoteStream(){return this.perso.getStream()}stopSession(){this.close()}onClose(t){return 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 i=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(!i.ok){const t=await i.json(),e=new r(new a(i.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 o=i.body?.getReader(),c=new TextDecoder("utf-8");let l="",h=null,d="";for(;;){const{done:t,value:e}=await o.read();if(t)break;let a;for(d+=c.decode(e,{stream:!0});-1!==(a=d.indexOf("\n"));){if(this.llmCancel)return l.length>0&&this.addMessageToChatLog(l,!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 r(new n("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 r(new n(e.reason));return this.setError(t),void this.setChatState(null,exports.ChatState.LLM)}l.length>0&&"message"!=e.type&&(s.push({role:"assistant",type:"message",content:l}),this.addMessageToChatLog(l,!1),l=""),"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):(l+=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;async clearLLMJob(){null!=this.llmJob&&(this.llmCancel=!0,await this.llmJob,this.llmCancel=!1)}processTTSTFInternal(t){const e=this.removeEmoji(t).trim();0!==e.length&&(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),i=this.exchangeChatStateMapToSet(s);this.chatStateMap=s,this.isEqualChatStateMap(n,i)||this.dispatchChatState(i)}resetChatState(){this.chatStateMap=new Map([[exports.ChatState.RECORDING,0],[exports.ChatState.LLM,0],[exports.ChatState.ANALYZING,0],[exports.ChatState.SPEAKING,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}setError(t){this.errorHandler.dispatchEvent(new CustomEvent("error",{detail:{error:t}}))}close(){this.perso.closeSelf()}removeEmoji(t){return t.replace(this.emojiRegex,"")}}async function l(t,e){return await i.getLLMs(t,e)}async function h(t,e){return await i.getTTSs(t,e)}async function d(t,e){return await i.getSTTs(t,e)}async function p(t,e){return await i.getModelStyles(t,e)}async function u(t,e){return await i.getBackgroundImages(t,e)}async function S(t,e){return await i.getPrompts(t,e)}async function g(t,e){return await i.getDocuments(t,e)}async function m(t,e){return await i.getMcpServers(t,e)}exports.ApiError=a,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=r,exports.LLMStreamingResponseError=n,exports.Session=c,exports.createSession=async function(t,e,s,a,r,n){return await(async(t,e,s,a,r,n)=>{let i=null,l=null;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)}}const h=await o.create(t,e,i,s,a),d=new c(t,e,i,h,n);return d.onClose(t=>{l()}),d})(t,e,s,a,r,n)},exports.createSessionId=async(t,e,s)=>{"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");const a={capability:[],...s};s.using_stf_webrtc&&a.capability.push("STF_WEBRTC"),s?.llm_type&&(a.capability.push("LLM"),a.llm_type=s.llm_type),s?.tts_type&&(a.capability.push("TTS"),a.tts_type=s.tts_type),s?.stt_type&&(a.capability.push("STT"),a.stt_type=s.stt_type);const r=await fetch(`${t}/api/v1/session/`,{body:JSON.stringify(a),headers:{"PersoLive-APIKey":e,"Content-Type":"application/json"},method:"POST"});return(await i.parseJson(r)).session_id},exports.getAllSettings=async function(t,e){const s=await l(t,e),a=await p(t,e),r=await u(t,e);return{llms:s,ttsTypes:await h(t,e),sttTypes:await d(t,e),modelStyles:a,backgroundImages:r,prompts:await S(t,e),documents:await g(t,e),mcpServers:await m(t,e)}},exports.getBackgroundImages=u,exports.getDocuments=g,exports.getLLMs=l,exports.getMcpServers=m,exports.getModelStyles=p,exports.getPrompts=S,exports.getSTTs=d,exports.getSessionInfo=async function(t,e){return await i.getSessionInfo(t,e)},exports.getTTSs=h;
|
|
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}){const a=await fetch(`${t}/api/v1/session/${e}/tts/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:s})});return await this.parseJson(a)}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 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){const a=await fetch(`${t}/api/v1/session/${e}/event/create/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detail:"",event:s})});await this.parseJson(a)}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 S(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 m 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 m("Cannot resample empty audio data");if(e<=0||s<=0)throw new m(`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 m(`Failed to resample audio from ${e}Hz to ${s}Hz: ${a}`)}}const w=16e3;async function y(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,m=Math.floor(u/(g*n)),f=new Float32Array(m*n);let w=0;for(let s=0;s<m*n;s++){const a=l+s*g;if(a+g>t.byteLength)break;f[w++]=Math.max(-1,Math.min(1,S(e,a,i)))}if(2===n){const t=new Float32Array(m);for(let e=0;e<m;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,w)}}(t);if(e.sampleRate===w)return{samples:e.samples,sampleRate:e.sampleRate};return{samples:await f(e.samples,e.sampleRate,w,e.channels),sampleRate:w}}const s=new AudioContext({sampleRate:w});try{const e=await s.decodeAudioData(t.slice(0));if(e.sampleRate===w)return{samples:e.getChannelData(0),sampleRate:w};return{samples:await f(e.getChannelData(0),e.sampleRate,w,1),sampleRate:w}}finally{await s.close()}}(s,a),e=u(t.samples,w,1);return new Blob([e],{type:"audio/wav"})}catch{return new Blob([s],{type:a})}}var T;exports.ChatState=void 0,(T=exports.ChatState||(exports.ChatState={})).RECORDING="RECORDING",T.LLM="LLM",T.ANALYZING="ANALYZING",T.SPEAKING="SPEAKING",T.TTS="TTS";class C{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)}"message"!==a.type?"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}):(i+=a.content,e.message+=a.content,e.allChunks.push(a.content))}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)i+=t.content,e.message+=t.content,e.allChunks.push(t.content);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 E=`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 v{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(E),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 R 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 R.createPeerConnection(l),d=c.createDataChannel("message",{protocol:"message"}),u=new R(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 S=await c.createOffer();await c.setLocalDescription(S);const m=await h.exchangeSDP(s,a,S);return await c.setRemoteDescription(m),await R.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;sendFile(t,e=65536){return new Promise((s,a)=>{const r=this.pc.createDataChannel("file",{protocol:"file"});r.onerror=t=>{r.close(),a(new Error(`File channel error: ${t}`))},r.addEventListener("message",async n=>{try{if(0===n.data.length){const s=new Uint8Array(await t.arrayBuffer());let n=0;const o=()=>{for(;n<s.length;){if(r.bufferedAmount>R.BACKPRESSURE_THRESHOLD)return r.bufferedAmountLowThreshold=R.BACKPRESSURE_THRESHOLD/2,r.onbufferedamountlow=()=>{r.onbufferedamountlow=null,r.onclose=null,o()},void(r.onclose=()=>{r.onbufferedamountlow=null,a(new Error("File channel closed during transfer"))});r.send(s.slice(n,n+e)),n+=e}r.send(new Uint8Array(0))};o()}else r.close(),s(n.data)}catch(t){r.close(),a(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 y(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 I{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]]);emojiRegex=s();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 C({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)}}),this.startHeartbeat(),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)}))}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)");return this.pipelineSuppressed?"":(this.setChatState(exports.ChatState.ANALYZING),await this.perso.stf(t,e,s))}async processTTS(t,e={}){const{resample:s=!0}=e,a=this.removeEmoji(t).trim();if(0===a.length)return;if(this.pipelineSuppressed)return;const n=/[.?!]$/.test(a)?a:a+".";this.setChatState(exports.ChatState.TTS,null);try{const{audio:t}=await h.makeTTS(this.apiServer,{sessionId:this.sessionId,text:n});return await y(t,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 v({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+=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=this.removeEmoji(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}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)}removeEmoji(t){return t.replace(this.emojiRegex,"")}}async function L(t,e,s,a,r,n){if("boolean"!=typeof r){const n=await R.create(t,e,s,a);return new I(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 R.create(t,e,s,a,i);if(!c)return l(),new I(t,e,null,o);const h=new I(t,e,c,o,{stream:i,legacyVoiceChatMode:!0});return h.onClose(()=>{l()}),h}async function x(t,e){return await h.getLLMs(t,e)}async function M(t,e){return await h.getTTSs(t,e)}async function b(t,e){return await h.getSTTs(t,e)}async function N(t,e){return await h.getModelStyles(t,e)}async function A(t,e){return await h.getBackgroundImages(t,e)}async function _(t,e){return await h.getPrompts(t,e)}async function k(t,e){return await h.getDocuments(t,e)}async function O(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=C,exports.STTError=i,exports.Session=I,exports.TTSDecodeError=c,exports.TTSError=l,exports.TTS_TARGET_SAMPLE_RATE=w,exports.WavRecorder=v,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(e,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");const r={capability:[],...a};a.using_stf_webrtc&&r.capability.push(t.STF_WEBRTC),a?.llm_type&&(r.capability.push(t.LLM),r.llm_type=a.llm_type),a?.tts_type&&(r.capability.push(t.TTS),r.tts_type=a.tts_type),a?.stt_type&&(r.capability.push(t.STT),r.stt_type=a.stt_type);const n=await fetch(`${e}/api/v1/session/`,{body:JSON.stringify(r),headers:{"PersoLive-APIKey":s,"Content-Type":"application/json"},method:"POST"});return(await h.parseJson(n)).session_id},exports.createWavRecorder=function(t){return new v(t)},exports.getAllSettings=async function(t,e){const s=await x(t,e),a=await N(t,e),r=await A(t,e);return{llms:s,ttsTypes:await M(t,e),sttTypes:await b(t,e),modelStyles:a,backgroundImages:r,prompts:await _(t,e),documents:await k(t,e),mcpServers:await O(t,e)}},exports.getBackgroundImages=A,exports.getDocuments=k,exports.getLLMs=x,exports.getMcpServers=O,exports.getModelStyles=N,exports.getPrompts=_,exports.getSTTs=b,exports.getSessionInfo=async function(t,e){return await h.getSessionInfo(t,e)},exports.getTTSs=M,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")};
|