vox-ai-react 1.0.9 → 1.2.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/index.d.ts +1 -0
- package/dist/index.esm.js +80 -82
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +79 -81
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useState,
|
|
2
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
3
3
|
|
|
4
4
|
var ConnectionState;
|
|
5
5
|
(function (ConnectionState) {
|
|
@@ -33,23 +33,22 @@ async function decodeAudioData(arrayBuffer, ctx, sampleRate, numChannels) {
|
|
|
33
33
|
const audioBuffer = ctx.createBuffer(numChannels, numSamples, sampleRate);
|
|
34
34
|
const channelData = audioBuffer.getChannelData(0);
|
|
35
35
|
for (let i = 0; i < numSamples; i++) {
|
|
36
|
-
|
|
37
|
-
channelData[i] = sample / 32768;
|
|
36
|
+
channelData[i] = dataView.getInt16(i * 2, true) / 32768;
|
|
38
37
|
}
|
|
39
38
|
return audioBuffer;
|
|
40
39
|
}
|
|
41
|
-
// Icons
|
|
42
|
-
const
|
|
43
|
-
const MicIcon = ({ size = 14 }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", children: [jsx("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }), jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }), jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }));
|
|
44
|
-
const MicOffIcon = ({ size = 40 }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", children: [jsx("line", { x1: "2", x2: "22", y1: "2", y2: "22" }), jsx("path", { d: "M18.89 13.23A7.12 7.12 0 0 0 19 12v-2" }), jsx("path", { d: "M5 10v2a7 7 0 0 0 12 5" }), jsx("path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33" }), jsx("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }), jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }));
|
|
45
|
-
const XIcon = () => (
|
|
46
|
-
const PhoneOffIcon = () => (jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67m-2.67-3.34a19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91" }), jsx("line", { x1: "22", x2: "2", y1: "2", y2: "22" })] }));
|
|
47
|
-
const BotIcon = () => (jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "M12 8V4H8" }), jsx("rect", { width: "16", height: "12", x: "4", y: "8", rx: "2" }), jsx("path", { d: "M2 14h2" }), jsx("path", { d: "M20 14h2" }), jsx("path", { d: "M15 13v2" }), jsx("path", { d: "M9 13v2" })] }));
|
|
48
|
-
const UserIcon = () => (jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }), jsx("circle", { cx: "12", cy: "7", r: "4" })] }));
|
|
49
|
-
const
|
|
50
|
-
const
|
|
40
|
+
// Icons - matching exact website icons
|
|
41
|
+
const MessageCircleIcon = () => (jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z" }) }));
|
|
42
|
+
const MicIcon = ({ size = 14 }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }), jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }), jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }));
|
|
43
|
+
const MicOffIcon = ({ size = 40 }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("line", { x1: "2", x2: "22", y1: "2", y2: "22" }), jsx("path", { d: "M18.89 13.23A7.12 7.12 0 0 0 19 12v-2" }), jsx("path", { d: "M5 10v2a7 7 0 0 0 12 5" }), jsx("path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33" }), jsx("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }), jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }));
|
|
44
|
+
const XIcon = () => (jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M18 6 6 18" }), jsx("path", { d: "m6 6 12 12" })] }));
|
|
45
|
+
const PhoneOffIcon = () => (jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67m-2.67-3.34a19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91" }), jsx("line", { x1: "22", x2: "2", y1: "2", y2: "22" })] }));
|
|
46
|
+
const BotIcon = () => (jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M12 8V4H8" }), jsx("rect", { width: "16", height: "12", x: "4", y: "8", rx: "2" }), jsx("path", { d: "M2 14h2" }), jsx("path", { d: "M20 14h2" }), jsx("path", { d: "M15 13v2" }), jsx("path", { d: "M9 13v2" })] }));
|
|
47
|
+
const UserIcon = () => (jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }), jsx("circle", { cx: "12", cy: "7", r: "4" })] }));
|
|
48
|
+
const Loader2Icon = () => (jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { animation: 'vox-spin 1s linear infinite' }, children: jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }));
|
|
49
|
+
const AlertCircleIcon = () => (jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("circle", { cx: "12", cy: "12", r: "10" }), jsx("line", { x1: "12", x2: "12", y1: "8", y2: "12" }), jsx("line", { x1: "12", x2: "12.01", y1: "16", y2: "16" })] }));
|
|
51
50
|
// Visualizer Component
|
|
52
|
-
const Visualizer = ({ analyser, isActive
|
|
51
|
+
const Visualizer = ({ analyser, isActive }) => {
|
|
53
52
|
const canvasRef = useRef(null);
|
|
54
53
|
const animationRef = useRef();
|
|
55
54
|
useEffect(() => {
|
|
@@ -64,7 +63,7 @@ const Visualizer = ({ analyser, isActive, theme }) => {
|
|
|
64
63
|
const height = canvas.height;
|
|
65
64
|
ctx.clearRect(0, 0, width, height);
|
|
66
65
|
if (!analyser || !isActive) {
|
|
67
|
-
ctx.strokeStyle =
|
|
66
|
+
ctx.strokeStyle = '#3f3f46';
|
|
68
67
|
ctx.lineWidth = 1;
|
|
69
68
|
ctx.beginPath();
|
|
70
69
|
ctx.moveTo(0, height / 2);
|
|
@@ -76,7 +75,7 @@ const Visualizer = ({ analyser, isActive, theme }) => {
|
|
|
76
75
|
const bufferLength = analyser.frequencyBinCount;
|
|
77
76
|
const dataArray = new Uint8Array(bufferLength);
|
|
78
77
|
analyser.getByteTimeDomainData(dataArray);
|
|
79
|
-
ctx.strokeStyle =
|
|
78
|
+
ctx.strokeStyle = '#ffffff';
|
|
80
79
|
ctx.lineWidth = 1;
|
|
81
80
|
ctx.beginPath();
|
|
82
81
|
const sliceWidth = width / bufferLength;
|
|
@@ -95,15 +94,13 @@ const Visualizer = ({ analyser, isActive, theme }) => {
|
|
|
95
94
|
animationRef.current = requestAnimationFrame(draw);
|
|
96
95
|
};
|
|
97
96
|
draw();
|
|
98
|
-
return () => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}, [analyser, isActive, theme]);
|
|
103
|
-
return jsx("canvas", { ref: canvasRef, width: 300, height: 50, className: "vox-visualizer" });
|
|
97
|
+
return () => { if (animationRef.current)
|
|
98
|
+
cancelAnimationFrame(animationRef.current); };
|
|
99
|
+
}, [analyser, isActive]);
|
|
100
|
+
return jsx("canvas", { ref: canvasRef, width: 300, height: 50, style: { width: '100%', height: '50px', background: 'transparent' } });
|
|
104
101
|
};
|
|
105
102
|
// Text Interface Component
|
|
106
|
-
const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction
|
|
103
|
+
const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction }) => {
|
|
107
104
|
const [messages, setMessages] = useState([
|
|
108
105
|
{ id: 'welcome', role: 'assistant', text: `VOX INITIALIZED. GREETINGS. I AM ${agentName}. HOW MAY I ASSIST YOUR INQUIRY?` }
|
|
109
106
|
]);
|
|
@@ -119,16 +116,13 @@ const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction, theme })
|
|
|
119
116
|
e === null || e === void 0 ? void 0 : e.preventDefault();
|
|
120
117
|
if (!input.trim() || isLoading)
|
|
121
118
|
return;
|
|
122
|
-
const messageText = input;
|
|
119
|
+
const messageText = input;
|
|
123
120
|
const userMsg = { id: Date.now().toString(), role: 'user', text: messageText };
|
|
124
121
|
setMessages(prev => [...prev, userMsg]);
|
|
125
122
|
setInput('');
|
|
126
123
|
setIsLoading(true);
|
|
127
124
|
try {
|
|
128
|
-
|
|
129
|
-
const history = messages
|
|
130
|
-
.filter(m => m.id !== 'welcome')
|
|
131
|
-
.map(m => ({ role: m.role === 'assistant' ? 'model' : 'user', text: m.text }));
|
|
125
|
+
const history = messages.filter(m => m.id !== 'welcome').map(m => ({ role: m.role === 'assistant' ? 'model' : 'user', text: m.text }));
|
|
132
126
|
const response = await fetch(`${apiUrl}/api/v1/chat`, {
|
|
133
127
|
method: 'POST',
|
|
134
128
|
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
|
@@ -151,29 +145,26 @@ const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction, theme })
|
|
|
151
145
|
setIsLoading(false);
|
|
152
146
|
}
|
|
153
147
|
};
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
160
|
-
border: `1px solid ${msg.role === 'user' ? textColor : borderColor}`,
|
|
161
|
-
background: msg.role === 'user' ? textColor : 'transparent', color: msg.role === 'user' ? bgColor : textColor
|
|
148
|
+
return (jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: '#000000' }, children: [jsxs("div", { ref: scrollRef, style: { flex: 1, overflowY: 'auto', padding: '24px', display: 'flex', flexDirection: 'column', gap: '24px' }, children: [messages.map((msg) => (jsx("div", { style: { display: 'flex', justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start' }, children: jsxs("div", { style: { display: 'flex', flexDirection: msg.role === 'user' ? 'row-reverse' : 'row', alignItems: 'flex-start', gap: '12px', maxWidth: '85%' }, children: [jsx("div", { style: {
|
|
149
|
+
flexShrink: 0, width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
150
|
+
border: msg.role === 'user' ? '1px solid #ffffff' : '1px solid #52525b',
|
|
151
|
+
background: msg.role === 'user' ? '#ffffff' : 'transparent',
|
|
152
|
+
color: msg.role === 'user' ? '#000000' : '#ffffff'
|
|
162
153
|
}, children: msg.role === 'user' ? jsx(UserIcon, {}) : jsx(BotIcon, {}) }), jsx("div", { style: {
|
|
163
154
|
padding: '12px 16px', fontSize: '11px', fontWeight: 700, letterSpacing: '0.05em', lineHeight: 1.6,
|
|
164
|
-
border:
|
|
165
|
-
color: msg.role === 'user' ?
|
|
166
|
-
}, children: msg.text })] }) }, msg.id))), isLoading && (jsx("div", { style: { display: 'flex', justifyContent: 'flex-start' }, children: jsxs("div", { style: { display: 'flex', alignItems: '
|
|
167
|
-
flex: 1, padding: '16px 24px', border:
|
|
155
|
+
border: '1px solid #3f3f46', background: msg.role === 'user' ? '#27272a' : '#000000',
|
|
156
|
+
color: msg.role === 'user' ? '#ffffff' : '#d4d4d8'
|
|
157
|
+
}, children: msg.text })] }) }, msg.id))), isLoading && (jsx("div", { style: { display: 'flex', justifyContent: 'flex-start' }, children: jsxs("div", { style: { display: 'flex', alignItems: 'flex-start', gap: '12px' }, children: [jsx("div", { style: { flexShrink: 0, width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid #3f3f46', color: '#ffffff', opacity: 0.2 }, children: jsx(BotIcon, {}) }), jsx("div", { style: { padding: '16px 24px', border: '1px solid #3f3f46', background: '#000000', color: '#71717a' }, children: jsx(Loader2Icon, {}) })] }) }))] }), jsx("div", { style: { padding: '24px' }, children: jsxs("form", { onSubmit: handleSend, style: { display: 'flex', gap: '16px' }, children: [jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: "INPUT COMMAND...", disabled: isLoading, style: {
|
|
158
|
+
flex: 1, padding: '16px 24px', border: '1px solid #52525b', background: '#000000', color: '#ffffff',
|
|
168
159
|
outline: 'none', fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.1em'
|
|
169
160
|
} }), jsx("button", { type: "submit", disabled: !input.trim() || isLoading, style: {
|
|
170
|
-
padding: '16px 24px', background:
|
|
161
|
+
padding: '16px 24px', background: '#ffffff', color: '#000000', border: 'none', cursor: 'pointer',
|
|
171
162
|
fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.1em',
|
|
172
163
|
opacity: !input.trim() || isLoading ? 0.2 : 1
|
|
173
164
|
}, children: "EXECUTE" })] }) })] }));
|
|
174
165
|
};
|
|
175
166
|
// Voice Interface Component
|
|
176
|
-
const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction,
|
|
167
|
+
const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall }) => {
|
|
177
168
|
const [connectionState, setConnectionState] = useState(ConnectionState.DISCONNECTED);
|
|
178
169
|
const [errorMsg, setErrorMsg] = useState('');
|
|
179
170
|
const [isMuted, setIsMuted] = useState(false);
|
|
@@ -295,14 +286,12 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, theme, onE
|
|
|
295
286
|
monitorSpeaking();
|
|
296
287
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
297
288
|
streamRef.current = stream;
|
|
298
|
-
// Connect to Gemini Live API via WebSocket
|
|
299
289
|
const wsUrl = `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent?key=${geminiApiKey}`;
|
|
300
290
|
console.log('[VOX] Connecting to Gemini Live API...');
|
|
301
291
|
const ws = new WebSocket(wsUrl);
|
|
302
292
|
sessionRef.current = ws;
|
|
303
293
|
ws.onopen = () => {
|
|
304
294
|
console.log('[VOX] WebSocket connected, sending setup...');
|
|
305
|
-
// Send setup message
|
|
306
295
|
const setupMsg = {
|
|
307
296
|
setup: {
|
|
308
297
|
model: 'models/gemini-2.5-flash-native-audio-preview-09-2025',
|
|
@@ -315,14 +304,12 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, theme, onE
|
|
|
315
304
|
ws.onmessage = async (event) => {
|
|
316
305
|
var _a, _b, _c, _d, _e, _f;
|
|
317
306
|
let data;
|
|
318
|
-
// Handle both text and blob responses
|
|
319
307
|
if (event.data instanceof Blob) {
|
|
320
308
|
const text = await event.data.text();
|
|
321
309
|
try {
|
|
322
310
|
data = JSON.parse(text);
|
|
323
311
|
}
|
|
324
312
|
catch (e) {
|
|
325
|
-
console.log('[VOX] Non-JSON blob received');
|
|
326
313
|
return;
|
|
327
314
|
}
|
|
328
315
|
}
|
|
@@ -331,7 +318,6 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, theme, onE
|
|
|
331
318
|
data = JSON.parse(event.data);
|
|
332
319
|
}
|
|
333
320
|
catch (e) {
|
|
334
|
-
console.log('[VOX] Non-JSON message received');
|
|
335
321
|
return;
|
|
336
322
|
}
|
|
337
323
|
}
|
|
@@ -339,7 +325,6 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, theme, onE
|
|
|
339
325
|
if (data.setupComplete) {
|
|
340
326
|
console.log('[VOX] Setup complete, voice ready!');
|
|
341
327
|
setConnectionState(ConnectionState.CONNECTED);
|
|
342
|
-
// Setup audio input pipeline
|
|
343
328
|
const source = inputCtx.createMediaStreamSource(stream);
|
|
344
329
|
sourceNodeRef.current = source;
|
|
345
330
|
const processor = inputCtx.createScriptProcessor(2048, 1, 1);
|
|
@@ -355,7 +340,6 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, theme, onE
|
|
|
355
340
|
source.connect(processor);
|
|
356
341
|
processor.connect(inputCtx.destination);
|
|
357
342
|
}
|
|
358
|
-
// Handle audio output
|
|
359
343
|
const audioData = (_e = (_d = (_c = (_b = (_a = data.serverContent) === null || _a === void 0 ? void 0 : _a.modelTurn) === null || _b === void 0 ? void 0 : _b.parts) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.inlineData) === null || _e === void 0 ? void 0 : _e.data;
|
|
360
344
|
if (audioData) {
|
|
361
345
|
const ctx = outputAudioContextRef.current;
|
|
@@ -418,36 +402,49 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, theme, onE
|
|
|
418
402
|
connect();
|
|
419
403
|
return () => disconnect();
|
|
420
404
|
}, []);
|
|
421
|
-
|
|
422
|
-
const textColor = theme === 'light' ? '#000000' : '#ffffff';
|
|
423
|
-
const borderColor = theme === 'light' ? '#a1a1aa' : '#52525b';
|
|
424
|
-
const mutedColor = '#71717a';
|
|
425
|
-
return (jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '32px', height: '100%', padding: '24px' }, children: [jsxs("div", { style: { position: 'relative' }, children: [connectionState === ConnectionState.CONNECTED && isSpeaking && (jsx("div", { style: {
|
|
426
|
-
position: 'absolute', inset: 0, borderRadius: '50%', border: `1px solid ${textColor}`,
|
|
427
|
-
transform: `scale(${1 + speakingIntensity * 0.3})`, opacity: 0.2, animation: 'vox-ping 1s infinite'
|
|
428
|
-
} })), jsx("div", { style: {
|
|
405
|
+
return (jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '32px', height: '100%', padding: '24px' }, children: [jsxs("div", { style: { position: 'relative' }, children: [connectionState === ConnectionState.CONNECTED && isSpeaking && (jsxs(Fragment, { children: [jsx("div", { style: { position: 'absolute', inset: 0, borderRadius: '50%', border: '1px solid rgba(255,255,255,0.2)', transform: `scale(${1 + speakingIntensity * 0.3})`, animation: 'vox-ping 1s infinite' } }), jsx("div", { style: { position: 'absolute', inset: 0, borderRadius: '50%', border: '1px solid rgba(255,255,255,0.1)', transform: `scale(${1 + speakingIntensity * 0.5})`, transition: 'transform 0.1s ease-out' } })] })), jsx("div", { style: {
|
|
429
406
|
width: '112px', height: '112px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
430
|
-
border: `1px solid ${connectionState === ConnectionState.CONNECTED ?
|
|
431
|
-
boxShadow: connectionState === ConnectionState.CONNECTED ? `0 0 ${30 + speakingIntensity * 40}px
|
|
407
|
+
border: `1px solid ${connectionState === ConnectionState.CONNECTED ? '#ffffff' : '#52525b'}`,
|
|
408
|
+
boxShadow: connectionState === ConnectionState.CONNECTED ? `0 0 ${30 + speakingIntensity * 40}px rgba(255,255,255,${0.15 + speakingIntensity * 0.25})` : 'none',
|
|
432
409
|
transform: isSpeaking ? `scale(${1 + speakingIntensity * 0.08})` : 'scale(1)', transition: 'all 0.3s'
|
|
433
|
-
}, children: connectionState === ConnectionState.CONNECTING ? (jsx("div", { style: { width: '40px', height: '40px', border:
|
|
434
|
-
padding: '12px 24px', border:
|
|
410
|
+
}, children: connectionState === ConnectionState.CONNECTING ? (jsx("div", { style: { width: '40px', height: '40px', border: '2px solid #ffffff', borderTopColor: 'transparent', borderRadius: '50%', animation: 'vox-spin 1s linear infinite' } })) : (jsx("div", { style: { padding: '20px', color: isMuted ? '#52525b' : '#ffffff' }, children: isMuted ? jsx(MicOffIcon, {}) : jsx(MicIcon, { size: 40 }) })) }), connectionState === ConnectionState.CONNECTED && (jsxs("span", { style: { position: 'absolute', bottom: '-4px', right: '-4px', display: 'flex', width: '12px', height: '12px' }, children: [jsx("span", { style: { position: 'absolute', display: 'inline-flex', width: '100%', height: '100%', borderRadius: '50%', background: '#ffffff', opacity: isSpeaking ? 1 : 0.75, animation: isSpeaking ? 'vox-ping 1s infinite' : 'none' } }), jsx("span", { style: { position: 'relative', display: 'inline-flex', borderRadius: '50%', width: '12px', height: '12px', background: '#ffffff' } })] }))] }), jsxs("div", { style: { width: '100%', maxWidth: '280px', display: 'flex', flexDirection: 'column', gap: '16px' }, children: [jsxs("p", { style: { textAlign: 'center', fontSize: '10px', fontWeight: 900, color: '#71717a', textTransform: 'uppercase', letterSpacing: '0.2em', height: '20px' }, children: [connectionState === ConnectionState.CONNECTING && 'Initializing...', connectionState === ConnectionState.CONNECTED && (isSpeaking ? 'Agent Speaking...' : isMuted ? 'Signal Muted' : 'Listening...'), connectionState === ConnectionState.ERROR && 'Connection Error', connectionState === ConnectionState.DISCONNECTED && 'Disconnected'] }), connectionState === ConnectionState.CONNECTED && callDuration > 0 && (jsxs("p", { style: { textAlign: 'center', fontSize: '11px', fontFamily: 'monospace', color: '#ffffff' }, children: [Math.floor(callDuration / 60).toString().padStart(2, '0'), ":", (callDuration % 60).toString().padStart(2, '0')] })), jsx(Visualizer, { analyser: inputAnalyserRef.current, isActive: connectionState === ConnectionState.CONNECTED && !isMuted })] }), connectionState === ConnectionState.ERROR && (jsxs("div", { style: { display: 'flex', alignItems: 'center', fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.1em', padding: '12px 16px', maxWidth: '280px', textAlign: 'center', color: '#71717a', background: '#18181b', border: '1px solid #3f3f46' }, children: [jsx(AlertCircleIcon, {}), jsx("span", { style: { marginLeft: '12px' }, children: errorMsg })] })), connectionState === ConnectionState.CONNECTED && (jsxs("button", { onClick: () => { disconnect(); onEndCall === null || onEndCall === void 0 ? void 0 : onEndCall(); }, style: {
|
|
411
|
+
padding: '12px 24px', border: '1px solid #3f3f46', background: '#000000', color: '#71717a', cursor: 'pointer',
|
|
435
412
|
fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.1em', display: 'flex', alignItems: 'center', gap: '8px'
|
|
436
413
|
}, children: [jsx(PhoneOffIcon, {}), "End Call"] }))] }));
|
|
437
414
|
};
|
|
438
415
|
// Main VoxChat Component
|
|
439
|
-
const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', agentName = 'VOX-01', voiceName = 'Kore', systemInstruction = 'You are a helpful AI assistant.', position = 'bottom-right', theme = 'dark', buttonColor, buttonSize = 56, borderRadius = 0, iconColor, onOpen, onClose }) => {
|
|
416
|
+
const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', agentName: propAgentName = 'VOX-01', voiceName: propVoiceName = 'Kore', systemInstruction: propSystemInstruction = 'You are a helpful AI assistant.', position = 'bottom-right', theme = 'dark', buttonColor, buttonSize = 56, borderRadius = 0, iconColor, onOpen, onClose, useServerConfig = false }) => {
|
|
440
417
|
const [isOpen, setIsOpen] = useState(false);
|
|
441
418
|
const [activeTab, setActiveTab] = useState('text');
|
|
442
419
|
const [isVoiceActive, setIsVoiceActive] = useState(false);
|
|
443
|
-
//
|
|
444
|
-
const
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
420
|
+
// Server config state
|
|
421
|
+
const [serverConfig, setServerConfig] = useState(null);
|
|
422
|
+
const [configLoaded, setConfigLoaded] = useState(!useServerConfig);
|
|
423
|
+
// Fetch config from server if useServerConfig is true
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
if (useServerConfig && apiKey && apiUrl) {
|
|
426
|
+
fetch(`${apiUrl}/api/v1/config`, {
|
|
427
|
+
headers: { 'x-api-key': apiKey }
|
|
428
|
+
})
|
|
429
|
+
.then(res => res.json())
|
|
430
|
+
.then(data => {
|
|
431
|
+
if (data.agentConfig) {
|
|
432
|
+
setServerConfig(data.agentConfig);
|
|
433
|
+
}
|
|
434
|
+
setConfigLoaded(true);
|
|
435
|
+
})
|
|
436
|
+
.catch(err => {
|
|
437
|
+
console.error('[VOX] Failed to fetch server config:', err);
|
|
438
|
+
setConfigLoaded(true);
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}, [useServerConfig, apiKey, apiUrl]);
|
|
442
|
+
// Use server config if available, otherwise use props
|
|
443
|
+
const agentName = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.name) || propAgentName;
|
|
444
|
+
const voiceName = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.voiceName) || propVoiceName;
|
|
445
|
+
const systemInstruction = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.systemInstruction) || propSystemInstruction;
|
|
449
446
|
const handleOpen = () => { setIsOpen(true); onOpen === null || onOpen === void 0 ? void 0 : onOpen(); };
|
|
450
|
-
const handleClose = () => { setIsOpen(false); setIsVoiceActive(false); onClose === null || onClose === void 0 ? void 0 : onClose(); };
|
|
447
|
+
const handleClose = () => { setIsOpen(false); setIsVoiceActive(false); setActiveTab('text'); onClose === null || onClose === void 0 ? void 0 : onClose(); };
|
|
451
448
|
const handleTabChange = (tab) => {
|
|
452
449
|
if (tab === 'voice' && activeTab !== 'voice') {
|
|
453
450
|
setActiveTab('voice');
|
|
@@ -458,9 +455,14 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
|
|
|
458
455
|
setActiveTab('text');
|
|
459
456
|
}
|
|
460
457
|
};
|
|
458
|
+
const handleEndCall = () => {
|
|
459
|
+
setIsVoiceActive(false);
|
|
460
|
+
setActiveTab('text');
|
|
461
|
+
};
|
|
461
462
|
const positionStyles = position === 'bottom-left'
|
|
462
463
|
? { bottom: '16px', left: '16px' }
|
|
463
464
|
: { bottom: '16px', right: '16px' };
|
|
465
|
+
// Button colors based on theme
|
|
464
466
|
const btnBg = buttonColor || (theme === 'light' ? '#000000' : '#ffffff');
|
|
465
467
|
const btnIcon = iconColor || (theme === 'light' ? '#ffffff' : '#000000');
|
|
466
468
|
return (jsxs(Fragment, { children: [jsx("style", { children: `
|
|
@@ -468,26 +470,22 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
|
|
|
468
470
|
@keyframes vox-spin { to { transform: rotate(360deg); } }
|
|
469
471
|
@keyframes vox-ping { 0% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.15); opacity: 0; } 100% { transform: scale(1); opacity: 0; } }
|
|
470
472
|
.vox-widget { animation: vox-fade-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
|
|
471
|
-
.vox-spin { animation: vox-spin 1s linear infinite; }
|
|
472
|
-
.vox-visualizer { width: 100%; height: 50px; background: transparent; }
|
|
473
|
-
.vox-scrollbar::-webkit-scrollbar { display: none; }
|
|
474
|
-
.vox-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
475
473
|
` }), jsxs("div", { style: { position: 'fixed', zIndex: 10000, display: 'flex', flexDirection: 'column', alignItems: position === 'bottom-left' ? 'flex-start' : 'flex-end', gap: '24px', ...positionStyles }, children: [isOpen && (jsxs("div", { className: "vox-widget", style: {
|
|
476
|
-
width: 'min(calc(100vw - 32px), 400px)', height: 'min(calc(100vh - 100px), 640px)',
|
|
477
|
-
background:
|
|
478
|
-
border:
|
|
479
|
-
}, children: [jsxs("div", { style: { height: '56px', background:
|
|
474
|
+
width: 'min(calc(100vw - 32px), 400px)', height: 'min(calc(100vh - 100px), 640px)', maxHeight: '640px',
|
|
475
|
+
background: '#000000', overflow: 'hidden', display: 'flex', flexDirection: 'column',
|
|
476
|
+
border: '1px solid #3f3f46', boxShadow: '0 30px 60px -15px rgba(0,0,0,0.5)'
|
|
477
|
+
}, children: [jsxs("div", { style: { height: '56px', background: '#000000', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 16px', flexShrink: 0, borderBottom: '1px solid #3f3f46' }, children: [jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '12px' }, children: [jsx("div", { style: { width: '32px', height: '32px', border: '1px solid #52525b', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#ffffff' }, children: jsx(MessageCircleIcon, {}) }), jsxs("div", { children: [jsx("h2", { style: { color: '#ffffff', fontWeight: 900, fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.2em', margin: 0 }, children: agentName }), jsxs("p", { style: { color: '#a1a1aa', fontSize: '9px', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', margin: 0, display: 'flex', alignItems: 'center' }, children: [jsx("span", { style: { width: '4px', height: '4px', background: '#ffffff', borderRadius: '50%', marginRight: '8px' } }), "Active"] })] })] }), jsx("button", { onClick: handleClose, style: { background: 'none', border: 'none', color: '#a1a1aa', cursor: 'pointer', padding: '4px', transition: 'color 0.2s' }, children: jsx(XIcon, {}) })] }), jsxs("div", { style: { display: 'flex', padding: '4px', background: '#18181b', borderBottom: '1px solid #3f3f46', flexShrink: 0 }, children: [jsx("button", { onClick: () => handleTabChange('text'), style: {
|
|
480
478
|
flex: 1, padding: '12px', fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.2em',
|
|
481
|
-
background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'text' ?
|
|
482
|
-
}, children: "Text Interface" }), jsx("div", { style: { width: '1px', background:
|
|
479
|
+
background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'text' ? '#ffffff' : '#71717a', transition: 'color 0.2s'
|
|
480
|
+
}, children: "Text Interface" }), jsx("div", { style: { width: '1px', background: '#3f3f46' } }), jsx("button", { onClick: () => handleTabChange('voice'), style: {
|
|
483
481
|
flex: 1, padding: '12px', fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.2em',
|
|
484
|
-
background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'voice' ?
|
|
485
|
-
}, children: "Voice Protocol" })] }), jsx("div", { style: { flex: 1, overflow: 'hidden', position: 'relative', background:
|
|
482
|
+
background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'voice' ? '#ffffff' : '#71717a', transition: 'color 0.2s'
|
|
483
|
+
}, children: "Voice Protocol" })] }), jsx("div", { style: { flex: 1, overflow: 'hidden', position: 'relative', background: '#000000' }, children: activeTab === 'text' ? (jsx(TextInterface, { apiKey: apiKey, apiUrl: apiUrl, agentName: agentName, systemInstruction: systemInstruction })) : (isVoiceActive ? (jsx(VoiceInterface, { geminiApiKey: geminiApiKey, voiceName: voiceName, systemInstruction: systemInstruction, onEndCall: handleEndCall })) : (jsx(TextInterface, { apiKey: apiKey, apiUrl: apiUrl, agentName: agentName, systemInstruction: systemInstruction }))) })] })), !isOpen && (jsx("button", { onClick: handleOpen, style: {
|
|
486
484
|
width: `${buttonSize}px`, height: `${buttonSize}px`, borderRadius: `${borderRadius}%`,
|
|
487
485
|
background: btnBg, color: btnIcon, border: 'none', cursor: 'pointer',
|
|
488
486
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
489
487
|
boxShadow: '0 4px 20px rgba(0,0,0,0.3)', transition: 'transform 0.2s'
|
|
490
|
-
}, children: jsx(
|
|
488
|
+
}, children: jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: jsx("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z" }) }) }))] })] }));
|
|
491
489
|
};
|
|
492
490
|
|
|
493
491
|
export { VoxChat, VoxChat as default };
|