Files
scripts/voiceai-server.js

58 lines
3.6 KiB
JavaScript

const express = require('express');
const twilio = require('twilio');
const axios = require('axios');
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
const PORT = process.env.PORT || 3000;
const AI_NODE = 'http://10.10.0.67:8765';
const AI_KEY = 'b1ca95570932bd14ae75426095f436912bb88e6b217712f0811e6501c68df465';
const AI_MODEL = 'llama3.1:70b';
const conversations = {};
const PROPS = {'+12133547300':{name:'broadcastbeat',greeting:'Thank you for calling Broadcast Beat. How can I help you today?',system:'You are the AI receptionist for Broadcast Beat. Be professional. Under 40 words.'},'+12134182600':{name:'pinkpulse',greeting:'Hey gorgeous! You have reached The Pink Pulse. How can I help you today?',system:'You are the AI receptionist for The Pink Pulse, an LGBTQ+ publication. Be warm and fabulous. Under 40 words.'},'+12136529233':{name:'avbeat',greeting:'Thank you for calling AV Beat. How can I help you today?',system:'You are the AI receptionist for AV Beat. Be professional. Under 40 words.'},'+12132977880':{name:'onsethost',greeting:'Thank you for calling OnSetHost. How can I help you today?',system:'You are the AI receptionist for OnSetHost, web hosting for production professionals. Under 40 words.'}};
async function askAI(prompt) {
try {
const r = await axios.post(AI_NODE+'/queue/add',{prompt,model:AI_MODEL,site:'voiceai',job_type:'text'},{headers:{'x-api-key':AI_KEY},timeout:5000});
const jobId = r.data.job_id;
console.log('Job submitted:',jobId);
for(let i=0;i<30;i++){
await new Promise(r=>setTimeout(r,500));
const j = await axios.get(AI_NODE+'/jobs',{headers:{'x-api-key':AI_KEY},timeout:3000});
const job = (j.data.jobs||[]).find(x=>x.job_id===jobId);
if(job&&job.status==='done'&&job.result){console.log('AI:',job.result.substring(0,80));return job.result.replace(/```json|```/g,'').trim();}
}
} catch(e){console.error('AI error:',e.message);}
return null;
}
app.post('/voice',async(req,res)=>{
const sid=req.body.CallSid,to=req.body.To;
const prop=PROPS[to]||PROPS['+12132977880'];
conversations[sid]={prop,history:[]};
console.log('Call to',to,sid);
const t=new twilio.twiml.VoiceResponse();
const g=t.gather({input:'speech',action:'https://voice.onsethost.com/respond',method:'POST',speechTimeout:'auto',language:'en-US'});
g.say({voice:'Polly.Joanna-Neural'},prop.greeting);
t.hangup();
res.type('text/xml');res.send(t.toString());
});
app.post('/respond',async(req,res)=>{
const sid=req.body.CallSid,speech=req.body.SpeechResult||'';
const sess=conversations[sid]||{prop:PROPS['+12132977880'],history:[]};
console.log('Speech:',speech);
sess.history.push({role:'user',content:speech});
const hist=sess.history.map(m=>(m.role==='user'?'Caller':'Assistant')+': '+m.content).join('\n');
const prompt=sess.prop.system+'\n\nConversation:\n'+hist+'\n\nRespond as AI receptionist. Under 40 words. No markdown.';
let reply=await askAI(prompt);
if(!reply)reply='I apologize for the difficulty. Please try again shortly.';
sess.history.push({role:'assistant',content:reply});
conversations[sid]=sess;
console.log('Reply:',reply);
const t=new twilio.twiml.VoiceResponse();
const g=t.gather({input:'speech',action:'https://voice.onsethost.com/respond',method:'POST',speechTimeout:'auto',language:'en-US'});
g.say({voice:'Polly.Joanna-Neural'},reply);
t.say({voice:'Polly.Joanna-Neural'},'Thank you for calling. Goodbye!');
t.hangup();
res.type('text/xml');res.send(t.toString());
});
app.get('/health',(req,res)=>res.json({status:'online',service:'VoiceAI'}));
app.listen(PORT,'0.0.0.0',()=>console.log('VoiceAI running on port '+PORT));