@craftedxp/voice-js 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONSUMING.md +1 -1
- package/README.md +8 -7
- package/dist/browser.d.mts +20 -4
- package/dist/browser.d.ts +334 -250
- package/dist/browser.js +818 -541
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +278 -9
- package/dist/browser.mjs.map +1 -1
- package/dist/embed.iife.js +1094 -4
- package/dist/node.d.mts +20 -4
- package/dist/node.d.ts +324 -247
- package/dist/node.js +480 -369
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +103 -6
- package/dist/node.mjs.map +1 -1
- package/package.json +1 -1
package/dist/embed.iife.js
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
var VoissiaEmbedBundle = (() => {
|
|
3
|
+
function B(n) {
|
|
4
|
+
if (!n) throw new Error('configureVoiceClient: config is required')
|
|
5
|
+
if ('apiKey' in n)
|
|
6
|
+
throw new Error(
|
|
7
|
+
'configureVoiceClient: `apiKey` is no longer supported. Embedding sk_ in JS code ships server-grade credentials to every client. Pass `fetchToken: async ({ agentId }) => { /* call YOUR backend mint */ }` instead \u2014 see the @craftedxp/voice-js README for the migration recipe.',
|
|
8
|
+
)
|
|
9
|
+
if (!n.apiBase) throw new Error('configureVoiceClient: apiBase is required')
|
|
10
|
+
if (typeof n.fetchToken != 'function')
|
|
11
|
+
throw new Error('configureVoiceClient: fetchToken must be a function')
|
|
12
|
+
return { ...n, apiBase: n.apiBase.replace(/\/+$/, '') }
|
|
13
|
+
}
|
|
14
|
+
function V(n, e) {
|
|
15
|
+
let t =
|
|
16
|
+
n.defaultContext || e.context
|
|
17
|
+
? { ...(n.defaultContext ?? {}), ...(e.context ?? {}) }
|
|
18
|
+
: void 0,
|
|
19
|
+
r =
|
|
20
|
+
n.defaultMetadata || e.metadata
|
|
21
|
+
? { ...(n.defaultMetadata ?? {}), ...(e.metadata ?? {}) }
|
|
22
|
+
: void 0
|
|
23
|
+
return { context: t, metadata: r }
|
|
24
|
+
}
|
|
25
|
+
var W = `// AudioWorklet \u2014 runs off the main thread in the audio rendering graph.
|
|
2
26
|
//
|
|
3
27
|
// Input: Float32 samples at the AudioContext's native sampleRate (typically
|
|
4
28
|
// 48000 Hz on desktop, 44100 Hz on some iOS devices).
|
|
@@ -79,14 +103,958 @@ class MicDownsampler extends AudioWorkletProcessor {
|
|
|
79
103
|
}
|
|
80
104
|
|
|
81
105
|
registerProcessor('mic-downsampler', MicDownsampler)
|
|
82
|
-
`;var X=100,W=n=>{let e=null,t=null,r=null,o=null,a=null,i=null,l=!1,d=!1,h=s=>{let p=0;for(let g=0;g<s.length;g++)p+=s[g]*s[g];let y=Math.sqrt(p/s.length);return Math.min(1,y*1.8)};return{start:async()=>{if(!d)try{t=await navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,channelCount:1}}),e=new AudioContext,e.state==="suspended"&&await e.resume();let s=new Blob([O],{type:"application/javascript"}),p=URL.createObjectURL(s);try{await e.audioWorklet.addModule(p)}finally{URL.revokeObjectURL(p)}if(r=e.createMediaStreamSource(t),o=new AudioWorkletNode(e,"mic-downsampler"),o.port.onmessage=g=>{l||n.onChunk(g.data)},n.onVolume){a=e.createAnalyser(),a.fftSize=256,r.connect(a);let g=new Float32Array(a.fftSize);i=setInterval(()=>{a&&(a.getFloatTimeDomainData(g),n.onVolume?.(h(g)))},X)}r.connect(o);let y=e.createGain();y.gain.value=0,o.connect(y).connect(e.destination),d=!0}catch(s){let p=s instanceof Error?s:new Error(typeof s=="string"?s:"capture failed");throw n.onError?.(p),p}},stop:()=>{if(d){d=!1,i&&(clearInterval(i),i=null);try{o?.disconnect(),a?.disconnect(),r?.disconnect()}catch{}if(o=null,a=null,r=null,t){for(let s of t.getTracks())s.stop();t=null}e&&e.state!=="closed"&&e.close().catch(()=>{}),e=null}},mute:s=>{l=s},isCapturing:()=>d}};var B=(n={})=>{let e=n.sampleRate??16e3,t=null,r=null,o=null,a=null,i=0,l=[],d=!1,h=async()=>{if(t){t.state==="suspended"&&await t.resume();return}if(t=new AudioContext({sampleRate:e}),r=t.createGain(),n.onVolume){o=t.createAnalyser(),o.fftSize=256,r.connect(o);let c=new Float32Array(o.fftSize);a=setInterval(()=>{if(!o)return;o.getFloatTimeDomainData(c);let f=0;for(let C=0;C<c.length;C++)f+=c[C]*c[C];let k=Math.sqrt(f/c.length);n.onVolume?.(Math.min(1,k*1.8))},100)}r.connect(t.destination),i=t.currentTime},m=c=>{c!==d&&(d=c,n.onSpeakingChange?.(c))},u=()=>{let c=t?.currentTime??0;l=l.filter(f=>(f._endsAt??0)>c),l.length===0&&m(!1)},s=c=>{if(!t){h().then(()=>s(c));return}if(!t||!r)return;let f=new Int16Array(c);if(f.length===0)return;let k=t.createBuffer(1,f.length,e),C=k.getChannelData(0);for(let S=0;S<f.length;S++)C[S]=f[S]/32768;let b=t.createBufferSource();b.buffer=k,b.connect(r);let G=t.currentTime,w=Math.max(G,i);b.start(w);let I=f.length/e;b._endsAt=w+I,i=w+I,l.push(b),m(!0),b.onended=()=>u()},p=()=>{if(!(!t||!r)){for(let c of l)try{c.stop()}catch{}l=[],r.disconnect(),r=t.createGain(),o&&(o.disconnect(),r.connect(o)),r.connect(t.destination),i=t.currentTime,m(!1)}};return{enqueue:s,flush:p,close:()=>{p(),a&&(clearInterval(a),a=null),t&&t.state!=="closed"&&t.close().catch(()=>{}),t=null,r=null,o=null},resume:async()=>{await h()}}};var P=(n,e)=>{let t=n.maxRetries??3,r=n.initialBackoffMs??500,o=n.maxBackoffMs??8e3,a=null,i=!1,l=0,d=r,h=null,m=()=>{a=n.wsFactory(n.url),a.binaryType="arraybuffer",a.onopen=()=>{e(l===0?{type:"open"}:{type:"reconnected"}),l=0,d=r},a.onmessage=u=>{e({type:"message",data:u.data})},a.onerror=()=>{e({type:"error",error:new Error("WebSocket error")})},a.onclose=u=>{if(a=null,!(!i&&l<t)){e({type:"close",code:u.code,reason:u.reason,permanent:!0});return}e({type:"close",code:u.code,reason:u.reason,permanent:!1}),l++;let p=Math.min(d,o);d=Math.min(d*2,o),h=setTimeout(m,p)}};return m(),{send:u=>{a&&a.readyState===1&&a.send(u)},close:(u=1e3,s="client-requested")=>{i=!0,h&&(clearTimeout(h),h=null);try{a?.close(u,s)}catch{}},readyState:()=>a?.readyState??3}};var L=()=>({state:"idle",transcript:[],agentBubbleId:null,idCounter:0,endReason:null}),Y=n=>n==="agent_ended"?"agent_ended":n==="caller_hung_up"?"user_hangup":n==="silence_timeout"||n==="max_duration"?"timeout":"error";function N(n,e,t){let r;try{r=JSON.parse(n)}catch{return}switch(r.type){case"connected":t.onConnected(),x(e,"listening",t);return;case"transcript":{let o=r.text??"";if(!o)return;let a=!!r.isFinal;a||x(e,"user_speaking",t),K(e,o,a),t.onTranscript(e.transcript);return}case"agent_turn_start":{let o=`m${e.idCounter++}`;e.agentBubbleId=o,e.transcript=[...e.transcript,{id:o,role:"agent",text:""}],t.onTranscript(e.transcript),t.onAgentTurnStart(),x(e,"agent_speaking",t);return}case"agent_text":{let o=r.text??"";if(!o||!e.agentBubbleId)return;let a=e.agentBubbleId;e.transcript=e.transcript.map(i=>i.id===a&&i.role==="agent"?{...i,text:i.text+o}:i),t.onTranscript(e.transcript);return}case"agent_turn_end":e.agentBubbleId=null,x(e,"listening",t);return;case"interrupt":t.onInterrupt();return;case"agent_turn_abort":{let o=(r.committedText??"").trim();if(e.agentBubbleId){let a=e.agentBubbleId;o?e.transcript=e.transcript.map(i=>i.id===a&&i.role==="agent"?{...i,text:o,interrupted:!0}:i):e.transcript=e.transcript.filter(i=>i.id!==a),t.onTranscript(e.transcript)}e.agentBubbleId=null;return}case"tool_call":e.transcript=[...e.transcript,{id:`m${e.idCounter++}`,role:"tool",text:`\u2192 ${String(r.tool??"?")}(${r.args?JSON.stringify(r.args):""})`}],t.onTranscript(e.transcript);return;case"tool_result":e.transcript=[...e.transcript,{id:`m${e.idCounter++}`,role:"tool",text:`${r.ok?"\u2713":"\u2717"} ${String(r.tool??"?")}`}],t.onTranscript(e.transcript);return;case"client_tool_call":{let o=String(r.toolCallId??""),a=String(r.name??""),i=r.args??{};if(!o||!a)return;t.onClientToolCall({toolCallId:o,name:a,args:i});return}case"call_end":{let o=String(r.reason??""),a=Y(o);e.endReason=a,e.transcript=[...e.transcript,{id:`m${e.idCounter++}`,role:"system",text:`call ended${o?` (${o})`:""}`}],t.onTranscript(e.transcript),t.onCallEnd(a);return}case"error":{let o=r.code??"server_error",a=r.message??"server error";t.onError({code:o,message:a});return}}}var x=(n,e,t)=>{n.state!==e&&(n.state=e,t.onState(e))},K=(n,e,t)=>{let r=-1;for(let i=n.transcript.length-1;i>=0;i--){let l=n.transcript[i];if(l.role==="user"&&l.committed===!1){r=i;break}}if(r===-1){n.transcript=[...n.transcript,{id:`m${n.idCounter++}`,role:"user",text:e,committed:t}];return}let o=n.transcript[r],a=[...n.transcript];a[r]={...o,text:e,committed:t},n.transcript=a};function $(n){let e=new URL(n.apiBase),t=e.protocol==="https:"?"wss:":"ws:",r=n.bargeIn===!1?"&barge=off":"";return`${t}//${e.host}/v1/agents/${encodeURIComponent(n.agentId)}/call?token=${encodeURIComponent(n.token)}${r}`}var Z=/^[a-zA-Z_][a-zA-Z0-9_]*$/;var D=n=>{if(n===void 0)return;if(typeof n!="object"||n===null||Array.isArray(n))throw new Error("clientTools must be an object keyed by tool name");let e=Object.entries(n);if(e.length>64)throw new Error(`clientTools may declare at most 64 tools (got ${e.length})`);for(let[t,r]of e){if(!Z.test(t))throw new Error(`clientTools["${t}"]: name must be a valid identifier (^[a-zA-Z_][a-zA-Z0-9_]*$)`);if(!r||typeof r!="object")throw new Error(`clientTools["${t}"]: must be an object`);if(typeof r.description!="string"||r.description.length===0)throw new Error(`clientTools["${t}"]: must have a description`);if(typeof r.handler!="function")throw new Error(`clientTools["${t}"]: must have a handler function`);if(r.usage!==void 0&&r.usage.length>500)throw new Error(`clientTools["${t}"]: usage must be \u2264500 chars`);if(r.timeoutMs!==void 0&&(!Number.isFinite(r.timeoutMs)||r.timeoutMs<=0||r.timeoutMs>3e4))throw new Error(`clientTools["${t}"]: timeoutMs must be in (0, 30000]`)}},U=n=>({type:"client_tools_register",tools:Object.entries(n).map(([e,t])=>({name:e,description:t.description,parameters:t.parameters,...t.usage!==void 0?{usage:t.usage}:{},...t.timeoutMs!==void 0?{timeoutMs:t.timeoutMs}:{}}))}),z=(n,e,t)=>{let r=a=>{try{n(a)}catch{}},o=e[t.name];if(!o){r({type:"client_tool_result",toolCallId:t.toolCallId,error:`No handler for ${t.name}`});return}(async()=>{try{let a=await o.handler(t.args);r({type:"client_tool_result",toolCallId:t.toolCallId,result:typeof a=="string"?a:JSON.stringify(a)})}catch(a){r({type:"client_tool_result",toolCallId:t.toolCallId,error:a instanceof Error?a.message:String(a)})}})()};var v=class{constructor(e){this.rws=null;this.capture=null;this.playback=null;this.muted=!1;this.inputVolume=0;this.outputVolume=0;this.startedAt=null;this.endedFired=!1;this.lastError=null;this.end=()=>{this.teardown("user_hangup")};this.mute=()=>{this.muted||(this.muted=!0,this.capture?.mute(!0))};this.unmute=()=>{this.muted&&(this.muted=!1,this.capture?.mute(!1))};this.sendClientToolsRegister=()=>{let e=U(this.args.options.clientTools??{});this.rws?.send(JSON.stringify(e))};this.setState=e=>{this.proto.state!==e&&(this.proto.state=e,this.args.options.onStateChange?.(e))};this.emitError=e=>{this.lastError=e,this.args.options.onError?.(e)};this.handleSocketEvent=e=>{switch(e.type){case"open":this.startCapture();break;case"reconnected":this.proto.transcript=[],this.proto.agentBubbleId=null,this.args.options.onTranscript?.(this.proto.transcript),this.startCapture(),this.setState("listening");break;case"message":typeof e.data=="string"?N(e.data,this.proto,{onState:this.setState,onTranscript:t=>this.args.options.onTranscript?.(t),onError:this.emitError,onInterrupt:()=>{this.playback?.flush(),this.args.options.onInterrupt?.()},onAgentTurnStart:()=>this.args.options.onAgentTurnStart?.(),onCallEnd:t=>this.teardown(t),onConnected:()=>this.sendClientToolsRegister(),onClientToolCall:t=>z(r=>this.rws?.send(JSON.stringify(r)),this.args.options.clientTools??{},t)}):this.playback?.enqueue(e.data);break;case"close":if(e.permanent){let t=this.proto.endReason??(this.lastError?"error":"user_hangup");this.teardown(t)}break;case"error":this.emitError({code:"socket_error",message:e.error.message});break}};this.startCapture=async()=>{if(!this.capture?.isCapturing()){this.capture=W({onChunk:e=>{this.rws?.send(e)},onVolume:e=>{this.inputVolume=e,this.args.options.onVolume?.({input:e,output:this.outputVolume})},onError:e=>{this.emitError({code:e.name==="NotAllowedError"?"mic_denied":"mic_start_failed",message:e.message})}}),this.muted&&this.capture.mute(!0);try{await this.capture.start()}catch{}}};this.teardown=e=>{this.capture?.stop(),this.capture=null,this.playback?.close(),this.playback=null;try{this.rws?.close(1e3,e)}catch{}this.rws=null,this.setState("ended"),this.fireEndOnce(e)};this.fireEndOnce=e=>{if(this.endedFired)return;this.endedFired=!0;let t=this.startedAt??Date.now();this.args.options.onEnd?.({reason:e,errorCode:e==="error"?this.lastError?.code:void 0,durationMs:Date.now()-t})};this.args=e,this.proto=L(),D(e.options.clientTools)}get state(){return this.proto.state}get transcript(){return this.proto.transcript.slice()}get isMuted(){return this.muted}async start(){this.setState("connecting"),this.startedAt=Date.now();let e=$({apiBase:this.args.config.apiBase,agentId:this.args.options.agentId,token:this.args.token,bargeIn:this.args.options.bargeIn});this.playback=B({onVolume:t=>{this.outputVolume=t,this.args.options.onVolume?.({input:this.inputVolume,output:t})}});try{await this.playback.resume()}catch{}this.rws=P({url:e,wsFactory:this.args.wsFactory,maxRetries:3},t=>this.handleSocketEvent(t))}};var Q=n=>new globalThis.WebSocket(n),T=class{constructor(e){this.startCall=async e=>{if(!e.agentId)throw new Error("startCall: agentId is required");let{context:t,metadata:r}=F(this.config,e),o={agentId:e.agentId,userId:e.userId,context:t,metadata:r},a;if(e.token)a=e.token;else if(a=await this.config.fetchToken(o),!a)throw new Error("configureVoiceClient.fetchToken returned empty token");let i=new v({config:this.config,options:{...e,context:t,metadata:r},token:a,wsFactory:Q});return await i.start(),i};this.config=e}};function j(n){return new T(V(n))}var H="voice-agent-embed-root",ee="M12 14a3 3 0 0 0 3-3V5a3 3 0 0 0-6 0v6a3 3 0 0 0 3 3zm5.3-3a.7.7 0 0 1 1.4 0 6.7 6.7 0 0 1-6 6.66V21h-1.4v-3.34A6.7 6.7 0 0 1 5.3 11a.7.7 0 0 1 1.4 0 5.3 5.3 0 0 0 10.6 0z",A="http://www.w3.org/2000/svg",q=n=>{let e=document.createElementNS(A,"svg");if(e.setAttribute("viewBox","0 0 24 24"),e.setAttribute("class","icon"),n.rect){let t=document.createElementNS(A,"rect");t.setAttribute("x","7"),t.setAttribute("y","7"),t.setAttribute("width","10"),t.setAttribute("height","10"),t.setAttribute("rx","1.5"),e.appendChild(t)}else if(n.path){let t=document.createElementNS(A,"path");t.setAttribute("d",n.path),e.appendChild(t)}return e},te=n=>{try{return JSON.parse(n)}catch{return}},re=n=>{if(n)try{return new URL(n.src,location.href).origin}catch{}return location.origin},ne=()=>{let n=document.querySelectorAll('script[src*="embed.js"]');for(let e of Array.from(n)){let t=e.dataset;if(t.apiKey&&console.error("[craftedxp/voice-js] data-api-key on the embed <script> is no longer supported. Mint a ct_ server-side via @craftedxp/sdk-node and inject it into data-token instead."),t.agentId&&t.token)return{apiBase:t.apiBase||re(e),token:t.token,agentId:t.agentId,vars:t.vars?te(t.vars):void 0,label:t.label||"Call agent",primaryColor:t.primaryColor||"#3d7dd0",position:t.position==="bottom-left"?"bottom-left":"bottom-right"}}return null},M=class{constructor(e){this.call=null;this.cfg=e,document.getElementById(H)?.remove(),this.host=document.createElement("div"),this.host.id=H,document.body.appendChild(this.host),this.shadow=this.host.attachShadow({mode:"open"}),this.mount()}mount(){let e=document.createElement("style");e.textContent=this.css(),this.shadow.appendChild(e);let t=document.createElement("div");t.className=`wrap ${this.cfg.position??"bottom-right"}`,this.panel=document.createElement("div"),this.panel.className="panel",this.panel.style.display="none";let r=document.createElement("div");r.className="panel-head";let o=document.createElement("div");o.className="title",o.textContent=this.cfg.label??"Call agent",this.statusEl=document.createElement("div"),this.statusEl.className="status",this.statusEl.textContent="idle",r.appendChild(o),r.appendChild(this.statusEl),this.transcriptEl=document.createElement("div"),this.transcriptEl.className="transcript",this.panel.appendChild(r),this.panel.appendChild(this.transcriptEl),t.appendChild(this.panel),this.fab=document.createElement("button"),this.fab.className="fab",this.fab.type="button",this.setFabState("idle"),this.fab.addEventListener("click",()=>this.toggle()),t.appendChild(this.fab),this.shadow.appendChild(t),(!this.cfg.agentId||!this.cfg.token)&&(this.setStatus("Widget not configured \u2014 data-token + data-agent-id are required","warn"),this.fab.disabled=!0,this.fab.title="Missing data-token or data-agent-id")}destroy(){this.call?.end(),this.call=null,this.host.remove()}css(){return`
|
|
106
|
+
`
|
|
107
|
+
var K = 100,
|
|
108
|
+
P = (n) => {
|
|
109
|
+
let e = null,
|
|
110
|
+
t = null,
|
|
111
|
+
r = null,
|
|
112
|
+
o = null,
|
|
113
|
+
a = null,
|
|
114
|
+
s = null,
|
|
115
|
+
l = !1,
|
|
116
|
+
f = !1,
|
|
117
|
+
m = (p) => {
|
|
118
|
+
let g = 0
|
|
119
|
+
for (let i = 0; i < p.length; i++) g += p[i] * p[i]
|
|
120
|
+
let b = Math.sqrt(g / p.length)
|
|
121
|
+
return Math.min(1, b * 1.8)
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
start: async () => {
|
|
125
|
+
if (!f)
|
|
126
|
+
try {
|
|
127
|
+
;((t = await navigator.mediaDevices.getUserMedia({
|
|
128
|
+
audio: {
|
|
129
|
+
echoCancellation: !0,
|
|
130
|
+
noiseSuppression: !0,
|
|
131
|
+
autoGainControl: !0,
|
|
132
|
+
channelCount: 1,
|
|
133
|
+
},
|
|
134
|
+
})),
|
|
135
|
+
(e = new AudioContext()),
|
|
136
|
+
e.state === 'suspended' && (await e.resume()))
|
|
137
|
+
let p = new Blob([W], { type: 'application/javascript' }),
|
|
138
|
+
g = URL.createObjectURL(p)
|
|
139
|
+
try {
|
|
140
|
+
await e.audioWorklet.addModule(g)
|
|
141
|
+
} finally {
|
|
142
|
+
URL.revokeObjectURL(g)
|
|
143
|
+
}
|
|
144
|
+
if (
|
|
145
|
+
((r = e.createMediaStreamSource(t)),
|
|
146
|
+
(o = new AudioWorkletNode(e, 'mic-downsampler')),
|
|
147
|
+
(o.port.onmessage = (i) => {
|
|
148
|
+
l || n.onChunk(i.data)
|
|
149
|
+
}),
|
|
150
|
+
n.onVolume)
|
|
151
|
+
) {
|
|
152
|
+
;((a = e.createAnalyser()), (a.fftSize = 256), r.connect(a))
|
|
153
|
+
let i = new Float32Array(a.fftSize)
|
|
154
|
+
s = setInterval(() => {
|
|
155
|
+
a && (a.getFloatTimeDomainData(i), n.onVolume?.(m(i)))
|
|
156
|
+
}, K)
|
|
157
|
+
}
|
|
158
|
+
r.connect(o)
|
|
159
|
+
let b = e.createGain()
|
|
160
|
+
;((b.gain.value = 0), o.connect(b).connect(e.destination), (f = !0))
|
|
161
|
+
} catch (p) {
|
|
162
|
+
let g =
|
|
163
|
+
p instanceof Error ? p : new Error(typeof p == 'string' ? p : 'capture failed')
|
|
164
|
+
throw (n.onError?.(g), g)
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
stop: () => {
|
|
168
|
+
if (f) {
|
|
169
|
+
;((f = !1), s && (clearInterval(s), (s = null)))
|
|
170
|
+
try {
|
|
171
|
+
;(o?.disconnect(), a?.disconnect(), r?.disconnect())
|
|
172
|
+
} catch {}
|
|
173
|
+
if (((o = null), (a = null), (r = null), t)) {
|
|
174
|
+
for (let p of t.getTracks()) p.stop()
|
|
175
|
+
t = null
|
|
176
|
+
}
|
|
177
|
+
;(e && e.state !== 'closed' && e.close().catch(() => {}), (e = null))
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
mute: (p) => {
|
|
181
|
+
l = p
|
|
182
|
+
},
|
|
183
|
+
isCapturing: () => f,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
var N = (n = {}) => {
|
|
187
|
+
let e = n.sampleRate ?? 16e3,
|
|
188
|
+
t = null,
|
|
189
|
+
r = null,
|
|
190
|
+
o = null,
|
|
191
|
+
a = null,
|
|
192
|
+
s = 0,
|
|
193
|
+
l = [],
|
|
194
|
+
f = !1,
|
|
195
|
+
m = async () => {
|
|
196
|
+
if (t) {
|
|
197
|
+
t.state === 'suspended' && (await t.resume())
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
if (((t = new AudioContext({ sampleRate: e })), (r = t.createGain()), n.onVolume)) {
|
|
201
|
+
;((o = t.createAnalyser()), (o.fftSize = 256), r.connect(o))
|
|
202
|
+
let u = new Float32Array(o.fftSize)
|
|
203
|
+
a = setInterval(() => {
|
|
204
|
+
if (!o) return
|
|
205
|
+
o.getFloatTimeDomainData(u)
|
|
206
|
+
let h = 0
|
|
207
|
+
for (let y = 0; y < u.length; y++) h += u[y] * u[y]
|
|
208
|
+
let C = Math.sqrt(h / u.length)
|
|
209
|
+
n.onVolume?.(Math.min(1, C * 1.8))
|
|
210
|
+
}, 100)
|
|
211
|
+
}
|
|
212
|
+
;(r.connect(t.destination), (s = t.currentTime))
|
|
213
|
+
},
|
|
214
|
+
c = (u) => {
|
|
215
|
+
u !== f && ((f = u), n.onSpeakingChange?.(u))
|
|
216
|
+
},
|
|
217
|
+
d = () => {
|
|
218
|
+
let u = t?.currentTime ?? 0
|
|
219
|
+
;((l = l.filter((h) => (h._endsAt ?? 0) > u)), l.length === 0 && c(!1))
|
|
220
|
+
},
|
|
221
|
+
p = (u) => {
|
|
222
|
+
if (!t) {
|
|
223
|
+
m().then(() => p(u))
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
if (!t || !r) return
|
|
227
|
+
let h = new Int16Array(u)
|
|
228
|
+
if (h.length === 0) return
|
|
229
|
+
let C = t.createBuffer(1, h.length, e),
|
|
230
|
+
y = C.getChannelData(0)
|
|
231
|
+
for (let w = 0; w < h.length; w++) y[w] = h[w] / 32768
|
|
232
|
+
let k = t.createBufferSource()
|
|
233
|
+
;((k.buffer = C), k.connect(r))
|
|
234
|
+
let X = t.currentTime,
|
|
235
|
+
A = Math.max(X, s)
|
|
236
|
+
k.start(A)
|
|
237
|
+
let F = h.length / e
|
|
238
|
+
;((k._endsAt = A + F), (s = A + F), l.push(k), c(!0), (k.onended = () => d()))
|
|
239
|
+
},
|
|
240
|
+
g = () => {
|
|
241
|
+
if (!(!t || !r)) {
|
|
242
|
+
for (let u of l)
|
|
243
|
+
try {
|
|
244
|
+
u.stop()
|
|
245
|
+
} catch {}
|
|
246
|
+
;((l = []),
|
|
247
|
+
r.disconnect(),
|
|
248
|
+
(r = t.createGain()),
|
|
249
|
+
o && (o.disconnect(), r.connect(o)),
|
|
250
|
+
r.connect(t.destination),
|
|
251
|
+
(s = t.currentTime),
|
|
252
|
+
c(!1))
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
enqueue: p,
|
|
257
|
+
flush: g,
|
|
258
|
+
close: () => {
|
|
259
|
+
;(g(),
|
|
260
|
+
a && (clearInterval(a), (a = null)),
|
|
261
|
+
t && t.state !== 'closed' && t.close().catch(() => {}),
|
|
262
|
+
(t = null),
|
|
263
|
+
(r = null),
|
|
264
|
+
(o = null))
|
|
265
|
+
},
|
|
266
|
+
resume: async () => {
|
|
267
|
+
await m()
|
|
268
|
+
},
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
var L = (n, e) => {
|
|
272
|
+
let t = n.maxRetries ?? 3,
|
|
273
|
+
r = n.initialBackoffMs ?? 500,
|
|
274
|
+
o = n.maxBackoffMs ?? 8e3,
|
|
275
|
+
a = null,
|
|
276
|
+
s = !1,
|
|
277
|
+
l = 0,
|
|
278
|
+
f = r,
|
|
279
|
+
m = null,
|
|
280
|
+
c = () => {
|
|
281
|
+
;((a = n.wsFactory(n.url)),
|
|
282
|
+
(a.binaryType = 'arraybuffer'),
|
|
283
|
+
(a.onopen = () => {
|
|
284
|
+
;(e(l === 0 ? { type: 'open' } : { type: 'reconnected' }), (l = 0), (f = r))
|
|
285
|
+
}),
|
|
286
|
+
(a.onmessage = (d) => {
|
|
287
|
+
e({ type: 'message', data: d.data })
|
|
288
|
+
}),
|
|
289
|
+
(a.onerror = () => {
|
|
290
|
+
e({ type: 'error', error: new Error('WebSocket error') })
|
|
291
|
+
}),
|
|
292
|
+
(a.onclose = (d) => {
|
|
293
|
+
if (((a = null), !(!s && l < t))) {
|
|
294
|
+
e({ type: 'close', code: d.code, reason: d.reason, permanent: !0 })
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
;(e({ type: 'close', code: d.code, reason: d.reason, permanent: !1 }), l++)
|
|
298
|
+
let g = Math.min(f, o)
|
|
299
|
+
;((f = Math.min(f * 2, o)), (m = setTimeout(c, g)))
|
|
300
|
+
}))
|
|
301
|
+
}
|
|
302
|
+
return (
|
|
303
|
+
c(),
|
|
304
|
+
{
|
|
305
|
+
send: (d) => {
|
|
306
|
+
a && a.readyState === 1 && a.send(d)
|
|
307
|
+
},
|
|
308
|
+
close: (d = 1e3, p = 'client-requested') => {
|
|
309
|
+
;((s = !0), m && (clearTimeout(m), (m = null)))
|
|
310
|
+
try {
|
|
311
|
+
a?.close(d, p)
|
|
312
|
+
} catch {}
|
|
313
|
+
},
|
|
314
|
+
readyState: () => a?.readyState ?? 3,
|
|
315
|
+
}
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
var E = () => ({
|
|
319
|
+
state: 'idle',
|
|
320
|
+
transcript: [],
|
|
321
|
+
agentBubbleId: null,
|
|
322
|
+
idCounter: 0,
|
|
323
|
+
endReason: null,
|
|
324
|
+
}),
|
|
325
|
+
Z = (n) =>
|
|
326
|
+
n === 'agent_ended'
|
|
327
|
+
? 'agent_ended'
|
|
328
|
+
: n === 'caller_hung_up'
|
|
329
|
+
? 'user_hangup'
|
|
330
|
+
: n === 'silence_timeout' || n === 'max_duration'
|
|
331
|
+
? 'timeout'
|
|
332
|
+
: 'error'
|
|
333
|
+
function x(n, e, t) {
|
|
334
|
+
let r
|
|
335
|
+
try {
|
|
336
|
+
r = JSON.parse(n)
|
|
337
|
+
} catch {
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
switch (r.type) {
|
|
341
|
+
case 'connected':
|
|
342
|
+
;(t.onConnected(), S(e, 'listening', t))
|
|
343
|
+
return
|
|
344
|
+
case 'transcript': {
|
|
345
|
+
let o = r.text ?? ''
|
|
346
|
+
if (!o) return
|
|
347
|
+
let a = !!r.isFinal
|
|
348
|
+
;(a || S(e, 'user_speaking', t), Q(e, o, a), t.onTranscript(e.transcript))
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
case 'agent_turn_start': {
|
|
352
|
+
let o = `m${e.idCounter++}`
|
|
353
|
+
;((e.agentBubbleId = o),
|
|
354
|
+
(e.transcript = [...e.transcript, { id: o, role: 'agent', text: '' }]),
|
|
355
|
+
t.onTranscript(e.transcript))
|
|
356
|
+
let a = typeof r.seq == 'number' ? r.seq : void 0
|
|
357
|
+
;(t.onAgentTurnStart(a), S(e, 'agent_speaking', t))
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
case 'agent_text': {
|
|
361
|
+
let o = r.text ?? ''
|
|
362
|
+
if (!o || !e.agentBubbleId) return
|
|
363
|
+
let a = e.agentBubbleId
|
|
364
|
+
;((e.transcript = e.transcript.map((s) =>
|
|
365
|
+
s.id === a && s.role === 'agent' ? { ...s, text: s.text + o } : s,
|
|
366
|
+
)),
|
|
367
|
+
t.onTranscript(e.transcript))
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
case 'agent_turn_end': {
|
|
371
|
+
e.agentBubbleId = null
|
|
372
|
+
let o = typeof r.seq == 'number' ? r.seq : void 0
|
|
373
|
+
;(t.onAgentTurnEnd(o), S(e, 'listening', t))
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
case 'interrupt':
|
|
377
|
+
t.onInterrupt()
|
|
378
|
+
return
|
|
379
|
+
case 'agent_turn_abort': {
|
|
380
|
+
let o = (r.committedText ?? '').trim()
|
|
381
|
+
if (e.agentBubbleId) {
|
|
382
|
+
let a = e.agentBubbleId
|
|
383
|
+
;(o
|
|
384
|
+
? (e.transcript = e.transcript.map((s) =>
|
|
385
|
+
s.id === a && s.role === 'agent' ? { ...s, text: o, interrupted: !0 } : s,
|
|
386
|
+
))
|
|
387
|
+
: (e.transcript = e.transcript.filter((s) => s.id !== a)),
|
|
388
|
+
t.onTranscript(e.transcript))
|
|
389
|
+
}
|
|
390
|
+
e.agentBubbleId = null
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
case 'tool_call':
|
|
394
|
+
;((e.transcript = [
|
|
395
|
+
...e.transcript,
|
|
396
|
+
{
|
|
397
|
+
id: `m${e.idCounter++}`,
|
|
398
|
+
role: 'tool',
|
|
399
|
+
text: `\u2192 ${String(r.tool ?? '?')}(${r.args ? JSON.stringify(r.args) : ''})`,
|
|
400
|
+
},
|
|
401
|
+
]),
|
|
402
|
+
t.onTranscript(e.transcript))
|
|
403
|
+
return
|
|
404
|
+
case 'tool_result':
|
|
405
|
+
;((e.transcript = [
|
|
406
|
+
...e.transcript,
|
|
407
|
+
{
|
|
408
|
+
id: `m${e.idCounter++}`,
|
|
409
|
+
role: 'tool',
|
|
410
|
+
text: `${r.ok ? '\u2713' : '\u2717'} ${String(r.tool ?? '?')}`,
|
|
411
|
+
},
|
|
412
|
+
]),
|
|
413
|
+
t.onTranscript(e.transcript))
|
|
414
|
+
return
|
|
415
|
+
case 'client_tool_call': {
|
|
416
|
+
let o = String(r.toolCallId ?? ''),
|
|
417
|
+
a = String(r.name ?? ''),
|
|
418
|
+
s = r.args ?? {}
|
|
419
|
+
if (!o || !a) return
|
|
420
|
+
t.onClientToolCall({ toolCallId: o, name: a, args: s })
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
case 'call_end': {
|
|
424
|
+
let o = String(r.reason ?? ''),
|
|
425
|
+
a = Z(o)
|
|
426
|
+
;((e.endReason = a),
|
|
427
|
+
(e.transcript = [
|
|
428
|
+
...e.transcript,
|
|
429
|
+
{ id: `m${e.idCounter++}`, role: 'system', text: `call ended${o ? ` (${o})` : ''}` },
|
|
430
|
+
]),
|
|
431
|
+
t.onTranscript(e.transcript),
|
|
432
|
+
t.onCallEnd(a))
|
|
433
|
+
return
|
|
434
|
+
}
|
|
435
|
+
case 'error': {
|
|
436
|
+
let o = r.code ?? 'server_error',
|
|
437
|
+
a = r.message ?? 'server error'
|
|
438
|
+
t.onError({ code: o, message: a })
|
|
439
|
+
return
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
var S = (n, e, t) => {
|
|
444
|
+
n.state !== e && t.onState(e)
|
|
445
|
+
},
|
|
446
|
+
Q = (n, e, t) => {
|
|
447
|
+
let r = -1
|
|
448
|
+
for (let s = n.transcript.length - 1; s >= 0; s--) {
|
|
449
|
+
let l = n.transcript[s]
|
|
450
|
+
if (l.role === 'user' && l.committed === !1) {
|
|
451
|
+
r = s
|
|
452
|
+
break
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (r === -1) {
|
|
456
|
+
n.transcript = [
|
|
457
|
+
...n.transcript,
|
|
458
|
+
{ id: `m${n.idCounter++}`, role: 'user', text: e, committed: t },
|
|
459
|
+
]
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
let o = n.transcript[r],
|
|
463
|
+
a = [...n.transcript]
|
|
464
|
+
;((a[r] = { ...o, text: e, committed: t }), (n.transcript = a))
|
|
465
|
+
}
|
|
466
|
+
function $(n) {
|
|
467
|
+
let e = new URL(n.apiBase),
|
|
468
|
+
t = e.protocol === 'https:' ? 'wss:' : 'ws:',
|
|
469
|
+
r = n.bargeIn === !1 ? '&barge=off' : ''
|
|
470
|
+
return `${t}//${e.host}/v1/agents/${encodeURIComponent(n.agentId)}/call?token=${encodeURIComponent(n.token)}${r}`
|
|
471
|
+
}
|
|
472
|
+
var ee = /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
|
473
|
+
var U = (n) => {
|
|
474
|
+
if (n === void 0) return
|
|
475
|
+
if (typeof n != 'object' || n === null || Array.isArray(n))
|
|
476
|
+
throw new Error('clientTools must be an object keyed by tool name')
|
|
477
|
+
let e = Object.entries(n)
|
|
478
|
+
if (e.length > 64)
|
|
479
|
+
throw new Error(`clientTools may declare at most 64 tools (got ${e.length})`)
|
|
480
|
+
for (let [t, r] of e) {
|
|
481
|
+
if (!ee.test(t))
|
|
482
|
+
throw new Error(
|
|
483
|
+
`clientTools["${t}"]: name must be a valid identifier (^[a-zA-Z_][a-zA-Z0-9_]*$)`,
|
|
484
|
+
)
|
|
485
|
+
if (!r || typeof r != 'object') throw new Error(`clientTools["${t}"]: must be an object`)
|
|
486
|
+
if (typeof r.description != 'string' || r.description.length === 0)
|
|
487
|
+
throw new Error(`clientTools["${t}"]: must have a description`)
|
|
488
|
+
if (typeof r.handler != 'function')
|
|
489
|
+
throw new Error(`clientTools["${t}"]: must have a handler function`)
|
|
490
|
+
if (r.usage !== void 0 && r.usage.length > 500)
|
|
491
|
+
throw new Error(`clientTools["${t}"]: usage must be \u2264500 chars`)
|
|
492
|
+
if (
|
|
493
|
+
r.timeoutMs !== void 0 &&
|
|
494
|
+
(!Number.isFinite(r.timeoutMs) || r.timeoutMs <= 0 || r.timeoutMs > 3e4)
|
|
495
|
+
)
|
|
496
|
+
throw new Error(`clientTools["${t}"]: timeoutMs must be in (0, 30000]`)
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
D = (n) => ({
|
|
500
|
+
type: 'client_tools_register',
|
|
501
|
+
tools: Object.entries(n).map(([e, t]) => ({
|
|
502
|
+
name: e,
|
|
503
|
+
description: t.description,
|
|
504
|
+
parameters: t.parameters,
|
|
505
|
+
...(t.usage !== void 0 ? { usage: t.usage } : {}),
|
|
506
|
+
...(t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {}),
|
|
507
|
+
})),
|
|
508
|
+
}),
|
|
509
|
+
j = (n, e, t) => {
|
|
510
|
+
let r = (a) => {
|
|
511
|
+
try {
|
|
512
|
+
n(a)
|
|
513
|
+
} catch {}
|
|
514
|
+
},
|
|
515
|
+
o = e[t.name]
|
|
516
|
+
if (!o) {
|
|
517
|
+
r({
|
|
518
|
+
type: 'client_tool_result',
|
|
519
|
+
toolCallId: t.toolCallId,
|
|
520
|
+
error: `No handler for ${t.name}`,
|
|
521
|
+
})
|
|
522
|
+
return
|
|
523
|
+
}
|
|
524
|
+
;(async () => {
|
|
525
|
+
try {
|
|
526
|
+
let a = await o.handler(t.args)
|
|
527
|
+
r({
|
|
528
|
+
type: 'client_tool_result',
|
|
529
|
+
toolCallId: t.toolCallId,
|
|
530
|
+
result: typeof a == 'string' ? a : JSON.stringify(a),
|
|
531
|
+
})
|
|
532
|
+
} catch (a) {
|
|
533
|
+
r({
|
|
534
|
+
type: 'client_tool_result',
|
|
535
|
+
toolCallId: t.toolCallId,
|
|
536
|
+
error: a instanceof Error ? a.message : String(a),
|
|
537
|
+
})
|
|
538
|
+
}
|
|
539
|
+
})()
|
|
540
|
+
}
|
|
541
|
+
var z = (n) => {
|
|
542
|
+
let e = n.now ?? (() => performance.now()),
|
|
543
|
+
t = null,
|
|
544
|
+
r = new Map(),
|
|
545
|
+
o = (c) => {
|
|
546
|
+
let d = r.get(c)
|
|
547
|
+
if (!d || !d.ended) return
|
|
548
|
+
let p = {}
|
|
549
|
+
;(d.firstOutboundAt !== null &&
|
|
550
|
+
d.firstAudibleAt !== null &&
|
|
551
|
+
(p.client_mic_to_first_audible_ms = d.firstAudibleAt - d.firstOutboundAt),
|
|
552
|
+
n.send({ type: 'client_marks', seq: c, marks: p, clientNow: Date.now() }),
|
|
553
|
+
r.delete(c))
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
markFirstOutboundAudio: () => {
|
|
557
|
+
t === null && (t = e())
|
|
558
|
+
},
|
|
559
|
+
markFirstAudibleOutput: () => {
|
|
560
|
+
let c
|
|
561
|
+
for (let d of r.values()) d.ended || (c = d)
|
|
562
|
+
c && c.firstAudibleAt === null && (c.firstAudibleAt = e())
|
|
563
|
+
},
|
|
564
|
+
onAgentTurnStart: (c) => {
|
|
565
|
+
;(r.set(c, { firstOutboundAt: t, firstAudibleAt: null, ended: !1 }), (t = null))
|
|
566
|
+
},
|
|
567
|
+
onAgentTurnEnd: (c) => {
|
|
568
|
+
let d = r.get(c)
|
|
569
|
+
if (!d) {
|
|
570
|
+
n.send({ type: 'client_marks', seq: c, marks: {}, clientNow: Date.now() })
|
|
571
|
+
return
|
|
572
|
+
}
|
|
573
|
+
;((d.ended = !0), o(c))
|
|
574
|
+
},
|
|
575
|
+
flush: () => {
|
|
576
|
+
for (let c of [...r.keys()]) {
|
|
577
|
+
let d = r.get(c)
|
|
578
|
+
;((d.ended = !0), o(c))
|
|
579
|
+
}
|
|
580
|
+
t = null
|
|
581
|
+
},
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
var v = class {
|
|
585
|
+
constructor(e) {
|
|
586
|
+
this.rws = null
|
|
587
|
+
this.capture = null
|
|
588
|
+
this.playback = null
|
|
589
|
+
this.muted = !1
|
|
590
|
+
this.inputVolume = 0
|
|
591
|
+
this.outputVolume = 0
|
|
592
|
+
this.startedAt = null
|
|
593
|
+
this.endedFired = !1
|
|
594
|
+
this.lastError = null
|
|
595
|
+
this.end = () => {
|
|
596
|
+
this.teardown('user_hangup')
|
|
597
|
+
}
|
|
598
|
+
this.mute = () => {
|
|
599
|
+
this.muted || ((this.muted = !0), this.capture?.mute(!0))
|
|
600
|
+
}
|
|
601
|
+
this.unmute = () => {
|
|
602
|
+
this.muted && ((this.muted = !1), this.capture?.mute(!1))
|
|
603
|
+
}
|
|
604
|
+
this.sendClientToolsRegister = () => {
|
|
605
|
+
let e = D(this.args.options.clientTools ?? {})
|
|
606
|
+
this.rws?.send(JSON.stringify(e))
|
|
607
|
+
}
|
|
608
|
+
this.setState = (e) => {
|
|
609
|
+
this.proto.state !== e && ((this.proto.state = e), this.args.options.onStateChange?.(e))
|
|
610
|
+
}
|
|
611
|
+
this.emitError = (e) => {
|
|
612
|
+
;((this.lastError = e), this.args.options.onError?.(e))
|
|
613
|
+
}
|
|
614
|
+
this.handleSocketEvent = (e) => {
|
|
615
|
+
switch (e.type) {
|
|
616
|
+
case 'open':
|
|
617
|
+
this.startCapture()
|
|
618
|
+
break
|
|
619
|
+
case 'reconnected':
|
|
620
|
+
;((this.proto.transcript = []),
|
|
621
|
+
(this.proto.agentBubbleId = null),
|
|
622
|
+
this.args.options.onTranscript?.(this.proto.transcript),
|
|
623
|
+
this.startCapture(),
|
|
624
|
+
this.setState('listening'))
|
|
625
|
+
break
|
|
626
|
+
case 'message':
|
|
627
|
+
typeof e.data == 'string'
|
|
628
|
+
? x(e.data, this.proto, {
|
|
629
|
+
onState: this.setState,
|
|
630
|
+
onTranscript: (t) => this.args.options.onTranscript?.(t),
|
|
631
|
+
onError: this.emitError,
|
|
632
|
+
onInterrupt: () => {
|
|
633
|
+
;(this.playback?.flush(), this.args.options.onInterrupt?.())
|
|
634
|
+
},
|
|
635
|
+
onAgentTurnStart: (t) => {
|
|
636
|
+
;(typeof t == 'number' && this.marks.onAgentTurnStart(t),
|
|
637
|
+
this.args.options.onAgentTurnStart?.())
|
|
638
|
+
},
|
|
639
|
+
onAgentTurnEnd: (t) => {
|
|
640
|
+
typeof t == 'number' && this.marks.onAgentTurnEnd(t)
|
|
641
|
+
},
|
|
642
|
+
onCallEnd: (t) => this.teardown(t),
|
|
643
|
+
onConnected: () => this.sendClientToolsRegister(),
|
|
644
|
+
onClientToolCall: (t) =>
|
|
645
|
+
j(
|
|
646
|
+
(r) => this.rws?.send(JSON.stringify(r)),
|
|
647
|
+
this.args.options.clientTools ?? {},
|
|
648
|
+
t,
|
|
649
|
+
),
|
|
650
|
+
})
|
|
651
|
+
: (this.marks.markFirstAudibleOutput(), this.playback?.enqueue(e.data))
|
|
652
|
+
break
|
|
653
|
+
case 'close':
|
|
654
|
+
if (e.permanent) {
|
|
655
|
+
let t = this.proto.endReason ?? (this.lastError ? 'error' : 'user_hangup')
|
|
656
|
+
this.teardown(t)
|
|
657
|
+
}
|
|
658
|
+
break
|
|
659
|
+
case 'error':
|
|
660
|
+
this.emitError({ code: 'socket_error', message: e.error.message })
|
|
661
|
+
break
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
this.startCapture = async () => {
|
|
665
|
+
if (!this.capture?.isCapturing()) {
|
|
666
|
+
;((this.capture = P({
|
|
667
|
+
onChunk: (e) => {
|
|
668
|
+
;(this.marks.markFirstOutboundAudio(), this.rws?.send(e))
|
|
669
|
+
},
|
|
670
|
+
onVolume: (e) => {
|
|
671
|
+
;((this.inputVolume = e),
|
|
672
|
+
this.args.options.onVolume?.({ input: e, output: this.outputVolume }))
|
|
673
|
+
},
|
|
674
|
+
onError: (e) => {
|
|
675
|
+
this.emitError({
|
|
676
|
+
code: e.name === 'NotAllowedError' ? 'mic_denied' : 'mic_start_failed',
|
|
677
|
+
message: e.message,
|
|
678
|
+
})
|
|
679
|
+
},
|
|
680
|
+
})),
|
|
681
|
+
this.muted && this.capture.mute(!0))
|
|
682
|
+
try {
|
|
683
|
+
await this.capture.start()
|
|
684
|
+
} catch {}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
this.teardown = (e) => {
|
|
688
|
+
try {
|
|
689
|
+
this.marks.flush()
|
|
690
|
+
} catch {}
|
|
691
|
+
;(this.capture?.stop(),
|
|
692
|
+
(this.capture = null),
|
|
693
|
+
this.playback?.close(),
|
|
694
|
+
(this.playback = null))
|
|
695
|
+
try {
|
|
696
|
+
this.rws?.close(1e3, e)
|
|
697
|
+
} catch {}
|
|
698
|
+
;((this.rws = null), this.setState('ended'), this.fireEndOnce(e))
|
|
699
|
+
}
|
|
700
|
+
this.fireEndOnce = (e) => {
|
|
701
|
+
if (this.endedFired) return
|
|
702
|
+
this.endedFired = !0
|
|
703
|
+
let t = this.startedAt ?? Date.now()
|
|
704
|
+
this.args.options.onEnd?.({
|
|
705
|
+
reason: e,
|
|
706
|
+
errorCode: e === 'error' ? this.lastError?.code : void 0,
|
|
707
|
+
durationMs: Date.now() - t,
|
|
708
|
+
})
|
|
709
|
+
}
|
|
710
|
+
;((this.args = e),
|
|
711
|
+
(this.proto = E()),
|
|
712
|
+
U(e.options.clientTools),
|
|
713
|
+
(this.marks = z({
|
|
714
|
+
send: (t) => {
|
|
715
|
+
try {
|
|
716
|
+
this.rws?.send(JSON.stringify(t))
|
|
717
|
+
} catch {}
|
|
718
|
+
},
|
|
719
|
+
})))
|
|
720
|
+
}
|
|
721
|
+
get state() {
|
|
722
|
+
return this.proto.state
|
|
723
|
+
}
|
|
724
|
+
get transcript() {
|
|
725
|
+
return this.proto.transcript.slice()
|
|
726
|
+
}
|
|
727
|
+
get isMuted() {
|
|
728
|
+
return this.muted
|
|
729
|
+
}
|
|
730
|
+
async start() {
|
|
731
|
+
;(this.setState('connecting'), (this.startedAt = Date.now()))
|
|
732
|
+
let e = $({
|
|
733
|
+
apiBase: this.args.config.apiBase,
|
|
734
|
+
agentId: this.args.options.agentId,
|
|
735
|
+
token: this.args.token,
|
|
736
|
+
bargeIn: this.args.options.bargeIn,
|
|
737
|
+
})
|
|
738
|
+
this.playback = N({
|
|
739
|
+
onVolume: (t) => {
|
|
740
|
+
;((this.outputVolume = t),
|
|
741
|
+
this.args.options.onVolume?.({ input: this.inputVolume, output: t }))
|
|
742
|
+
},
|
|
743
|
+
})
|
|
744
|
+
try {
|
|
745
|
+
await this.playback.resume()
|
|
746
|
+
} catch {}
|
|
747
|
+
this.rws = L({ url: e, wsFactory: this.args.wsFactory, maxRetries: 3 }, (t) =>
|
|
748
|
+
this.handleSocketEvent(t),
|
|
749
|
+
)
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
async function q(n) {
|
|
753
|
+
let e = E(),
|
|
754
|
+
t = !1,
|
|
755
|
+
r = !1,
|
|
756
|
+
o = (i) => {
|
|
757
|
+
e.state !== i && ((e.state = i), n.onStateChange?.(i))
|
|
758
|
+
},
|
|
759
|
+
a = (i) => {
|
|
760
|
+
x(i, e, {
|
|
761
|
+
onState: o,
|
|
762
|
+
onTranscript: (u) => n.onTranscript?.(u),
|
|
763
|
+
onError: (u) => n.onError?.(u),
|
|
764
|
+
onInterrupt: () => n.onInterrupt?.(),
|
|
765
|
+
onAgentTurnStart: () => n.onAgentTurnStart?.(),
|
|
766
|
+
onAgentTurnEnd: () => {},
|
|
767
|
+
onCallEnd: () => b(),
|
|
768
|
+
onConnected: () => {},
|
|
769
|
+
onClientToolCall: () => {},
|
|
770
|
+
})
|
|
771
|
+
}
|
|
772
|
+
o('connecting')
|
|
773
|
+
let s = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }),
|
|
774
|
+
l = document.createElement('audio')
|
|
775
|
+
;((l.autoplay = !0),
|
|
776
|
+
(l.style.display = 'none'),
|
|
777
|
+
document.body.appendChild(l),
|
|
778
|
+
(s.ontrack = (i) => {
|
|
779
|
+
l.srcObject = i.streams[0] ?? new MediaStream([i.track])
|
|
780
|
+
}))
|
|
781
|
+
let f
|
|
782
|
+
try {
|
|
783
|
+
f = await navigator.mediaDevices.getUserMedia({ audio: !0 })
|
|
784
|
+
} catch (i) {
|
|
785
|
+
let u =
|
|
786
|
+
i instanceof DOMException && i.name === 'NotAllowedError'
|
|
787
|
+
? 'mic_denied'
|
|
788
|
+
: 'mic_start_failed'
|
|
789
|
+
throw (
|
|
790
|
+
n.onError?.({ code: u, message: i instanceof Error ? i.message : 'getUserMedia failed' }),
|
|
791
|
+
o('error'),
|
|
792
|
+
s.close(),
|
|
793
|
+
l.remove(),
|
|
794
|
+
i
|
|
795
|
+
)
|
|
796
|
+
}
|
|
797
|
+
for (let i of f.getAudioTracks()) s.addTrack(i, f)
|
|
798
|
+
let m = s.createDataChannel('control', { ordered: !0 })
|
|
799
|
+
;((m.onmessage = (i) => {
|
|
800
|
+
typeof i.data == 'string' && a(i.data)
|
|
801
|
+
}),
|
|
802
|
+
(m.onerror = () => {
|
|
803
|
+
n.onError?.({ code: 'socket_error', message: 'control channel error' })
|
|
804
|
+
}))
|
|
805
|
+
let c = n.webrtcGatewayBase || '',
|
|
806
|
+
d = c
|
|
807
|
+
? `${c}/webrtc/offer?token=${encodeURIComponent(n.token)}`
|
|
808
|
+
: `${n.apiBase}/v1/agents/${encodeURIComponent(n.agentId)}/webrtc/offer?token=${encodeURIComponent(n.token)}`,
|
|
809
|
+
p = c
|
|
810
|
+
? `${c}/webrtc/ice?token=${encodeURIComponent(n.token)}`
|
|
811
|
+
: `${n.apiBase}/v1/agents/${encodeURIComponent(n.agentId)}/webrtc/ice?token=${encodeURIComponent(n.token)}`
|
|
812
|
+
await s.setLocalDescription(await s.createOffer())
|
|
813
|
+
let g
|
|
814
|
+
try {
|
|
815
|
+
let i = await fetch(d, {
|
|
816
|
+
method: 'POST',
|
|
817
|
+
headers: { 'content-type': 'application/json' },
|
|
818
|
+
body: JSON.stringify({ sdp: s.localDescription.sdp, type: 'offer', agentId: n.agentId }),
|
|
819
|
+
})
|
|
820
|
+
if (!i.ok) {
|
|
821
|
+
let h = i.status === 401 ? 'unauthorized' : 'server_error'
|
|
822
|
+
throw (
|
|
823
|
+
n.onError?.({ code: h, message: `signaling failed: HTTP ${i.status}` }),
|
|
824
|
+
o('error'),
|
|
825
|
+
f.getTracks().forEach((C) => C.stop()),
|
|
826
|
+
s.close(),
|
|
827
|
+
l.remove(),
|
|
828
|
+
new Error(`webrtc offer failed: ${i.status}`)
|
|
829
|
+
)
|
|
830
|
+
}
|
|
831
|
+
let u = await i.json()
|
|
832
|
+
;((g = u.callId), await s.setRemoteDescription({ type: 'answer', sdp: u.sdp }))
|
|
833
|
+
} catch (i) {
|
|
834
|
+
throw (
|
|
835
|
+
r ||
|
|
836
|
+
(n.onError?.({
|
|
837
|
+
code: 'network_unreachable',
|
|
838
|
+
message: i instanceof Error ? i.message : 'signaling failed',
|
|
839
|
+
}),
|
|
840
|
+
o('error'),
|
|
841
|
+
f.getTracks().forEach((u) => u.stop()),
|
|
842
|
+
s.close(),
|
|
843
|
+
l.remove()),
|
|
844
|
+
i
|
|
845
|
+
)
|
|
846
|
+
}
|
|
847
|
+
;((s.onicecandidate = (i) => {
|
|
848
|
+
i.candidate &&
|
|
849
|
+
fetch(p, {
|
|
850
|
+
method: 'POST',
|
|
851
|
+
headers: { 'content-type': 'application/json' },
|
|
852
|
+
body: JSON.stringify({ callId: g, candidate: i.candidate }),
|
|
853
|
+
}).catch(() => {})
|
|
854
|
+
}),
|
|
855
|
+
(s.onconnectionstatechange = () => {
|
|
856
|
+
let i = s.connectionState
|
|
857
|
+
;(i === 'connected' && o('listening'),
|
|
858
|
+
(i === 'failed' || i === 'disconnected') &&
|
|
859
|
+
(n.onError?.({ code: 'socket_error', message: `webrtc connection ${i}` }), b()),
|
|
860
|
+
i === 'closed' && !r && b())
|
|
861
|
+
}))
|
|
862
|
+
let b = () => {
|
|
863
|
+
if (!r) {
|
|
864
|
+
r = !0
|
|
865
|
+
try {
|
|
866
|
+
f.getTracks().forEach((i) => i.stop())
|
|
867
|
+
} catch {}
|
|
868
|
+
try {
|
|
869
|
+
s.close()
|
|
870
|
+
} catch {}
|
|
871
|
+
try {
|
|
872
|
+
l.remove()
|
|
873
|
+
} catch {}
|
|
874
|
+
;(o('ended'), n.onEnd?.())
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return {
|
|
878
|
+
get state() {
|
|
879
|
+
return e.state
|
|
880
|
+
},
|
|
881
|
+
get transcript() {
|
|
882
|
+
return e.transcript.slice()
|
|
883
|
+
},
|
|
884
|
+
get isMuted() {
|
|
885
|
+
return t
|
|
886
|
+
},
|
|
887
|
+
end: () => b(),
|
|
888
|
+
mute: () => {
|
|
889
|
+
t || ((t = !0), f.getAudioTracks().forEach((i) => (i.enabled = !1)))
|
|
890
|
+
},
|
|
891
|
+
unmute: () => {
|
|
892
|
+
t && ((t = !1), f.getAudioTracks().forEach((i) => (i.enabled = !0)))
|
|
893
|
+
},
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
var te = (n) => new globalThis.WebSocket(n),
|
|
897
|
+
M = class {
|
|
898
|
+
constructor(e) {
|
|
899
|
+
this.startCall = async (e) => {
|
|
900
|
+
if (!e.agentId) throw new Error('startCall: agentId is required')
|
|
901
|
+
let { context: t, metadata: r } = V(this.config, e),
|
|
902
|
+
o = { agentId: e.agentId, userId: e.userId, context: t, metadata: r },
|
|
903
|
+
a
|
|
904
|
+
if (e.token) a = { token: e.token, transport: 'ws' }
|
|
905
|
+
else {
|
|
906
|
+
let l = await this.config.fetchToken(o)
|
|
907
|
+
if (!l) throw new Error('configureVoiceClient.fetchToken returned empty token')
|
|
908
|
+
if (((a = typeof l == 'string' ? { token: l, transport: 'ws' } : l), !a.token))
|
|
909
|
+
throw new Error('configureVoiceClient.fetchToken returned an object without `token`')
|
|
910
|
+
}
|
|
911
|
+
if (a.transport === 'webrtc')
|
|
912
|
+
return q({
|
|
913
|
+
agentId: e.agentId,
|
|
914
|
+
apiBase: this.config.apiBase,
|
|
915
|
+
token: a.token,
|
|
916
|
+
webrtcGatewayBase: a.webrtcGatewayBase,
|
|
917
|
+
onStateChange: e.onStateChange,
|
|
918
|
+
onTranscript: e.onTranscript,
|
|
919
|
+
onError: e.onError,
|
|
920
|
+
onEnd: e.onEnd ? () => e.onEnd({ reason: 'agent_ended', durationMs: 0 }) : void 0,
|
|
921
|
+
onInterrupt: e.onInterrupt,
|
|
922
|
+
onAgentTurnStart: e.onAgentTurnStart,
|
|
923
|
+
})
|
|
924
|
+
let s = new v({
|
|
925
|
+
config: this.config,
|
|
926
|
+
options: { ...e, context: t, metadata: r },
|
|
927
|
+
token: a.token,
|
|
928
|
+
wsFactory: te,
|
|
929
|
+
})
|
|
930
|
+
return (await s.start(), s)
|
|
931
|
+
}
|
|
932
|
+
this.config = e
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
function G(n) {
|
|
936
|
+
return new M(B(n))
|
|
937
|
+
}
|
|
938
|
+
var H = 'voice-agent-embed-root',
|
|
939
|
+
re =
|
|
940
|
+
'M12 14a3 3 0 0 0 3-3V5a3 3 0 0 0-6 0v6a3 3 0 0 0 3 3zm5.3-3a.7.7 0 0 1 1.4 0 6.7 6.7 0 0 1-6 6.66V21h-1.4v-3.34A6.7 6.7 0 0 1 5.3 11a.7.7 0 0 1 1.4 0 5.3 5.3 0 0 0 10.6 0z',
|
|
941
|
+
R = 'http://www.w3.org/2000/svg',
|
|
942
|
+
J = (n) => {
|
|
943
|
+
let e = document.createElementNS(R, 'svg')
|
|
944
|
+
if ((e.setAttribute('viewBox', '0 0 24 24'), e.setAttribute('class', 'icon'), n.rect)) {
|
|
945
|
+
let t = document.createElementNS(R, 'rect')
|
|
946
|
+
;(t.setAttribute('x', '7'),
|
|
947
|
+
t.setAttribute('y', '7'),
|
|
948
|
+
t.setAttribute('width', '10'),
|
|
949
|
+
t.setAttribute('height', '10'),
|
|
950
|
+
t.setAttribute('rx', '1.5'),
|
|
951
|
+
e.appendChild(t))
|
|
952
|
+
} else if (n.path) {
|
|
953
|
+
let t = document.createElementNS(R, 'path')
|
|
954
|
+
;(t.setAttribute('d', n.path), e.appendChild(t))
|
|
955
|
+
}
|
|
956
|
+
return e
|
|
957
|
+
},
|
|
958
|
+
ne = (n) => {
|
|
959
|
+
try {
|
|
960
|
+
return JSON.parse(n)
|
|
961
|
+
} catch {
|
|
962
|
+
return
|
|
963
|
+
}
|
|
964
|
+
},
|
|
965
|
+
oe = (n) => {
|
|
966
|
+
if (n)
|
|
967
|
+
try {
|
|
968
|
+
return new URL(n.src, location.href).origin
|
|
969
|
+
} catch {}
|
|
970
|
+
return location.origin
|
|
971
|
+
},
|
|
972
|
+
ae = () => {
|
|
973
|
+
let n = document.querySelectorAll('script[src*="embed.js"]')
|
|
974
|
+
for (let e of Array.from(n)) {
|
|
975
|
+
let t = e.dataset
|
|
976
|
+
if (
|
|
977
|
+
(t.apiKey &&
|
|
978
|
+
console.error(
|
|
979
|
+
'[craftedxp/voice-js] data-api-key on the embed <script> is no longer supported. Mint a ct_ server-side via @craftedxp/sdk-node and inject it into data-token instead.',
|
|
980
|
+
),
|
|
981
|
+
t.agentId && t.token)
|
|
982
|
+
)
|
|
983
|
+
return {
|
|
984
|
+
apiBase: t.apiBase || oe(e),
|
|
985
|
+
token: t.token,
|
|
986
|
+
agentId: t.agentId,
|
|
987
|
+
vars: t.vars ? ne(t.vars) : void 0,
|
|
988
|
+
label: t.label || 'Call agent',
|
|
989
|
+
primaryColor: t.primaryColor || '#3d7dd0',
|
|
990
|
+
position: t.position === 'bottom-left' ? 'bottom-left' : 'bottom-right',
|
|
991
|
+
transport: t.transport || void 0,
|
|
992
|
+
webrtcGatewayBase: t.webrtcGatewayBase || void 0,
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return null
|
|
996
|
+
},
|
|
997
|
+
I = class {
|
|
998
|
+
constructor(e) {
|
|
999
|
+
this.call = null
|
|
1000
|
+
;((this.cfg = e),
|
|
1001
|
+
document.getElementById(H)?.remove(),
|
|
1002
|
+
(this.host = document.createElement('div')),
|
|
1003
|
+
(this.host.id = H),
|
|
1004
|
+
document.body.appendChild(this.host),
|
|
1005
|
+
(this.shadow = this.host.attachShadow({ mode: 'open' })),
|
|
1006
|
+
this.mount())
|
|
1007
|
+
}
|
|
1008
|
+
mount() {
|
|
1009
|
+
let e = document.createElement('style')
|
|
1010
|
+
;((e.textContent = this.css()), this.shadow.appendChild(e))
|
|
1011
|
+
let t = document.createElement('div')
|
|
1012
|
+
;((t.className = `wrap ${this.cfg.position ?? 'bottom-right'}`),
|
|
1013
|
+
(this.panel = document.createElement('div')),
|
|
1014
|
+
(this.panel.className = 'panel'),
|
|
1015
|
+
(this.panel.style.display = 'none'))
|
|
1016
|
+
let r = document.createElement('div')
|
|
1017
|
+
r.className = 'panel-head'
|
|
1018
|
+
let o = document.createElement('div')
|
|
1019
|
+
;((o.className = 'title'),
|
|
1020
|
+
(o.textContent = this.cfg.label ?? 'Call agent'),
|
|
1021
|
+
(this.statusEl = document.createElement('div')),
|
|
1022
|
+
(this.statusEl.className = 'status'),
|
|
1023
|
+
(this.statusEl.textContent = 'idle'),
|
|
1024
|
+
r.appendChild(o),
|
|
1025
|
+
r.appendChild(this.statusEl),
|
|
1026
|
+
(this.transcriptEl = document.createElement('div')),
|
|
1027
|
+
(this.transcriptEl.className = 'transcript'),
|
|
1028
|
+
this.panel.appendChild(r),
|
|
1029
|
+
this.panel.appendChild(this.transcriptEl),
|
|
1030
|
+
t.appendChild(this.panel),
|
|
1031
|
+
(this.fab = document.createElement('button')),
|
|
1032
|
+
(this.fab.className = 'fab'),
|
|
1033
|
+
(this.fab.type = 'button'),
|
|
1034
|
+
this.setFabState('idle'),
|
|
1035
|
+
this.fab.addEventListener('click', () => this.toggle()),
|
|
1036
|
+
t.appendChild(this.fab),
|
|
1037
|
+
this.shadow.appendChild(t),
|
|
1038
|
+
(!this.cfg.agentId || !this.cfg.token) &&
|
|
1039
|
+
(this.setStatus(
|
|
1040
|
+
'Widget not configured \u2014 data-token + data-agent-id are required',
|
|
1041
|
+
'warn',
|
|
1042
|
+
),
|
|
1043
|
+
(this.fab.disabled = !0),
|
|
1044
|
+
(this.fab.title = 'Missing data-token or data-agent-id')))
|
|
1045
|
+
}
|
|
1046
|
+
destroy() {
|
|
1047
|
+
;(this.call?.end(), (this.call = null), this.host.remove())
|
|
1048
|
+
}
|
|
1049
|
+
css() {
|
|
1050
|
+
return `
|
|
83
1051
|
:host, * { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
|
84
1052
|
.wrap { position: fixed; z-index: 2147483000; display: flex; flex-direction: column; align-items: flex-end; gap: 10px; }
|
|
85
1053
|
.wrap.bottom-right { bottom: 24px; right: 24px; }
|
|
86
1054
|
.wrap.bottom-left { bottom: 24px; left: 24px; align-items: flex-start; }
|
|
87
1055
|
.fab {
|
|
88
1056
|
width: 58px; height: 58px; border-radius: 50%;
|
|
89
|
-
background: ${this.cfg.primaryColor??
|
|
1057
|
+
background: ${this.cfg.primaryColor ?? '#3d7dd0'}; color: white; border: 0;
|
|
90
1058
|
cursor: pointer; box-shadow: 0 6px 18px rgba(0,0,0,0.25);
|
|
91
1059
|
display: flex; align-items: center; justify-content: center;
|
|
92
1060
|
transition: transform 0.15s ease, background 0.2s ease;
|
|
@@ -122,4 +1090,126 @@ registerProcessor('mic-downsampler', MicDownsampler)
|
|
|
122
1090
|
.line.user.interim { opacity: 0.55; font-style: italic; }
|
|
123
1091
|
.line.agent.interrupted { opacity: 0.7; }
|
|
124
1092
|
.line.agent.interrupted::after { content: ' (interrupted)'; color: #e8b660; font-style: italic; font-size: 11px; }
|
|
125
|
-
`
|
|
1093
|
+
`
|
|
1094
|
+
}
|
|
1095
|
+
setStatus(e, t = '') {
|
|
1096
|
+
;((this.statusEl.className = `status ${t}`.trim()), (this.statusEl.textContent = e))
|
|
1097
|
+
}
|
|
1098
|
+
setFabState(e) {
|
|
1099
|
+
for (
|
|
1100
|
+
this.fab.classList.remove('live', 'connecting'),
|
|
1101
|
+
e === 'live'
|
|
1102
|
+
? this.fab.classList.add('live')
|
|
1103
|
+
: e === 'connecting' && this.fab.classList.add('connecting');
|
|
1104
|
+
this.fab.firstChild;
|
|
1105
|
+
)
|
|
1106
|
+
this.fab.removeChild(this.fab.firstChild)
|
|
1107
|
+
;(this.fab.appendChild(J(e === 'live' ? { rect: !0 } : { path: re })),
|
|
1108
|
+
this.fab.setAttribute('aria-label', e === 'live' ? 'End call' : 'Start call'))
|
|
1109
|
+
}
|
|
1110
|
+
async toggle() {
|
|
1111
|
+
if (this.call) {
|
|
1112
|
+
this.call.end()
|
|
1113
|
+
return
|
|
1114
|
+
}
|
|
1115
|
+
if (!this.cfg.token) {
|
|
1116
|
+
this.setStatus('data-token="ct_..." is required', 'err')
|
|
1117
|
+
return
|
|
1118
|
+
}
|
|
1119
|
+
;((this.panel.style.display = 'flex'),
|
|
1120
|
+
this.setFabState('connecting'),
|
|
1121
|
+
this.setStatus('connecting\u2026'))
|
|
1122
|
+
let e = G({
|
|
1123
|
+
apiBase: this.cfg.apiBase ?? location.origin,
|
|
1124
|
+
fetchToken: async () => ({
|
|
1125
|
+
token: this.cfg.token,
|
|
1126
|
+
transport: this.cfg.transport ?? 'ws',
|
|
1127
|
+
webrtcGatewayBase: this.cfg.webrtcGatewayBase,
|
|
1128
|
+
}),
|
|
1129
|
+
})
|
|
1130
|
+
try {
|
|
1131
|
+
let t = await e.startCall({
|
|
1132
|
+
agentId: this.cfg.agentId,
|
|
1133
|
+
context: this.cfg.vars,
|
|
1134
|
+
onStateChange: (r) => this.onState(r),
|
|
1135
|
+
onTranscript: (r) => this.renderTranscript(r),
|
|
1136
|
+
onError: (r) => {
|
|
1137
|
+
this.setStatus(`${r.code}: ${r.message}`, 'err')
|
|
1138
|
+
},
|
|
1139
|
+
onEnd: () => {
|
|
1140
|
+
;(this.setFabState('idle'), (this.call = null))
|
|
1141
|
+
},
|
|
1142
|
+
})
|
|
1143
|
+
this.call = t
|
|
1144
|
+
} catch (t) {
|
|
1145
|
+
;(this.setStatus(t instanceof Error ? t.message : 'connect failed', 'err'),
|
|
1146
|
+
this.setFabState('idle'),
|
|
1147
|
+
(this.call = null))
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
onState(e) {
|
|
1151
|
+
switch (e) {
|
|
1152
|
+
case 'connecting':
|
|
1153
|
+
;(this.setFabState('connecting'), this.setStatus('connecting\u2026'))
|
|
1154
|
+
break
|
|
1155
|
+
case 'listening':
|
|
1156
|
+
;(this.setFabState('live'), this.setStatus('listening', 'ok'))
|
|
1157
|
+
break
|
|
1158
|
+
case 'user_speaking':
|
|
1159
|
+
this.setStatus('you: \u2026speaking', 'ok')
|
|
1160
|
+
break
|
|
1161
|
+
case 'agent_speaking':
|
|
1162
|
+
this.setStatus('agent speaking', 'ok')
|
|
1163
|
+
break
|
|
1164
|
+
case 'ended':
|
|
1165
|
+
;(this.setFabState('idle'), this.setStatus('ended'))
|
|
1166
|
+
break
|
|
1167
|
+
case 'error':
|
|
1168
|
+
this.setFabState('idle')
|
|
1169
|
+
break
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
renderTranscript(e) {
|
|
1173
|
+
for (; this.transcriptEl.firstChild; )
|
|
1174
|
+
this.transcriptEl.removeChild(this.transcriptEl.firstChild)
|
|
1175
|
+
for (let t of e) {
|
|
1176
|
+
let r = document.createElement('div')
|
|
1177
|
+
if (((r.className = 'line'), t.role === 'user')) {
|
|
1178
|
+
;(r.classList.add('user'), t.committed || r.classList.add('interim'))
|
|
1179
|
+
let o = document.createElement('span')
|
|
1180
|
+
;((o.className = 'label'),
|
|
1181
|
+
(o.textContent = 'You:'),
|
|
1182
|
+
r.appendChild(o),
|
|
1183
|
+
r.append(' ' + t.text))
|
|
1184
|
+
} else if (t.role === 'agent') {
|
|
1185
|
+
;(r.classList.add('agent'), t.interrupted && r.classList.add('interrupted'))
|
|
1186
|
+
let o = document.createElement('span')
|
|
1187
|
+
;((o.className = 'label'),
|
|
1188
|
+
(o.textContent = 'Agent:'),
|
|
1189
|
+
r.appendChild(o),
|
|
1190
|
+
r.append(' ' + t.text))
|
|
1191
|
+
} else (t.role, r.classList.add('sys'), (r.textContent = t.text))
|
|
1192
|
+
this.transcriptEl.appendChild(r)
|
|
1193
|
+
}
|
|
1194
|
+
this.transcriptEl.scrollTop = this.transcriptEl.scrollHeight
|
|
1195
|
+
}
|
|
1196
|
+
},
|
|
1197
|
+
T = null,
|
|
1198
|
+
O = (n) => {
|
|
1199
|
+
;(T?.destroy(), (T = new I(n)))
|
|
1200
|
+
},
|
|
1201
|
+
ie = () => {
|
|
1202
|
+
;(T?.destroy(), (T = null))
|
|
1203
|
+
}
|
|
1204
|
+
window.PlatformWidget = { init: O, destroy: ie }
|
|
1205
|
+
var _ = ae()
|
|
1206
|
+
if (_) {
|
|
1207
|
+
let n = document.querySelectorAll('script[src*="embed.js"]'),
|
|
1208
|
+
e = !0
|
|
1209
|
+
for (let t of Array.from(n)) t.dataset.autoInit === 'false' && (e = !1)
|
|
1210
|
+
e &&
|
|
1211
|
+
(document.readyState !== 'loading'
|
|
1212
|
+
? O(_)
|
|
1213
|
+
: document.addEventListener('DOMContentLoaded', () => O(_)))
|
|
1214
|
+
}
|
|
1215
|
+
})()
|