aillom-vox-client 1.0.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.
Files changed (45) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +272 -0
  3. package/dist/AillomVox.d.ts +36 -0
  4. package/dist/AillomVox.js +152 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +18 -0
  7. package/dist/types.d.ts +36 -0
  8. package/dist/types.js +2 -0
  9. package/docs/ASTERISK.md +411 -0
  10. package/docs/PROTOCOL.md +156 -0
  11. package/docs/PROVIDERS.md +40 -0
  12. package/docs/TOOLS.md +314 -0
  13. package/docs/TROUBLESHOOTING.md +86 -0
  14. package/docs/VOICES.md +219 -0
  15. package/docs/providers/AILLOMVOX.md +185 -0
  16. package/docs/providers/AWS.md +32 -0
  17. package/docs/providers/GEMINI.md +33 -0
  18. package/docs/providers/GROK.md +25 -0
  19. package/docs/providers/OPENAI.md +39 -0
  20. package/docs/providers/QWEN.md +27 -0
  21. package/docs/providers/ULTRAVOX.md +29 -0
  22. package/examples/01-basic/app.js +196 -0
  23. package/examples/01-basic/index.html +27 -0
  24. package/examples/02-advanced-dashboard/app.js +465 -0
  25. package/examples/02-advanced-dashboard/index.html +200 -0
  26. package/examples/02-advanced-dashboard/style.css +501 -0
  27. package/examples/03-smart-home/index.html +377 -0
  28. package/examples/04-customer-support/index.html +474 -0
  29. package/examples/sdk-usage.ts +44 -0
  30. package/integrations/n8n-nodes-aillomvox/README.md +56 -0
  31. package/integrations/n8n-nodes-aillomvox/credentials/AillomVoxApi.credentials.ts +29 -0
  32. package/integrations/n8n-nodes-aillomvox/dist/credentials/AillomVoxApi.credentials.js +30 -0
  33. package/integrations/n8n-nodes-aillomvox/dist/nodes/AillomVox/AillomVox.node.js +219 -0
  34. package/integrations/n8n-nodes-aillomvox/dist/nodes/AillomVox/aillomvox.svg +6 -0
  35. package/integrations/n8n-nodes-aillomvox/gulpfile.js +10 -0
  36. package/integrations/n8n-nodes-aillomvox/nodes/AillomVox/AillomVox.node.ts +229 -0
  37. package/integrations/n8n-nodes-aillomvox/nodes/AillomVox/aillomvox.svg +6 -0
  38. package/integrations/n8n-nodes-aillomvox/package-lock.json +11741 -0
  39. package/integrations/n8n-nodes-aillomvox/package.json +56 -0
  40. package/integrations/n8n-nodes-aillomvox/tsconfig.json +32 -0
  41. package/package.json +55 -0
  42. package/src/AillomVox.ts +169 -0
  43. package/src/index.ts +2 -0
  44. package/src/types.ts +50 -0
  45. package/tsconfig.json +23 -0
