@glydeunity/voice-sdk 1.2.2 → 1.3.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/dist/voice-sdk.es.js +111 -49
- package/dist/voice-sdk.umd.js +44 -4
- package/package.json +1 -1
package/dist/voice-sdk.es.js
CHANGED
|
@@ -174,16 +174,16 @@ class g {
|
|
|
174
174
|
* @returns Voice configuration including system prompt, tools, and Deepgram settings
|
|
175
175
|
*/
|
|
176
176
|
async fetchConfig() {
|
|
177
|
-
const e = `${this.unityUrl}/api/unity/voice/config/${this.config.contextType}`, t = this.config.contextId ? `${e}/${this.config.contextId}` : e,
|
|
177
|
+
const e = `${this.unityUrl}/api/unity/voice/config/${this.config.contextType}`, t = this.config.contextId ? `${e}/${this.config.contextId}` : e, i = await fetch(t, {
|
|
178
178
|
method: "GET",
|
|
179
179
|
headers: this.getAuthHeaders()
|
|
180
180
|
});
|
|
181
|
-
if (!
|
|
182
|
-
const
|
|
183
|
-
throw new Error(
|
|
181
|
+
if (!i.ok) {
|
|
182
|
+
const o = await i.json();
|
|
183
|
+
throw new Error(o.error?.message || o.message || "Failed to fetch voice config");
|
|
184
184
|
}
|
|
185
|
-
const { data:
|
|
186
|
-
return
|
|
185
|
+
const { data: a } = await i.json();
|
|
186
|
+
return a;
|
|
187
187
|
}
|
|
188
188
|
/**
|
|
189
189
|
* Initialize and start the voice session
|
|
@@ -204,14 +204,14 @@ class g {
|
|
|
204
204
|
body: JSON.stringify(e)
|
|
205
205
|
});
|
|
206
206
|
if (!t.ok) {
|
|
207
|
-
const
|
|
208
|
-
throw new Error(
|
|
207
|
+
const s = await t.json();
|
|
208
|
+
throw new Error(s.error?.message || s.message || "Failed to authenticate voice session");
|
|
209
209
|
}
|
|
210
|
-
const { data:
|
|
210
|
+
const { data: i } = await t.json(), { token: a, agent_config: o, deepgram_config: r } = i, h = this.config.systemPrompt || o.instructions || this.serverConfig?.system_prompt || "You are a helpful AI assistant.";
|
|
211
211
|
await this.initializeAudio();
|
|
212
|
-
const
|
|
213
|
-
this.ws = new WebSocket(
|
|
214
|
-
const
|
|
212
|
+
const p = "wss://agent.deepgram.com/v1/agent/converse";
|
|
213
|
+
this.ws = new WebSocket(p, ["bearer", a]), this.ws.onopen = () => {
|
|
214
|
+
const s = this.config.deepgramConfig || r || this.serverConfig?.deepgram_config || {
|
|
215
215
|
think: {
|
|
216
216
|
provider: { type: "open_ai", model: "gpt-4.1-mini" },
|
|
217
217
|
functions: [
|
|
@@ -241,12 +241,43 @@ End the conversation immediately if:
|
|
|
241
241
|
},
|
|
242
242
|
required: ["item"]
|
|
243
243
|
}
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: "other_opportunities",
|
|
247
|
+
description: `You are an AI assistant that monitors conversations to identify whether the candidate should be informed about other job opportunities.
|
|
248
|
+
|
|
249
|
+
If the candidate appears to be a poor fit for any of the must have requirements, gently suggest that there are other job opportunities with the company and you could inform about other roles if they are interested. If they are not interested, you should continue with the conversation about the current role.
|
|
250
|
+
|
|
251
|
+
Here is a list of phrases to listen for but not restricted to:
|
|
252
|
+
-other opportunities
|
|
253
|
+
-other jobs
|
|
254
|
+
-other roles
|
|
255
|
+
-other job opportunities
|
|
256
|
+
-other job roles
|
|
257
|
+
-other job opportunities
|
|
258
|
+
|
|
259
|
+
When monitoring the conversation, pay close attention to any input that matches or closely resembles the phrases listed above. The matching should be case-insensitive and allow for minor variations or typos. Additionally monitor for input that suggests the candidate does not meet the criteria for the current role or if they'd like to know about urgent or immediate opportunities.
|
|
260
|
+
|
|
261
|
+
Suggest other opportunities if:
|
|
262
|
+
1. The user's input exactly matches any phrase in the list.
|
|
263
|
+
2. The user's input is a close variation of any phrase in the list (e.g., "please other opportunities" instead of "other opportunities").
|
|
264
|
+
3. The user's input clearly expresses a desire to end the conversation, even if it doesn't use the exact phrases listed.
|
|
265
|
+
4. The user's input clearly expresses a desire to know about urgent or immediate opportunities.
|
|
266
|
+
|
|
267
|
+
If the candidate is interested in other opportunities, you should call a GLYDE Unity MCP tool to identify other job openings.`,
|
|
268
|
+
parameters: {
|
|
269
|
+
type: "object",
|
|
270
|
+
properties: {
|
|
271
|
+
item: { type: "string", description: "The phrase or text that triggered the suggestion of other opportunities" }
|
|
272
|
+
},
|
|
273
|
+
required: ["item"]
|
|
274
|
+
}
|
|
244
275
|
}
|
|
245
276
|
]
|
|
246
277
|
},
|
|
247
278
|
speak: { provider: { type: "deepgram", model: "aura-2-thalia-en" } },
|
|
248
279
|
listen: { provider: { type: "deepgram", version: "v2", model: "flux-general-en" } }
|
|
249
|
-
},
|
|
280
|
+
}, l = {
|
|
250
281
|
type: "Settings",
|
|
251
282
|
audio: {
|
|
252
283
|
input: {
|
|
@@ -261,15 +292,15 @@ End the conversation immediately if:
|
|
|
261
292
|
},
|
|
262
293
|
agent: {
|
|
263
294
|
language: "en",
|
|
264
|
-
speak:
|
|
295
|
+
speak: s.speak || {
|
|
265
296
|
provider: { type: "deepgram", model: "aura-2-thalia-en" }
|
|
266
297
|
},
|
|
267
|
-
listen:
|
|
298
|
+
listen: s.listen || {
|
|
268
299
|
provider: { type: "deepgram", version: "v2", model: "flux-general-en" }
|
|
269
300
|
},
|
|
270
301
|
think: {
|
|
271
|
-
provider:
|
|
272
|
-
functions:
|
|
302
|
+
provider: s.think?.provider || { type: "open_ai", model: "gpt-4.1-mini" },
|
|
303
|
+
functions: s.think?.functions || [
|
|
273
304
|
{
|
|
274
305
|
name: "end_conversation",
|
|
275
306
|
description: `You are an AI assistant that monitors conversations and ends them when specific stop phrases are detected.
|
|
@@ -296,31 +327,62 @@ End the conversation immediately if:
|
|
|
296
327
|
},
|
|
297
328
|
required: ["item"]
|
|
298
329
|
}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "other_opportunities",
|
|
333
|
+
description: `You are an AI assistant that monitors conversations to identify whether the candidate should be informed about other job opportunities.
|
|
334
|
+
|
|
335
|
+
If the candidate appears to be a poor fit for any of the must have requirements, gently suggest that there are other job opportunities with the company and you could inform about other roles if they are interested. If they are not interested, you should continue with the conversation about the current role.
|
|
336
|
+
|
|
337
|
+
Here is a list of phrases to listen for but not restricted to:
|
|
338
|
+
-other opportunities
|
|
339
|
+
-other jobs
|
|
340
|
+
-other roles
|
|
341
|
+
-other job opportunities
|
|
342
|
+
-other job roles
|
|
343
|
+
-other job opportunities
|
|
344
|
+
|
|
345
|
+
When monitoring the conversation, pay close attention to any input that matches or closely resembles the phrases listed above. The matching should be case-insensitive and allow for minor variations or typos. Additionally monitor for input that suggests the candidate does not meet the criteria for the current role or if they'd like to know about urgent or immediate opportunities.
|
|
346
|
+
|
|
347
|
+
Suggest other opportunities if:
|
|
348
|
+
1. The user's input exactly matches any phrase in the list.
|
|
349
|
+
2. The user's input is a close variation of any phrase in the list (e.g., "please other opportunities" instead of "other opportunities").
|
|
350
|
+
3. The user's input clearly expresses a desire to end the conversation, even if it doesn't use the exact phrases listed.
|
|
351
|
+
4. The user's input clearly expresses a desire to know about urgent or immediate opportunities.
|
|
352
|
+
|
|
353
|
+
If the candidate is interested in other opportunities, you should call a GLYDE Unity MCP tool to identify other job openings.`,
|
|
354
|
+
parameters: {
|
|
355
|
+
type: "object",
|
|
356
|
+
properties: {
|
|
357
|
+
item: { type: "string", description: "The phrase or text that triggered the suggestion of other opportunities" }
|
|
358
|
+
},
|
|
359
|
+
required: ["item"]
|
|
360
|
+
}
|
|
299
361
|
}
|
|
300
362
|
]
|
|
301
363
|
},
|
|
302
|
-
greeting: "Hi! I'm
|
|
364
|
+
greeting: "Hi! I'm excited you chose to speak with me. Are you ready to start?"
|
|
303
365
|
}
|
|
304
366
|
};
|
|
305
|
-
this.ws.send(JSON.stringify(
|
|
367
|
+
this.ws.send(JSON.stringify(l)), this.emit({ type: "open", payload: { config: o, serverConfig: this.serverConfig } });
|
|
306
368
|
};
|
|
307
|
-
const n =
|
|
308
|
-
this.ws.onmessage = (
|
|
309
|
-
if (typeof
|
|
369
|
+
const n = h;
|
|
370
|
+
this.ws.onmessage = (s) => {
|
|
371
|
+
if (typeof s.data == "string") {
|
|
310
372
|
try {
|
|
311
|
-
if (JSON.parse(
|
|
312
|
-
const
|
|
373
|
+
if (JSON.parse(s.data).type === "SettingsApplied") {
|
|
374
|
+
const c = {
|
|
313
375
|
type: "UpdatePrompt",
|
|
314
376
|
prompt: n
|
|
315
377
|
};
|
|
316
|
-
this.ws.send(JSON.stringify(
|
|
378
|
+
this.ws.send(JSON.stringify(c)), this.startMicrophone();
|
|
317
379
|
}
|
|
318
380
|
} catch {
|
|
319
381
|
}
|
|
320
|
-
this.handleTextMessage(
|
|
321
|
-
} else
|
|
322
|
-
}, this.ws.onerror = (
|
|
323
|
-
console.error("[GlydeVoice] WebSocket error:",
|
|
382
|
+
this.handleTextMessage(s.data);
|
|
383
|
+
} else s.data instanceof Blob ? this.handleAudioData(s.data) : s.data instanceof ArrayBuffer && this.handleAudioBuffer(s.data);
|
|
384
|
+
}, this.ws.onerror = (s) => {
|
|
385
|
+
console.error("[GlydeVoice] WebSocket error:", s), this.emit({ type: "error", payload: s });
|
|
324
386
|
}, this.ws.onclose = () => {
|
|
325
387
|
this.cleanup(), this.emit({ type: "close" });
|
|
326
388
|
}, this.renderUI();
|
|
@@ -354,9 +416,9 @@ End the conversation immediately if:
|
|
|
354
416
|
} finally {
|
|
355
417
|
URL.revokeObjectURL(e), URL.revokeObjectURL(t);
|
|
356
418
|
}
|
|
357
|
-
this.playbackWorkletNode = new AudioWorkletNode(this.audioContext, "audio-playback-processor"), this.playbackWorkletNode.connect(this.audioContext.destination), this.playbackWorkletNode.port.onmessage = (
|
|
358
|
-
const { type:
|
|
359
|
-
(
|
|
419
|
+
this.playbackWorkletNode = new AudioWorkletNode(this.audioContext, "audio-playback-processor"), this.playbackWorkletNode.connect(this.audioContext.destination), this.playbackWorkletNode.port.onmessage = (i) => {
|
|
420
|
+
const { type: a } = i.data;
|
|
421
|
+
(a === "cleared" || a === "bufferEmpty") && (this.isAgentSpeaking = !1, this.agentAudioDoneReceived = !1, this.emit({ type: "agent_speaking", payload: !1 }));
|
|
360
422
|
};
|
|
361
423
|
}
|
|
362
424
|
/**
|
|
@@ -379,8 +441,8 @@ End the conversation immediately if:
|
|
|
379
441
|
break;
|
|
380
442
|
case "ConversationText":
|
|
381
443
|
if (t.content && t.content.trim()) {
|
|
382
|
-
const
|
|
383
|
-
this.config.onTranscript && this.config.onTranscript(t.content,
|
|
444
|
+
const i = t.role === "assistant" ? "agent" : "user";
|
|
445
|
+
this.config.onTranscript && this.config.onTranscript(t.content, i), this.emit({ type: "transcript", payload: { text: t.content, role: i } }), this.saveTranscript(t.content, t.role);
|
|
384
446
|
}
|
|
385
447
|
break;
|
|
386
448
|
case "AgentStartedSpeaking":
|
|
@@ -413,30 +475,30 @@ End the conversation immediately if:
|
|
|
413
475
|
this.audioContext.state === "suspended" && this.audioContext.resume();
|
|
414
476
|
const t = e.byteLength;
|
|
415
477
|
if (t === 0) return;
|
|
416
|
-
const
|
|
417
|
-
if (
|
|
418
|
-
const
|
|
419
|
-
for (let n = 0; n <
|
|
420
|
-
r[n] =
|
|
421
|
-
const
|
|
478
|
+
const i = t - t % 2;
|
|
479
|
+
if (i === 0) return;
|
|
480
|
+
const a = i === t ? e : e.slice(0, i), o = new Int16Array(a), r = new Float32Array(o.length);
|
|
481
|
+
for (let n = 0; n < o.length; n++)
|
|
482
|
+
r[n] = o[n] / 32768;
|
|
483
|
+
const h = this.resample24kTo48k(r);
|
|
422
484
|
!this.isAgentSpeaking && !this.agentAudioDoneReceived && (this.isAgentSpeaking = !0, this.emit({ type: "agent_speaking", payload: !0 }));
|
|
423
|
-
const
|
|
485
|
+
const p = new Float32Array(h);
|
|
424
486
|
this.playbackWorkletNode.port.postMessage({
|
|
425
487
|
type: "audio",
|
|
426
|
-
data:
|
|
427
|
-
}, [
|
|
488
|
+
data: p
|
|
489
|
+
}, [p.buffer]);
|
|
428
490
|
}
|
|
429
491
|
/**
|
|
430
492
|
* Resample audio from 24kHz to 48kHz using linear interpolation
|
|
431
493
|
*/
|
|
432
494
|
resample24kTo48k(e) {
|
|
433
|
-
const t = e.length * 2,
|
|
434
|
-
for (let
|
|
435
|
-
const r = e[
|
|
436
|
-
|
|
495
|
+
const t = e.length * 2, i = new Float32Array(t);
|
|
496
|
+
for (let o = 0; o < e.length - 1; o++) {
|
|
497
|
+
const r = e[o], h = e[o + 1];
|
|
498
|
+
i[o * 2] = r, i[o * 2 + 1] = (r + h) / 2;
|
|
437
499
|
}
|
|
438
|
-
const
|
|
439
|
-
return
|
|
500
|
+
const a = e.length - 1;
|
|
501
|
+
return i[a * 2] = e[a], i[a * 2 + 1] = e[a], i;
|
|
440
502
|
}
|
|
441
503
|
/**
|
|
442
504
|
* Clear the playback buffer (for interruption handling)
|
package/dist/voice-sdk.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(n,
|
|
1
|
+
(function(n,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(n=typeof globalThis<"u"?globalThis:n||self,l(n.GlydeVoice={}))})(this,(function(n){"use strict";const l=`
|
|
2
2
|
class AudioCaptureProcessor extends AudioWorkletProcessor {
|
|
3
3
|
constructor() {
|
|
4
4
|
super();
|
|
@@ -130,7 +130,7 @@ class AudioPlaybackProcessor extends AudioWorkletProcessor {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
registerProcessor('audio-playback-processor', AudioPlaybackProcessor);
|
|
133
|
-
`;class f{config;unityUrl;active=!1;serverConfig=null;ws=null;audioContext=null;mediaStream=null;captureWorkletNode=null;playbackWorkletNode=null;isMuted=!1;outputSampleRate=24e3;inputSampleRate=48e3;isAgentSpeaking=!1;agentAudioDoneReceived=!1;constructor(e){this.config=e,this.unityUrl=e.unityBaseUrl||"https://api.glydeunity.com",!e.publishableKey&&!e.apiKey&&!e.authToken&&console.warn("[GlydeVoice] No authentication method provided. One of publishableKey, apiKey, or authToken is required.")}getAuthHeaders(){const e={"Content-Type":"application/json"};return this.config.publishableKey&&(e["x-publishable-key"]=this.config.publishableKey),this.config.apiKey&&(e["x-api-key"]=this.config.apiKey),this.config.authToken&&(e.Authorization=`Bearer ${this.config.authToken}`),e}async fetchConfig(){const e=`${this.unityUrl}/api/unity/voice/config/${this.config.contextType}`,t=this.config.contextId?`${e}/${this.config.contextId}`:e,i=await fetch(t,{method:"GET",headers:this.getAuthHeaders()});if(!i.ok){const o=await i.json();throw new Error(o.error?.message||o.message||"Failed to fetch voice config")}const{data:a}=await i.json();return a}async start(){if(!this.active){this.active=!0;try{this.config.systemPrompt||(this.serverConfig=await this.fetchConfig(),console.log("[GlydeVoice] Fetched config:",this.serverConfig));const e={context_id:this.config.contextId,domain:typeof window<"u"?window.location.hostname:"localhost"};this.config.systemPrompt&&(e.system_prompt=this.config.systemPrompt),this.config.deepgramConfig&&(e.deepgram_config=this.config.deepgramConfig);const t=await fetch(`${this.unityUrl}/api/unity/voice/auth`,{method:"POST",headers:this.getAuthHeaders(),body:JSON.stringify(e)});if(!t.ok){const s=await t.json();throw new Error(s.error?.message||s.message||"Failed to authenticate voice session")}const{data:i}=await t.json(),{token:a,agent_config:o,deepgram_config:r}=i,
|
|
133
|
+
`;class f{config;unityUrl;active=!1;serverConfig=null;ws=null;audioContext=null;mediaStream=null;captureWorkletNode=null;playbackWorkletNode=null;isMuted=!1;outputSampleRate=24e3;inputSampleRate=48e3;isAgentSpeaking=!1;agentAudioDoneReceived=!1;constructor(e){this.config=e,this.unityUrl=e.unityBaseUrl||"https://api.glydeunity.com",!e.publishableKey&&!e.apiKey&&!e.authToken&&console.warn("[GlydeVoice] No authentication method provided. One of publishableKey, apiKey, or authToken is required.")}getAuthHeaders(){const e={"Content-Type":"application/json"};return this.config.publishableKey&&(e["x-publishable-key"]=this.config.publishableKey),this.config.apiKey&&(e["x-api-key"]=this.config.apiKey),this.config.authToken&&(e.Authorization=`Bearer ${this.config.authToken}`),e}async fetchConfig(){const e=`${this.unityUrl}/api/unity/voice/config/${this.config.contextType}`,t=this.config.contextId?`${e}/${this.config.contextId}`:e,i=await fetch(t,{method:"GET",headers:this.getAuthHeaders()});if(!i.ok){const o=await i.json();throw new Error(o.error?.message||o.message||"Failed to fetch voice config")}const{data:a}=await i.json();return a}async start(){if(!this.active){this.active=!0;try{this.config.systemPrompt||(this.serverConfig=await this.fetchConfig(),console.log("[GlydeVoice] Fetched config:",this.serverConfig));const e={context_id:this.config.contextId,domain:typeof window<"u"?window.location.hostname:"localhost"};this.config.systemPrompt&&(e.system_prompt=this.config.systemPrompt),this.config.deepgramConfig&&(e.deepgram_config=this.config.deepgramConfig);const t=await fetch(`${this.unityUrl}/api/unity/voice/auth`,{method:"POST",headers:this.getAuthHeaders(),body:JSON.stringify(e)});if(!t.ok){const s=await t.json();throw new Error(s.error?.message||s.message||"Failed to authenticate voice session")}const{data:i}=await t.json(),{token:a,agent_config:o,deepgram_config:r}=i,p=this.config.systemPrompt||o.instructions||this.serverConfig?.system_prompt||"You are a helpful AI assistant.";await this.initializeAudio();const c="wss://agent.deepgram.com/v1/agent/converse";this.ws=new WebSocket(c,["bearer",a]),this.ws.onopen=()=>{const s=this.config.deepgramConfig||r||this.serverConfig?.deepgram_config||{think:{provider:{type:"open_ai",model:"gpt-4.1-mini"},functions:[{name:"end_conversation",description:`You are an AI assistant that monitors conversations and ends them when specific stop phrases are detected.
|
|
134
134
|
|
|
135
135
|
Here is a list of phrases to listen for but not restricted to:
|
|
136
136
|
-stop
|
|
@@ -146,7 +146,27 @@ When monitoring the conversation, pay close attention to any input that matches
|
|
|
146
146
|
End the conversation immediately if:
|
|
147
147
|
1. The user's input exactly matches any phrase in the list.
|
|
148
148
|
2. The user's input is a close variation of any phrase in the list (e.g., "please shut up" instead of "shut up").
|
|
149
|
-
3. The user's input clearly expresses a desire to end the conversation, even if it doesn't use the exact phrases listed.`,parameters:{type:"object",properties:{item:{type:"string",description:"The phrase or text that triggered the end of conversation"}},required:["item"]}}
|
|
149
|
+
3. The user's input clearly expresses a desire to end the conversation, even if it doesn't use the exact phrases listed.`,parameters:{type:"object",properties:{item:{type:"string",description:"The phrase or text that triggered the end of conversation"}},required:["item"]}},{name:"other_opportunities",description:`You are an AI assistant that monitors conversations to identify whether the candidate should be informed about other job opportunities.
|
|
150
|
+
|
|
151
|
+
If the candidate appears to be a poor fit for any of the must have requirements, gently suggest that there are other job opportunities with the company and you could inform about other roles if they are interested. If they are not interested, you should continue with the conversation about the current role.
|
|
152
|
+
|
|
153
|
+
Here is a list of phrases to listen for but not restricted to:
|
|
154
|
+
-other opportunities
|
|
155
|
+
-other jobs
|
|
156
|
+
-other roles
|
|
157
|
+
-other job opportunities
|
|
158
|
+
-other job roles
|
|
159
|
+
-other job opportunities
|
|
160
|
+
|
|
161
|
+
When monitoring the conversation, pay close attention to any input that matches or closely resembles the phrases listed above. The matching should be case-insensitive and allow for minor variations or typos. Additionally monitor for input that suggests the candidate does not meet the criteria for the current role or if they'd like to know about urgent or immediate opportunities.
|
|
162
|
+
|
|
163
|
+
Suggest other opportunities if:
|
|
164
|
+
1. The user's input exactly matches any phrase in the list.
|
|
165
|
+
2. The user's input is a close variation of any phrase in the list (e.g., "please other opportunities" instead of "other opportunities").
|
|
166
|
+
3. The user's input clearly expresses a desire to end the conversation, even if it doesn't use the exact phrases listed.
|
|
167
|
+
4. The user's input clearly expresses a desire to know about urgent or immediate opportunities.
|
|
168
|
+
|
|
169
|
+
If the candidate is interested in other opportunities, you should call a GLYDE Unity MCP tool to identify other job openings.`,parameters:{type:"object",properties:{item:{type:"string",description:"The phrase or text that triggered the suggestion of other opportunities"}},required:["item"]}}]},speak:{provider:{type:"deepgram",model:"aura-2-thalia-en"}},listen:{provider:{type:"deepgram",version:"v2",model:"flux-general-en"}}},d={type:"Settings",audio:{input:{encoding:"linear16",sample_rate:this.inputSampleRate},output:{encoding:"linear16",sample_rate:this.outputSampleRate,container:"none"}},agent:{language:"en",speak:s.speak||{provider:{type:"deepgram",model:"aura-2-thalia-en"}},listen:s.listen||{provider:{type:"deepgram",version:"v2",model:"flux-general-en"}},think:{provider:s.think?.provider||{type:"open_ai",model:"gpt-4.1-mini"},functions:s.think?.functions||[{name:"end_conversation",description:`You are an AI assistant that monitors conversations and ends them when specific stop phrases are detected.
|
|
150
170
|
|
|
151
171
|
Here is a list of phrases to listen for but not restricted to:
|
|
152
172
|
-stop
|
|
@@ -162,7 +182,27 @@ When monitoring the conversation, pay close attention to any input that matches
|
|
|
162
182
|
End the conversation immediately if:
|
|
163
183
|
1. The user's input exactly matches any phrase in the list.
|
|
164
184
|
2. The user's input is a close variation of any phrase in the list (e.g., "please shut up" instead of "shut up").
|
|
165
|
-
3. The user's input clearly expresses a desire to end the conversation, even if it doesn't use the exact phrases listed.`,parameters:{type:"object",properties:{item:{type:"string",description:"The phrase or text that triggered the end of conversation"}},required:["item"]}}
|
|
185
|
+
3. The user's input clearly expresses a desire to end the conversation, even if it doesn't use the exact phrases listed.`,parameters:{type:"object",properties:{item:{type:"string",description:"The phrase or text that triggered the end of conversation"}},required:["item"]}},{name:"other_opportunities",description:`You are an AI assistant that monitors conversations to identify whether the candidate should be informed about other job opportunities.
|
|
186
|
+
|
|
187
|
+
If the candidate appears to be a poor fit for any of the must have requirements, gently suggest that there are other job opportunities with the company and you could inform about other roles if they are interested. If they are not interested, you should continue with the conversation about the current role.
|
|
188
|
+
|
|
189
|
+
Here is a list of phrases to listen for but not restricted to:
|
|
190
|
+
-other opportunities
|
|
191
|
+
-other jobs
|
|
192
|
+
-other roles
|
|
193
|
+
-other job opportunities
|
|
194
|
+
-other job roles
|
|
195
|
+
-other job opportunities
|
|
196
|
+
|
|
197
|
+
When monitoring the conversation, pay close attention to any input that matches or closely resembles the phrases listed above. The matching should be case-insensitive and allow for minor variations or typos. Additionally monitor for input that suggests the candidate does not meet the criteria for the current role or if they'd like to know about urgent or immediate opportunities.
|
|
198
|
+
|
|
199
|
+
Suggest other opportunities if:
|
|
200
|
+
1. The user's input exactly matches any phrase in the list.
|
|
201
|
+
2. The user's input is a close variation of any phrase in the list (e.g., "please other opportunities" instead of "other opportunities").
|
|
202
|
+
3. The user's input clearly expresses a desire to end the conversation, even if it doesn't use the exact phrases listed.
|
|
203
|
+
4. The user's input clearly expresses a desire to know about urgent or immediate opportunities.
|
|
204
|
+
|
|
205
|
+
If the candidate is interested in other opportunities, you should call a GLYDE Unity MCP tool to identify other job openings.`,parameters:{type:"object",properties:{item:{type:"string",description:"The phrase or text that triggered the suggestion of other opportunities"}},required:["item"]}}]},greeting:"Hi! I'm excited you chose to speak with me. Are you ready to start?"}};this.ws.send(JSON.stringify(d)),this.emit({type:"open",payload:{config:o,serverConfig:this.serverConfig}})};const h=p;this.ws.onmessage=s=>{if(typeof s.data=="string"){try{if(JSON.parse(s.data).type==="SettingsApplied"){const y={type:"UpdatePrompt",prompt:h};this.ws.send(JSON.stringify(y)),this.startMicrophone()}}catch{}this.handleTextMessage(s.data)}else s.data instanceof Blob?this.handleAudioData(s.data):s.data instanceof ArrayBuffer&&this.handleAudioBuffer(s.data)},this.ws.onerror=s=>{console.error("[GlydeVoice] WebSocket error:",s),this.emit({type:"error",payload:s})},this.ws.onclose=()=>{this.cleanup(),this.emit({type:"close"})},this.renderUI()}catch(e){throw console.error("[GlydeVoice] Error starting session:",e),this.active=!1,this.emit({type:"error",payload:e}),e}}}createWorkletBlobUrl(e){const t=new Blob([e],{type:"application/javascript"});return URL.createObjectURL(t)}async initializeAudio(){this.audioContext=new AudioContext({sampleRate:this.inputSampleRate});const e=this.createWorkletBlobUrl(l),t=this.createWorkletBlobUrl(u);try{await Promise.all([this.audioContext.audioWorklet.addModule(e),this.audioContext.audioWorklet.addModule(t)])}finally{URL.revokeObjectURL(e),URL.revokeObjectURL(t)}this.playbackWorkletNode=new AudioWorkletNode(this.audioContext,"audio-playback-processor"),this.playbackWorkletNode.connect(this.audioContext.destination),this.playbackWorkletNode.port.onmessage=i=>{const{type:a}=i.data;(a==="cleared"||a==="bufferEmpty")&&(this.isAgentSpeaking=!1,this.agentAudioDoneReceived=!1,this.emit({type:"agent_speaking",payload:!1}))}}handleTextMessage(e){try{const t=JSON.parse(e);switch(t.type){case"Welcome":this.emit({type:"ready"});break;case"SettingsApplied":break;case"UserStartedSpeaking":this.emit({type:"user_speaking",payload:!0}),this.clearPlaybackBuffer(),this.isAgentSpeaking=!1,this.agentAudioDoneReceived=!1;break;case"UserStoppedSpeaking":this.emit({type:"user_speaking",payload:!1});break;case"ConversationText":if(t.content&&t.content.trim()){const i=t.role==="assistant"?"agent":"user";this.config.onTranscript&&this.config.onTranscript(t.content,i),this.emit({type:"transcript",payload:{text:t.content,role:i}}),this.saveTranscript(t.content,t.role)}break;case"AgentStartedSpeaking":this.isAgentSpeaking=!0,this.agentAudioDoneReceived=!1,this.emit({type:"agent_speaking",payload:!0});break;case"AgentAudioDone":this.agentAudioDoneReceived=!0;break;case"Error":console.error("[GlydeVoice] Agent error:",t),this.emit({type:"error",payload:t});break}}catch(t){console.error("[GlydeVoice] Failed to parse message:",t)}}async handleAudioData(e){const t=await e.arrayBuffer();this.handleAudioBuffer(t)}handleAudioBuffer(e){if(!this.playbackWorkletNode||!this.audioContext)return;this.audioContext.state==="suspended"&&this.audioContext.resume();const t=e.byteLength;if(t===0)return;const i=t-t%2;if(i===0)return;const a=i===t?e:e.slice(0,i),o=new Int16Array(a),r=new Float32Array(o.length);for(let h=0;h<o.length;h++)r[h]=o[h]/32768;const p=this.resample24kTo48k(r);!this.isAgentSpeaking&&!this.agentAudioDoneReceived&&(this.isAgentSpeaking=!0,this.emit({type:"agent_speaking",payload:!0}));const c=new Float32Array(p);this.playbackWorkletNode.port.postMessage({type:"audio",data:c},[c.buffer])}resample24kTo48k(e){const t=e.length*2,i=new Float32Array(t);for(let o=0;o<e.length-1;o++){const r=e[o],p=e[o+1];i[o*2]=r,i[o*2+1]=(r+p)/2}const a=e.length-1;return i[a*2]=e[a],i[a*2+1]=e[a],i}clearPlaybackBuffer(){this.playbackWorkletNode&&this.playbackWorkletNode.port.postMessage({type:"clear"})}async startMicrophone(){if(!this.audioContext)throw new Error("Audio context not initialized");try{this.mediaStream=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,sampleRate:this.inputSampleRate,echoCancellation:!0,noiseSuppression:!0}});const e=this.audioContext.createMediaStreamSource(this.mediaStream);this.captureWorkletNode=new AudioWorkletNode(this.audioContext,"audio-capture-processor"),this.captureWorkletNode.port.onmessage=t=>{!this.active||!this.ws||this.ws.readyState!==WebSocket.OPEN||this.isMuted||this.ws.send(t.data)},e.connect(this.captureWorkletNode),this.emit({type:"microphone_ready"})}catch(e){throw console.error("[GlydeVoice] Microphone error:",e),e}}async saveTranscript(e,t){if(!(!this.config.contextId||!e))try{await fetch(`${this.unityUrl}/api/unity/voice/transcript`,{method:"POST",headers:this.getAuthHeaders(),body:JSON.stringify({context_id:this.config.contextId,content:e,role:t==="assistant"?"assistant":"user"})})}catch{}}setMuted(e){this.isMuted=e}getMuted(){return this.isMuted}isActive(){return this.active}getServerConfig(){return this.serverConfig}stop(){this.active=!1,this.cleanup()}cleanup(){this.captureWorkletNode&&(this.captureWorkletNode.disconnect(),this.captureWorkletNode.port.close(),this.captureWorkletNode=null),this.playbackWorkletNode&&(this.playbackWorkletNode.disconnect(),this.playbackWorkletNode.port.close(),this.playbackWorkletNode=null),this.mediaStream&&(this.mediaStream.getTracks().forEach(e=>e.stop()),this.mediaStream=null),this.audioContext&&(this.audioContext.close(),this.audioContext=null),this.ws&&(this.ws.readyState===WebSocket.OPEN&&this.ws.close(),this.ws=null)}emit(e){this.config.onEvent&&this.config.onEvent(e)}renderUI(){if(!this.config.container)return;const e=typeof this.config.container=="string"?document.querySelector(this.config.container):this.config.container;e&&(e.innerHTML=`
|
|
166
206
|
<div style="padding: 20px; border: 1px solid #ccc; border-radius: 8px; background: #fff;">
|
|
167
207
|
<h3>Glyde Voice Agent</h3>
|
|
168
208
|
<p>Status: Active</p>
|