perso-interactive-sdk-web 1.4.0 → 1.5.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 +8 -1
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.ts +56 -3
- 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 +62 -18
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -321,6 +321,9 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
321
321
|
| `makeTTS(apiServer, params)` | Generate TTS audio from text (standalone) |
|
|
322
322
|
| `PersoUtilServer` | Low-level API utilities |
|
|
323
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`) |
|
|
324
327
|
|
|
325
328
|
### Client Exports
|
|
326
329
|
|
|
@@ -352,6 +355,9 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
352
355
|
| `STTError` | Error class for STT errors |
|
|
353
356
|
| `TTSError` | Error class for TTS errors |
|
|
354
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`) |
|
|
355
361
|
| `LlmProcessor` | Standalone LLM streaming processor |
|
|
356
362
|
| `WavRecorder` | Audio recorder producing WAV files |
|
|
357
363
|
| `createWavRecorder(options?)` | Factory function for WavRecorder |
|
|
@@ -372,6 +378,7 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
372
378
|
| `stopProcessSTT(language?)` | Stop recording and get text |
|
|
373
379
|
| `isSTTRecording()` | Check if STT recording is in progress |
|
|
374
380
|
| `transcribeAudio(audio, language?)` | Transcribe audio Blob/File to text |
|
|
381
|
+
| `transcribeAudioDetailed(audio, language?)` | Transcribe audio Blob/File and return `STTResponse` (currently `{ text }`) |
|
|
375
382
|
| `getMessageHistory()` | Get LLM conversation history |
|
|
376
383
|
| `getRemoteStream()` | Get AI human's media stream |
|
|
377
384
|
| `getLocalStream()` | ~~Get user's audio stream~~ (Deprecated) |
|
|
@@ -392,7 +399,7 @@ For direct browser usage via `<script>` tag without a bundler. The SDK exposes a
|
|
|
392
399
|
| ------------------------ | -------------- | ---------------------------------------------- |
|
|
393
400
|
| `lastRecordedAudioFile` | `File \| null` | Last recorded WAV audio file from STT |
|
|
394
401
|
|
|
395
|
-
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/).
|
|
396
403
|
|
|
397
404
|
## License
|
|
398
405
|
|
package/dist/client/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t,e,s,a=require("emoji-regex");exports.ChatState=void 0,(t=exports.ChatState||(exports.ChatState={})).RECORDING="RECORDING",t.LLM="LLM",t.ANALYZING="ANALYZING",t.SPEAKING="SPEAKING",t.TTS="TTS";class r extends Error{constructor(){super("WebRTC connection timeout")}}class n extends Error{errorCode;code;detail;attr;constructor(t,e,s,a){let r;r=null!=a?`${t}:${a}_${s}`:`${t}:${s}`,super(r),this.errorCode=t,this.code=e,this.detail=s,this.attr=a}}class o extends Error{underlyingError;constructor(t){super(),this.underlyingError=t}}class i extends Error{description;constructor(t){super(),this.description=t}}class l extends Error{underlyingError;constructor(t){super(`STT Error: ${t.detail}`),this.underlyingError=t}}class c extends Error{underlyingError;constructor(t){super(t.message),this.underlyingError=t}}class h extends Error{description;constructor(t){super(`TTS decode error: ${t}`),this.description=t}}!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 d{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)}}}async function u(t,e){return await d.getLLMs(t,e)}async function p(t,e){return await d.getTTSs(t,e)}async function g(t,e){return await d.getSTTs(t,e)}async function m(t,e){return await d.getModelStyles(t,e)}async function S(t,e){return await d.getBackgroundImages(t,e)}async function f(t,e){return await d.getPrompts(t,e)}async function y(t,e){return await d.getDocuments(t,e)}async function w(t,e){return await d.getMcpServers(t,e)}async function T(t,e){return await d.getTextNormalizations(t,e)}class C extends Error{constructor(t){super(`WAV parse error: ${t}`),this.name="WavParseError"}}function E(t,e,s=1){const a=2*t.length,r=new ArrayBuffer(44+a),n=new DataView(r);_(n,0,"RIFF"),n.setUint32(4,36+a,!0),_(n,8,"WAVE"),_(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),_(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 v(t,e,s){let a="";for(let r=0;r<s;r++)a+=String.fromCharCode(t.getUint8(e+r));return a}function _(t,e,s){for(let a=0;a<s.length;a++)t.setUint8(e+a,s.charCodeAt(a))}function I(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 R extends Error{constructor(t){super(`Audio resample error: ${t}`),this.name="AudioResampleError"}}async function x(t,e,s,a=1){if(0===t.length)throw new R("Cannot resample empty audio data");if(e<=0||s<=0)throw new R(`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 R(`Failed to resample audio from ${e}Hz to ${s}Hz: ${a}`)}}const L=16e3;async function M(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 C("File too small to be a valid WAV");if("RIFF"!==v(e,0,4))throw new C("Missing RIFF header");if("WAVE"!==v(e,8,4))throw new C("Missing WAVE format identifier");let s=12,a=!1,r=0,n=0,o=0,i=0;for(;s<t.byteLength-8;){const l=v(e,s,4),c=e.getUint32(s+4,!0);if("fmt "===l){if(s+24>t.byteLength)throw new C("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 C("Missing fmt chunk");if(1!==r)throw new C(`Unsupported audio format: ${r} (only PCM format 1 is supported)`);if(0===n||n>8)throw new C(`Invalid channel count: ${n}`);if(0===o||o>192e3)throw new C(`Invalid sample rate: ${o}`);if(![8,16,24,32].includes(i))throw new C(`Unsupported bits per sample: ${i}`);let l=-1,c=0;for(;s<t.byteLength-8;){const t=v(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 C("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 m=0;for(let s=0;s<p*n;s++){const a=l+s*u;if(a+u>t.byteLength)break;g[m++]=Math.max(-1,Math.min(1,I(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,m)}}(t);if(e.sampleRate===L)return{samples:e.samples,sampleRate:e.sampleRate};return{samples:await x(e.samples,e.sampleRate,L,e.channels),sampleRate:L}}const s=new AudioContext({sampleRate:L});try{const e=await s.decodeAudioData(t.slice(0));if(e.sampleRate===L)return{samples:e.getChannelData(0),sampleRate:L};return{samples:await x(e.getChannelData(0),e.sampleRate,L,1),sampleRate:L}}finally{await s.close()}}(s,a),e=E(t.samples,L,1);return new Blob([e],{type:"audio/wav"})}catch{return new Blob([s],{type:a})}}const b=a();function N(t){return t.replace(b,"")}class A{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 d.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=N(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=N(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 k=`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 O{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(k),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 x(a,e,this.targetSampleRate,this.channels),n=this.targetSampleRate):(r=a,n=e);const i=E(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 P 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 d.getSessionInfo(t,a);if(!(Array.isArray(i.capability)&&i.capability.some(t=>t.name===e.STF_ONPREMISE||t.name===e.STF_WEBRTC)))return await d.sessionEvent(t,a,s.SESSION_START),null;const l=await d.getIceServers(t,a);let c=await P.createPeerConnection(l),h=c.createDataChannel("message",{protocol:"message"}),u=new P(c,h);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 d.exchangeSDP(t,a,m);return await c.setRemoteDescription(S),await P.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 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"))})},P.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>P.BACKPRESSURE_THRESHOLD)return o.bufferedAmountLowThreshold=P.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 M(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 H{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 A({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 d.makeSTT(this.apiServer,this.sessionId,s,e)).text}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=N(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 d.makeTTS(this.apiServer,t);if(this.pipelineSuppressed)return;return await M(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 O({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 d.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+=N(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=N(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 d.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 d.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 F(t,e,s,a,r,n){if("boolean"!=typeof r){const n=await P.create(t,e,s,a);return new H(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 P.create(t,e,s,a,i);if(!c)return l(),new H(t,e,null,o);const h=new H(t,e,c,o,{stream:i,legacyVoiceChatMode:!0});return h.onClose(()=>{l()}),h}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.LLMError=o,exports.LLMStreamingResponseError=i,exports.LlmProcessor=A,exports.STTError=l,exports.Session=H,exports.TTSDecodeError=h,exports.TTSError=c,exports.TTS_TARGET_SAMPLE_RATE=L,exports.WavRecorder=O,exports.createSession=async function(t,e,s,a,r,n){return"boolean"==typeof r?await F(t,e,s,a,r,n??[]):await F(t,e,s,a,r)},exports.createSessionId=async function(t,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 d.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 d.parseJson(o)).session_id},exports.createWavRecorder=function(t){return new O(t)},exports.getAllSettings=async function(t,e){const[s,a,r,n,o,i,l,c,h]=await Promise.all([u(t,e),p(t,e),g(t,e),m(t,e),S(t,e),f(t,e),y(t,e),w(t,e),T(t,e).catch(()=>[])]);return{llms:s,ttsTypes:a,sttTypes:r,modelStyles:n,backgroundImages:o,prompts:i,documents:l,mcpServers:c,textNormalizations:h}},exports.getBackgroundImages=S,exports.getDocuments=y,exports.getLLMs=u,exports.getMcpServers=w,exports.getModelStyles=m,exports.getPrompts=f,exports.getSTTs=g,exports.getSessionInfo=async function(t,e){return await d.getSessionInfo(t,e)},exports.getSessionTemplates=async function(t,e){return await d.getSessionTemplates(t,e)},exports.getTTSs=p,exports.getTextNormalization=async function(t,e,s){return await d.downloadTextNormalization(t,e,s)},exports.getTextNormalizations=T,exports.getWavSampleRate=function(t){const e=new DataView(t);if(t.byteLength<28)throw new C("File too small to be a valid WAV");if("RIFF"!==v(e,0,4))throw new C("Missing RIFF header");let s=12;for(;s<t.byteLength-8;){const a=v(e,s,4),r=e.getUint32(s+4,!0);if("fmt "===a){if(s+16>t.byteLength)throw new C("fmt chunk extends beyond file");return e.getUint32(s+12,!0)}const n=s+8+r;if(n<=s)break;s=n}throw new C("Missing fmt chunk")},exports.makeTTS=async function(t,e){return await d.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{text:(await this.parseJson(n)).text}}static async makeLLM(t,e,s,a){const r=await fetch(`${t}/api/v1/session/${e}/llm/v2/`,{body:JSON.stringify(s),headers:{"Content-Type":"application/json"},method:"POST",signal:a});if(!r.ok){const t=await r.json(),e=t.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${r.status} with no error details`,attr:null};throw new 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,15 @@ 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
|
+
*
|
|
47
|
+
* The wire payload includes additional fields (e.g., `locale`,
|
|
48
|
+
* `normalized_text`) that the SDK intentionally does not expose.
|
|
49
|
+
*/
|
|
50
|
+
interface STTResponse {
|
|
51
|
+
text: string;
|
|
52
|
+
}
|
|
44
53
|
interface TextNormalizationConfig {
|
|
45
54
|
textnormalizationconfig_id: string;
|
|
46
55
|
name: string;
|
|
@@ -382,6 +391,13 @@ declare class Session {
|
|
|
382
391
|
*/
|
|
383
392
|
processTTSTF(message: string): void;
|
|
384
393
|
transcribeAudio(audio: Blob | File, language?: string): Promise<string>;
|
|
394
|
+
/**
|
|
395
|
+
* Same as transcribeAudio but returns the full STTResponse object.
|
|
396
|
+
*
|
|
397
|
+
* The SDK currently exposes only `text`. Other server-side fields
|
|
398
|
+
* (e.g., `locale`, `normalized_text`) are intentionally omitted.
|
|
399
|
+
*/
|
|
400
|
+
transcribeAudioDetailed(audio: Blob | File, language?: string): Promise<STTResponse>;
|
|
385
401
|
/**
|
|
386
402
|
* Sends an audio file for Speech-to-Face processing.
|
|
387
403
|
*
|
|
@@ -846,7 +862,9 @@ type CreateSessionIdBody = {
|
|
|
846
862
|
* @param sessionTemplateId SessionTemplate ID to resolve configuration from.
|
|
847
863
|
* @returns Session ID returned by the server.
|
|
848
864
|
* @throws {Error} If the template's `model_style.platform_type` is not `"webrtc"`.
|
|
849
|
-
* @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`).
|
|
850
868
|
*/
|
|
851
869
|
declare function createSessionId(apiServer: string, apiKey: string, sessionTemplateId: string): Promise<string>;
|
|
852
870
|
/**
|
|
@@ -885,10 +903,45 @@ declare class TTSDecodeError extends Error {
|
|
|
885
903
|
description: string;
|
|
886
904
|
constructor(description: string);
|
|
887
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
|
+
}
|
|
888
941
|
|
|
889
942
|
declare function getWavSampleRate(arrayBuffer: ArrayBuffer): number;
|
|
890
943
|
|
|
891
944
|
declare const TTS_TARGET_SAMPLE_RATE = 16000;
|
|
892
945
|
|
|
893
|
-
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 };
|
|
894
|
-
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 };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var PersoInteractive=function(t){"use strict";var e,u,D;t.ChatState=void 0,(e=t.ChatState||(t.ChatState={})).RECORDING="RECORDING",e.LLM="LLM",e.ANALYZING="ANALYZING",e.SPEAKING="SPEAKING",e.TTS="TTS";class s extends Error{constructor(){super("WebRTC connection timeout")}}class a extends Error{errorCode;code;detail;attr;constructor(t,e,u,D){let s;s=null!=D?`${t}:${D}_${u}`:`${t}:${u}`,super(s),this.errorCode=t,this.code=e,this.detail=u,this.attr=D}}class n extends Error{underlyingError;constructor(t){super(),this.underlyingError=t}}class r extends Error{description;constructor(t){super(),this.description=t}}class o extends Error{underlyingError;constructor(t){super(`STT Error: ${t.detail}`),this.underlyingError=t}}class i extends Error{underlyingError;constructor(t){super(t.message),this.underlyingError=t}}class F 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"}(u||(u={})),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"}(D||(D={}));class l{static async getLLMs(t,e){const u=fetch(`${t}/api/v1/settings/llm_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getModelStyles(t,e){const u=fetch(`${t}/api/v1/settings/modelstyle/?platform_type=webrtc`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getBackgroundImages(t,e){const u=fetch(`${t}/api/v1/background_image/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getTTSs(t,e){const u=fetch(`${t}/api/v1/settings/tts_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getSTTs(t,e){const u=fetch(`${t}/api/v1/settings/stt_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async makeTTS(t,{sessionId:e,text:u,locale:D,output_format:s}){const a={text:u};D&&(a.locale=D),s&&(a.output_format=s);const n=await fetch(`${t}/api/v1/session/${e}/tts/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)});return await this.parseJson(n)}static async getPrompts(t,e){const u=fetch(`${t}/api/v1/prompt/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getDocuments(t,e){const u=fetch(`${t}/api/v1/document/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getTextNormalizations(t,e){const u=fetch(`${t}/api/v1/settings/text_normalization_config/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async downloadTextNormalization(t,e,u){const D=await fetch(`${t}/api/v1/settings/text_normalization_config/${u}/download/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(D)}static async getSessionTemplates(t,e){const u=await fetch(`${t}/api/v1/session_template/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(u)}static async getSessionTemplate(t,e,u){const D=await fetch(`${t}/api/v1/session_template/${u}/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(D)}static async getMcpServers(t,e){const u=fetch(`${t}/api/v1/settings/mcp_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getSessionInfo(t,e){const u=fetch(`${t}/api/v1/session/${e}/`,{method:"GET"}),D=await u;return await this.parseJson(D)}static async sessionEvent(t,e,u,D=""){const s=await fetch(`${t}/api/v1/session/${e}/event/create/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detail:D,event:u})});await this.parseJson(s)}static async makeSTT(t,e,u,D){const s=new FormData;s.append("audio",u),D&&s.append("language",D);const a=await fetch(`${t}/api/v1/session/${e}/stt/`,{method:"POST",body:s});return await this.parseJson(a)}static async makeLLM(t,e,u,D){const s=await fetch(`${t}/api/v1/session/${e}/llm/v2/`,{body:JSON.stringify(u),headers:{"Content-Type":"application/json"},method:"POST",signal:D});if(!s.ok){const t=await s.json(),e=t.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${s.status} with no error details`,attr:null};throw new a(s.status,e.code,e.detail,e.attr)}return s.body.getReader()}static async getIceServers(t,e){const u=await fetch(`${t}/api/v1/session/${e}/ice-servers/`,{method:"GET"});return(await this.parseJson(u)).ice_servers}static async exchangeSDP(t,e,u){const D=await fetch(`${t}/api/v1/session/${e}/exchange/`,{body:JSON.stringify({client_sdp:u}),headers:{"Content-Type":"application/json"},method:"POST"});return(await this.parseJson(D)).server_sdp}static async parseJson(t){const e=await t.json();if(t.ok)return e;{const u=e.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${t.status} with no error details`,attr:null};throw new a(t.status,u.code,u.detail,u.attr)}}}async function c(t,e){return await l.getLLMs(t,e)}async function h(t,e){return await l.getTTSs(t,e)}async function C(t,e){return await l.getSTTs(t,e)}async function E(t,e){return await l.getModelStyles(t,e)}async function d(t,e){return await l.getBackgroundImages(t,e)}async function p(t,e){return await l.getPrompts(t,e)}async function g(t,e){return await l.getDocuments(t,e)}async function m(t,e){return await l.getMcpServers(t,e)}async function S(t,e){return await l.getTextNormalizations(t,e)}class f extends Error{constructor(t){super(`WAV parse error: ${t}`),this.name="WavParseError"}}function y(t,e,u=1){const D=2*t.length,s=new ArrayBuffer(44+D),a=new DataView(s);w(a,0,"RIFF"),a.setUint32(4,36+D,!0),w(a,8,"WAVE"),w(a,12,"fmt "),a.setUint32(16,16,!0),a.setUint16(20,1,!0),a.setUint16(22,u,!0),a.setUint32(24,e,!0),a.setUint32(28,e*u*2,!0),a.setUint16(32,2*u,!0),a.setUint16(34,16,!0),w(a,36,"data"),a.setUint32(40,D,!0);let n=44;for(let e=0;e<t.length;e++){const u=Math.max(-1,Math.min(1,t[e]));a.setInt16(n,u<0?32768*u:32767*u,!0),n+=2}return s}function B(t,e,u){let D="";for(let s=0;s<u;s++)D+=String.fromCharCode(t.getUint8(e+s));return D}function w(t,e,u){for(let D=0;D<u.length;D++)t.setUint8(e+D,u.charCodeAt(D))}function T(t,e,u){switch(u){case 8:return(t.getUint8(e)-128)/128;case 16:return t.getInt16(e,!0)/32768;case 24:{const u=t.getUint8(e),D=t.getUint8(e+1),s=t.getUint8(e+2)<<16|D<<8|u;return(s>8388607?s-16777216:s)/8388608}case 32:return t.getInt32(e,!0)/2147483648;default:return 0}}class A extends Error{constructor(t){super(`Audio resample error: ${t}`),this.name="AudioResampleError"}}async function v(t,e,u,D=1){if(0===t.length)throw new A("Cannot resample empty audio data");if(e<=0||u<=0)throw new A(`Invalid sample rate: original=${e}, target=${u}`);if(e===u)return t;try{const s=t.length/e,a=Math.ceil(s*u),n=new OfflineAudioContext(D,t.length,e).createBuffer(D,t.length,e);n.getChannelData(0).set(t);const r=new OfflineAudioContext(D,a,u),o=r.createBufferSource();o.buffer=n,o.connect(r.destination),o.start(0);return(await r.startRendering()).getChannelData(0)}catch(t){const D=t instanceof Error?t.message:String(t);throw new A(`Failed to resample audio from ${e}Hz to ${u}Hz: ${D}`)}}const _=16e3;async function I(t,e=!0){let u;try{const e=atob(t),D=new Array(e.length);for(let t=0;t<e.length;t++)D[t]=e.charCodeAt(t);u=new Uint8Array(D).buffer}catch{throw new F("Invalid Base64 audio data")}const D=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"}(u);if(!e)return new Blob([u],{type:D});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 f("File too small to be a valid WAV");if("RIFF"!==B(e,0,4))throw new f("Missing RIFF header");if("WAVE"!==B(e,8,4))throw new f("Missing WAVE format identifier");let u=12,D=!1,s=0,a=0,n=0,r=0;for(;u<t.byteLength-8;){const o=B(e,u,4),i=e.getUint32(u+4,!0);if("fmt "===o){if(u+24>t.byteLength)throw new f("fmt chunk extends beyond file");s=e.getUint16(u+8,!0),a=e.getUint16(u+10,!0),n=e.getUint32(u+12,!0),r=e.getUint16(u+22,!0),D=!0,u+=8+i;break}const F=u+8+i;if(F<=u)break;u=F}if(!D)throw new f("Missing fmt chunk");if(1!==s)throw new f(`Unsupported audio format: ${s} (only PCM format 1 is supported)`);if(0===a||a>8)throw new f(`Invalid channel count: ${a}`);if(0===n||n>192e3)throw new f(`Invalid sample rate: ${n}`);if(![8,16,24,32].includes(r))throw new f(`Unsupported bits per sample: ${r}`);let o=-1,i=0;for(;u<t.byteLength-8;){const t=B(e,u,4),D=e.getUint32(u+4,!0);if("data"===t){o=u+8,i=D;break}const s=u+8+D;if(s<=u)break;u=s}if(-1===o)throw new f("Missing data chunk");const F=t.byteLength-o,l=Math.min(i,F),c=r/8,h=Math.floor(l/(c*a)),C=new Float32Array(h*a);let E=0;for(let u=0;u<h*a;u++){const D=o+u*c;if(D+c>t.byteLength)break;C[E++]=Math.max(-1,Math.min(1,T(e,D,r)))}if(2===a){const t=new Float32Array(h);for(let e=0;e<h;e++)t[e]=(C[2*e]+C[2*e+1])/2;return{sampleRate:n,channels:1,bitsPerSample:r,samples:t}}return{sampleRate:n,channels:a,bitsPerSample:r,samples:C.slice(0,E)}}(t);if(e.sampleRate===_)return{samples:e.samples,sampleRate:e.sampleRate};return{samples:await v(e.samples,e.sampleRate,_,e.channels),sampleRate:_}}const u=new AudioContext({sampleRate:_});try{const e=await u.decodeAudioData(t.slice(0));if(e.sampleRate===_)return{samples:e.getChannelData(0),sampleRate:_};return{samples:await v(e.getChannelData(0),e.sampleRate,_,1),sampleRate:_}}finally{await u.close()}}(u,D),e=y(t.samples,_,1);return new Blob([e],{type:"audio/wav"})}catch{return new Blob([u],{type:D})}}const R=/[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E-\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED8\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])))?))?|\uDD75(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3C-\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE8A\uDE8E-\uDEC2\uDEC6\uDEC8\uDECD-\uDEDC\uDEDF-\uDEEA\uDEEF]|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g;function L(t){return t.replace(R,"")}class M{config;messageHistory=[];constructor(t){this.config=t}async*processLLM(e){if(0===e.message.length)throw new Error("Message cannot be empty");const u=e.tools??this.config.clientTools,D=u.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),s={newMessageHistory:[{role:"user",content:e.message}],allChunks:[],message:"",lastYieldedChunkCount:0,pendingToolCallsMessage:null,aborted:!1,streamingError:null};let o=0,i=[...this.messageHistory,...s.newMessageHistory];this.config.callbacks.onChatStateChange(t.ChatState.LLM,null);try{for(;;){if(e.signal?.aborted)return void(s.allChunks.length>0&&(yield{type:"message",chunks:[...s.allChunks],message:s.message,finish:!0}));let t;try{t=await l.makeLLM(this.config.apiServer,this.config.sessionId,{messages:i,tools:D},e.signal)}catch(t){if(t instanceof a)return void(yield{type:"error",error:new n(t)});throw t}if(s.streamingError=null,yield*this.parseSSEStream(t,s,e),s.streamingError)return;if(s.aborted)return void(s.allChunks.length>0&&(yield{type:"message",chunks:[...s.allChunks],message:s.message,finish:!0}));if(null!=s.pendingToolCallsMessage){yield*this.executeToolCalls(s,u);const t=s.lastToolCallResults,e=t.length>0&&s.pendingToolCallsMessage.tool_calls.length!==t.length,D=t.some(t=>!t.chatTool.executeOnly);if(e||D){if(o++,o>=10)return void(yield{type:"error",error:new n(new r("Tool follow-up loop exceeded maximum rounds (10)"))});i=[...this.messageHistory,...s.newMessageHistory],s.pendingToolCallsMessage=null;continue}}return this.messageHistory.push(...s.newMessageHistory),void(yield{type:"message",chunks:[...s.allChunks],message:s.message,finish:!0})}}finally{this.config.callbacks.onChatStateChange(null,t.ChatState.LLM)}}async*parseSSEStream(t,e,u){const D=new TextDecoder("utf-8");let s="",a="";e.pendingToolCallsMessage=null;const o=()=>e.allChunks.length>e.lastYieldedChunkCount?(e.lastYieldedChunkCount=e.allChunks.length,{type:"message",chunks:[...e.allChunks],message:e.message,finish:!1}):null;for(;;){const{done:i,value:F}=await t.read();if(i)break;let l;for(s+=D.decode(F,{stream:!0});-1!==(l=s.indexOf("\n"));){if(u.signal?.aborted)return void(e.aborted=!0);const t=s.slice(0,l).trim();if(s=s.slice(l+1),!t.startsWith("data: {"))return e.streamingError=new n(new r("Failed to parse SSE response")),void(yield{type:"error",error:e.streamingError});let D;try{D=JSON.parse(t.slice(6).trim())}catch{return e.streamingError=new n(new r("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==D.status)return e.streamingError=new n(new r(D.reason)),void(yield{type:"error",error:e.streamingError});if(a.length>0&&"message"!=D.type){e.newMessageHistory.push({role:"assistant",type:"message",content:a}),a="";const t=o();t&&(yield t)}if("message"===D.type){const t=L(D.content);a+=t,e.message+=t,e.allChunks.push(t);continue}"tool_call"!==D.type||null==D.tool_calls?"tool"!==D.role||"tool_call"===D.type&&e.newMessageHistory.push({role:D.role,type:D.type,content:D.content,tool_call_id:D.tool_call_id}):(e.newMessageHistory.push({role:"assistant",type:D.type,content:D.content,tool_calls:D.tool_calls}),e.pendingToolCallsMessage=D,yield{type:"tool_call",tool_calls:D.tool_calls})}const c=o();c&&(yield c)}const i=s.trim();if(i.length>0){if(!i.startsWith("data: {"))return e.streamingError=new n(new r("Failed to parse SSE response")),void(yield{type:"error",error:e.streamingError});let t;try{t=JSON.parse(i.slice(6).trim())}catch{return e.streamingError=new n(new r("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==t.status)return e.streamingError=new n(new r(t.reason)),void(yield{type:"error",error:e.streamingError});if("message"===t.type){const u=L(t.content);a+=u,e.message+=u,e.allChunks.push(u)}else if("tool_call"===t.type&&null!=t.tool_calls){if(a.length>0){e.newMessageHistory.push({role:"assistant",type:"message",content:a}),a="";const t=o();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}}}a.length>0&&e.newMessageHistory.push({role:"assistant",type:"message",content:a})}async*executeToolCalls(t,e){const u=t=>{for(const u of e)if(u.name===t)return u;return null},D=[];for(const e of t.pendingToolCallsMessage.tool_calls){const t=u(e.function.name);null!=t&&D.push((async()=>{try{const u=await t.call(JSON.parse(e.function.arguments));return{toolCallId:e.id,chatTool:t,chatToolResult:u}}catch(u){return{toolCallId:e.id,chatTool:t,chatToolResult:{error:u.message}}}})())}const s=await Promise.all(D);t.lastToolCallResults=s;for(const e of s)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 b=`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 N{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(b),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 u=t.reduce((t,e)=>t+e.length,0),D=new Float32Array(u);let s,a,n=0;for(const e of t)D.set(e,n),n+=e.length;this.targetSampleRate&&this.targetSampleRate!==e?(s=await v(D,e,this.targetSampleRate,this.channels),a=this.targetSampleRate):(s=D,a=e);const r=y(s,a,this.channels),o=new Blob([r],{type:"audio/wav"});return new File([o],"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 k 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,e,s,a,n){const r=await l.getSessionInfo(t,e);if(!(Array.isArray(r.capability)&&r.capability.some(t=>t.name===u.STF_ONPREMISE||t.name===u.STF_WEBRTC)))return await l.sessionEvent(t,e,D.SESSION_START),null;const o=await l.getIceServers(t,e);let i=await k.createPeerConnection(o),F=i.createDataChannel("message",{protocol:"message"}),c=new k(i,F);n?n.getTracks().forEach(function(t){i.addTrack(t,n)}):i.addTransceiver("audio",{direction:"recvonly"});const h=i.addTransceiver("video",{direction:"recvonly"}),C=RTCRtpReceiver.getCapabilities("video");null!=C&&h.setCodecPreferences(C.codecs);const E=await i.createOffer();await i.setLocalDescription(E);const d=await l.exchangeSDP(t,e,E);return await i.setRemoteDescription(d),await k.waitFor(()=>c.isReady(),100,50),c.changeSize(s,a),c}static async createPeerConnection(t){return new RTCPeerConnection({sdpSemantics:"unified-plan",iceServers:t})}static async waitFor(t,e,u){let D=0;if(await new Promise(s=>{const a=setInterval(()=>{D+=1,D>=u&&(clearInterval(a),s("bad")),t()&&(clearInterval(a),s("good"))},e)}),D>=u)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})}static BACKPRESSURE_THRESHOLD=524288;static FILE_TRANSFER_TIMEOUT=3e4;sendFile(t,e=65536){return new Promise((u,D)=>{let s=!1;const a=t=>{s||(s=!0,clearTimeout(r),t())},n=this.pc.createDataChannel("file",{protocol:"file"}),r=setTimeout(()=>{a(()=>{n.close(),D(new Error("File transfer timed out"))})},k.FILE_TRANSFER_TIMEOUT);n.onclose=()=>{a(()=>{D(new Error("File channel closed before transfer completed"))})},n.onerror=t=>{a(()=>{n.close(),D(new Error(`File channel error: ${t}`))})},n.addEventListener("message",async s=>{try{if(0===s.data.length){const u=new Uint8Array(await t.arrayBuffer());let s=0;const r=()=>{for(;s<u.length;){if(n.bufferedAmount>k.BACKPRESSURE_THRESHOLD)return n.bufferedAmountLowThreshold=k.BACKPRESSURE_THRESHOLD/2,n.onbufferedamountlow=()=>{n.onbufferedamountlow=null,n.onclose=null,r()},void(n.onclose=()=>{n.onbufferedamountlow=null,a(()=>{D(new Error("File channel closed during transfer"))})});n.send(u.slice(s,s+e)),s+=e}n.send(new Uint8Array(0))};r()}else a(()=>{n.close(),u(s.data)})}catch(t){a(()=>{n.close(),D(t instanceof Error?t:new Error(String(t)))})}})})}async stf(t,e,u){const D=await this.sendFile(t);return this.sendMessage("stf",{message:u,file_ref:D,format:e}),D}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 u=u=>{const D=JSON.parse(u.data);D.type===t&&e(D.data)};return this.dc.addEventListener("message",u),()=>{this.dc.removeEventListener("message",u)}}async tts(t,e=!0){return I(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([[t.ChatState.RECORDING,0],[t.ChatState.LLM,0],[t.ChatState.ANALYZING,0],[t.ChatState.SPEAKING,0],[t.ChatState.TTS,0]]);sttRecorder=null;sttTimeoutHandle=null;sttTimeoutAudioFile=null;heartbeatIntervalId=null;legacyVoiceChatMode;stream;constructor(e,u,D,s,a){this.apiServer=e,this.sessionId=u,this.perso=D,this.clientTools=s,this.legacyVoiceChatMode=a?.legacyVoiceChatMode??!1,this.stream=a?.stream??null,this.resetChatState(),this.llmProcessor=new M({apiServer:e,sessionId:u,clientTools:s,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)}}),D?(D.subscribeStatus(t=>{!1===t.detail?.live&&this.stopHeartbeat()}),D.setMessageCallback("stf",e=>{if(this.chatStateMap.get(t.ChatState.ANALYZING)||this.chatStateMap.get(t.ChatState.SPEAKING))if(this.setChatState(t.ChatState.SPEAKING,t.ChatState.ANALYZING),null!==this.lastStfTimeoutHandle){clearTimeout(this.lastStfTimeoutHandle);let u=Date.now();this.stfTotalDuration+=e.duration+1e3-(u-this.stfTimeoutStartTime),this.stfTimeoutStartTime=u,this.lastStfTimeoutHandle=setTimeout(()=>{this.lastStfTimeoutHandle=null,this.stfTimeoutStartTime=0,this.stfTotalDuration=0,this.setChatState(null,t.ChatState.SPEAKING)},this.stfTotalDuration)}else this.stfTimeoutStartTime=Date.now(),this.stfTotalDuration=e.duration+2e3,this.lastStfTimeoutHandle=setTimeout(()=>{this.lastStfTimeoutHandle=null,this.stfTimeoutStartTime=0,this.stfTotalDuration=0,this.setChatState(null,t.ChatState.SPEAKING)},this.stfTotalDuration)}),D.setMessageCallback("stt",e=>{if(this.setChatState(null,t.ChatState.ANALYZING),null!=this.sttEventHandler)this.sttEventHandler.dispatchEvent(new CustomEvent("stt",{detail:e.text}));else{if(""===e.text)return;this.processChat(e.text)}}),D.setMessageCallback("stt-error",e=>{this.setChatState(null,t.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 u=t instanceof File?t:new File([t],"audio.wav",{type:t.type});try{return(await l.makeSTT(this.apiServer,this.sessionId,u,e)).text}catch(t){if(t instanceof a)throw new o(t);throw t}}async processSTF(e,u,D=""){if(!this.perso)throw new Error("processSTF requires WebRTC (STF mode)");this.pipelineSuppressed=!1,this.setChatState(t.ChatState.ANALYZING);try{const s=function(t,e){const u=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 u(t)??u(e.type)??"wav"}(u,e),a=await this.perso.stf(e,s,D);return this.pipelineSuppressed?(this.setChatState(null,t.ChatState.ANALYZING),a):a}catch(e){throw this.setChatState(null,t.ChatState.ANALYZING),e}}async processTTS(e,u={}){const{resample:D=!1,locale:s,output_format:n}=u,r=L(e).trim();if(0===r.length)return;this.pipelineSuppressed=!1;const o=/[.?!]$/.test(r)?r:r+".";this.setChatState(t.ChatState.TTS,null);try{const t={sessionId:this.sessionId,text:o,...s&&{locale:s},...n&&{output_format:n}},{audio:e}=await l.makeTTS(this.apiServer,t);if(this.pipelineSuppressed)return;return await I(e,D)}catch(t){t instanceof a||t instanceof F?this.setError(new i(t)):this.setError(t instanceof Error?t:new Error(String(t)))}finally{this.setChatState(null,t.ChatState.TTS)}}startVoiceChat(){if(!this.perso)throw new Error("startVoiceChat requires WebRTC (STF mode)");return this.pipelineSuppressed=!1,this.setChatState(t.ChatState.RECORDING),this.perso.recordStart()}stopVoiceChat(){if(!this.perso)throw new Error("stopVoiceChat requires WebRTC (STF mode)");this.setChatState(t.ChatState.ANALYZING,t.ChatState.RECORDING),this.perso.recordEndStt()}async startProcessSTT(e){if(this.sttRecorder?.isRecording())throw new Error("STT recording is already in progress");this.pipelineSuppressed=!1,this.setChatState(t.ChatState.RECORDING);try{this.sttRecorder=new N({targetSampleRate:16e3}),await this.sttRecorder.start(),e&&e>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,t.ChatState.RECORDING)}this.sttRecorder=null}},e))}catch(e){throw this.setChatState(null,t.ChatState.RECORDING),this.sttRecorder=null,e}}lastRecordedAudioFile=null;async stopProcessSTT(e){let u;if(this.sttTimeoutHandle&&(clearTimeout(this.sttTimeoutHandle),this.sttTimeoutHandle=null),this.setChatState(null,t.ChatState.RECORDING),this.sttTimeoutAudioFile)u=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");u=await this.sttRecorder.stop(),this.sttRecorder=null}this.lastRecordedAudioFile=u;try{return(await l.makeSTT(this.apiServer,this.sessionId,u,e)).text}catch(t){if(t instanceof a)throw new o(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(e){this.setChatState(t.ChatState.LLM);const u=this.clientTools.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),D=new Array;null===e||(e instanceof Array?D.push(...e):"string"==typeof e&&D.push({role:"user",content:e}));const s=await fetch(`${this.apiServer}/api/v1/session/${this.sessionId}/llm/v2/`,{body:JSON.stringify({messages:[...this.messageHistory,...D],tools:u}),headers:{"Content-Type":"application/json"},method:"POST"});if(!s.ok){const e=await s.json(),u=new n(new a(s.status,e.errors[0].code,e.errors[0].detail,e.errors[0].attr));return this.setError(u),void this.setChatState(null,t.ChatState.LLM)}const o=s.body?.getReader(),i=new TextDecoder("utf-8");let F="",l=null,c="";for(;;){const{done:e,value:u}=await o.read();if(e)break;let s;for(c+=i.decode(u,{stream:!0});-1!==(s=c.indexOf("\n"));){if(this.llmCancel)return F.length>0&&this.addMessageToChatLog(F,!1),void this.setChatState(null,t.ChatState.LLM);const e=c.slice(0,s).trim();if(c=c.slice(s+1),!e.startsWith("data: {")){const e=new n(new r("Failed to parse SSE response"));return this.setError(e),void this.setChatState(null,t.ChatState.LLM)}const u=JSON.parse(e.slice(6).trim());if("success"!==u.status){const e=new n(new r(u.reason));return this.setError(e),void this.setChatState(null,t.ChatState.LLM)}F.length>0&&"message"!=u.type&&(D.push({role:"assistant",type:"message",content:F}),this.addMessageToChatLog(F,!1),F=""),"message"!==u.type?"tool_call"!==u.type||null==u.tool_calls?"tool"!==u.role||"tool_call"===u.type&&D.push({role:u.role,type:u.type,content:u.content,tool_call_id:u.tool_call_id}):(D.push({role:"assistant",type:u.type,content:u.content,tool_calls:u.tool_calls}),l=u):(F+=L(u.content),this.processTTSTFInternal(u.content))}}if(this.llmCancel)this.setChatState(null,t.ChatState.LLM);else{if(null!=l){const t=[];for(const e of l.tool_calls){const u=this.getChatTool(this.clientTools,e.function.name);null!=u&&t.push(new Promise(async t=>{try{const D=await u.call(JSON.parse(e.function.arguments));t({toolCallId:e.id,chatTool:u,chatToolResult:D})}catch(D){t({toolCallId:e.id,chatTool:u,chatToolResult:{result:"error!"}})}}))}const e=await Promise.all(t);for(const t of e)D.push({role:"tool",content:JSON.stringify(t.chatToolResult),tool_call_id:t.toolCallId});const u=e.length>0&&l.tool_calls.length!==e.length,s=e.some(t=>!t.chatTool.executeOnly);u||s?await this.processChatInternal(D):this.messageHistory.push(...D)}else this.messageHistory.push(...D);this.setChatState(null,t.ChatState.LLM)}}getChatTool(t,e){for(const u of t)if(u.name===e)return u;return null}llmCancel=!1;pipelineSuppressed=!1;async clearLLMJob(){null!=this.llmJob&&(this.llmCancel=!0,await this.llmJob,this.llmCancel=!1)}processTTSTFInternal(e){const u=L(e).trim();0!==u.length&&this.perso&&(this.setChatState(t.ChatState.ANALYZING),this.perso.ttstf(u))}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(e=null,u=null){const D=new Map(this.chatStateMap);function s(e){e===t.ChatState.ANALYZING?D.set(e,(D.get(e)||0)+1):D.set(e,1)}function a(e){e===t.ChatState.ANALYZING?D.set(e,Math.max((D.get(e)||0)-1,0)):D.set(e,0)}if(null!=e)if(e instanceof Array)for(let t of e)s(t);else s(e);if(null!=u)if(u instanceof Array)for(let t of u)a(t);else a(u);const n=this.exchangeChatStateMapToSet(this.chatStateMap),r=this.exchangeChatStateMapToSet(D);this.chatStateMap=D,this.isEqualChatStateMap(n,r)||this.dispatchChatState(r)}resetChatState(){this.chatStateMap=new Map([[t.ChatState.RECORDING,0],[t.ChatState.LLM,0],[t.ChatState.ANALYZING,0],[t.ChatState.SPEAKING,0],[t.ChatState.TTS,0]]),this.dispatchChatState(this.exchangeChatStateMapToSet(this.chatStateMap))}exchangeChatStateMapToSet(t){const e=new Set;for(const u of t)u[1]>0&&e.add(u[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 u of t)if(t.has(u)!==e.has(u))return!1;return!0}async logSessionEvent(t){const e="object"==typeof t?JSON.stringify(t):t;await l.sessionEvent(this.apiServer,this.sessionId,D.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 l.sessionEvent(this.apiServer,this.sessionId,D.SESSION_DURING),null!==this.heartbeatIntervalId&&(this.heartbeatIntervalId=setTimeout(t,1e4))}catch(t){t instanceof a?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 O(t,e,u,D,s,a){if("boolean"!=typeof s){const a=await k.create(t,e,u,D);return new x(t,e,a,s)}const n=a??[];let r,o;if(s)r=await navigator.mediaDevices.getUserMedia({audio:!0,video:!1}),o=()=>{};else{const t=new AudioContext,e=t.createOscillator();e.frequency.value=0;const u=t.createMediaStreamDestination();e.connect(u),e.start(),r=u.stream,o=()=>{e.stop(),e.disconnect(u),t.close()}}const i=await k.create(t,e,u,D,r);if(!i)return o(),new x(t,e,null,n);const F=new x(t,e,i,n,{stream:r,legacyVoiceChatMode:!0});return F.onClose(()=>{o()}),F}return t.ApiError=a,t.ChatTool=class{name;description;parameters;call;executeOnly;constructor(t,e,u,D,s=!1){this.name=t,this.description=e,this.parameters=u,this.call=D,this.executeOnly=s}},t.LLMError=n,t.LLMStreamingResponseError=r,t.LlmProcessor=M,t.STTError=o,t.Session=x,t.TTSDecodeError=F,t.TTSError=i,t.TTS_TARGET_SAMPLE_RATE=_,t.WavRecorder=N,t.createSession=async function(t,e,u,D,s,a){return"boolean"==typeof s?await O(t,e,u,D,s,a??[]):await O(t,e,u,D,s)},t.createSessionId=async function(t,e,D){let s;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 D){const a=await l.getSessionTemplate(t,e,D);if("webrtc"!==a.model_style.platform_type)throw new Error(`SessionTemplate "${D}" uses platform_type "${a.model_style.platform_type}", but only "webrtc" is supported`);s=function(t){const e=e=>t.capability.some(t=>t.name===e);return{using_stf_webrtc:e(u.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:e(u.LLM)?t.llm_type.name:void 0,tts_type:e(u.TTS)?t.tts_type.name:void 0,stt_type:e(u.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}}(a)}else s=D;const a={capability:[],...s};s.using_stf_webrtc&&a.capability.push(u.STF_WEBRTC),s?.llm_type&&(a.capability.push(u.LLM),a.llm_type=s.llm_type),s?.tts_type&&(a.capability.push(u.TTS),a.tts_type=s.tts_type),s?.stt_type&&(a.capability.push(u.STT),a.stt_type=s.stt_type);const n=await fetch(`${t}/api/v1/session/`,{body:JSON.stringify(a),headers:{"PersoLive-APIKey":e,"Content-Type":"application/json"},method:"POST"});return(await l.parseJson(n)).session_id},t.createWavRecorder=function(t){return new N(t)},t.getAllSettings=async function(t,e){const[u,D,s,a,n,r,o,i,F]=await Promise.all([c(t,e),h(t,e),C(t,e),E(t,e),d(t,e),p(t,e),g(t,e),m(t,e),S(t,e).catch(()=>[])]);return{llms:u,ttsTypes:D,sttTypes:s,modelStyles:a,backgroundImages:n,prompts:r,documents:o,mcpServers:i,textNormalizations:F}},t.getBackgroundImages=d,t.getDocuments=g,t.getLLMs=c,t.getMcpServers=m,t.getModelStyles=E,t.getPrompts=p,t.getSTTs=C,t.getSessionInfo=async function(t,e){return await l.getSessionInfo(t,e)},t.getSessionTemplates=async function(t,e){return await l.getSessionTemplates(t,e)},t.getTTSs=h,t.getTextNormalization=async function(t,e,u){return await l.downloadTextNormalization(t,e,u)},t.getTextNormalizations=S,t.getWavSampleRate=function(t){const e=new DataView(t);if(t.byteLength<28)throw new f("File too small to be a valid WAV");if("RIFF"!==B(e,0,4))throw new f("Missing RIFF header");let u=12;for(;u<t.byteLength-8;){const D=B(e,u,4),s=e.getUint32(u+4,!0);if("fmt "===D){if(u+16>t.byteLength)throw new f("fmt chunk extends beyond file");return e.getUint32(u+12,!0)}const a=u+8+s;if(a<=u)break;u=a}throw new f("Missing fmt chunk")},t.makeTTS=async function(t,e){return await l.makeTTS(t,e)},t}({});
|
|
1
|
+
var PersoInteractive=function(t){"use strict";var e,u,D;t.ChatState=void 0,(e=t.ChatState||(t.ChatState={})).RECORDING="RECORDING",e.LLM="LLM",e.ANALYZING="ANALYZING",e.SPEAKING="SPEAKING",e.TTS="TTS";class s extends Error{constructor(){super("WebRTC connection timeout")}}class a extends Error{errorCode;code;detail;attr;constructor(t,e,u,D){let s;s=null!=D?`${t}:${D}_${u}`:`${t}:${u}`,super(s),this.errorCode=t,this.code=e,this.detail=u,this.attr=D}}class n extends Error{underlyingError;constructor(t){super(),this.underlyingError=t}}class r extends Error{description;constructor(t){super(),this.description=t}}class o extends Error{underlyingError;constructor(t){super(`STT Error: ${t.detail}`),this.underlyingError=t}}class i extends Error{underlyingError;constructor(t){super(t.message),this.underlyingError=t}}class F extends Error{description;constructor(t){super(`TTS decode error: ${t}`),this.description=t}}class l extends a{constructor(t){super(t.errorCode,t.code,t.detail,t.attr),this.name="SessionCreationError"}}class c extends l{constructor(t){super(t),this.name="DoesNotExistError"}}class h extends l{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"}(u||(u={})),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"}(D||(D={}));class C{static async getLLMs(t,e){const u=fetch(`${t}/api/v1/settings/llm_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getModelStyles(t,e){const u=fetch(`${t}/api/v1/settings/modelstyle/?platform_type=webrtc`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getBackgroundImages(t,e){const u=fetch(`${t}/api/v1/background_image/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getTTSs(t,e){const u=fetch(`${t}/api/v1/settings/tts_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getSTTs(t,e){const u=fetch(`${t}/api/v1/settings/stt_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async makeTTS(t,{sessionId:e,text:u,locale:D,output_format:s}){const a={text:u};D&&(a.locale=D),s&&(a.output_format=s);const n=await fetch(`${t}/api/v1/session/${e}/tts/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)});return await this.parseJson(n)}static async getPrompts(t,e){const u=fetch(`${t}/api/v1/prompt/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getDocuments(t,e){const u=fetch(`${t}/api/v1/document/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getTextNormalizations(t,e){const u=fetch(`${t}/api/v1/settings/text_normalization_config/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async downloadTextNormalization(t,e,u){const D=await fetch(`${t}/api/v1/settings/text_normalization_config/${u}/download/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(D)}static async getSessionTemplates(t,e){const u=await fetch(`${t}/api/v1/session_template/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(u)}static async getSessionTemplate(t,e,u){const D=await fetch(`${t}/api/v1/session_template/${u}/`,{headers:{"PersoLive-APIKey":e},method:"GET"});return await this.parseJson(D)}static async getMcpServers(t,e){const u=fetch(`${t}/api/v1/settings/mcp_type/`,{headers:{"PersoLive-APIKey":e},method:"GET"}),D=await u;return await this.parseJson(D)}static async getSessionInfo(t,e){const u=fetch(`${t}/api/v1/session/${e}/`,{method:"GET"}),D=await u;return await this.parseJson(D)}static async sessionEvent(t,e,u,D=""){const s=await fetch(`${t}/api/v1/session/${e}/event/create/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detail:D,event:u})});await this.parseJson(s)}static async makeSTT(t,e,u,D){const s=new FormData;s.append("audio",u),D&&s.append("language",D);const a=await fetch(`${t}/api/v1/session/${e}/stt/`,{method:"POST",body:s});return{text:(await this.parseJson(a)).text}}static async makeLLM(t,e,u,D){const s=await fetch(`${t}/api/v1/session/${e}/llm/v2/`,{body:JSON.stringify(u),headers:{"Content-Type":"application/json"},method:"POST",signal:D});if(!s.ok){const t=await s.json(),e=t.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${s.status} with no error details`,attr:null};throw new a(s.status,e.code,e.detail,e.attr)}return s.body.getReader()}static async getIceServers(t,e){const u=await fetch(`${t}/api/v1/session/${e}/ice-servers/`,{method:"GET"});return(await this.parseJson(u)).ice_servers}static async exchangeSDP(t,e,u){const D=await fetch(`${t}/api/v1/session/${e}/exchange/`,{body:JSON.stringify({client_sdp:u}),headers:{"Content-Type":"application/json"},method:"POST"});return(await this.parseJson(D)).server_sdp}static async parseJson(t){const e=await t.json();if(t.ok)return e;{const u=e.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${t.status} with no error details`,attr:null};throw new a(t.status,u.code,u.detail,u.attr)}}}class E extends Error{constructor(t){super(`WAV parse error: ${t}`),this.name="WavParseError"}}function d(t,e,u=1){const D=2*t.length,s=new ArrayBuffer(44+D),a=new DataView(s);g(a,0,"RIFF"),a.setUint32(4,36+D,!0),g(a,8,"WAVE"),g(a,12,"fmt "),a.setUint32(16,16,!0),a.setUint16(20,1,!0),a.setUint16(22,u,!0),a.setUint32(24,e,!0),a.setUint32(28,e*u*2,!0),a.setUint16(32,2*u,!0),a.setUint16(34,16,!0),g(a,36,"data"),a.setUint32(40,D,!0);let n=44;for(let e=0;e<t.length;e++){const u=Math.max(-1,Math.min(1,t[e]));a.setInt16(n,u<0?32768*u:32767*u,!0),n+=2}return s}function p(t,e,u){let D="";for(let s=0;s<u;s++)D+=String.fromCharCode(t.getUint8(e+s));return D}function g(t,e,u){for(let D=0;D<u.length;D++)t.setUint8(e+D,u.charCodeAt(D))}function m(t,e,u){switch(u){case 8:return(t.getUint8(e)-128)/128;case 16:return t.getInt16(e,!0)/32768;case 24:{const u=t.getUint8(e),D=t.getUint8(e+1),s=t.getUint8(e+2)<<16|D<<8|u;return(s>8388607?s-16777216:s)/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,u,D=1){if(0===t.length)throw new S("Cannot resample empty audio data");if(e<=0||u<=0)throw new S(`Invalid sample rate: original=${e}, target=${u}`);if(e===u)return t;try{const s=t.length/e,a=Math.ceil(s*u),n=new OfflineAudioContext(D,t.length,e).createBuffer(D,t.length,e);n.getChannelData(0).set(t);const r=new OfflineAudioContext(D,a,u),o=r.createBufferSource();o.buffer=n,o.connect(r.destination),o.start(0);return(await r.startRendering()).getChannelData(0)}catch(t){const D=t instanceof Error?t.message:String(t);throw new S(`Failed to resample audio from ${e}Hz to ${u}Hz: ${D}`)}}const w=16e3;async function y(t,e=!0){let u;try{const e=atob(t),D=new Array(e.length);for(let t=0;t<e.length;t++)D[t]=e.charCodeAt(t);u=new Uint8Array(D).buffer}catch{throw new F("Invalid Base64 audio data")}const D=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"}(u);if(!e)return new Blob([u],{type:D});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 E("File too small to be a valid WAV");if("RIFF"!==p(e,0,4))throw new E("Missing RIFF header");if("WAVE"!==p(e,8,4))throw new E("Missing WAVE format identifier");let u=12,D=!1,s=0,a=0,n=0,r=0;for(;u<t.byteLength-8;){const o=p(e,u,4),i=e.getUint32(u+4,!0);if("fmt "===o){if(u+24>t.byteLength)throw new E("fmt chunk extends beyond file");s=e.getUint16(u+8,!0),a=e.getUint16(u+10,!0),n=e.getUint32(u+12,!0),r=e.getUint16(u+22,!0),D=!0,u+=8+i;break}const F=u+8+i;if(F<=u)break;u=F}if(!D)throw new E("Missing fmt chunk");if(1!==s)throw new E(`Unsupported audio format: ${s} (only PCM format 1 is supported)`);if(0===a||a>8)throw new E(`Invalid channel count: ${a}`);if(0===n||n>192e3)throw new E(`Invalid sample rate: ${n}`);if(![8,16,24,32].includes(r))throw new E(`Unsupported bits per sample: ${r}`);let o=-1,i=0;for(;u<t.byteLength-8;){const t=p(e,u,4),D=e.getUint32(u+4,!0);if("data"===t){o=u+8,i=D;break}const s=u+8+D;if(s<=u)break;u=s}if(-1===o)throw new E("Missing data chunk");const F=t.byteLength-o,l=Math.min(i,F),c=r/8,h=Math.floor(l/(c*a)),C=new Float32Array(h*a);let d=0;for(let u=0;u<h*a;u++){const D=o+u*c;if(D+c>t.byteLength)break;C[d++]=Math.max(-1,Math.min(1,m(e,D,r)))}if(2===a){const t=new Float32Array(h);for(let e=0;e<h;e++)t[e]=(C[2*e]+C[2*e+1])/2;return{sampleRate:n,channels:1,bitsPerSample:r,samples:t}}return{sampleRate:n,channels:a,bitsPerSample:r,samples:C.slice(0,d)}}(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 u=new AudioContext({sampleRate:w});try{const e=await u.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 u.close()}}(u,D),e=d(t.samples,w,1);return new Blob([e],{type:"audio/wav"})}catch{return new Blob([u],{type:D})}}const B=/[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E-\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED8\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])))?))?|\uDD75(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3C-\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE8A\uDE8E-\uDEC2\uDEC6\uDEC8\uDECD-\uDEDC\uDEDF-\uDEEA\uDEEF]|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g;function T(t){return t.replace(B,"")}class A{config;messageHistory=[];constructor(t){this.config=t}async*processLLM(e){if(0===e.message.length)throw new Error("Message cannot be empty");const u=e.tools??this.config.clientTools,D=u.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),s={newMessageHistory:[{role:"user",content:e.message}],allChunks:[],message:"",lastYieldedChunkCount:0,pendingToolCallsMessage:null,aborted:!1,streamingError:null};let o=0,i=[...this.messageHistory,...s.newMessageHistory];this.config.callbacks.onChatStateChange(t.ChatState.LLM,null);try{for(;;){if(e.signal?.aborted)return void(s.allChunks.length>0&&(yield{type:"message",chunks:[...s.allChunks],message:s.message,finish:!0}));let t;try{t=await C.makeLLM(this.config.apiServer,this.config.sessionId,{messages:i,tools:D},e.signal)}catch(t){if(t instanceof a)return void(yield{type:"error",error:new n(t)});throw t}if(s.streamingError=null,yield*this.parseSSEStream(t,s,e),s.streamingError)return;if(s.aborted)return void(s.allChunks.length>0&&(yield{type:"message",chunks:[...s.allChunks],message:s.message,finish:!0}));if(null!=s.pendingToolCallsMessage){yield*this.executeToolCalls(s,u);const t=s.lastToolCallResults,e=t.length>0&&s.pendingToolCallsMessage.tool_calls.length!==t.length,D=t.some(t=>!t.chatTool.executeOnly);if(e||D){if(o++,o>=10)return void(yield{type:"error",error:new n(new r("Tool follow-up loop exceeded maximum rounds (10)"))});i=[...this.messageHistory,...s.newMessageHistory],s.pendingToolCallsMessage=null;continue}}return this.messageHistory.push(...s.newMessageHistory),void(yield{type:"message",chunks:[...s.allChunks],message:s.message,finish:!0})}}finally{this.config.callbacks.onChatStateChange(null,t.ChatState.LLM)}}async*parseSSEStream(t,e,u){const D=new TextDecoder("utf-8");let s="",a="";e.pendingToolCallsMessage=null;const o=()=>e.allChunks.length>e.lastYieldedChunkCount?(e.lastYieldedChunkCount=e.allChunks.length,{type:"message",chunks:[...e.allChunks],message:e.message,finish:!1}):null;for(;;){const{done:i,value:F}=await t.read();if(i)break;let l;for(s+=D.decode(F,{stream:!0});-1!==(l=s.indexOf("\n"));){if(u.signal?.aborted)return void(e.aborted=!0);const t=s.slice(0,l).trim();if(s=s.slice(l+1),!t.startsWith("data: {"))return e.streamingError=new n(new r("Failed to parse SSE response")),void(yield{type:"error",error:e.streamingError});let D;try{D=JSON.parse(t.slice(6).trim())}catch{return e.streamingError=new n(new r("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==D.status)return e.streamingError=new n(new r(D.reason)),void(yield{type:"error",error:e.streamingError});if(a.length>0&&"message"!=D.type){e.newMessageHistory.push({role:"assistant",type:"message",content:a}),a="";const t=o();t&&(yield t)}if("message"===D.type){const t=T(D.content);a+=t,e.message+=t,e.allChunks.push(t);continue}"tool_call"!==D.type||null==D.tool_calls?"tool"!==D.role||"tool_call"===D.type&&e.newMessageHistory.push({role:D.role,type:D.type,content:D.content,tool_call_id:D.tool_call_id}):(e.newMessageHistory.push({role:"assistant",type:D.type,content:D.content,tool_calls:D.tool_calls}),e.pendingToolCallsMessage=D,yield{type:"tool_call",tool_calls:D.tool_calls})}const c=o();c&&(yield c)}const i=s.trim();if(i.length>0){if(!i.startsWith("data: {"))return e.streamingError=new n(new r("Failed to parse SSE response")),void(yield{type:"error",error:e.streamingError});let t;try{t=JSON.parse(i.slice(6).trim())}catch{return e.streamingError=new n(new r("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==t.status)return e.streamingError=new n(new r(t.reason)),void(yield{type:"error",error:e.streamingError});if("message"===t.type){const u=T(t.content);a+=u,e.message+=u,e.allChunks.push(u)}else if("tool_call"===t.type&&null!=t.tool_calls){if(a.length>0){e.newMessageHistory.push({role:"assistant",type:"message",content:a}),a="";const t=o();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}}}a.length>0&&e.newMessageHistory.push({role:"assistant",type:"message",content:a})}async*executeToolCalls(t,e){const u=t=>{for(const u of e)if(u.name===t)return u;return null},D=[];for(const e of t.pendingToolCallsMessage.tool_calls){const t=u(e.function.name);null!=t&&D.push((async()=>{try{const u=await t.call(JSON.parse(e.function.arguments));return{toolCallId:e.id,chatTool:t,chatToolResult:u}}catch(u){return{toolCallId:e.id,chatTool:t,chatToolResult:{error:u.message}}}})())}const s=await Promise.all(D);t.lastToolCallResults=s;for(const e of s)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 v=`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 _{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(v),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 u=t.reduce((t,e)=>t+e.length,0),D=new Float32Array(u);let s,a,n=0;for(const e of t)D.set(e,n),n+=e.length;this.targetSampleRate&&this.targetSampleRate!==e?(s=await f(D,e,this.targetSampleRate,this.channels),a=this.targetSampleRate):(s=D,a=e);const r=d(s,a,this.channels),o=new Blob([r],{type:"audio/wav"});return new File([o],"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 I 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,e,s,a,n){const r=await C.getSessionInfo(t,e);if(!(Array.isArray(r.capability)&&r.capability.some(t=>t.name===u.STF_ONPREMISE||t.name===u.STF_WEBRTC)))return await C.sessionEvent(t,e,D.SESSION_START),null;const o=await C.getIceServers(t,e);let i=await I.createPeerConnection(o),F=i.createDataChannel("message",{protocol:"message"}),l=new I(i,F);n?n.getTracks().forEach(function(t){i.addTrack(t,n)}):i.addTransceiver("audio",{direction:"recvonly"});const c=i.addTransceiver("video",{direction:"recvonly"}),h=RTCRtpReceiver.getCapabilities("video");null!=h&&c.setCodecPreferences(h.codecs);const E=await i.createOffer();await i.setLocalDescription(E);const d=await C.exchangeSDP(t,e,E);return await i.setRemoteDescription(d),await I.waitFor(()=>l.isReady(),100,50),l.changeSize(s,a),l}static async createPeerConnection(t){return new RTCPeerConnection({sdpSemantics:"unified-plan",iceServers:t})}static async waitFor(t,e,u){let D=0;if(await new Promise(s=>{const a=setInterval(()=>{D+=1,D>=u&&(clearInterval(a),s("bad")),t()&&(clearInterval(a),s("good"))},e)}),D>=u)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})}static BACKPRESSURE_THRESHOLD=524288;static FILE_TRANSFER_TIMEOUT=3e4;sendFile(t,e=65536){return new Promise((u,D)=>{let s=!1;const a=t=>{s||(s=!0,clearTimeout(r),t())},n=this.pc.createDataChannel("file",{protocol:"file"}),r=setTimeout(()=>{a(()=>{n.close(),D(new Error("File transfer timed out"))})},I.FILE_TRANSFER_TIMEOUT);n.onclose=()=>{a(()=>{D(new Error("File channel closed before transfer completed"))})},n.onerror=t=>{a(()=>{n.close(),D(new Error(`File channel error: ${t}`))})},n.addEventListener("message",async s=>{try{if(0===s.data.length){const u=new Uint8Array(await t.arrayBuffer());let s=0;const r=()=>{for(;s<u.length;){if(n.bufferedAmount>I.BACKPRESSURE_THRESHOLD)return n.bufferedAmountLowThreshold=I.BACKPRESSURE_THRESHOLD/2,n.onbufferedamountlow=()=>{n.onbufferedamountlow=null,n.onclose=null,r()},void(n.onclose=()=>{n.onbufferedamountlow=null,a(()=>{D(new Error("File channel closed during transfer"))})});n.send(u.slice(s,s+e)),s+=e}n.send(new Uint8Array(0))};r()}else a(()=>{n.close(),u(s.data)})}catch(t){a(()=>{n.close(),D(t instanceof Error?t:new Error(String(t)))})}})})}async stf(t,e,u){const D=await this.sendFile(t);return this.sendMessage("stf",{message:u,file_ref:D,format:e}),D}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 u=u=>{const D=JSON.parse(u.data);D.type===t&&e(D.data)};return this.dc.addEventListener("message",u),()=>{this.dc.removeEventListener("message",u)}}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 R{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([[t.ChatState.RECORDING,0],[t.ChatState.LLM,0],[t.ChatState.ANALYZING,0],[t.ChatState.SPEAKING,0],[t.ChatState.TTS,0]]);sttRecorder=null;sttTimeoutHandle=null;sttTimeoutAudioFile=null;heartbeatIntervalId=null;legacyVoiceChatMode;stream;constructor(e,u,D,s,a){this.apiServer=e,this.sessionId=u,this.perso=D,this.clientTools=s,this.legacyVoiceChatMode=a?.legacyVoiceChatMode??!1,this.stream=a?.stream??null,this.resetChatState(),this.llmProcessor=new A({apiServer:e,sessionId:u,clientTools:s,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)}}),D?(D.subscribeStatus(t=>{!1===t.detail?.live&&this.stopHeartbeat()}),D.setMessageCallback("stf",e=>{if(this.chatStateMap.get(t.ChatState.ANALYZING)||this.chatStateMap.get(t.ChatState.SPEAKING))if(this.setChatState(t.ChatState.SPEAKING,t.ChatState.ANALYZING),null!==this.lastStfTimeoutHandle){clearTimeout(this.lastStfTimeoutHandle);let u=Date.now();this.stfTotalDuration+=e.duration+1e3-(u-this.stfTimeoutStartTime),this.stfTimeoutStartTime=u,this.lastStfTimeoutHandle=setTimeout(()=>{this.lastStfTimeoutHandle=null,this.stfTimeoutStartTime=0,this.stfTotalDuration=0,this.setChatState(null,t.ChatState.SPEAKING)},this.stfTotalDuration)}else this.stfTimeoutStartTime=Date.now(),this.stfTotalDuration=e.duration+2e3,this.lastStfTimeoutHandle=setTimeout(()=>{this.lastStfTimeoutHandle=null,this.stfTimeoutStartTime=0,this.stfTotalDuration=0,this.setChatState(null,t.ChatState.SPEAKING)},this.stfTotalDuration)}),D.setMessageCallback("stt",e=>{if(this.setChatState(null,t.ChatState.ANALYZING),null!=this.sttEventHandler)this.sttEventHandler.dispatchEvent(new CustomEvent("stt",{detail:e.text}));else{if(""===e.text)return;this.processChat(e.text)}}),D.setMessageCallback("stt-error",e=>{this.setChatState(null,t.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 u=t instanceof File?t:new File([t],"audio.wav",{type:t.type});try{return await C.makeSTT(this.apiServer,this.sessionId,u,e)}catch(t){if(t instanceof a)throw new o(t);throw t}}async processSTF(e,u,D=""){if(!this.perso)throw new Error("processSTF requires WebRTC (STF mode)");this.pipelineSuppressed=!1,this.setChatState(t.ChatState.ANALYZING);try{const s=function(t,e){const u=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 u(t)??u(e.type)??"wav"}(u,e),a=await this.perso.stf(e,s,D);return this.pipelineSuppressed?(this.setChatState(null,t.ChatState.ANALYZING),a):a}catch(e){throw this.setChatState(null,t.ChatState.ANALYZING),e}}async processTTS(e,u={}){const{resample:D=!1,locale:s,output_format:n}=u,r=T(e).trim();if(0===r.length)return;this.pipelineSuppressed=!1;const o=/[.?!]$/.test(r)?r:r+".";this.setChatState(t.ChatState.TTS,null);try{const t={sessionId:this.sessionId,text:o,...s&&{locale:s},...n&&{output_format:n}},{audio:e}=await C.makeTTS(this.apiServer,t);if(this.pipelineSuppressed)return;return await y(e,D)}catch(t){t instanceof a||t instanceof F?this.setError(new i(t)):this.setError(t instanceof Error?t:new Error(String(t)))}finally{this.setChatState(null,t.ChatState.TTS)}}startVoiceChat(){if(!this.perso)throw new Error("startVoiceChat requires WebRTC (STF mode)");return this.pipelineSuppressed=!1,this.setChatState(t.ChatState.RECORDING),this.perso.recordStart()}stopVoiceChat(){if(!this.perso)throw new Error("stopVoiceChat requires WebRTC (STF mode)");this.setChatState(t.ChatState.ANALYZING,t.ChatState.RECORDING),this.perso.recordEndStt()}async startProcessSTT(e){if(this.sttRecorder?.isRecording())throw new Error("STT recording is already in progress");this.pipelineSuppressed=!1,this.setChatState(t.ChatState.RECORDING);try{this.sttRecorder=new _({targetSampleRate:16e3}),await this.sttRecorder.start(),e&&e>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,t.ChatState.RECORDING)}this.sttRecorder=null}},e))}catch(e){throw this.setChatState(null,t.ChatState.RECORDING),this.sttRecorder=null,e}}lastRecordedAudioFile=null;async stopProcessSTT(e){let u;if(this.sttTimeoutHandle&&(clearTimeout(this.sttTimeoutHandle),this.sttTimeoutHandle=null),this.setChatState(null,t.ChatState.RECORDING),this.sttTimeoutAudioFile)u=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");u=await this.sttRecorder.stop(),this.sttRecorder=null}this.lastRecordedAudioFile=u;try{return(await C.makeSTT(this.apiServer,this.sessionId,u,e)).text}catch(t){if(t instanceof a)throw new o(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(e){this.setChatState(t.ChatState.LLM);const u=this.clientTools.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),D=new Array;null===e||(e instanceof Array?D.push(...e):"string"==typeof e&&D.push({role:"user",content:e}));const s=await fetch(`${this.apiServer}/api/v1/session/${this.sessionId}/llm/v2/`,{body:JSON.stringify({messages:[...this.messageHistory,...D],tools:u}),headers:{"Content-Type":"application/json"},method:"POST"});if(!s.ok){const e=await s.json(),u=new n(new a(s.status,e.errors[0].code,e.errors[0].detail,e.errors[0].attr));return this.setError(u),void this.setChatState(null,t.ChatState.LLM)}const o=s.body?.getReader(),i=new TextDecoder("utf-8");let F="",l=null,c="";for(;;){const{done:e,value:u}=await o.read();if(e)break;let s;for(c+=i.decode(u,{stream:!0});-1!==(s=c.indexOf("\n"));){if(this.llmCancel)return F.length>0&&this.addMessageToChatLog(F,!1),void this.setChatState(null,t.ChatState.LLM);const e=c.slice(0,s).trim();if(c=c.slice(s+1),!e.startsWith("data: {")){const e=new n(new r("Failed to parse SSE response"));return this.setError(e),void this.setChatState(null,t.ChatState.LLM)}const u=JSON.parse(e.slice(6).trim());if("success"!==u.status){const e=new n(new r(u.reason));return this.setError(e),void this.setChatState(null,t.ChatState.LLM)}F.length>0&&"message"!=u.type&&(D.push({role:"assistant",type:"message",content:F}),this.addMessageToChatLog(F,!1),F=""),"message"!==u.type?"tool_call"!==u.type||null==u.tool_calls?"tool"!==u.role||"tool_call"===u.type&&D.push({role:u.role,type:u.type,content:u.content,tool_call_id:u.tool_call_id}):(D.push({role:"assistant",type:u.type,content:u.content,tool_calls:u.tool_calls}),l=u):(F+=T(u.content),this.processTTSTFInternal(u.content))}}if(this.llmCancel)this.setChatState(null,t.ChatState.LLM);else{if(null!=l){const t=[];for(const e of l.tool_calls){const u=this.getChatTool(this.clientTools,e.function.name);null!=u&&t.push(new Promise(async t=>{try{const D=await u.call(JSON.parse(e.function.arguments));t({toolCallId:e.id,chatTool:u,chatToolResult:D})}catch(D){t({toolCallId:e.id,chatTool:u,chatToolResult:{result:"error!"}})}}))}const e=await Promise.all(t);for(const t of e)D.push({role:"tool",content:JSON.stringify(t.chatToolResult),tool_call_id:t.toolCallId});const u=e.length>0&&l.tool_calls.length!==e.length,s=e.some(t=>!t.chatTool.executeOnly);u||s?await this.processChatInternal(D):this.messageHistory.push(...D)}else this.messageHistory.push(...D);this.setChatState(null,t.ChatState.LLM)}}getChatTool(t,e){for(const u of t)if(u.name===e)return u;return null}llmCancel=!1;pipelineSuppressed=!1;async clearLLMJob(){null!=this.llmJob&&(this.llmCancel=!0,await this.llmJob,this.llmCancel=!1)}processTTSTFInternal(e){const u=T(e).trim();0!==u.length&&this.perso&&(this.setChatState(t.ChatState.ANALYZING),this.perso.ttstf(u))}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(e=null,u=null){const D=new Map(this.chatStateMap);function s(e){e===t.ChatState.ANALYZING?D.set(e,(D.get(e)||0)+1):D.set(e,1)}function a(e){e===t.ChatState.ANALYZING?D.set(e,Math.max((D.get(e)||0)-1,0)):D.set(e,0)}if(null!=e)if(e instanceof Array)for(let t of e)s(t);else s(e);if(null!=u)if(u instanceof Array)for(let t of u)a(t);else a(u);const n=this.exchangeChatStateMapToSet(this.chatStateMap),r=this.exchangeChatStateMapToSet(D);this.chatStateMap=D,this.isEqualChatStateMap(n,r)||this.dispatchChatState(r)}resetChatState(){this.chatStateMap=new Map([[t.ChatState.RECORDING,0],[t.ChatState.LLM,0],[t.ChatState.ANALYZING,0],[t.ChatState.SPEAKING,0],[t.ChatState.TTS,0]]),this.dispatchChatState(this.exchangeChatStateMapToSet(this.chatStateMap))}exchangeChatStateMapToSet(t){const e=new Set;for(const u of t)u[1]>0&&e.add(u[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 u of t)if(t.has(u)!==e.has(u))return!1;return!0}async logSessionEvent(t){const e="object"==typeof t?JSON.stringify(t):t;await C.sessionEvent(this.apiServer,this.sessionId,D.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 C.sessionEvent(this.apiServer,this.sessionId,D.SESSION_DURING),null!==this.heartbeatIntervalId&&(this.heartbeatIntervalId=setTimeout(t,1e4))}catch(t){t instanceof a?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,u,D,s,a){if("boolean"!=typeof s){const a=await I.create(t,e,u,D);return new R(t,e,a,s)}const n=a??[];let r,o;if(s)r=await navigator.mediaDevices.getUserMedia({audio:!0,video:!1}),o=()=>{};else{const t=new AudioContext,e=t.createOscillator();e.frequency.value=0;const u=t.createMediaStreamDestination();e.connect(u),e.start(),r=u.stream,o=()=>{e.stop(),e.disconnect(u),t.close()}}const i=await I.create(t,e,u,D,r);if(!i)return o(),new R(t,e,null,n);const F=new R(t,e,i,n,{stream:r,legacyVoiceChatMode:!0});return F.onClose(()=>{o()}),F}async function b(t,e){return await C.getLLMs(t,e)}async function M(t,e){return await C.getTTSs(t,e)}async function N(t,e){return await C.getSTTs(t,e)}async function x(t,e){return await C.getModelStyles(t,e)}async function k(t,e){return await C.getBackgroundImages(t,e)}async function O(t,e){return await C.getPrompts(t,e)}async function P(t,e){return await C.getDocuments(t,e)}async function H(t,e){return await C.getMcpServers(t,e)}async function $(t,e){return await C.getTextNormalizations(t,e)}return t.ApiError=a,t.ChatTool=class{name;description;parameters;call;executeOnly;constructor(t,e,u,D,s=!1){this.name=t,this.description=e,this.parameters=u,this.call=D,this.executeOnly=s}},t.DoesNotExistError=c,t.LLMError=n,t.LLMStreamingResponseError=r,t.LlmProcessor=A,t.NotInOrganizationError=h,t.STTError=o,t.Session=R,t.SessionCreationError=l,t.TTSDecodeError=F,t.TTSError=i,t.TTS_TARGET_SAMPLE_RATE=w,t.WavRecorder=_,t.createSession=async function(t,e,u,D,s,a){return"boolean"==typeof s?await L(t,e,u,D,s,a??[]):await L(t,e,u,D,s)},t.createSessionId=async function(t,e,D){"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 s;if("string"==typeof D){const a=await C.getSessionTemplate(t,e,D);if("webrtc"!==a.model_style.platform_type)throw new Error(`SessionTemplate "${D}" uses platform_type "${a.model_style.platform_type}", but only "webrtc" is supported`);s=function(t){const e=e=>t.capability.some(t=>t.name===e);return{using_stf_webrtc:e(u.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:e(u.LLM)?t.llm_type.name:void 0,tts_type:e(u.TTS)?t.tts_type.name:void 0,stt_type:e(u.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}}(a)}else s=D;const a={capability:[],...s};s.using_stf_webrtc&&a.capability.push(u.STF_WEBRTC),s?.llm_type&&(a.capability.push(u.LLM),a.llm_type=s.llm_type),s?.tts_type&&(a.capability.push(u.TTS),a.tts_type=s.tts_type),s?.stt_type&&(a.capability.push(u.STT),a.stt_type=s.stt_type);const n=await fetch(`${t}/api/v1/session/`,{body:JSON.stringify(a),headers:{"PersoLive-APIKey":e,"Content-Type":"application/json"},method:"POST"});return(await C.parseJson(n)).session_id}catch(t){throw function(t){if(t instanceof l)return t;if(t instanceof a)switch(t.code){case"does_not_exist":return new c(t);case"not_in_organization":return new h(t);default:return new l(t)}return t}(t)}},t.createWavRecorder=function(t){return new _(t)},t.getAllSettings=async function(t,e){const[u,D,s,a,n,r,o,i,F]=await Promise.all([b(t,e),M(t,e),N(t,e),x(t,e),k(t,e),O(t,e),P(t,e),H(t,e),$(t,e).catch(()=>[])]);return{llms:u,ttsTypes:D,sttTypes:s,modelStyles:a,backgroundImages:n,prompts:r,documents:o,mcpServers:i,textNormalizations:F}},t.getBackgroundImages=k,t.getDocuments=P,t.getLLMs=b,t.getMcpServers=H,t.getModelStyles=x,t.getPrompts=O,t.getSTTs=N,t.getSessionInfo=async function(t,e){return await C.getSessionInfo(t,e)},t.getSessionTemplates=async function(t,e){return await C.getSessionTemplates(t,e)},t.getTTSs=M,t.getTextNormalization=async function(t,e,u){return await C.downloadTextNormalization(t,e,u)},t.getTextNormalizations=$,t.getWavSampleRate=function(t){const e=new DataView(t);if(t.byteLength<28)throw new E("File too small to be a valid WAV");if("RIFF"!==p(e,0,4))throw new E("Missing RIFF header");let u=12;for(;u<t.byteLength-8;){const D=p(e,u,4),s=e.getUint32(u+4,!0);if("fmt "===D){if(u+16>t.byteLength)throw new E("fmt chunk extends beyond file");return e.getUint32(u+12,!0)}const a=u+8+s;if(a<=u)break;u=a}throw new E("Missing fmt chunk")},t.makeTTS=async function(t,e){return await C.makeTTS(t,e)},t}({});
|
package/dist/client/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"emoji-regex";var e,s,a;!function(t){t.RECORDING="RECORDING",t.LLM="LLM",t.ANALYZING="ANALYZING",t.SPEAKING="SPEAKING",t.TTS="TTS"}(e||(e={}));class n{name;description;parameters;call;executeOnly;constructor(t,e,s,a,n=!1){this.name=t,this.description=e,this.parameters=s,this.call=a,this.executeOnly=n}}class r extends Error{constructor(){super("WebRTC connection timeout")}}class o extends Error{errorCode;code;detail;attr;constructor(t,e,s,a){let n;n=null!=a?`${t}:${a}_${s}`:`${t}:${s}`,super(n),this.errorCode=t,this.code=e,this.detail=s,this.attr=a}}class i extends Error{underlyingError;constructor(t){super(),this.underlyingError=t}}class l extends Error{description;constructor(t){super(),this.description=t}}class c extends Error{underlyingError;constructor(t){super(`STT Error: ${t.detail}`),this.underlyingError=t}}class h extends Error{underlyingError;constructor(t){super(t.message),this.underlyingError=t}}class d 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"}(s||(s={})),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"}(a||(a={}));class u{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:n}){const r={text:s};a&&(r.locale=a),n&&(r.output_format=n);const o=await fetch(`${t}/api/v1/session/${e}/tts/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});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 n=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(n)}static async makeSTT(t,e,s,a){const n=new FormData;n.append("audio",s),a&&n.append("language",a);const r=await fetch(`${t}/api/v1/session/${e}/stt/`,{method:"POST",body:n});return await this.parseJson(r)}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 o(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 o(t.status,s.code,s.detail,s.attr)}}}async function p(t,e){return await u.getLLMs(t,e)}async function m(t,e){return await u.getTTSs(t,e)}async function g(t,e){return await u.getSTTs(t,e)}async function f(t,e){return await u.getModelStyles(t,e)}async function S(t,e){return await u.getBackgroundImages(t,e)}async function y(t,e){return await u.getPrompts(t,e)}async function w(t,e){return await u.getDocuments(t,e)}async function T(t,e){return await u.getMcpServers(t,e)}async function E(t,e){return await u.getTextNormalizations(t,e)}async function v(t,e,s){return await u.downloadTextNormalization(t,e,s)}async function C(t,e){return await u.getSessionTemplates(t,e)}async function _(t,e){const[s,a,n,r,o,i,l,c,h]=await Promise.all([p(t,e),m(t,e),g(t,e),f(t,e),S(t,e),y(t,e),w(t,e),T(t,e),E(t,e).catch(()=>[])]);return{llms:s,ttsTypes:a,sttTypes:n,modelStyles:r,backgroundImages:o,prompts:i,documents:l,mcpServers:c,textNormalizations:h}}async function I(t,e){return await u.makeTTS(t,e)}async function R(t,e){return await u.getSessionInfo(t,e)}class L extends Error{constructor(t){super(`WAV parse error: ${t}`),this.name="WavParseError"}}function b(t,e,s=1){const a=2*t.length,n=new ArrayBuffer(44+a),r=new DataView(n);A(r,0,"RIFF"),r.setUint32(4,36+a,!0),A(r,8,"WAVE"),A(r,12,"fmt "),r.setUint32(16,16,!0),r.setUint16(20,1,!0),r.setUint16(22,s,!0),r.setUint32(24,e,!0),r.setUint32(28,e*s*2,!0),r.setUint16(32,2*s,!0),r.setUint16(34,16,!0),A(r,36,"data"),r.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]));r.setInt16(o,s<0?32768*s:32767*s,!0),o+=2}return n}function M(t){const e=new DataView(t);if(t.byteLength<28)throw new L("File too small to be a valid WAV");if("RIFF"!==N(e,0,4))throw new L("Missing RIFF header");let s=12;for(;s<t.byteLength-8;){const a=N(e,s,4),n=e.getUint32(s+4,!0);if("fmt "===a){if(s+16>t.byteLength)throw new L("fmt chunk extends beyond file");return e.getUint32(s+12,!0)}const r=s+8+n;if(r<=s)break;s=r}throw new L("Missing fmt chunk")}function N(t,e,s){let a="";for(let n=0;n<s;n++)a+=String.fromCharCode(t.getUint8(e+n));return a}function A(t,e,s){for(let a=0;a<s.length;a++)t.setUint8(e+a,s.charCodeAt(a))}function k(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),n=t.getUint8(e+2)<<16|a<<8|s;return(n>8388607?n-16777216:n)/8388608}case 32:return t.getInt32(e,!0)/2147483648;default:return 0}}class O extends Error{constructor(t){super(`Audio resample error: ${t}`),this.name="AudioResampleError"}}async function x(t,e,s,a=1){if(0===t.length)throw new O("Cannot resample empty audio data");if(e<=0||s<=0)throw new O(`Invalid sample rate: original=${e}, target=${s}`);if(e===s)return t;try{const n=t.length/e,r=Math.ceil(n*s),o=new OfflineAudioContext(a,t.length,e).createBuffer(a,t.length,e);o.getChannelData(0).set(t);const i=new OfflineAudioContext(a,r,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 O(`Failed to resample audio from ${e}Hz to ${s}Hz: ${a}`)}}const P=16e3;async function H(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 d("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 L("File too small to be a valid WAV");if("RIFF"!==N(e,0,4))throw new L("Missing RIFF header");if("WAVE"!==N(e,8,4))throw new L("Missing WAVE format identifier");let s=12,a=!1,n=0,r=0,o=0,i=0;for(;s<t.byteLength-8;){const l=N(e,s,4),c=e.getUint32(s+4,!0);if("fmt "===l){if(s+24>t.byteLength)throw new L("fmt chunk extends beyond file");n=e.getUint16(s+8,!0),r=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 L("Missing fmt chunk");if(1!==n)throw new L(`Unsupported audio format: ${n} (only PCM format 1 is supported)`);if(0===r||r>8)throw new L(`Invalid channel count: ${r}`);if(0===o||o>192e3)throw new L(`Invalid sample rate: ${o}`);if(![8,16,24,32].includes(i))throw new L(`Unsupported bits per sample: ${i}`);let l=-1,c=0;for(;s<t.byteLength-8;){const t=N(e,s,4),a=e.getUint32(s+4,!0);if("data"===t){l=s+8,c=a;break}const n=s+8+a;if(n<=s)break;s=n}if(-1===l)throw new L("Missing data chunk");const h=t.byteLength-l,d=Math.min(c,h),u=i/8,p=Math.floor(d/(u*r)),m=new Float32Array(p*r);let g=0;for(let s=0;s<p*r;s++){const a=l+s*u;if(a+u>t.byteLength)break;m[g++]=Math.max(-1,Math.min(1,k(e,a,i)))}if(2===r){const t=new Float32Array(p);for(let e=0;e<p;e++)t[e]=(m[2*e]+m[2*e+1])/2;return{sampleRate:o,channels:1,bitsPerSample:i,samples:t}}return{sampleRate:o,channels:r,bitsPerSample:i,samples:m.slice(0,g)}}(t);if(e.sampleRate===P)return{samples:e.samples,sampleRate:e.sampleRate};return{samples:await x(e.samples,e.sampleRate,P,e.channels),sampleRate:P}}const s=new AudioContext({sampleRate:P});try{const e=await s.decodeAudioData(t.slice(0));if(e.sampleRate===P)return{samples:e.getChannelData(0),sampleRate:P};return{samples:await x(e.getChannelData(0),e.sampleRate,P,1),sampleRate:P}}finally{await s.close()}}(s,a),e=b(t.samples,P,1);return new Blob([e],{type:"audio/wav"})}catch{return new Blob([s],{type:a})}}const F=t();function D(t){return t.replace(F,"")}class ${config;messageHistory=[];constructor(t){this.config=t}async*processLLM(t){if(0===t.message.length)throw new Error("Message cannot be empty");const s=t.tools??this.config.clientTools,a=s.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),n={newMessageHistory:[{role:"user",content:t.message}],allChunks:[],message:"",lastYieldedChunkCount:0,pendingToolCallsMessage:null,aborted:!1,streamingError:null};let r=0,c=[...this.messageHistory,...n.newMessageHistory];this.config.callbacks.onChatStateChange(e.LLM,null);try{for(;;){if(t.signal?.aborted)return void(n.allChunks.length>0&&(yield{type:"message",chunks:[...n.allChunks],message:n.message,finish:!0}));let e;try{e=await u.makeLLM(this.config.apiServer,this.config.sessionId,{messages:c,tools:a},t.signal)}catch(t){if(t instanceof o)return void(yield{type:"error",error:new i(t)});throw t}if(n.streamingError=null,yield*this.parseSSEStream(e,n,t),n.streamingError)return;if(n.aborted)return void(n.allChunks.length>0&&(yield{type:"message",chunks:[...n.allChunks],message:n.message,finish:!0}));if(null!=n.pendingToolCallsMessage){yield*this.executeToolCalls(n,s);const t=n.lastToolCallResults,e=t.length>0&&n.pendingToolCallsMessage.tool_calls.length!==t.length,a=t.some(t=>!t.chatTool.executeOnly);if(e||a){if(r++,r>=10)return void(yield{type:"error",error:new i(new l("Tool follow-up loop exceeded maximum rounds (10)"))});c=[...this.messageHistory,...n.newMessageHistory],n.pendingToolCallsMessage=null;continue}}return this.messageHistory.push(...n.newMessageHistory),void(yield{type:"message",chunks:[...n.allChunks],message:n.message,finish:!0})}}finally{this.config.callbacks.onChatStateChange(null,e.LLM)}}async*parseSSEStream(t,e,s){const a=new TextDecoder("utf-8");let n="",r="";e.pendingToolCallsMessage=null;const o=()=>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(n+=a.decode(h,{stream:!0});-1!==(d=n.indexOf("\n"));){if(s.signal?.aborted)return void(e.aborted=!0);const t=n.slice(0,d).trim();if(n=n.slice(d+1),!t.startsWith("data: {"))return e.streamingError=new i(new l("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 i(new l("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==a.status)return e.streamingError=new i(new l(a.reason)),void(yield{type:"error",error:e.streamingError});if(r.length>0&&"message"!=a.type){e.newMessageHistory.push({role:"assistant",type:"message",content:r}),r="";const t=o();t&&(yield t)}if("message"===a.type){const t=D(a.content);r+=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=o();u&&(yield u)}const c=n.trim();if(c.length>0){if(!c.startsWith("data: {"))return e.streamingError=new i(new l("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 i(new l("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==t.status)return e.streamingError=new i(new l(t.reason)),void(yield{type:"error",error:e.streamingError});if("message"===t.type){const s=D(t.content);r+=s,e.message+=s,e.allChunks.push(s)}else if("tool_call"===t.type&&null!=t.tool_calls){if(r.length>0){e.newMessageHistory.push({role:"assistant",type:"message",content:r}),r="";const t=o();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}}}r.length>0&&e.newMessageHistory.push({role:"assistant",type:"message",content:r})}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 n=await Promise.all(a);t.lastToolCallResults=n;for(const e of n)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 G=`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 J{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(G),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 n,r,o=0;for(const e of t)a.set(e,o),o+=e.length;this.targetSampleRate&&this.targetSampleRate!==e?(n=await x(a,e,this.targetSampleRate,this.channels),r=this.targetSampleRate):(n=a,r=e);const i=b(n,r,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}}function U(t){return new J(t)}class W 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,e,n,r,o){const i=await u.getSessionInfo(t,e);if(!(Array.isArray(i.capability)&&i.capability.some(t=>t.name===s.STF_ONPREMISE||t.name===s.STF_WEBRTC)))return await u.sessionEvent(t,e,a.SESSION_START),null;const l=await u.getIceServers(t,e);let c=await W.createPeerConnection(l),h=c.createDataChannel("message",{protocol:"message"}),d=new W(c,h);o?o.getTracks().forEach(function(t){c.addTrack(t,o)}):c.addTransceiver("audio",{direction:"recvonly"});const p=c.addTransceiver("video",{direction:"recvonly"}),m=RTCRtpReceiver.getCapabilities("video");null!=m&&p.setCodecPreferences(m.codecs);const g=await c.createOffer();await c.setLocalDescription(g);const f=await u.exchangeSDP(t,e,g);return await c.setRemoteDescription(f),await W.waitFor(()=>d.isReady(),100,50),d.changeSize(n,r),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(n=>{const r=setInterval(()=>{a+=1,a>=s&&(clearInterval(r),n("bad")),t()&&(clearInterval(r),n("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 n=!1;const r=t=>{n||(n=!0,clearTimeout(i),t())},o=this.pc.createDataChannel("file",{protocol:"file"}),i=setTimeout(()=>{r(()=>{o.close(),a(new Error("File transfer timed out"))})},W.FILE_TRANSFER_TIMEOUT);o.onclose=()=>{r(()=>{a(new Error("File channel closed before transfer completed"))})},o.onerror=t=>{r(()=>{o.close(),a(new Error(`File channel error: ${t}`))})},o.addEventListener("message",async n=>{try{if(0===n.data.length){const s=new Uint8Array(await t.arrayBuffer());let n=0;const i=()=>{for(;n<s.length;){if(o.bufferedAmount>W.BACKPRESSURE_THRESHOLD)return o.bufferedAmountLowThreshold=W.BACKPRESSURE_THRESHOLD/2,o.onbufferedamountlow=()=>{o.onbufferedamountlow=null,o.onclose=null,i()},void(o.onclose=()=>{o.onbufferedamountlow=null,r(()=>{a(new Error("File channel closed during transfer"))})});o.send(s.slice(n,n+e)),n+=e}o.send(new Uint8Array(0))};i()}else r(()=>{o.close(),s(n.data)})}catch(t){r(()=>{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 H(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 K{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([[e.RECORDING,0],[e.LLM,0],[e.ANALYZING,0],[e.SPEAKING,0],[e.TTS,0]]);sttRecorder=null;sttTimeoutHandle=null;sttTimeoutAudioFile=null;heartbeatIntervalId=null;legacyVoiceChatMode;stream;constructor(t,s,a,n,r){this.apiServer=t,this.sessionId=s,this.perso=a,this.clientTools=n,this.legacyVoiceChatMode=r?.legacyVoiceChatMode??!1,this.stream=r?.stream??null,this.resetChatState(),this.llmProcessor=new $({apiServer:t,sessionId:s,clientTools:n,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)}}),a?(a.subscribeStatus(t=>{!1===t.detail?.live&&this.stopHeartbeat()}),a.setMessageCallback("stf",t=>{if(this.chatStateMap.get(e.ANALYZING)||this.chatStateMap.get(e.SPEAKING))if(this.setChatState(e.SPEAKING,e.ANALYZING),null!==this.lastStfTimeoutHandle){clearTimeout(this.lastStfTimeoutHandle);let s=Date.now();this.stfTotalDuration+=t.duration+1e3-(s-this.stfTimeoutStartTime),this.stfTimeoutStartTime=s,this.lastStfTimeoutHandle=setTimeout(()=>{this.lastStfTimeoutHandle=null,this.stfTimeoutStartTime=0,this.stfTotalDuration=0,this.setChatState(null,e.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,e.SPEAKING)},this.stfTotalDuration)}),a.setMessageCallback("stt",t=>{if(this.setChatState(null,e.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,e.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 u.makeSTT(this.apiServer,this.sessionId,s,e)).text}catch(t){if(t instanceof o)throw new c(t);throw t}}async processSTF(t,s,a=""){if(!this.perso)throw new Error("processSTF requires WebRTC (STF mode)");this.pipelineSuppressed=!1,this.setChatState(e.ANALYZING);try{const n=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"}(s,t),r=await this.perso.stf(t,n,a);return this.pipelineSuppressed?(this.setChatState(null,e.ANALYZING),r):r}catch(t){throw this.setChatState(null,e.ANALYZING),t}}async processTTS(t,s={}){const{resample:a=!1,locale:n,output_format:r}=s,i=D(t).trim();if(0===i.length)return;this.pipelineSuppressed=!1;const l=/[.?!]$/.test(i)?i:i+".";this.setChatState(e.TTS,null);try{const t={sessionId:this.sessionId,text:l,...n&&{locale:n},...r&&{output_format:r}},{audio:e}=await u.makeTTS(this.apiServer,t);if(this.pipelineSuppressed)return;return await H(e,a)}catch(t){t instanceof o||t instanceof d?this.setError(new h(t)):this.setError(t instanceof Error?t:new Error(String(t)))}finally{this.setChatState(null,e.TTS)}}startVoiceChat(){if(!this.perso)throw new Error("startVoiceChat requires WebRTC (STF mode)");return this.pipelineSuppressed=!1,this.setChatState(e.RECORDING),this.perso.recordStart()}stopVoiceChat(){if(!this.perso)throw new Error("stopVoiceChat requires WebRTC (STF mode)");this.setChatState(e.ANALYZING,e.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(e.RECORDING);try{this.sttRecorder=new J({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,e.RECORDING)}this.sttRecorder=null}},t))}catch(t){throw this.setChatState(null,e.RECORDING),this.sttRecorder=null,t}}lastRecordedAudioFile=null;async stopProcessSTT(t){let s;if(this.sttTimeoutHandle&&(clearTimeout(this.sttTimeoutHandle),this.sttTimeoutHandle=null),this.setChatState(null,e.RECORDING),this.sttTimeoutAudioFile)s=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");s=await this.sttRecorder.stop(),this.sttRecorder=null}this.lastRecordedAudioFile=s;try{return(await u.makeSTT(this.apiServer,this.sessionId,s,t)).text}catch(t){if(t instanceof o)throw new c(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(e.LLM);const s=this.clientTools.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),a=new Array;null===t||(t instanceof Array?a.push(...t):"string"==typeof t&&a.push({role:"user",content:t}));const n=await fetch(`${this.apiServer}/api/v1/session/${this.sessionId}/llm/v2/`,{body:JSON.stringify({messages:[...this.messageHistory,...a],tools:s}),headers:{"Content-Type":"application/json"},method:"POST"});if(!n.ok){const t=await n.json(),s=new i(new o(n.status,t.errors[0].code,t.errors[0].detail,t.errors[0].attr));return this.setError(s),void this.setChatState(null,e.LLM)}const r=n.body?.getReader(),c=new TextDecoder("utf-8");let h="",d=null,u="";for(;;){const{done:t,value:s}=await r.read();if(t)break;let n;for(u+=c.decode(s,{stream:!0});-1!==(n=u.indexOf("\n"));){if(this.llmCancel)return h.length>0&&this.addMessageToChatLog(h,!1),void this.setChatState(null,e.LLM);const t=u.slice(0,n).trim();if(u=u.slice(n+1),!t.startsWith("data: {")){const t=new i(new l("Failed to parse SSE response"));return this.setError(t),void this.setChatState(null,e.LLM)}const s=JSON.parse(t.slice(6).trim());if("success"!==s.status){const t=new i(new l(s.reason));return this.setError(t),void this.setChatState(null,e.LLM)}h.length>0&&"message"!=s.type&&(a.push({role:"assistant",type:"message",content:h}),this.addMessageToChatLog(h,!1),h=""),"message"!==s.type?"tool_call"!==s.type||null==s.tool_calls?"tool"!==s.role||"tool_call"===s.type&&a.push({role:s.role,type:s.type,content:s.content,tool_call_id:s.tool_call_id}):(a.push({role:"assistant",type:s.type,content:s.content,tool_calls:s.tool_calls}),d=s):(h+=D(s.content),this.processTTSTFInternal(s.content))}}if(this.llmCancel)this.setChatState(null,e.LLM);else{if(null!=d){const t=[];for(const e of d.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)a.push({role:"tool",content:JSON.stringify(t.chatToolResult),tool_call_id:t.toolCallId});const s=e.length>0&&d.tool_calls.length!==e.length,n=e.some(t=>!t.chatTool.executeOnly);s||n?await this.processChatInternal(a):this.messageHistory.push(...a)}else this.messageHistory.push(...a);this.setChatState(null,e.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 s=D(t).trim();0!==s.length&&this.perso&&(this.setChatState(e.ANALYZING),this.perso.ttstf(s))}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,s=null){const a=new Map(this.chatStateMap);function n(t){t===e.ANALYZING?a.set(t,(a.get(t)||0)+1):a.set(t,1)}function r(t){t===e.ANALYZING?a.set(t,Math.max((a.get(t)||0)-1,0)):a.set(t,0)}if(null!=t)if(t instanceof Array)for(let e of t)n(e);else n(t);if(null!=s)if(s instanceof Array)for(let t of s)r(t);else r(s);const o=this.exchangeChatStateMapToSet(this.chatStateMap),i=this.exchangeChatStateMapToSet(a);this.chatStateMap=a,this.isEqualChatStateMap(o,i)||this.dispatchChatState(i)}resetChatState(){this.chatStateMap=new Map([[e.RECORDING,0],[e.LLM,0],[e.ANALYZING,0],[e.SPEAKING,0],[e.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 u.sessionEvent(this.apiServer,this.sessionId,a.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 u.sessionEvent(this.apiServer,this.sessionId,a.SESSION_DURING),null!==this.heartbeatIntervalId&&(this.heartbeatIntervalId=setTimeout(t,1e4))}catch(t){t instanceof o?this.setError(t):this.setError(t instanceof Error?t:new Error(String(t))),this.close()}};this.heartbeatIntervalId=setTimeout(t,1e4)}stopHeartbeat(){null!==this.heartbeatIntervalId&&(clearTimeout(this.heartbeatIntervalId),this.heartbeatIntervalId=null)}}async function z(t,e,s,a,n,r){if("boolean"!=typeof n){const r=await W.create(t,e,s,a);return new K(t,e,r,n)}const o=r??[];let i,l;if(n)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 W.create(t,e,s,a,i);if(!c)return l(),new K(t,e,null,o);const h=new K(t,e,c,o,{stream:i,legacyVoiceChatMode:!0});return h.onClose(()=>{l()}),h}async function B(t,e,s,a,n,r){return"boolean"==typeof n?await z(t,e,s,a,n,r??[]):await z(t,e,s,a,n)}async function Y(t,e,a){let n;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 r=await u.getSessionTemplate(t,e,a);if("webrtc"!==r.model_style.platform_type)throw new Error(`SessionTemplate "${a}" uses platform_type "${r.model_style.platform_type}", but only "webrtc" is supported`);n=function(t){const e=e=>t.capability.some(t=>t.name===e);return{using_stf_webrtc:e(s.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:e(s.LLM)?t.llm_type.name:void 0,tts_type:e(s.TTS)?t.tts_type.name:void 0,stt_type:e(s.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}}(r)}else n=a;const r={capability:[],...n};n.using_stf_webrtc&&r.capability.push(s.STF_WEBRTC),n?.llm_type&&(r.capability.push(s.LLM),r.llm_type=n.llm_type),n?.tts_type&&(r.capability.push(s.TTS),r.tts_type=n.tts_type),n?.stt_type&&(r.capability.push(s.STT),r.stt_type=n.stt_type);const o=await fetch(`${t}/api/v1/session/`,{body:JSON.stringify(r),headers:{"PersoLive-APIKey":e,"Content-Type":"application/json"},method:"POST"});return(await u.parseJson(o)).session_id}export{o as ApiError,e as ChatState,n as ChatTool,i as LLMError,l as LLMStreamingResponseError,$ as LlmProcessor,c as STTError,K as Session,d as TTSDecodeError,h as TTSError,P as TTS_TARGET_SAMPLE_RATE,J as WavRecorder,B as createSession,Y as createSessionId,U as createWavRecorder,_ as getAllSettings,S as getBackgroundImages,w as getDocuments,p as getLLMs,T as getMcpServers,f as getModelStyles,y as getPrompts,g as getSTTs,R as getSessionInfo,C as getSessionTemplates,m as getTTSs,v as getTextNormalization,E as getTextNormalizations,M as getWavSampleRate,I as makeTTS};
|
|
1
|
+
import t from"emoji-regex";var e,s,a;!function(t){t.RECORDING="RECORDING",t.LLM="LLM",t.ANALYZING="ANALYZING",t.SPEAKING="SPEAKING",t.TTS="TTS"}(e||(e={}));class n{name;description;parameters;call;executeOnly;constructor(t,e,s,a,n=!1){this.name=t,this.description=e,this.parameters=s,this.call=a,this.executeOnly=n}}class r extends Error{constructor(){super("WebRTC connection timeout")}}class o extends Error{errorCode;code;detail;attr;constructor(t,e,s,a){let n;n=null!=a?`${t}:${a}_${s}`:`${t}:${s}`,super(n),this.errorCode=t,this.code=e,this.detail=s,this.attr=a}}class i extends Error{underlyingError;constructor(t){super(),this.underlyingError=t}}class l extends Error{description;constructor(t){super(),this.description=t}}class c extends Error{underlyingError;constructor(t){super(`STT Error: ${t.detail}`),this.underlyingError=t}}class h extends Error{underlyingError;constructor(t){super(t.message),this.underlyingError=t}}class d extends Error{description;constructor(t){super(`TTS decode error: ${t}`),this.description=t}}class u extends o{constructor(t){super(t.errorCode,t.code,t.detail,t.attr),this.name="SessionCreationError"}}class p extends u{constructor(t){super(t),this.name="DoesNotExistError"}}class m extends u{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"}(s||(s={})),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"}(a||(a={}));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:n}){const r={text:s};a&&(r.locale=a),n&&(r.output_format=n);const o=await fetch(`${t}/api/v1/session/${e}/tts/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});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 n=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(n)}static async makeSTT(t,e,s,a){const n=new FormData;n.append("audio",s),a&&n.append("language",a);const r=await fetch(`${t}/api/v1/session/${e}/stt/`,{method:"POST",body:n});return{text:(await this.parseJson(r)).text}}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 o(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 o(t.status,s.code,s.detail,s.attr)}}}class f extends Error{constructor(t){super(`WAV parse error: ${t}`),this.name="WavParseError"}}function S(t,e,s=1){const a=2*t.length,n=new ArrayBuffer(44+a),r=new DataView(n);T(r,0,"RIFF"),r.setUint32(4,36+a,!0),T(r,8,"WAVE"),T(r,12,"fmt "),r.setUint32(16,16,!0),r.setUint16(20,1,!0),r.setUint16(22,s,!0),r.setUint32(24,e,!0),r.setUint32(28,e*s*2,!0),r.setUint16(32,2*s,!0),r.setUint16(34,16,!0),T(r,36,"data"),r.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]));r.setInt16(o,s<0?32768*s:32767*s,!0),o+=2}return n}function w(t){const e=new DataView(t);if(t.byteLength<28)throw new f("File too small to be a valid WAV");if("RIFF"!==y(e,0,4))throw new f("Missing RIFF header");let s=12;for(;s<t.byteLength-8;){const a=y(e,s,4),n=e.getUint32(s+4,!0);if("fmt "===a){if(s+16>t.byteLength)throw new f("fmt chunk extends beyond file");return e.getUint32(s+12,!0)}const r=s+8+n;if(r<=s)break;s=r}throw new f("Missing fmt chunk")}function y(t,e,s){let a="";for(let n=0;n<s;n++)a+=String.fromCharCode(t.getUint8(e+n));return a}function T(t,e,s){for(let a=0;a<s.length;a++)t.setUint8(e+a,s.charCodeAt(a))}function E(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),n=t.getUint8(e+2)<<16|a<<8|s;return(n>8388607?n-16777216:n)/8388608}case 32:return t.getInt32(e,!0)/2147483648;default:return 0}}class v 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 v("Cannot resample empty audio data");if(e<=0||s<=0)throw new v(`Invalid sample rate: original=${e}, target=${s}`);if(e===s)return t;try{const n=t.length/e,r=Math.ceil(n*s),o=new OfflineAudioContext(a,t.length,e).createBuffer(a,t.length,e);o.getChannelData(0).set(t);const i=new OfflineAudioContext(a,r,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 v(`Failed to resample audio from ${e}Hz to ${s}Hz: ${a}`)}}const _=16e3;async function I(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 d("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 f("File too small to be a valid WAV");if("RIFF"!==y(e,0,4))throw new f("Missing RIFF header");if("WAVE"!==y(e,8,4))throw new f("Missing WAVE format identifier");let s=12,a=!1,n=0,r=0,o=0,i=0;for(;s<t.byteLength-8;){const l=y(e,s,4),c=e.getUint32(s+4,!0);if("fmt "===l){if(s+24>t.byteLength)throw new f("fmt chunk extends beyond file");n=e.getUint16(s+8,!0),r=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 f("Missing fmt chunk");if(1!==n)throw new f(`Unsupported audio format: ${n} (only PCM format 1 is supported)`);if(0===r||r>8)throw new f(`Invalid channel count: ${r}`);if(0===o||o>192e3)throw new f(`Invalid sample rate: ${o}`);if(![8,16,24,32].includes(i))throw new f(`Unsupported bits per sample: ${i}`);let l=-1,c=0;for(;s<t.byteLength-8;){const t=y(e,s,4),a=e.getUint32(s+4,!0);if("data"===t){l=s+8,c=a;break}const n=s+8+a;if(n<=s)break;s=n}if(-1===l)throw new f("Missing data chunk");const h=t.byteLength-l,d=Math.min(c,h),u=i/8,p=Math.floor(d/(u*r)),m=new Float32Array(p*r);let g=0;for(let s=0;s<p*r;s++){const a=l+s*u;if(a+u>t.byteLength)break;m[g++]=Math.max(-1,Math.min(1,E(e,a,i)))}if(2===r){const t=new Float32Array(p);for(let e=0;e<p;e++)t[e]=(m[2*e]+m[2*e+1])/2;return{sampleRate:o,channels:1,bitsPerSample:i,samples:t}}return{sampleRate:o,channels:r,bitsPerSample:i,samples:m.slice(0,g)}}(t);if(e.sampleRate===_)return{samples:e.samples,sampleRate:e.sampleRate};return{samples:await C(e.samples,e.sampleRate,_,e.channels),sampleRate:_}}const s=new AudioContext({sampleRate:_});try{const e=await s.decodeAudioData(t.slice(0));if(e.sampleRate===_)return{samples:e.getChannelData(0),sampleRate:_};return{samples:await C(e.getChannelData(0),e.sampleRate,_,1),sampleRate:_}}finally{await s.close()}}(s,a),e=S(t.samples,_,1);return new Blob([e],{type:"audio/wav"})}catch{return new Blob([s],{type:a})}}const R=t();function L(t){return t.replace(R,"")}class b{config;messageHistory=[];constructor(t){this.config=t}async*processLLM(t){if(0===t.message.length)throw new Error("Message cannot be empty");const s=t.tools??this.config.clientTools,a=s.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),n={newMessageHistory:[{role:"user",content:t.message}],allChunks:[],message:"",lastYieldedChunkCount:0,pendingToolCallsMessage:null,aborted:!1,streamingError:null};let r=0,c=[...this.messageHistory,...n.newMessageHistory];this.config.callbacks.onChatStateChange(e.LLM,null);try{for(;;){if(t.signal?.aborted)return void(n.allChunks.length>0&&(yield{type:"message",chunks:[...n.allChunks],message:n.message,finish:!0}));let e;try{e=await g.makeLLM(this.config.apiServer,this.config.sessionId,{messages:c,tools:a},t.signal)}catch(t){if(t instanceof o)return void(yield{type:"error",error:new i(t)});throw t}if(n.streamingError=null,yield*this.parseSSEStream(e,n,t),n.streamingError)return;if(n.aborted)return void(n.allChunks.length>0&&(yield{type:"message",chunks:[...n.allChunks],message:n.message,finish:!0}));if(null!=n.pendingToolCallsMessage){yield*this.executeToolCalls(n,s);const t=n.lastToolCallResults,e=t.length>0&&n.pendingToolCallsMessage.tool_calls.length!==t.length,a=t.some(t=>!t.chatTool.executeOnly);if(e||a){if(r++,r>=10)return void(yield{type:"error",error:new i(new l("Tool follow-up loop exceeded maximum rounds (10)"))});c=[...this.messageHistory,...n.newMessageHistory],n.pendingToolCallsMessage=null;continue}}return this.messageHistory.push(...n.newMessageHistory),void(yield{type:"message",chunks:[...n.allChunks],message:n.message,finish:!0})}}finally{this.config.callbacks.onChatStateChange(null,e.LLM)}}async*parseSSEStream(t,e,s){const a=new TextDecoder("utf-8");let n="",r="";e.pendingToolCallsMessage=null;const o=()=>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(n+=a.decode(h,{stream:!0});-1!==(d=n.indexOf("\n"));){if(s.signal?.aborted)return void(e.aborted=!0);const t=n.slice(0,d).trim();if(n=n.slice(d+1),!t.startsWith("data: {"))return e.streamingError=new i(new l("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 i(new l("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==a.status)return e.streamingError=new i(new l(a.reason)),void(yield{type:"error",error:e.streamingError});if(r.length>0&&"message"!=a.type){e.newMessageHistory.push({role:"assistant",type:"message",content:r}),r="";const t=o();t&&(yield t)}if("message"===a.type){const t=L(a.content);r+=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=o();u&&(yield u)}const c=n.trim();if(c.length>0){if(!c.startsWith("data: {"))return e.streamingError=new i(new l("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 i(new l("Failed to parse SSE JSON")),void(yield{type:"error",error:e.streamingError})}if("success"!==t.status)return e.streamingError=new i(new l(t.reason)),void(yield{type:"error",error:e.streamingError});if("message"===t.type){const s=L(t.content);r+=s,e.message+=s,e.allChunks.push(s)}else if("tool_call"===t.type&&null!=t.tool_calls){if(r.length>0){e.newMessageHistory.push({role:"assistant",type:"message",content:r}),r="";const t=o();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}}}r.length>0&&e.newMessageHistory.push({role:"assistant",type:"message",content:r})}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 n=await Promise.all(a);t.lastToolCallResults=n;for(const e of n)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 N=`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 M{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(N),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 n,r,o=0;for(const e of t)a.set(e,o),o+=e.length;this.targetSampleRate&&this.targetSampleRate!==e?(n=await C(a,e,this.targetSampleRate,this.channels),r=this.targetSampleRate):(n=a,r=e);const i=S(n,r,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}}function A(t){return new M(t)}class x 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,e,n,r,o){const i=await g.getSessionInfo(t,e);if(!(Array.isArray(i.capability)&&i.capability.some(t=>t.name===s.STF_ONPREMISE||t.name===s.STF_WEBRTC)))return await g.sessionEvent(t,e,a.SESSION_START),null;const l=await g.getIceServers(t,e);let c=await x.createPeerConnection(l),h=c.createDataChannel("message",{protocol:"message"}),d=new x(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 f=await g.exchangeSDP(t,e,m);return await c.setRemoteDescription(f),await x.waitFor(()=>d.isReady(),100,50),d.changeSize(n,r),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(n=>{const r=setInterval(()=>{a+=1,a>=s&&(clearInterval(r),n("bad")),t()&&(clearInterval(r),n("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 n=!1;const r=t=>{n||(n=!0,clearTimeout(i),t())},o=this.pc.createDataChannel("file",{protocol:"file"}),i=setTimeout(()=>{r(()=>{o.close(),a(new Error("File transfer timed out"))})},x.FILE_TRANSFER_TIMEOUT);o.onclose=()=>{r(()=>{a(new Error("File channel closed before transfer completed"))})},o.onerror=t=>{r(()=>{o.close(),a(new Error(`File channel error: ${t}`))})},o.addEventListener("message",async n=>{try{if(0===n.data.length){const s=new Uint8Array(await t.arrayBuffer());let n=0;const i=()=>{for(;n<s.length;){if(o.bufferedAmount>x.BACKPRESSURE_THRESHOLD)return o.bufferedAmountLowThreshold=x.BACKPRESSURE_THRESHOLD/2,o.onbufferedamountlow=()=>{o.onbufferedamountlow=null,o.onclose=null,i()},void(o.onclose=()=>{o.onbufferedamountlow=null,r(()=>{a(new Error("File channel closed during transfer"))})});o.send(s.slice(n,n+e)),n+=e}o.send(new Uint8Array(0))};i()}else r(()=>{o.close(),s(n.data)})}catch(t){r(()=>{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 I(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 k{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([[e.RECORDING,0],[e.LLM,0],[e.ANALYZING,0],[e.SPEAKING,0],[e.TTS,0]]);sttRecorder=null;sttTimeoutHandle=null;sttTimeoutAudioFile=null;heartbeatIntervalId=null;legacyVoiceChatMode;stream;constructor(t,s,a,n,r){this.apiServer=t,this.sessionId=s,this.perso=a,this.clientTools=n,this.legacyVoiceChatMode=r?.legacyVoiceChatMode??!1,this.stream=r?.stream??null,this.resetChatState(),this.llmProcessor=new b({apiServer:t,sessionId:s,clientTools:n,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)}}),a?(a.subscribeStatus(t=>{!1===t.detail?.live&&this.stopHeartbeat()}),a.setMessageCallback("stf",t=>{if(this.chatStateMap.get(e.ANALYZING)||this.chatStateMap.get(e.SPEAKING))if(this.setChatState(e.SPEAKING,e.ANALYZING),null!==this.lastStfTimeoutHandle){clearTimeout(this.lastStfTimeoutHandle);let s=Date.now();this.stfTotalDuration+=t.duration+1e3-(s-this.stfTimeoutStartTime),this.stfTimeoutStartTime=s,this.lastStfTimeoutHandle=setTimeout(()=>{this.lastStfTimeoutHandle=null,this.stfTimeoutStartTime=0,this.stfTotalDuration=0,this.setChatState(null,e.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,e.SPEAKING)},this.stfTotalDuration)}),a.setMessageCallback("stt",t=>{if(this.setChatState(null,e.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,e.ANALYZING)})):this.startHeartbeat()}llmJob=null;async processChat(t){0!==t.trim().length&&(this.pipelineSuppressed=!1,this.addMessageToChatLog(t,!0),this.llmJob=this.processChatInternal(t))}processLLM(t){return this.pipelineSuppressed=!1,this.llmProcessor.processLLM(t)}getMessageHistory(){return this.llmProcessor.getHistory()}processCustomChat(t){0!==t.trim().length&&this.processTTSTFInternal(t)}processTTSTF(t){0!==t.trim().length&&(this.pipelineSuppressed=!1,this.messageHistory.push({role:"assistant",type:"message",content:t}),this.addMessageToChatLog(t,!1),this.processTTSTFInternal(t))}async transcribeAudio(t,e){return(await this.transcribeAudioDetailed(t,e)).text}async transcribeAudioDetailed(t,e){const s=t instanceof File?t:new File([t],"audio.wav",{type:t.type});try{return await g.makeSTT(this.apiServer,this.sessionId,s,e)}catch(t){if(t instanceof o)throw new c(t);throw t}}async processSTF(t,s,a=""){if(!this.perso)throw new Error("processSTF requires WebRTC (STF mode)");this.pipelineSuppressed=!1,this.setChatState(e.ANALYZING);try{const n=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"}(s,t),r=await this.perso.stf(t,n,a);return this.pipelineSuppressed?(this.setChatState(null,e.ANALYZING),r):r}catch(t){throw this.setChatState(null,e.ANALYZING),t}}async processTTS(t,s={}){const{resample:a=!1,locale:n,output_format:r}=s,i=L(t).trim();if(0===i.length)return;this.pipelineSuppressed=!1;const l=/[.?!]$/.test(i)?i:i+".";this.setChatState(e.TTS,null);try{const t={sessionId:this.sessionId,text:l,...n&&{locale:n},...r&&{output_format:r}},{audio:e}=await g.makeTTS(this.apiServer,t);if(this.pipelineSuppressed)return;return await I(e,a)}catch(t){t instanceof o||t instanceof d?this.setError(new h(t)):this.setError(t instanceof Error?t:new Error(String(t)))}finally{this.setChatState(null,e.TTS)}}startVoiceChat(){if(!this.perso)throw new Error("startVoiceChat requires WebRTC (STF mode)");return this.pipelineSuppressed=!1,this.setChatState(e.RECORDING),this.perso.recordStart()}stopVoiceChat(){if(!this.perso)throw new Error("stopVoiceChat requires WebRTC (STF mode)");this.setChatState(e.ANALYZING,e.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(e.RECORDING);try{this.sttRecorder=new M({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,e.RECORDING)}this.sttRecorder=null}},t))}catch(t){throw this.setChatState(null,e.RECORDING),this.sttRecorder=null,t}}lastRecordedAudioFile=null;async stopProcessSTT(t){let s;if(this.sttTimeoutHandle&&(clearTimeout(this.sttTimeoutHandle),this.sttTimeoutHandle=null),this.setChatState(null,e.RECORDING),this.sttTimeoutAudioFile)s=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");s=await this.sttRecorder.stop(),this.sttRecorder=null}this.lastRecordedAudioFile=s;try{return(await g.makeSTT(this.apiServer,this.sessionId,s,t)).text}catch(t){if(t instanceof o)throw new c(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(e.LLM);const s=this.clientTools.map(t=>({type:"function",function:{description:t.description,name:t.name,parameters:t.parameters}})),a=new Array;null===t||(t instanceof Array?a.push(...t):"string"==typeof t&&a.push({role:"user",content:t}));const n=await fetch(`${this.apiServer}/api/v1/session/${this.sessionId}/llm/v2/`,{body:JSON.stringify({messages:[...this.messageHistory,...a],tools:s}),headers:{"Content-Type":"application/json"},method:"POST"});if(!n.ok){const t=await n.json(),s=new i(new o(n.status,t.errors[0].code,t.errors[0].detail,t.errors[0].attr));return this.setError(s),void this.setChatState(null,e.LLM)}const r=n.body?.getReader(),c=new TextDecoder("utf-8");let h="",d=null,u="";for(;;){const{done:t,value:s}=await r.read();if(t)break;let n;for(u+=c.decode(s,{stream:!0});-1!==(n=u.indexOf("\n"));){if(this.llmCancel)return h.length>0&&this.addMessageToChatLog(h,!1),void this.setChatState(null,e.LLM);const t=u.slice(0,n).trim();if(u=u.slice(n+1),!t.startsWith("data: {")){const t=new i(new l("Failed to parse SSE response"));return this.setError(t),void this.setChatState(null,e.LLM)}const s=JSON.parse(t.slice(6).trim());if("success"!==s.status){const t=new i(new l(s.reason));return this.setError(t),void this.setChatState(null,e.LLM)}h.length>0&&"message"!=s.type&&(a.push({role:"assistant",type:"message",content:h}),this.addMessageToChatLog(h,!1),h=""),"message"!==s.type?"tool_call"!==s.type||null==s.tool_calls?"tool"!==s.role||"tool_call"===s.type&&a.push({role:s.role,type:s.type,content:s.content,tool_call_id:s.tool_call_id}):(a.push({role:"assistant",type:s.type,content:s.content,tool_calls:s.tool_calls}),d=s):(h+=L(s.content),this.processTTSTFInternal(s.content))}}if(this.llmCancel)this.setChatState(null,e.LLM);else{if(null!=d){const t=[];for(const e of d.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)a.push({role:"tool",content:JSON.stringify(t.chatToolResult),tool_call_id:t.toolCallId});const s=e.length>0&&d.tool_calls.length!==e.length,n=e.some(t=>!t.chatTool.executeOnly);s||n?await this.processChatInternal(a):this.messageHistory.push(...a)}else this.messageHistory.push(...a);this.setChatState(null,e.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 s=L(t).trim();0!==s.length&&this.perso&&(this.setChatState(e.ANALYZING),this.perso.ttstf(s))}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,s=null){const a=new Map(this.chatStateMap);function n(t){t===e.ANALYZING?a.set(t,(a.get(t)||0)+1):a.set(t,1)}function r(t){t===e.ANALYZING?a.set(t,Math.max((a.get(t)||0)-1,0)):a.set(t,0)}if(null!=t)if(t instanceof Array)for(let e of t)n(e);else n(t);if(null!=s)if(s instanceof Array)for(let t of s)r(t);else r(s);const o=this.exchangeChatStateMapToSet(this.chatStateMap),i=this.exchangeChatStateMapToSet(a);this.chatStateMap=a,this.isEqualChatStateMap(o,i)||this.dispatchChatState(i)}resetChatState(){this.chatStateMap=new Map([[e.RECORDING,0],[e.LLM,0],[e.ANALYZING,0],[e.SPEAKING,0],[e.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,a.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,a.SESSION_DURING),null!==this.heartbeatIntervalId&&(this.heartbeatIntervalId=setTimeout(t,1e4))}catch(t){t instanceof o?this.setError(t):this.setError(t instanceof Error?t:new Error(String(t))),this.close()}};this.heartbeatIntervalId=setTimeout(t,1e4)}stopHeartbeat(){null!==this.heartbeatIntervalId&&(clearTimeout(this.heartbeatIntervalId),this.heartbeatIntervalId=null)}}async function O(t,e,s,a,n,r){if("boolean"!=typeof n){const r=await x.create(t,e,s,a);return new k(t,e,r,n)}const o=r??[];let i,l;if(n)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 x.create(t,e,s,a,i);if(!c)return l(),new k(t,e,null,o);const h=new k(t,e,c,o,{stream:i,legacyVoiceChatMode:!0});return h.onClose(()=>{l()}),h}async function P(t,e){return await g.getLLMs(t,e)}async function H(t,e){return await g.getTTSs(t,e)}async function F(t,e){return await g.getSTTs(t,e)}async function D(t,e){return await g.getModelStyles(t,e)}async function $(t,e){return await g.getBackgroundImages(t,e)}async function G(t,e){return await g.getPrompts(t,e)}async function J(t,e){return await g.getDocuments(t,e)}async function U(t,e){return await g.getMcpServers(t,e)}async function W(t,e){return await g.getTextNormalizations(t,e)}async function z(t,e,s){return await g.downloadTextNormalization(t,e,s)}async function K(t,e){return await g.getSessionTemplates(t,e)}async function B(t,e){const[s,a,n,r,o,i,l,c,h]=await Promise.all([P(t,e),H(t,e),F(t,e),D(t,e),$(t,e),G(t,e),J(t,e),U(t,e),W(t,e).catch(()=>[])]);return{llms:s,ttsTypes:a,sttTypes:n,modelStyles:r,backgroundImages:o,prompts:i,documents:l,mcpServers:c,textNormalizations:h}}async function Y(t,e){return await g.makeTTS(t,e)}async function V(t,e){return await g.getSessionInfo(t,e)}async function Z(t,e,s,a,n,r){return"boolean"==typeof n?await O(t,e,s,a,n,r??[]):await O(t,e,s,a,n)}async function j(t,e,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 n;if("string"==typeof a){const r=await g.getSessionTemplate(t,e,a);if("webrtc"!==r.model_style.platform_type)throw new Error(`SessionTemplate "${a}" uses platform_type "${r.model_style.platform_type}", but only "webrtc" is supported`);n=function(t){const e=e=>t.capability.some(t=>t.name===e);return{using_stf_webrtc:e(s.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:e(s.LLM)?t.llm_type.name:void 0,tts_type:e(s.TTS)?t.tts_type.name:void 0,stt_type:e(s.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}}(r)}else n=a;const r={capability:[],...n};n.using_stf_webrtc&&r.capability.push(s.STF_WEBRTC),n?.llm_type&&(r.capability.push(s.LLM),r.llm_type=n.llm_type),n?.tts_type&&(r.capability.push(s.TTS),r.tts_type=n.tts_type),n?.stt_type&&(r.capability.push(s.STT),r.stt_type=n.stt_type);const o=await fetch(`${t}/api/v1/session/`,{body:JSON.stringify(r),headers:{"PersoLive-APIKey":e,"Content-Type":"application/json"},method:"POST"});return(await g.parseJson(o)).session_id}catch(t){throw function(t){if(t instanceof u)return t;if(t instanceof o)switch(t.code){case"does_not_exist":return new p(t);case"not_in_organization":return new m(t);default:return new u(t)}return t}(t)}}export{o as ApiError,e as ChatState,n as ChatTool,p as DoesNotExistError,i as LLMError,l as LLMStreamingResponseError,b as LlmProcessor,m as NotInOrganizationError,c as STTError,k as Session,u as SessionCreationError,d as TTSDecodeError,h as TTSError,_ as TTS_TARGET_SAMPLE_RATE,M as WavRecorder,Z as createSession,j as createSessionId,A as createWavRecorder,B as getAllSettings,$ as getBackgroundImages,J as getDocuments,P as getLLMs,U as getMcpServers,D as getModelStyles,G as getPrompts,F as getSTTs,V as getSessionInfo,K as getSessionTemplates,H as getTTSs,z as getTextNormalization,W as getTextNormalizations,w as getWavSampleRate,Y as makeTTS};
|
package/dist/server/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";class t extends Error{errorCode;code;detail;attr;constructor(t,e,s,a){let
|
|
1
|
+
"use strict";class t extends Error{errorCode;code;detail;attr;constructor(t,e,s,a){let o;o=null!=a?`${t}:${a}_${s}`:`${t}:${s}`,super(o),this.errorCode=t,this.code=e,this.detail=s,this.attr=a}}class e extends t{constructor(t){super(t.errorCode,t.code,t.detail,t.attr),this.name="SessionCreationError"}}class s extends e{constructor(t){super(t),this.name="DoesNotExistError"}}class a extends e{constructor(t){super(t),this.name="NotInOrganizationError"}}var o,n;!function(t){t.LLM="LLM",t.TTS="TTS",t.STT="STT",t.STF_ONPREMISE="STF_ONPREMISE",t.STF_WEBRTC="STF_WEBRTC"}(o||(o={})),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"}(n||(n={}));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 makeTTS(t,{sessionId:e,text:s,locale:a,output_format:o}){const n={text:s};a&&(n.locale=a),o&&(n.output_format=o);const i=await fetch(`${t}/api/v1/session/${e}/tts/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});return await this.parseJson(i)}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 o=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(o)}static async makeSTT(t,e,s,a){const o=new FormData;o.append("audio",s),a&&o.append("language",a);const n=await fetch(`${t}/api/v1/session/${e}/stt/`,{method:"POST",body:o});return{text:(await this.parseJson(n)).text}}static async makeLLM(e,s,a,o){const n=await fetch(`${e}/api/v1/session/${s}/llm/v2/`,{body:JSON.stringify(a),headers:{"Content-Type":"application/json"},method:"POST",signal:o});if(!n.ok){const e=await n.json(),s=e.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${n.status} with no error details`,attr:null};throw new t(n.status,s.code,s.detail,s.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(e){const s=await e.json();if(e.ok)return s;{const a=s.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${e.status} with no error details`,attr:null};throw new t(e.status,a.code,a.detail,a.attr)}}}async function r(t,e){return await i.getLLMs(t,e)}async function c(t,e){return await i.getTTSs(t,e)}async function p(t,e){return await i.getSTTs(t,e)}async function d(t,e){return await i.getModelStyles(t,e)}async function l(t,e){return await i.getBackgroundImages(t,e)}async function m(t,e){return await i.getPrompts(t,e)}async function S(t,e){return await i.getDocuments(t,e)}async function u(t,e){return await i.getMcpServers(t,e)}async function _(t,e){return await i.getTextNormalizations(t,e)}exports.ApiError=t,exports.DoesNotExistError=s,exports.NotInOrganizationError=a,exports.PersoUtilServer=i,exports.SessionCreationError=e,exports.createSessionId=async function(n,r,c){try{let t;if("string"==typeof c){const e=await i.getSessionTemplate(n,r,c);if("webrtc"!==e.model_style.platform_type)throw new Error(`SessionTemplate "${c}" uses platform_type "${e.model_style.platform_type}", but only "webrtc" is supported`);t=function(t){const e=e=>t.capability.some(t=>t.name===e);return{using_stf_webrtc:e(o.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):void 0,llm_type:e(o.LLM)?t.llm_type.name:void 0,tts_type:e(o.TTS)?t.tts_type.name:void 0,stt_type:e(o.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}}(e)}else t=c;const e={capability:[],...t};t.using_stf_webrtc&&e.capability.push("STF_WEBRTC"),t?.llm_type&&(e.capability.push("LLM"),e.llm_type=t.llm_type),t?.tts_type&&(e.capability.push("TTS"),e.tts_type=t.tts_type),t?.stt_type&&(e.capability.push("STT"),e.stt_type=t.stt_type);const s=await fetch(`${n}/api/v1/session/`,{body:JSON.stringify(e),headers:{"PersoLive-APIKey":r,"Content-Type":"application/json"},method:"POST"});return(await i.parseJson(s)).session_id}catch(o){throw function(o){if(o instanceof e)return o;if(o instanceof t)switch(o.code){case"does_not_exist":return new s(o);case"not_in_organization":return new a(o);default:return new e(o)}return o}(o)}},exports.getAllSettings=async function(t,e){const[s,a,o,n,i,y,h,g,T]=await Promise.all([r(t,e),c(t,e),p(t,e),d(t,e),l(t,e),m(t,e),S(t,e),u(t,e),_(t,e).catch(()=>[])]);return{llms:s,ttsTypes:a,sttTypes:o,modelStyles:n,backgroundImages:i,prompts:y,documents:h,mcpServers:g,textNormalizations:T}},exports.getBackgroundImages=l,exports.getDocuments=S,exports.getIntroMessage=async(e,s,a)=>{try{const t=(await i.getPrompts(e,s)).find(t=>t.prompt_id===a);if(!t)throw new Error(`Prompt (${a}) not found`,{cause:404});return t.intro_message}catch(e){if(e instanceof t)throw new Error(e.detail,{cause:e.errorCode??500});throw e}},exports.getLLMs=r,exports.getMcpServers=u,exports.getModelStyles=d,exports.getPrompts=m,exports.getSTTs=p,exports.getSessionInfo=async function(t,e){return await i.getSessionInfo(t,e)},exports.getSessionTemplate=async function(t,e,s){return await i.getSessionTemplate(t,e,s)},exports.getSessionTemplates=async function(t,e){return await i.getSessionTemplates(t,e)},exports.getTTSs=c,exports.getTextNormalization=async function(t,e,s){return await i.downloadTextNormalization(t,e,s)},exports.getTextNormalizations=_,exports.makeTTS=async function(t,e){return await i.makeTTS(t,e)};
|
package/dist/server/index.d.ts
CHANGED
|
@@ -27,7 +27,9 @@ type CreateSessionIdBody = {
|
|
|
27
27
|
* @param sessionTemplateId SessionTemplate ID to resolve configuration from.
|
|
28
28
|
* @returns Session ID returned by the server.
|
|
29
29
|
* @throws {Error} If the template's `model_style.platform_type` is not `"webrtc"`.
|
|
30
|
-
* @throws {
|
|
30
|
+
* @throws {SessionCreationError} When the API returns an error during session creation.
|
|
31
|
+
* @throws {DoesNotExistError} When the server response `code` is `'does_not_exist'` (subclass of `SessionCreationError`).
|
|
32
|
+
* @throws {NotInOrganizationError} When the server response `code` is `'not_in_organization'` (subclass of `SessionCreationError`).
|
|
31
33
|
*/
|
|
32
34
|
declare function createSessionId(apiServer: string, apiKey: string, sessionTemplateId: string): Promise<string>;
|
|
33
35
|
/**
|
|
@@ -47,14 +49,6 @@ declare function createSessionId(apiServer: string, apiKey: string, params: Crea
|
|
|
47
49
|
*/
|
|
48
50
|
declare const getIntroMessage: (apiServer: string, apiKey: string, promptId: string) => Promise<string>;
|
|
49
51
|
|
|
50
|
-
declare class ApiError extends Error {
|
|
51
|
-
errorCode: number;
|
|
52
|
-
code: string;
|
|
53
|
-
detail: string;
|
|
54
|
-
attr?: string | undefined;
|
|
55
|
-
constructor(errorCode: number, code: string, detail: string, attr?: string | undefined);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
52
|
interface Prompt {
|
|
59
53
|
prompt_id: string;
|
|
60
54
|
name: string;
|
|
@@ -98,6 +92,15 @@ interface STTType {
|
|
|
98
92
|
service: string;
|
|
99
93
|
options?: unknown | null;
|
|
100
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Response from POST /api/v1/session/{session_id}/stt/.
|
|
97
|
+
*
|
|
98
|
+
* The wire payload includes additional fields (e.g., `locale`,
|
|
99
|
+
* `normalized_text`) that the SDK intentionally does not expose.
|
|
100
|
+
*/
|
|
101
|
+
interface STTResponse {
|
|
102
|
+
text: string;
|
|
103
|
+
}
|
|
101
104
|
interface TextNormalizationConfig {
|
|
102
105
|
textnormalizationconfig_id: string;
|
|
103
106
|
name: string;
|
|
@@ -420,14 +423,12 @@ declare class PersoUtil {
|
|
|
420
423
|
* @param sessionId Session ID for the current session
|
|
421
424
|
* @param audioFile Audio file (WAV format)
|
|
422
425
|
* @param language Optional language code (e.g., 'ko', 'en')
|
|
423
|
-
* @returns
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
426
|
+
* @returns STTResponse with only the transcribed text.
|
|
427
|
+
*
|
|
428
|
+
* The server returns additional fields (e.g., `locale`, `normalized_text`)
|
|
429
|
+
* which are intentionally not exposed by the SDK.
|
|
427
430
|
*/
|
|
428
|
-
static makeSTT(apiServer: string, sessionId: string, audioFile: File, language?: string): Promise<
|
|
429
|
-
text: string;
|
|
430
|
-
}>;
|
|
431
|
+
static makeSTT(apiServer: string, sessionId: string, audioFile: File, language?: string): Promise<STTResponse>;
|
|
431
432
|
/**
|
|
432
433
|
* Initiates an LLM streaming request and returns the response body reader.
|
|
433
434
|
* @param apiServer Perso Interactive API Server
|
|
@@ -574,5 +575,48 @@ declare function makeTTS(apiServer: string, params: {
|
|
|
574
575
|
*/
|
|
575
576
|
declare function getSessionInfo(apiServer: string, sessionId: string): Promise<any>;
|
|
576
577
|
|
|
577
|
-
|
|
578
|
-
|
|
578
|
+
declare class ApiError extends Error {
|
|
579
|
+
errorCode: number;
|
|
580
|
+
code: string;
|
|
581
|
+
detail: string;
|
|
582
|
+
attr?: string | undefined;
|
|
583
|
+
constructor(errorCode: number, code: string, detail: string, attr?: string | undefined);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Domain error thrown by `createSessionId()` (and the `getSessionTemplate`
|
|
587
|
+
* path) when the underlying API returns an `ApiError`. The raw
|
|
588
|
+
* `errorCode`, `code`, `detail`, and `attr` fields are preserved as-is —
|
|
589
|
+
* callers inspect them to decide how to react (e.g. treat
|
|
590
|
+
* `code === 'invalid'` with a "not found" detail as feature-unavailable
|
|
591
|
+
* per LIV-1681).
|
|
592
|
+
*
|
|
593
|
+
* Extends `ApiError`, so existing `instanceof ApiError` branches keep
|
|
594
|
+
* working.
|
|
595
|
+
*/
|
|
596
|
+
declare class SessionCreationError extends ApiError {
|
|
597
|
+
constructor(source: ApiError);
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Session creation failed because a referenced resource does not exist.
|
|
601
|
+
* Triggered when the server returns `code === 'does_not_exist'` — for
|
|
602
|
+
* example, a `prompt_id` that has been deleted or never existed. The
|
|
603
|
+
* `attr` field, when present, identifies which input field referenced
|
|
604
|
+
* the missing resource (e.g. `'prompt'`).
|
|
605
|
+
*/
|
|
606
|
+
declare class DoesNotExistError extends SessionCreationError {
|
|
607
|
+
constructor(source: ApiError);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Session creation failed because a referenced resource is not assigned
|
|
611
|
+
* to the caller's organization. Triggered when the server returns
|
|
612
|
+
* `code === 'not_in_organization'` — for example, an LLM/TTS/STT type
|
|
613
|
+
* that exists in the platform catalog but is not enabled for this
|
|
614
|
+
* organization. The `attr` field, when present, identifies which input
|
|
615
|
+
* field referenced the unavailable resource.
|
|
616
|
+
*/
|
|
617
|
+
declare class NotInOrganizationError extends SessionCreationError {
|
|
618
|
+
constructor(source: ApiError);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export { ApiError, DoesNotExistError, NotInOrganizationError, PersoUtil as PersoUtilServer, SessionCreationError, createSessionId, getAllSettings, getBackgroundImages, getDocuments, getIntroMessage, getLLMs, getMcpServers, getModelStyles, getPrompts, getSTTs, getSessionInfo, getSessionTemplate, getSessionTemplates, getTTSs, getTextNormalization, getTextNormalizations, makeTTS };
|
|
622
|
+
export type { STTResponse, SessionTemplate };
|
package/dist/server/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class t extends Error{errorCode;code;detail;attr;constructor(t,e,a
|
|
1
|
+
class t extends Error{errorCode;code;detail;attr;constructor(t,e,s,a){let n;n=null!=a?`${t}:${a}_${s}`:`${t}:${s}`,super(n),this.errorCode=t,this.code=e,this.detail=s,this.attr=a}}class e extends t{constructor(t){super(t.errorCode,t.code,t.detail,t.attr),this.name="SessionCreationError"}}class s extends e{constructor(t){super(t),this.name="DoesNotExistError"}}class a extends e{constructor(t){super(t),this.name="NotInOrganizationError"}}var n,i;!function(t){t.LLM="LLM",t.TTS="TTS",t.STT="STT",t.STF_ONPREMISE="STF_ONPREMISE",t.STF_WEBRTC="STF_WEBRTC"}(n||(n={})),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"}(i||(i={}));class o{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:n}){const i={text:s};a&&(i.locale=a),n&&(i.output_format=n);const o=await fetch(`${t}/api/v1/session/${e}/tts/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});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 n=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(n)}static async makeSTT(t,e,s,a){const n=new FormData;n.append("audio",s),a&&n.append("language",a);const i=await fetch(`${t}/api/v1/session/${e}/stt/`,{method:"POST",body:n});return{text:(await this.parseJson(i)).text}}static async makeLLM(e,s,a,n){const i=await fetch(`${e}/api/v1/session/${s}/llm/v2/`,{body:JSON.stringify(a),headers:{"Content-Type":"application/json"},method:"POST",signal:n});if(!i.ok){const e=await i.json(),s=e.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${i.status} with no error details`,attr:null};throw new t(i.status,s.code,s.detail,s.attr)}return i.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(e){const s=await e.json();if(e.ok)return s;{const a=s.errors?.[0]??{code:"UNKNOWN_ERROR",detail:`Server returned status ${e.status} with no error details`,attr:null};throw new t(e.status,a.code,a.detail,a.attr)}}}async function r(i,r,c){try{let t;if("string"==typeof c){const e=await o.getSessionTemplate(i,r,c);if("webrtc"!==e.model_style.platform_type)throw new Error(`SessionTemplate "${c}" uses platform_type "${e.model_style.platform_type}", but only "webrtc" is supported`);t=function(t){const e=e=>t.capability.some(t=>t.name===e);return{using_stf_webrtc:e(n.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):void 0,llm_type:e(n.LLM)?t.llm_type.name:void 0,tts_type:e(n.TTS)?t.tts_type.name:void 0,stt_type:e(n.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}}(e)}else t=c;const e={capability:[],...t};t.using_stf_webrtc&&e.capability.push("STF_WEBRTC"),t?.llm_type&&(e.capability.push("LLM"),e.llm_type=t.llm_type),t?.tts_type&&(e.capability.push("TTS"),e.tts_type=t.tts_type),t?.stt_type&&(e.capability.push("STT"),e.stt_type=t.stt_type);const s=await fetch(`${i}/api/v1/session/`,{body:JSON.stringify(e),headers:{"PersoLive-APIKey":r,"Content-Type":"application/json"},method:"POST"});return(await o.parseJson(s)).session_id}catch(n){throw function(n){if(n instanceof e)return n;if(n instanceof t)switch(n.code){case"does_not_exist":return new s(n);case"not_in_organization":return new a(n);default:return new e(n)}return n}(n)}}const c=async(e,s,a)=>{try{const t=(await o.getPrompts(e,s)).find(t=>t.prompt_id===a);if(!t)throw new Error(`Prompt (${a}) not found`,{cause:404});return t.intro_message}catch(e){if(e instanceof t)throw new Error(e.detail,{cause:e.errorCode??500});throw e}};async function p(t,e){return await o.getLLMs(t,e)}async function d(t,e){return await o.getTTSs(t,e)}async function l(t,e){return await o.getSTTs(t,e)}async function u(t,e){return await o.getModelStyles(t,e)}async function m(t,e){return await o.getBackgroundImages(t,e)}async function _(t,e){return await o.getPrompts(t,e)}async function y(t,e){return await o.getDocuments(t,e)}async function S(t,e){return await o.getMcpServers(t,e)}async function h(t,e){return await o.getTextNormalizations(t,e)}async function T(t,e,s){return await o.downloadTextNormalization(t,e,s)}async function w(t,e){return await o.getSessionTemplates(t,e)}async function g(t,e,s){return await o.getSessionTemplate(t,e,s)}async function f(t,e){const[s,a,n,i,o,r,c,T,w]=await Promise.all([p(t,e),d(t,e),l(t,e),u(t,e),m(t,e),_(t,e),y(t,e),S(t,e),h(t,e).catch(()=>[])]);return{llms:s,ttsTypes:a,sttTypes:n,modelStyles:i,backgroundImages:o,prompts:r,documents:c,mcpServers:T,textNormalizations:w}}async function v(t,e){return await o.makeTTS(t,e)}async function E(t,e){return await o.getSessionInfo(t,e)}export{t as ApiError,s as DoesNotExistError,a as NotInOrganizationError,o as PersoUtilServer,e as SessionCreationError,r as createSessionId,f as getAllSettings,m as getBackgroundImages,y as getDocuments,c as getIntroMessage,p as getLLMs,S as getMcpServers,u as getModelStyles,_ as getPrompts,l as getSTTs,E as getSessionInfo,g as getSessionTemplate,w as getSessionTemplates,d as getTTSs,T as getTextNormalization,h as getTextNormalizations,v as makeTTS};
|