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 CHANGED
@@ -14,6 +14,7 @@ export interface VoxChatProps {
14
14
  iconColor?: string;
15
15
  onOpen?: () => void;
16
16
  onClose?: () => void;
17
+ useServerConfig?: boolean;
17
18
  }
18
19
  export declare const VoxChat: React.FC<VoxChatProps>;
19
20
  export default VoxChat;
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
- import { useState, useRef, useEffect, useCallback } from 'react';
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
- 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,36 +402,49 @@ 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
  };
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
- // 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';
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: 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: {
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' ? textColor : mutedColor
482
- }, children: "Text Interface" }), jsx("div", { style: { width: '1px', background: borderColor } }), jsx("button", { onClick: () => handleTabChange('voice'), style: {
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' ? 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: {
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(MessageIcon, {}) }))] })] }));
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 };