vox-ai-react 1.0.9 → 1.1.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.esm.js CHANGED
@@ -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
- const sample = dataView.getInt16(i * 2, true);
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 MessageIcon = () => (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" }) }));
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 = () => (jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { d: "M18 6L6 18M6 6l12 12" }) }));
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 LoaderIcon = () => (jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "vox-spin", children: jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }));
50
- const AlertIcon = () => (jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }), jsx("path", { d: "M12 9v4" }), jsx("path", { d: "M12 17h.01" })] }));
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, theme }) => {
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 = theme === 'light' ? '#a1a1aa' : '#3f3f46';
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 = theme === 'light' ? '#000000' : '#ffffff';
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
- if (animationRef.current)
100
- cancelAnimationFrame(animationRef.current);
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, theme }) => {
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; // Capture before clearing
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
- // Filter out welcome message and ensure history starts with user
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
- const bgColor = theme === 'light' ? '#ffffff' : '#000000';
155
- const textColor = theme === 'light' ? '#000000' : '#ffffff';
156
- const borderColor = theme === 'light' ? '#a1a1aa' : '#52525b';
157
- const mutedColor = theme === 'light' ? '#71717a' : '#a1a1aa';
158
- return (jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: bgColor }, children: [jsxs("div", { ref: scrollRef, style: { flex: 1, overflowY: 'auto', padding: '24px', display: 'flex', flexDirection: 'column', gap: '16px' }, 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: {
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: `1px solid ${borderColor}`, background: msg.role === 'user' ? (theme === 'light' ? '#f4f4f5' : '#27272a') : bgColor,
165
- color: msg.role === 'user' ? textColor : mutedColor
166
- }, children: msg.text })] }) }, msg.id))), isLoading && (jsx("div", { style: { display: 'flex', justifyContent: 'flex-start' }, children: jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '12px' }, children: [jsx("div", { style: { width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', border: `1px solid ${borderColor}`, color: textColor, opacity: 0.2 }, children: jsx(BotIcon, {}) }), jsx("div", { style: { padding: '16px 24px', border: `1px solid ${borderColor}`, background: bgColor, color: mutedColor }, children: jsx(LoaderIcon, {}) })] }) }))] }), 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: {
167
- flex: 1, padding: '16px 24px', border: `1px solid ${borderColor}`, background: bgColor, color: textColor,
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: textColor, color: bgColor, border: 'none', cursor: 'pointer',
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, theme, onEndCall }) => {
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,20 +402,13 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, theme, onE
418
402
  connect();
419
403
  return () => disconnect();
420
404
  }, []);
421
- const bgColor = theme === 'light' ? '#ffffff' : '#000000';
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 ? textColor : borderColor}`,
431
- boxShadow: connectionState === ConnectionState.CONNECTED ? `0 0 ${30 + speakingIntensity * 40}px ${theme === 'light' ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.15)'}` : 'none',
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: `2px solid ${textColor}`, borderTopColor: 'transparent', borderRadius: '50%', animation: 'vox-spin 1s linear infinite' } })) : (jsx("div", { style: { padding: '20px', color: isMuted ? mutedColor : textColor }, children: isMuted ? jsx(MicOffIcon, {}) : jsx(MicIcon, { size: 40 }) })) }), connectionState === ConnectionState.CONNECTED && (jsx("span", { style: { position: 'absolute', bottom: '-4px', right: '-4px', width: '12px', height: '12px', borderRadius: '50%', background: textColor } }))] }), jsxs("div", { style: { width: '100%', maxWidth: '280px', display: 'flex', flexDirection: 'column', gap: '16px' }, children: [jsxs("p", { style: { textAlign: 'center', fontSize: '10px', fontWeight: 900, color: mutedColor, 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: textColor }, 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, theme: theme })] }), 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: mutedColor, background: theme === 'light' ? '#f4f4f5' : '#18181b', border: `1px solid ${borderColor}` }, children: [jsx(AlertIcon, {}), jsx("span", { style: { marginLeft: '12px' }, children: errorMsg })] })), connectionState === ConnectionState.CONNECTED && (jsxs("button", { onClick: () => { disconnect(); onEndCall === null || onEndCall === void 0 ? void 0 : onEndCall(); }, style: {
434
- padding: '12px 24px', border: `1px solid ${borderColor}`, background: bgColor, color: mutedColor, cursor: 'pointer',
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
  };
@@ -440,14 +417,8 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
440
417
  const [isOpen, setIsOpen] = useState(false);
441
418
  const [activeTab, setActiveTab] = useState('text');
442
419
  const [isVoiceActive, setIsVoiceActive] = useState(false);
443
- // Invert theme for widget (black in light mode, white in dark mode)
444
- const widgetTheme = theme === 'light' ? 'dark' : 'light';
445
- const bgColor = widgetTheme === 'light' ? '#ffffff' : '#000000';
446
- const textColor = widgetTheme === 'light' ? '#000000' : '#ffffff';
447
- const borderColor = widgetTheme === 'light' ? '#a1a1aa' : '#52525b';
448
- const mutedColor = '#71717a';
449
420
  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(); };
421
+ const handleClose = () => { setIsOpen(false); setIsVoiceActive(false); setActiveTab('text'); onClose === null || onClose === void 0 ? void 0 : onClose(); };
451
422
  const handleTabChange = (tab) => {
452
423
  if (tab === 'voice' && activeTab !== 'voice') {
453
424
  setActiveTab('voice');
@@ -458,9 +429,14 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
458
429
  setActiveTab('text');
459
430
  }
460
431
  };
432
+ const handleEndCall = () => {
433
+ setIsVoiceActive(false);
434
+ setActiveTab('text');
435
+ };
461
436
  const positionStyles = position === 'bottom-left'
462
437
  ? { bottom: '16px', left: '16px' }
463
438
  : { bottom: '16px', right: '16px' };
439
+ // Button colors based on theme
464
440
  const btnBg = buttonColor || (theme === 'light' ? '#000000' : '#ffffff');
465
441
  const btnIcon = iconColor || (theme === 'light' ? '#ffffff' : '#000000');
466
442
  return (jsxs(Fragment, { children: [jsx("style", { children: `
@@ -468,26 +444,22 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
468
444
  @keyframes vox-spin { to { transform: rotate(360deg); } }
469
445
  @keyframes vox-ping { 0% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.15); opacity: 0; } 100% { transform: scale(1); opacity: 0; } }
470
446
  .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
447
  ` }), 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: bgColor, overflow: 'hidden', display: 'flex', flexDirection: 'column',
478
- border: `1px solid ${borderColor}`, boxShadow: '0 30px 60px -15px rgba(0,0,0,0.5)'
479
- }, children: [jsxs("div", { style: { height: '56px', background: bgColor, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 24px', flexShrink: 0, borderBottom: `1px solid ${borderColor}` }, children: [jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '12px' }, children: [jsx("div", { style: { width: '32px', height: '32px', border: `1px solid ${borderColor}`, display: 'flex', alignItems: 'center', justifyContent: 'center', color: textColor }, children: jsx(MicIcon, {}) }), jsxs("div", { children: [jsx("h2", { style: { color: textColor, fontWeight: 900, fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.2em', margin: 0 }, children: agentName }), jsxs("p", { style: { color: mutedColor, fontSize: '9px', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', margin: 0, display: 'flex', alignItems: 'center' }, children: [jsx("span", { style: { width: '4px', height: '4px', background: textColor, borderRadius: '50%', marginRight: '8px' } }), "Active"] })] })] }), jsx("button", { onClick: handleClose, style: { background: 'none', border: 'none', color: mutedColor, cursor: 'pointer', padding: '4px' }, children: jsx(XIcon, {}) })] }), jsxs("div", { style: { display: 'flex', padding: '4px', background: widgetTheme === 'light' ? '#f4f4f5' : '#09090b', borderBottom: `1px solid ${borderColor}`, flexShrink: 0 }, children: [jsx("button", { onClick: () => handleTabChange('text'), style: {
448
+ width: 'min(calc(100vw - 32px), 400px)', height: 'min(calc(100vh - 100px), 640px)', maxHeight: '640px',
449
+ background: '#000000', overflow: 'hidden', display: 'flex', flexDirection: 'column',
450
+ border: '1px solid #3f3f46', boxShadow: '0 30px 60px -15px rgba(0,0,0,0.5)'
451
+ }, 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
452
  flex: 1, padding: '12px', fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.2em',
481
- background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'text' ? textColor : mutedColor
482
- }, children: "Text Interface" }), jsx("div", { style: { width: '1px', background: borderColor } }), jsx("button", { onClick: () => handleTabChange('voice'), style: {
453
+ background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'text' ? '#ffffff' : '#71717a', transition: 'color 0.2s'
454
+ }, children: "Text Interface" }), jsx("div", { style: { width: '1px', background: '#3f3f46' } }), jsx("button", { onClick: () => handleTabChange('voice'), style: {
483
455
  flex: 1, padding: '12px', fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.2em',
484
- background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'voice' ? textColor : mutedColor
485
- }, children: "Voice Protocol" })] }), jsx("div", { style: { flex: 1, overflow: 'hidden', position: 'relative', background: bgColor }, children: activeTab === 'text' ? (jsx(TextInterface, { apiKey: apiKey, apiUrl: apiUrl, agentName: agentName, systemInstruction: systemInstruction, theme: widgetTheme })) : (isVoiceActive && jsx(VoiceInterface, { geminiApiKey: geminiApiKey, voiceName: voiceName, systemInstruction: systemInstruction, theme: widgetTheme, onEndCall: () => setIsVoiceActive(false) })) })] })), !isOpen && (jsx("button", { onClick: handleOpen, style: {
456
+ background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'voice' ? '#ffffff' : '#71717a', transition: 'color 0.2s'
457
+ }, 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
458
  width: `${buttonSize}px`, height: `${buttonSize}px`, borderRadius: `${borderRadius}%`,
487
459
  background: btnBg, color: btnIcon, border: 'none', cursor: 'pointer',
488
460
  display: 'flex', alignItems: 'center', justifyContent: 'center',
489
461
  boxShadow: '0 4px 20px rgba(0,0,0,0.3)', transition: 'transform 0.2s'
490
- }, children: jsx(MessageIcon, {}) }))] })] }));
462
+ }, 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
463
  };
492
464
 
493
465
  export { VoxChat, VoxChat as default };