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.
- package/LICENSE +15 -0
- package/README.md +272 -0
- package/dist/AillomVox.d.ts +36 -0
- package/dist/AillomVox.js +152 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.js +2 -0
- package/docs/ASTERISK.md +411 -0
- package/docs/PROTOCOL.md +156 -0
- package/docs/PROVIDERS.md +40 -0
- package/docs/TOOLS.md +314 -0
- package/docs/TROUBLESHOOTING.md +86 -0
- package/docs/VOICES.md +219 -0
- package/docs/providers/AILLOMVOX.md +185 -0
- package/docs/providers/AWS.md +32 -0
- package/docs/providers/GEMINI.md +33 -0
- package/docs/providers/GROK.md +25 -0
- package/docs/providers/OPENAI.md +39 -0
- package/docs/providers/QWEN.md +27 -0
- package/docs/providers/ULTRAVOX.md +29 -0
- package/examples/01-basic/app.js +196 -0
- package/examples/01-basic/index.html +27 -0
- package/examples/02-advanced-dashboard/app.js +465 -0
- package/examples/02-advanced-dashboard/index.html +200 -0
- package/examples/02-advanced-dashboard/style.css +501 -0
- package/examples/03-smart-home/index.html +377 -0
- package/examples/04-customer-support/index.html +474 -0
- package/examples/sdk-usage.ts +44 -0
- package/integrations/n8n-nodes-aillomvox/README.md +56 -0
- package/integrations/n8n-nodes-aillomvox/credentials/AillomVoxApi.credentials.ts +29 -0
- package/integrations/n8n-nodes-aillomvox/dist/credentials/AillomVoxApi.credentials.js +30 -0
- package/integrations/n8n-nodes-aillomvox/dist/nodes/AillomVox/AillomVox.node.js +219 -0
- package/integrations/n8n-nodes-aillomvox/dist/nodes/AillomVox/aillomvox.svg +6 -0
- package/integrations/n8n-nodes-aillomvox/gulpfile.js +10 -0
- package/integrations/n8n-nodes-aillomvox/nodes/AillomVox/AillomVox.node.ts +229 -0
- package/integrations/n8n-nodes-aillomvox/nodes/AillomVox/aillomvox.svg +6 -0
- package/integrations/n8n-nodes-aillomvox/package-lock.json +11741 -0
- package/integrations/n8n-nodes-aillomvox/package.json +56 -0
- package/integrations/n8n-nodes-aillomvox/tsconfig.json +32 -0
- package/package.json +55 -0
- package/src/AillomVox.ts +169 -0
- package/src/index.ts +2 -0
- package/src/types.ts +50 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title>Smart Home - 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: 'Segoe UI', sans-serif;
|
|
11
|
+
background: #1a1a1a;
|
|
12
|
+
color: white;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
align-items: center;
|
|
16
|
+
min-height: 100vh;
|
|
17
|
+
margin: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.header {
|
|
21
|
+
text-align: center;
|
|
22
|
+
padding: 20px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.house-grid {
|
|
26
|
+
display: grid;
|
|
27
|
+
grid-template-columns: repeat(2, 1fr);
|
|
28
|
+
gap: 20px;
|
|
29
|
+
max-width: 600px;
|
|
30
|
+
width: 100%;
|
|
31
|
+
padding: 20px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.room-card {
|
|
35
|
+
background: #2d2d2d;
|
|
36
|
+
padding: 20px;
|
|
37
|
+
border-radius: 15px;
|
|
38
|
+
text-align: center;
|
|
39
|
+
transition: all 0.3s;
|
|
40
|
+
border: 2px solid transparent;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.room-card.active {
|
|
44
|
+
border-color: #00ff9d;
|
|
45
|
+
box-shadow: 0 0 15px rgba(0, 255, 157, 0.2);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.room-card.on i {
|
|
49
|
+
color: #ffd700;
|
|
50
|
+
text-shadow: 0 0 10px #ffd700;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.room-card i {
|
|
54
|
+
font-size: 3em;
|
|
55
|
+
color: #555;
|
|
56
|
+
margin-bottom: 10px;
|
|
57
|
+
transition: color 0.3s;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.room-card h3 {
|
|
61
|
+
margin: 10px 0 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.value-display {
|
|
65
|
+
font-size: 1.5em;
|
|
66
|
+
font-weight: bold;
|
|
67
|
+
color: #00ff9d;
|
|
68
|
+
margin-top: 10px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.control-panel {
|
|
72
|
+
position: fixed;
|
|
73
|
+
bottom: 30px;
|
|
74
|
+
background: #333;
|
|
75
|
+
padding: 15px 30px;
|
|
76
|
+
border-radius: 50px;
|
|
77
|
+
display: flex;
|
|
78
|
+
gap: 20px;
|
|
79
|
+
align-items: center;
|
|
80
|
+
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.5);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#micBtn {
|
|
84
|
+
width: 60px;
|
|
85
|
+
height: 60px;
|
|
86
|
+
border-radius: 50%;
|
|
87
|
+
border: none;
|
|
88
|
+
background: #ea4335;
|
|
89
|
+
color: white;
|
|
90
|
+
font-size: 24px;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
transition: transform 0.2s;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#micBtn.listening {
|
|
96
|
+
background: #00ff9d;
|
|
97
|
+
animation: pulse 1.5s infinite;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@keyframes pulse {
|
|
101
|
+
0% {
|
|
102
|
+
transform: scale(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
50% {
|
|
106
|
+
transform: scale(1.1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
100% {
|
|
110
|
+
transform: scale(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.api-input {
|
|
115
|
+
position: absolute;
|
|
116
|
+
top: 20px;
|
|
117
|
+
right: 20px;
|
|
118
|
+
background: #333;
|
|
119
|
+
border: 1px solid #555;
|
|
120
|
+
color: white;
|
|
121
|
+
padding: 5px 10px;
|
|
122
|
+
border-radius: 5px;
|
|
123
|
+
}
|
|
124
|
+
</style>
|
|
125
|
+
</head>
|
|
126
|
+
|
|
127
|
+
<body>
|
|
128
|
+
|
|
129
|
+
<input type="password" id="apiKey" class="api-input" placeholder="Enter API Key">
|
|
130
|
+
|
|
131
|
+
<div class="header">
|
|
132
|
+
<h1>🏠 Aillom Home</h1>
|
|
133
|
+
<p>Try saying: "Turn on the living room lights" or "Set temperature to 22 degrees"</p>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div class="house-grid">
|
|
137
|
+
<div class="room-card" id="living-room">
|
|
138
|
+
<i class="fas fa-lightbulb"></i>
|
|
139
|
+
<h3>Living Room</h3>
|
|
140
|
+
<div class="status">OFF</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="room-card" id="kitchen">
|
|
144
|
+
<i class="fas fa-lightbulb"></i>
|
|
145
|
+
<h3>Kitchen</h3>
|
|
146
|
+
<div class="status">OFF</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div class="room-card" id="thermostat">
|
|
150
|
+
<i class="fas fa-thermometer-half"></i>
|
|
151
|
+
<h3>Thermostat</h3>
|
|
152
|
+
<div class="value-display">20°C</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<div class="room-card" id="security">
|
|
156
|
+
<i class="fas fa-lock"></i>
|
|
157
|
+
<h3>Security</h3>
|
|
158
|
+
<div class="status">ARMED</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="control-panel">
|
|
163
|
+
<div id="statusText">Ready to connect...</div>
|
|
164
|
+
<button id="micBtn"><i class="fas fa-microphone"></i></button>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<script>
|
|
168
|
+
// State
|
|
169
|
+
const state = {
|
|
170
|
+
living_room: false,
|
|
171
|
+
kitchen: false,
|
|
172
|
+
temperature: 20,
|
|
173
|
+
security: true
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// UI Updates
|
|
177
|
+
function updateUI() {
|
|
178
|
+
// Living Room
|
|
179
|
+
const lr = document.getElementById('living-room');
|
|
180
|
+
lr.classList.toggle('on', state.living_room);
|
|
181
|
+
lr.querySelector('.status').textContent = state.living_room ? 'ON' : 'OFF';
|
|
182
|
+
|
|
183
|
+
// Kitchen
|
|
184
|
+
const k = document.getElementById('kitchen');
|
|
185
|
+
k.classList.toggle('on', state.kitchen);
|
|
186
|
+
k.querySelector('.status').textContent = state.kitchen ? 'ON' : 'OFF';
|
|
187
|
+
|
|
188
|
+
// Thermostat
|
|
189
|
+
document.querySelector('#thermostat .value-display').textContent = `${state.temperature}°C`;
|
|
190
|
+
|
|
191
|
+
// Security
|
|
192
|
+
const sec = document.getElementById('security');
|
|
193
|
+
sec.querySelector('i').className = state.security ? 'fas fa-lock' : 'fas fa-lock-open';
|
|
194
|
+
sec.style.borderColor = state.security ? '#00ff9d' : '#ff4444';
|
|
195
|
+
sec.querySelector('.status').textContent = state.security ? 'ARMED' : 'DISARMED';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// AillomVox Connection
|
|
199
|
+
let socket;
|
|
200
|
+
let audioContext;
|
|
201
|
+
let processor;
|
|
202
|
+
let mediaStream;
|
|
203
|
+
let isConnected = false;
|
|
204
|
+
|
|
205
|
+
const micBtn = document.getElementById('micBtn');
|
|
206
|
+
const statusText = document.getElementById('statusText');
|
|
207
|
+
const apiKeyInput = document.getElementById('apiKey');
|
|
208
|
+
|
|
209
|
+
micBtn.onclick = async () => {
|
|
210
|
+
if (isConnected) {
|
|
211
|
+
disconnect();
|
|
212
|
+
} else {
|
|
213
|
+
const key = apiKeyInput.value.trim();
|
|
214
|
+
if (!key) return alert('Please enter API Key top right');
|
|
215
|
+
await connect(key);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
async function connect(apiKey) {
|
|
220
|
+
statusText.textContent = 'Connecting...';
|
|
221
|
+
|
|
222
|
+
audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
|
|
223
|
+
const wsUrl = window.location.hostname === 'localhost' ? 'ws://localhost:8080/ws' : 'wss://vox.aillom.com/ws';
|
|
224
|
+
|
|
225
|
+
socket = new WebSocket(wsUrl);
|
|
226
|
+
socket.binaryType = 'arraybuffer';
|
|
227
|
+
|
|
228
|
+
socket.onopen = async () => {
|
|
229
|
+
isConnected = true;
|
|
230
|
+
micBtn.classList.add('listening');
|
|
231
|
+
statusText.textContent = 'Listening...';
|
|
232
|
+
|
|
233
|
+
// Define Smart Home Tools
|
|
234
|
+
const tools = [
|
|
235
|
+
{
|
|
236
|
+
name: "set_light",
|
|
237
|
+
description: "Turn lights on or off in a specific room.",
|
|
238
|
+
parameters: {
|
|
239
|
+
type: "object",
|
|
240
|
+
properties: {
|
|
241
|
+
room: { type: "string", enum: ["living_room", "kitchen"] },
|
|
242
|
+
state: { type: "boolean" }
|
|
243
|
+
},
|
|
244
|
+
required: ["room", "state"]
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: "set_temperature",
|
|
249
|
+
description: "Set the thermostat temperature.",
|
|
250
|
+
parameters: {
|
|
251
|
+
type: "object",
|
|
252
|
+
properties: {
|
|
253
|
+
degrees: { type: "number" }
|
|
254
|
+
},
|
|
255
|
+
required: ["degrees"]
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "set_security",
|
|
260
|
+
description: "Arm or disarm the security system.",
|
|
261
|
+
parameters: {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
arm: { type: "boolean" }
|
|
265
|
+
},
|
|
266
|
+
required: ["arm"]
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
socket.send(JSON.stringify({
|
|
272
|
+
type: 'config',
|
|
273
|
+
apikey: apiKey,
|
|
274
|
+
provider: 'aillomvox',
|
|
275
|
+
voice: 'Heitor',
|
|
276
|
+
system_prompt: 'You are a smart home assistant. Control the house devices based on user requests.',
|
|
277
|
+
sample_rate: 16000,
|
|
278
|
+
tools: tools
|
|
279
|
+
}));
|
|
280
|
+
|
|
281
|
+
await startAudio();
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
socket.onmessage = (event) => {
|
|
285
|
+
if (typeof event.data === 'string') {
|
|
286
|
+
const msg = JSON.parse(event.data);
|
|
287
|
+
if (msg.type === 'tool_call') handleTool(msg);
|
|
288
|
+
if (msg.type === 'hangup') disconnect();
|
|
289
|
+
} else {
|
|
290
|
+
playAudio(event.data);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
socket.onclose = () => disconnect();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function disconnect() {
|
|
298
|
+
isConnected = false;
|
|
299
|
+
micBtn.classList.remove('listening');
|
|
300
|
+
statusText.textContent = 'Ready to connect...';
|
|
301
|
+
if (socket) socket.close();
|
|
302
|
+
if (audioContext) audioContext.close();
|
|
303
|
+
if (mediaStream) mediaStream.getTracks().forEach(t => t.stop());
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function handleTool(msg) {
|
|
307
|
+
console.log('Tool:', msg.name, msg.args);
|
|
308
|
+
|
|
309
|
+
if (msg.name === 'set_light') {
|
|
310
|
+
state[msg.args.room] = msg.args.state;
|
|
311
|
+
updateUI();
|
|
312
|
+
respondTool(msg.call_id, `Light in ${msg.args.room} turned ${msg.args.state ? 'ON' : 'OFF'}`);
|
|
313
|
+
}
|
|
314
|
+
else if (msg.name === 'set_temperature') {
|
|
315
|
+
state.temperature = msg.args.degrees;
|
|
316
|
+
updateUI();
|
|
317
|
+
respondTool(msg.call_id, `Temperature set to ${msg.args.degrees}`);
|
|
318
|
+
}
|
|
319
|
+
else if (msg.name === 'set_security') {
|
|
320
|
+
state.security = msg.args.arm;
|
|
321
|
+
updateUI();
|
|
322
|
+
respondTool(msg.call_id, `Security system ${msg.args.arm ? 'ARMED' : 'DISARMED'}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function respondTool(callId, result) {
|
|
327
|
+
socket.send(JSON.stringify({
|
|
328
|
+
type: 'tool_result',
|
|
329
|
+
call_id: callId,
|
|
330
|
+
result: result
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function startAudio() {
|
|
335
|
+
mediaStream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true } });
|
|
336
|
+
const source = audioContext.createMediaStreamSource(mediaStream);
|
|
337
|
+
processor = audioContext.createScriptProcessor(4096, 1, 1);
|
|
338
|
+
processor.onaudioprocess = (e) => {
|
|
339
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
340
|
+
socket.send(floatTo16BitPCM(e.inputBuffer.getChannelData(0)));
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
source.connect(processor);
|
|
344
|
+
processor.connect(audioContext.destination);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function playAudio(buffer) {
|
|
348
|
+
const float32 = new Float32Array(buffer.byteLength / 2);
|
|
349
|
+
const view = new DataView(buffer);
|
|
350
|
+
for (let i = 0; i < float32.length; i++) {
|
|
351
|
+
const s = view.getInt16(i * 2, true);
|
|
352
|
+
float32[i] = s < 0 ? s / 0x8000 : s / 0x7FFF;
|
|
353
|
+
}
|
|
354
|
+
const audioBuf = audioContext.createBuffer(1, float32.length, 16000);
|
|
355
|
+
audioBuf.getChannelData(0).set(float32);
|
|
356
|
+
const source = audioContext.createBufferSource();
|
|
357
|
+
source.buffer = audioBuf;
|
|
358
|
+
source.connect(audioContext.destination);
|
|
359
|
+
source.start();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function floatTo16BitPCM(input) {
|
|
363
|
+
const output = new Int16Array(input.length);
|
|
364
|
+
for (let i = 0; i < input.length; i++) {
|
|
365
|
+
const s = Math.max(-1, Math.min(1, input[i]));
|
|
366
|
+
output[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
|
|
367
|
+
}
|
|
368
|
+
return output.buffer;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Init UI
|
|
372
|
+
updateUI();
|
|
373
|
+
|
|
374
|
+
</script>
|
|
375
|
+
</body>
|
|
376
|
+
|
|
377
|
+
</html>
|