@@ -0,0 +1,474 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Customer Support - AillomVox</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
8
+ <style>
9
+ body {
10
+ font-family: 'Helvetica Neue', sans-serif;
11
+ background: #f4f6f8;
12
+ margin: 0;
13
+ display: flex;
14
+ height: 100vh;
15
+ }
16
+
17
+ /* Sidebar */
18
+ .sidebar {
19
+ width: 250px;
20
+ background: #2c3e50;
21
+ color: white;
22
+ display: flex;
23
+ flex-direction: column;
24
+ }
25
+
26
+ .logo {
27
+ padding: 20px;
28
+ font-size: 1.2rem;
29
+ font-weight: bold;
30
+ border-bottom: 1px solid #34495e;
31
+ }
32
+
33
+ .menu {
34
+ padding: 20px 0;
35
+ }
36
+
37
+ .menu-item {
38
+ padding: 15px 20px;
39
+ cursor: pointer;
40
+ transition: background 0.2s;
41
+ display: flex;
42
+ align-items: center;
43
+ gap: 10px;
44
+ }
45
+
46
+ .menu-item:hover,
47
+ .menu-item.active {
48
+ background: #34495e;
49
+ border-left: 4px solid #3498db;
50
+ }
51
+
52
+ /* Main */
53
+ .main {
54
+ flex: 1;
55
+ padding: 30px;
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 20px;
59
+ overflow-y: auto;
60
+ }
61
+
62
+ .header {
63
+ display: flex;
64
+ justify-content: space-between;
65
+ align-items: center;
66
+ background: white;
67
+ padding: 20px;
68
+ border-radius: 8px;
69
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
70
+ }
71
+
72
+ .agent-status {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 10px;
76
+ }
77
+
78
+ .status-dot {
79
+ width: 10px;
80
+ height: 10px;
81
+ border-radius: 50%;
82
+ background: #ccc;
83
+ }
84
+
85
+ .status-dot.online {
86
+ background: #2ecc71;
87
+ }
88
+
89
+ /* CRM Content */
90
+ .customer-card {
91
+ background: white;
92
+ padding: 30px;
93
+ border-radius: 8px;
94
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
95
+ flex: 1;
96
+ }
97
+
98
+ .crm-grid {
99
+ display: grid;
100
+ grid-template-columns: 2fr 1fr;
101
+ gap: 30px;
102
+ height: 100%;
103
+ }
104
+
105
+ .customer-info h2 {
106
+ margin-top: 0;
107
+ }
108
+
109
+ .info-row {
110
+ display: flex;
111
+ justify-content: space-between;
112
+ border-bottom: 1px solid #eee;
113
+ padding: 15px 0;
114
+ }
115
+
116
+ .info-label {
117
+ color: #7f8c8d;
118
+ }
119
+
120
+ .notes-section {
121
+ background: #f9f9f9;
122
+ padding: 20px;
123
+ border-radius: 8px;
124
+ }
125
+
126
+ .note {
127
+ background: white;
128
+ padding: 10px;
129
+ border-left: 3px solid #f1c40f;
130
+ margin-bottom: 10px;
131
+ font-size: 0.9rem;
132
+ }
133
+
134
+ /* AI Phone */
135
+ .phone-modal {
136
+ position: fixed;
137
+ bottom: 30px;
138
+ right: 30px;
139
+ background: white;
140
+ width: 320px;
141
+ border-radius: 12px;
142
+ box-shadow: 0 5px 25px rgba(0, 0, 0, 0.2);
143
+ overflow: hidden;
144
+ display: flex;
145
+ flex-direction: column;
146
+ }
147
+
148
+ .phone-header {
149
+ background: #2c3e50;
150
+ color: white;
151
+ padding: 15px;
152
+ display: flex;
153
+ justify-content: space-between;
154
+ align-items: center;
155
+ }
156
+
157
+ .phone-body {
158
+ padding: 20px;
159
+ text-align: center;
160
+ min-height: 200px;
161
+ display: flex;
162
+ flex-direction: column;
163
+ justify-content: center;
164
+ }
165
+
166
+ .timer {
167
+ font-size: 2rem;
168
+ font-weight: 300;
169
+ color: #333;
170
+ margin: 20px 0;
171
+ }
172
+
173
+ .actions {
174
+ display: flex;
175
+ justify-content: center;
176
+ gap: 20px;
177
+ }
178
+
179
+ .btn-phone {
180
+ width: 50px;
181
+ height: 50px;
182
+ border-radius: 50%;
183
+ border: none;
184
+ cursor: pointer;
185
+ font-size: 20px;
186
+ color: white;
187
+ display: flex;
188
+ align-items: center;
189
+ justify-content: center;
190
+ transition: transform 0.2s;
191
+ }
192
+
193
+ .btn-call {
194
+ background: #2ecc71;
195
+ }
196
+
197
+ .btn-hangup {
198
+ background: #e74c3c;
199
+ }
200
+
201
+ .btn-phone:hover {
202
+ transform: scale(1.1);
203
+ }
204
+
205
+ .btn-phone:disabled {
206
+ background: #ccc;
207
+ cursor: not-allowed;
208
+ }
209
+
210
+ .api-setup {
211
+ padding: 10px;
212
+ border-top: 1px solid #eee;
213
+ background: #fafafa;
214
+ }
215
+
216
+ .api-input {
217
+ width: 100%;
218
+ padding: 8px;
219
+ border: 1px solid #ddd;
220
+ border-radius: 4px;
221
+ box-sizing: border-box;
222
+ }
223
+ </style>
224
+ </head>
225
+
226
+ <body>
227
+
228
+ <div class="sidebar">
229
+ <div class="logo"><i class="fas fa-headset"></i> CRM Support</div>
230
+ <div class="menu">
231
+ <div class="menu-item active"><i class="fas fa-home"></i> Dashboard</div>
232
+ <div class="menu-item"><i class="fas fa-users"></i> Customers</div>
233
+ <div class="menu-item"><i class="fas fa-ticket-alt"></i> Tickets</div>
234
+ <div class="menu-item"><i class="fas fa-chart-bar"></i> Reports</div>
235
+ </div>
236
+ </div>
237
+
238
+ <div class="main">
239
+ <div class="header">
240
+ <h3>Case #49281 - Refund Request</h3>
241
+ <div class="agent-status">
242
+ <div class="status-dot" id="socketStatus"></div>
243
+ <span id="socketText">Offline</span>
244
+ </div>
245
+ </div>
246
+
247
+ <div class="customer-card">
248
+ <div class="crm-grid">
249
+ <div class="customer-info">
250
+ <h2>Marco A.</h2>
251
+ <div class="info-row">
252
+ <span class="info-label">Email</span>
253
+ <span>marco@gmail.com</span>
254
+ </div>
255
+ <div class="info-row">
256
+ <span class="info-label">Plan</span>
257
+ <span><b>Enterprise</b></span>
258
+ </div>
259
+ <div class="info-row">
260
+ <span class="info-label">Total Spend</span>
261
+ <span>$12,450.00</span>
262
+ </div>
263
+ <div class="info-row">
264
+ <span class="info-label">Last Mock Order</span>
265
+ <span id="lastOrder">ORD-992-12</span>
266
+ </div>
267
+ <div class="info-row">
268
+ <span class="info-label">Refund Status</span>
269
+ <span id="refundStatus" style="color: #e67e22; font-weight: bold;">PENDING APPROVAL</span>
270
+ </div>
271
+ </div>
272
+
273
+ <div class="notes-section" id="notesLog">
274
+ <h3>AI Interactions</h3>
275
+ <!-- Injected via tools -->
276
+ </div>
277
+ </div>
278
+ </div>
279
+ </div>
280
+
281
+ <div class="phone-modal">
282
+ <div class="phone-header">
283
+ <span>AI Assistant</span>
284
+ <i class="fas fa-wifi"></i>
285
+ </div>
286
+ <div class="phone-body">
287
+ <div class="timer" id="callTimer">00:00</div>
288
+ <div class="actions">
289
+ <button class="btn-phone btn-call" id="callBtn"><i class="fas fa-phone"></i></button>
290
+ <button class="btn-phone btn-hangup" id="hangupBtn" disabled><i class="fas fa-phone-slash"></i></button>
291
+ </div>
292
+ </div>
293
+ <div class="api-setup">
294
+ <input type="password" id="apiKey" class="api-input" placeholder="Paste API Key here...">
295
+ </div>
296
+ </div>
297
+
298
+ <script>
299
+ // Logic
300
+ const callBtn = document.getElementById('callBtn');
301
+ const hangupBtn = document.getElementById('hangupBtn');
302
+ const apiKeyInput = document.getElementById('apiKey');
303
+ const timerDisplay = document.getElementById('callTimer');
304
+ const statusDot = document.getElementById('socketStatus');
305
+ const statusText = document.getElementById('socketText');
306
+ const notesLog = document.getElementById('notesLog');
307
+
308
+ let socket, audioContext, processor, mediaStream, timerInterval;
309
+ let startTime;
310
+
311
+ callBtn.onclick = async () => {
312
+ const key = apiKeyInput.value.trim();
313
+ if (!key) return alert("Please enter API Key");
314
+
315
+ callBtn.disabled = true;
316
+ hangupBtn.disabled = false;
317
+
318
+ await connect(key);
319
+ };
320
+
321
+ hangupBtn.onclick = disconnect;
322
+
323
+ async function connect(apiKey) {
324
+ audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
325
+ const wsUrl = window.location.hostname === 'localhost' ? 'ws://localhost:8080/ws' : 'wss://vox.aillom.com/ws';
326
+
327
+ socket = new WebSocket(wsUrl);
328
+ socket.binaryType = 'arraybuffer';
329
+
330
+ socket.onopen = async () => {
331
+ statusDot.classList.add('online');
332
+ statusText.textContent = 'Connected';
333
+ startTimer();
334
+
335
+ // Support Tools
336
+ const tools = [
337
+ {
338
+ name: "approve_refund",
339
+ description: "Approves the pending refund request for the current customer case.",
340
+ parameters: { type: "object", properties: {}, required: [] }
341
+ },
342
+ {
343
+ name: "add_note",
344
+ description: "Add a note to the customer's case file.",
345
+ parameters: {
346
+ type: "object",
347
+ properties: { text: { type: "string" } },
348
+ required: ["text"]
349
+ }
350
+ }
351
+ ];
352
+
353
+ socket.send(JSON.stringify({
354
+ type: 'config',
355
+ apikey: apiKey,
356
+ provider: 'aillomvox',
357
+ voice: 'Heitor',
358
+ system_prompt: 'You are a helpful customer support agent processing a refund for Marco. You have tools to approve it and log notes.',
359
+ sample_rate: 16000,
360
+ tools: tools
361
+ }));
362
+
363
+ await startAudio();
364
+ };
365
+
366
+ socket.onmessage = (e) => {
367
+ if (typeof e.data === 'string') {
368
+ const msg = JSON.parse(e.data);
369
+ if (msg.type === 'tool_call') handleTool(msg);
370
+ if (msg.type === 'hangup') disconnect();
371
+ } else {
372
+ playAudio(e.data);
373
+ }
374
+ };
375
+
376
+ socket.onclose = () => disconnect();
377
+ }
378
+
379
+ function disconnect() {
380
+ if (socket) socket.close();
381
+ if (audioContext) audioContext.close();
382
+ if (mediaStream) mediaStream.getTracks().forEach(t => t.stop());
383
+
384
+ stopTimer();
385
+ callBtn.disabled = false;
386
+ hangupBtn.disabled = true;
387
+ statusDot.classList.remove('online');
388
+ statusText.textContent = 'Offline';
389
+ }
390
+
391
+ function handleTool(msg) {
392
+ if (msg.name === 'approve_refund') {
393
+ document.getElementById('refundStatus').textContent = "APPROVED ✅";
394
+ document.getElementById('refundStatus').style.color = "#2ecc71";
395
+ addNote("System: Refund Approved by Agent");
396
+
397
+ socket.send(JSON.stringify({
398
+ type: 'tool_result',
399
+ call_id: msg.call_id,
400
+ result: "Refund approved successfully."
401
+ }));
402
+ }
403
+ else if (msg.name === 'add_note') {
404
+ addNote(msg.args.text);
405
+ socket.send(JSON.stringify({
406
+ type: 'tool_result',
407
+ call_id: msg.call_id,
408
+ result: "Note added to CRM."
409
+ }));
410
+ }
411
+ }
412
+
413
+ function addNote(text) {
414
+ const div = document.createElement('div');
415
+ div.className = 'note';
416
+ div.textContent = text;
417
+ notesLog.appendChild(div);
418
+ }
419
+
420
+ function startTimer() {
421
+ startTime = Date.now();
422
+ timerInterval = setInterval(() => {
423
+ const delta = Math.floor((Date.now() - startTime) / 1000);
424
+ const m = Math.floor(delta / 60).toString().padStart(2, '0');
425
+ const s = (delta % 60).toString().padStart(2, '0');
426
+ timerDisplay.textContent = `${m}:${s}`;
427
+ }, 1000);
428
+ }
429
+
430
+ function stopTimer() {
431
+ clearInterval(timerInterval);
432
+ timerDisplay.textContent = "00:00";
433
+ }
434
+
435
+ // Audio Logic (Same as other examples)
436
+ async function startAudio() {
437
+ mediaStream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true } });
438
+ const source = audioContext.createMediaStreamSource(mediaStream);
439
+ processor = audioContext.createScriptProcessor(4096, 1, 1);
440
+ processor.onaudioprocess = (e) => {
441
+ if (socket.readyState === WebSocket.OPEN) socket.send(floatTo16BitPCM(e.inputBuffer.getChannelData(0)));
442
+ };
443
+ source.connect(processor);
444
+ processor.connect(audioContext.destination);
445
+ }
446
+
447
+ function playAudio(buf) {
448
+ const f32 = new Float32Array(buf.byteLength / 2);
449
+ const view = new DataView(buf);
450
+ for (let i = 0; i < f32.length; i++) {
451
+ const s = view.getInt16(i * 2, true);
452
+ f32[i] = s < 0 ? s / 0x8000 : s / 0x7FFF;
453
+ }
454
+ const b = audioContext.createBuffer(1, f32.length, 16000);
455
+ b.getChannelData(0).set(f32);
456
+ const s = audioContext.createBufferSource();
457
+ s.buffer = b;
458
+ s.connect(audioContext.destination);
459
+ s.start();
460
+ }
461
+
462
+ function floatTo16BitPCM(input) {
463
+ const output = new Int16Array(input.length);
464
+ for (let i = 0; i < input.length; i++) {
465
+ const s = Math.max(-1, Math.min(1, input[i]));
466
+ output[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
467
+ }
468
+ return output.buffer;
469
+ }
470
+
471
+ </script>
472
+ </body>
473
+
474
+ </html>
@@ -0,0 +1,44 @@
1
+ import { AillomVox } from '../src'; // In production: from 'aillom-vox-client'
2
+
3
+ // 1. Initialize Client
4
+ const client = new AillomVox({
5
+ apiKey: 'av_YOUR_API_KEY_HERE',
6
+ debug: true,
7
+ voice: 'Edward',
8
+ systemPrompt: 'You are a helpful assistant.'
9
+ });
10
+
11
+ // 2. Setup Event Listeners
12
+ client.on('connected', () => {
13
+ console.log('✅ Connected to AillomVox!');
14
+ });
15
+
16
+ client.on('transcript', (msg) => {
17
+ if (msg.role === 'assistant') {
18
+ console.log(`🤖 AI: ${msg.text}`);
19
+ } else {
20
+ console.log(`👤 User: ${msg.text}`);
21
+ }
22
+ });
23
+
24
+ client.on('audio', (buffer) => {
25
+ // Received PCM 16-bit audio chunk
26
+ // Play with speaker or save to file
27
+ console.log(`🔊 Received ${buffer.byteLength} bytes of audio`);
28
+ });
29
+
30
+ client.on('disconnected', (reason) => {
31
+ console.log('❌ Disconnected:', reason);
32
+ });
33
+
34
+ // 3. Connect
35
+ async function start() {
36
+ try {
37
+ await client.connect();
38
+ console.log('Listening...');
39
+ } catch (err) {
40
+ console.error('Connection failed:', err);
41
+ }
42
+ }
43
+
44
+ start();
@@ -0,0 +1,56 @@
1
+ # n8n-nodes-aillomvox
2
+
3
+ Official AillomVox (Voice & AI Gateway) integration for n8n.
4
+
5
+ This node allows you to natively interact with the AillomVox API within your n8n workflows, without needing to configure manual HTTP requests.
6
+
7
+ ## Features
8
+
9
+ The AillomVox node supports the following operations:
10
+
11
+ ### Resource: Info
12
+ * **Get Voices**: Returns the full list of available voices, filterable by provider (AillomVox, ElevenLabs, PlayHT, etc.).
13
+ * **Get Providers**: Returns the list of supported AI providers and their respective pricing per minute.
14
+
15
+ ### Resource: Recording
16
+ * **Get Download URL**: Generates a secure, temporary link (Presigned URL) to download a call recording, given its ID.
17
+
18
+ ## Installation
19
+
20
+ ### Option 1: Via Community Node (Recommended)
21
+ 1. In your n8n instance, go to **Settings > Community Nodes**.
22
+ 2. Click **Install**.
23
+ 3. Paste the package name: `n8n-nodes-aillomvox`.
24
+ 4. Wait for installation and restart n8n if necessary.
25
+
26
+ ### Option 2: Manual Installation
27
+ If you are developing or running n8n locally:
28
+
29
+ 1. Clone this repository.
30
+ 2. Run `npm install` and `npm run build`.
31
+ 3. Create a symlink in your n8n custom nodes folder:
32
+ ```bash
33
+ mkdir -p ~/.n8n/custom
34
+ cd ~/.n8n/custom
35
+ npm install /path/to/n8n-nodes-aillomvox
36
+ ```
37
+ 4. Start n8n: `n8n start`.
38
+
39
+ ## Credentials
40
+
41
+ To use this node, you will need an **AillomVox API Key**.
42
+
43
+ 1. In the AillomVox node, select **Credentials > Create New**.
44
+ 2. Choose **AillomVox API**.
45
+ 3. Enter your key (starts with `av_...`).
46
+ 4. The **Base URL** field comes pre-filled with `https://vox.aillom.com`. Change only if you are using a self-hosted instance.
47
+
48
+ ## Usage Example
49
+
50
+ 1. **Webhook Node**: Receives the `call.ended` event from AillomVox.
51
+ 2. **AillomVox Node**: Uses the call ID (`{{$json.data.callId}}`) and the **Get Download URL** operation to get the audio link.
52
+ 3. **HTTP Request / Upload**: Sends the audio to Google Drive or Dropbox.
53
+
54
+ ## Support
55
+
56
+ For questions or support, visit: [https://vox.aillom.com/docs](https://vox.aillom.com/docs)
@@ -0,0 +1,29 @@
1
+ import {
2
+ ICredentialType,
3
+ INodeProperties,
4
+ } from 'n8n-workflow';
5
+
6
+ export class AillomVoxApi implements ICredentialType {
7
+ name = 'aillomVoxApi';
8
+ displayName = 'AillomVox API';
9
+ documentationUrl = 'https://vox.aillom.com/docs';
10
+ properties: INodeProperties[] = [
11
+ {
12
+ displayName: 'API Key',
13
+ name: 'apiKey',
14
+ type: 'string',
15
+ typeOptions: {
16
+ password: true,
17
+ },
18
+ default: '',
19
+ description: 'Your AillomVox API Key (starts with av_...)',
20
+ },
21
+ {
22
+ displayName: 'Base URL',
23
+ name: 'baseUrl',
24
+ type: 'string',
25
+ default: 'https://vox.aillom.com',
26
+ description: 'AillomVox server URL (if using self-hosted)',
27
+ },
28
+ ];
29
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AillomVoxApi = void 0;
4
+ class AillomVoxApi {
5
+ constructor() {
6
+ this.name = 'aillomVoxApi';
7
+ this.displayName = 'AillomVox API';
8
+ this.documentationUrl = 'https://vox.aillom.com/docs';
9
+ this.properties = [
10
+ {
11
+ displayName: 'API Key',
12
+ name: 'apiKey',
13
+ type: 'string',
14
+ typeOptions: {
15
+ password: true,
16
+ },
17
+ default: '',
18
+ description: 'Your AillomVox API Key (starts with av_...)',
19
+ },
20
+ {
21
+ displayName: 'Base URL',
22
+ name: 'baseUrl',
23
+ type: 'string',
24
+ default: 'https://vox.aillom.com',
25
+ description: 'AillomVox server URL (if using self-hosted)',
26
+ },
27
+ ];
28
+ }
29
+ }
30
+ exports.AillomVoxApi = AillomVoxApi;