Pepguy commited on
Commit
d644408
Β·
verified Β·
1 Parent(s): e97a49d

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +20 -24
app.js CHANGED
@@ -25,7 +25,6 @@ const log = (tag, ...args) => {
25
  let callLogs = [];
26
 
27
  // ─── UTILITY: GLOBAL TELNYX COMMAND DISPATCHER ───
28
- // STRICTLY FIXED: https://api.telnyx.com/v2/calls/...
29
  const sendTelnyxCmd = async (callId, action, body = {}) => {
30
  try {
31
  const response = await fetch(`https://api.telnyx.com/v2/calls/${callId}/actions/${action}`, {
@@ -214,6 +213,7 @@ app.get("/health", (req, res) => res.json({ status: "ok", uptime: process.uptime
214
  app.get("/play-recording/:call_control_id", async (req, res) => {
215
  const logEntry = callLogs.find(l => l.call_control_id === req.params.call_control_id);
216
  if (!logEntry || !logEntry.raw_telnyx_url) return res.sendStatus(404);
 
217
  try {
218
  const s3Res = await fetch(logEntry.raw_telnyx_url);
219
  if (!s3Res.ok) return res.sendStatus(s3Res.status);
@@ -229,7 +229,8 @@ app.get("/play-recording/:call_control_id", async (req, res) => {
229
  // ─── DIAL OUT ROUTE ────────────────────────────────────────
230
  app.post("/dial", async (req, res) => {
231
  const { to } = req.body;
232
- const voice = "Algieba";
 
233
  log("DIAL", `Dialing ${to}...`);
234
 
235
  try {
@@ -306,7 +307,7 @@ app.post("/webhook/call", async (req, res) => {
306
 
307
  callLogs.push({
308
  id: randomUUID(), call_control_id: callId, target_number: payload?.from,
309
- voice_used: "Algieba", status: "in-progress", last_transcript: "", recording_url: null,
310
  raw_telnyx_url: null, is_playing: false, timers: {}, created_at: new Date().toISOString()
311
  });
312
  await sendTelnyxCmd(callId, "answer");
@@ -319,7 +320,6 @@ app.post("/webhook/call", async (req, res) => {
319
 
320
  await sendTelnyxCmd(callId, "record_start", { format: "mp3", channels: "dual", play_beep: false });
321
 
322
- // Open WebSocket purely for the VAD staller
323
  const wsUrl = `wss://${req.headers.host}/audio-stream`;
324
  log("STREAM", "Connecting WebSocket for early turn detection...");
325
  await sendTelnyxCmd(callId, "streaming_start", {
@@ -327,7 +327,6 @@ app.post("/webhook/call", async (req, res) => {
327
  stream_track: "inbound_track"
328
  });
329
 
330
- // Start Native STT to handle the actual text
331
  log("STT", "Activating native Telnyx real-time transcription engine...");
332
  await sendTelnyxCmd(callId, "transcription_start", {
333
  transcription_engine: "Telnyx",
@@ -352,7 +351,7 @@ app.post("/webhook/call", async (req, res) => {
352
  const data = payload?.transcription_data;
353
  if (!data) break;
354
 
355
- const voice = entry ? entry.voice_used : "Algieba";
356
 
357
  // 1. BARGE-IN (User is currently speaking)
358
  if (!data.is_final) {
@@ -371,7 +370,7 @@ app.post("/webhook/call", async (req, res) => {
371
  log("TRANSCRIPT", `User said: "${transcript}"`);
372
  updateLog(callId, { last_transcript: transcript });
373
 
374
- // 3. FETCH FULL AI RESPONSE
375
  try {
376
  const aiRes = await fetch(`${AI_SERVER_URL}/api/process-text-turn`, {
377
  method: "POST",
@@ -382,7 +381,7 @@ app.post("/webhook/call", async (req, res) => {
382
  if (aiRes.ok) {
383
  const aiData = await aiRes.json();
384
  if (aiData.speak_url && entry && entry.status !== "completed") {
385
- log("AI_REPLY", `Playing full reply.`);
386
  updateLog(callId, { is_playing: true });
387
  await sendTelnyxCmd(callId, "playback_start", {
388
  audio_url: aiData.speak_url,
@@ -414,9 +413,9 @@ app.post("/webhook/call", async (req, res) => {
414
  res.sendStatus(200);
415
  });
416
 
417
- // ─── WEBSOCKET (Strictly for Early Staller & Barge-In) ───
418
  wss.on("connection", (ws, req) => {
419
- log("WS", `VAD WebSocket connected from ${req.socket.remoteAddress}`);
420
 
421
  let isSpeaking = false;
422
  let silenceFrames = 0;
@@ -425,8 +424,9 @@ wss.on("connection", (ws, req) => {
425
  let hasGreeted = false;
426
  let totalNoiseFrames = 0;
427
 
428
- const SILENCE_THRESHOLD_FRAMES = 25;
429
- const VOLUME_THRESHOLD = 500;
 
430
 
431
  ws.on("message", async (raw) => {
432
  try {
@@ -434,14 +434,13 @@ wss.on("connection", (ws, req) => {
434
 
435
  if (msg.event === "start") {
436
  callControlId = msg.start?.call_control_id;
437
- log("WS_STREAM", `Receiving audio for call ${callControlId}`);
438
 
439
  // Smart Initial Greeting
440
  setTimeout(async () => {
441
  if (!hasGreeted && callControlId) {
442
  const entry = callLogs.find(l => l.call_control_id === callControlId);
443
  if (entry && entry.status === "in-progress") {
444
- const voice = entry.voice_used || "Algieba";
445
  log("SMART_GREETING", "No immediate human audio detected. Saying Hello.");
446
  entry.is_playing = true;
447
  hasGreeted = true;
@@ -459,7 +458,6 @@ wss.on("connection", (ws, req) => {
459
  const pcm16 = decodeMuLaw(mulawBuffer);
460
  const rms = calculateRMS(pcm16);
461
 
462
- // ── 1. NOISE DETECTED (Human is speaking) ──
463
  if (rms > VOLUME_THRESHOLD) {
464
  hasGreeted = true;
465
  silenceFrames = 0;
@@ -469,7 +467,6 @@ wss.on("connection", (ws, req) => {
469
  isSpeaking = true;
470
  hasPlayedFiller = false;
471
 
472
- // BARGE IN: If AI is talking, tell it to shut up
473
  const entry = callLogs.find(l => l.call_control_id === callControlId);
474
  if (entry && entry.is_playing) {
475
  log("BARGE-IN", "User is speaking. Interrupting AI playback.");
@@ -478,21 +475,20 @@ wss.on("connection", (ws, req) => {
478
  }
479
  }
480
  }
481
-
482
- // ── 2. SILENCE DETECTED ──
483
  else if (isSpeaking) {
484
  silenceFrames++;
485
 
486
- // ── 3. EARLY STALL TRIGGER (500ms silence) ──
487
  if (silenceFrames > SILENCE_THRESHOLD_FRAMES && !hasPlayedFiller) {
488
  hasPlayedFiller = true;
489
  isSpeaking = false;
490
 
491
- // Only play filler if the human actually spoke a sentence
492
- if (totalNoiseFrames > 15) {
493
- log("VAD_EARLY_TURN", "500ms silence detected. Playing early staller...");
 
 
494
  const entry = callLogs.find(l => l.call_control_id === callControlId);
495
- const voice = entry ? entry.voice_used : "Algieba";
496
 
497
  const fillers = ["Right.", "Got it.", "Okay.", "Yeah.", "Ah, okay."];
498
  const filler = fillers[Math.floor(Math.random() * fillers.length)];
@@ -517,5 +513,5 @@ requiredEnv.forEach((key) => {
517
  });
518
 
519
  server.listen(PORT, "0.0.0.0", () => {
520
- log("STARTUP", `Telecom Server running on port ${PORT}`);
521
  });
 
25
  let callLogs = [];
26
 
27
  // ─── UTILITY: GLOBAL TELNYX COMMAND DISPATCHER ───
 
28
  const sendTelnyxCmd = async (callId, action, body = {}) => {
29
  try {
30
  const response = await fetch(`https://api.telnyx.com/v2/calls/${callId}/actions/${action}`, {
 
213
  app.get("/play-recording/:call_control_id", async (req, res) => {
214
  const logEntry = callLogs.find(l => l.call_control_id === req.params.call_control_id);
215
  if (!logEntry || !logEntry.raw_telnyx_url) return res.sendStatus(404);
216
+
217
  try {
218
  const s3Res = await fetch(logEntry.raw_telnyx_url);
219
  if (!s3Res.ok) return res.sendStatus(s3Res.status);
 
229
  // ─── DIAL OUT ROUTE ────────────────────────────────────────
230
  app.post("/dial", async (req, res) => {
231
  const { to } = req.body;
232
+ // SETTING ENCELADUS AS THE DEFAULT VOICE
233
+ const voice = "Enceladus";
234
  log("DIAL", `Dialing ${to}...`);
235
 
236
  try {
 
307
 
308
  callLogs.push({
309
  id: randomUUID(), call_control_id: callId, target_number: payload?.from,
310
+ voice_used: "Enceladus", status: "in-progress", last_transcript: "", recording_url: null,
311
  raw_telnyx_url: null, is_playing: false, timers: {}, created_at: new Date().toISOString()
312
  });
313
  await sendTelnyxCmd(callId, "answer");
 
320
 
321
  await sendTelnyxCmd(callId, "record_start", { format: "mp3", channels: "dual", play_beep: false });
322
 
 
323
  const wsUrl = `wss://${req.headers.host}/audio-stream`;
324
  log("STREAM", "Connecting WebSocket for early turn detection...");
325
  await sendTelnyxCmd(callId, "streaming_start", {
 
327
  stream_track: "inbound_track"
328
  });
329
 
 
330
  log("STT", "Activating native Telnyx real-time transcription engine...");
331
  await sendTelnyxCmd(callId, "transcription_start", {
332
  transcription_engine: "Telnyx",
 
351
  const data = payload?.transcription_data;
352
  if (!data) break;
353
 
354
+ const voice = entry ? entry.voice_used : "Enceladus";
355
 
356
  // 1. BARGE-IN (User is currently speaking)
357
  if (!data.is_final) {
 
370
  log("TRANSCRIPT", `User said: "${transcript}"`);
371
  updateLog(callId, { last_transcript: transcript });
372
 
373
+ // FETCH FULL AI RESPONSE
374
  try {
375
  const aiRes = await fetch(`${AI_SERVER_URL}/api/process-text-turn`, {
376
  method: "POST",
 
381
  if (aiRes.ok) {
382
  const aiData = await aiRes.json();
383
  if (aiData.speak_url && entry && entry.status !== "completed") {
384
+ log("AI_REPLY", `Playing full reply: ${aiData.raw_text}`);
385
  updateLog(callId, { is_playing: true });
386
  await sendTelnyxCmd(callId, "playback_start", {
387
  audio_url: aiData.speak_url,
 
413
  res.sendStatus(200);
414
  });
415
 
416
+ // ─── WEBSOCKET (VAD & Early Staller) ───
417
  wss.on("connection", (ws, req) => {
418
+ log("WS", `VAD WebSocket connected`);
419
 
420
  let isSpeaking = false;
421
  let silenceFrames = 0;
 
424
  let hasGreeted = false;
425
  let totalNoiseFrames = 0;
426
 
427
+ // INCREASED THRESHOLDS FOR A MORE PATIENT AI
428
+ const SILENCE_THRESHOLD_FRAMES = 37; // ~750ms of silence needed before declaring a turn end
429
+ const VOLUME_THRESHOLD = 500; // Require louder noise to prevent false barge-ins
430
 
431
  ws.on("message", async (raw) => {
432
  try {
 
434
 
435
  if (msg.event === "start") {
436
  callControlId = msg.start?.call_control_id;
 
437
 
438
  // Smart Initial Greeting
439
  setTimeout(async () => {
440
  if (!hasGreeted && callControlId) {
441
  const entry = callLogs.find(l => l.call_control_id === callControlId);
442
  if (entry && entry.status === "in-progress") {
443
+ const voice = entry.voice_used || "Enceladus";
444
  log("SMART_GREETING", "No immediate human audio detected. Saying Hello.");
445
  entry.is_playing = true;
446
  hasGreeted = true;
 
458
  const pcm16 = decodeMuLaw(mulawBuffer);
459
  const rms = calculateRMS(pcm16);
460
 
 
461
  if (rms > VOLUME_THRESHOLD) {
462
  hasGreeted = true;
463
  silenceFrames = 0;
 
467
  isSpeaking = true;
468
  hasPlayedFiller = false;
469
 
 
470
  const entry = callLogs.find(l => l.call_control_id === callControlId);
471
  if (entry && entry.is_playing) {
472
  log("BARGE-IN", "User is speaking. Interrupting AI playback.");
 
475
  }
476
  }
477
  }
 
 
478
  else if (isSpeaking) {
479
  silenceFrames++;
480
 
 
481
  if (silenceFrames > SILENCE_THRESHOLD_FRAMES && !hasPlayedFiller) {
482
  hasPlayedFiller = true;
483
  isSpeaking = false;
484
 
485
+ // LAZY BACKCHANNELING:
486
+ // Only play a filler word if they spoke continuously for ~800ms
487
+ // This prevents it from saying "Okay" to an IVR breath or a short "Yeah".
488
+ if (totalNoiseFrames > 40) {
489
+ log("VAD_EARLY_TURN", "Long sentence detected. Playing early staller...");
490
  const entry = callLogs.find(l => l.call_control_id === callControlId);
491
+ const voice = entry ? entry.voice_used : "Enceladus";
492
 
493
  const fillers = ["Right.", "Got it.", "Okay.", "Yeah.", "Ah, okay."];
494
  const filler = fillers[Math.floor(Math.random() * fillers.length)];
 
513
  });
514
 
515
  server.listen(PORT, "0.0.0.0", () => {
516
+ log("STARTUP", `Elite Telecom Server running on port ${PORT}`);
517
  });