vox-ai-react 1.3.4 → 1.6.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
@@ -37,16 +37,18 @@ async function decodeAudioData(arrayBuffer, ctx, sampleRate, numChannels) {
37
37
  }
38
38
  return audioBuffer;
39
39
  }
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 VoxLogoIcon = () => (jsxs("svg", { width: "12", height: "12", viewBox: "0 0 48 48", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M28.764 25.966c0-4.489-1.936-5.99-4.764-5.99s-4.764 1.501-4.764 5.99m-.55 0h.55v5.324h0h-.55a1.826 1.826 0 0 1-1.826-1.826v-1.672a1.826 1.826 0 0 1 1.826-1.826m10.628 5.324h-.55h0v-5.324h.55a1.826 1.826 0 0 1 1.826 1.826v1.672a1.826 1.826 0 0 1-1.826 1.826" }), jsx("path", { d: "M24 42.476L12.938 33.58V5.524L24 16.351" }), jsx("path", { d: "M24 42.476L9.125 35.557V7.501L24 16.351" }), jsx("path", { d: "M24 42.476L5.5 38.004V9.948L24 16.351m0 26.125l11.062-8.896V5.524L24 16.351" }), jsx("path", { d: "m24 42.476l14.875-6.919V7.501L24 16.351" }), jsx("path", { d: "m24 42.476l18.5-4.472V9.948L24 16.351" })] }));
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" })] }));
40
+ // Icons
41
+ const MessageCircleIcon = () => (jsx("svg", { width: "20", height: "20", 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 = 20 }) => (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 = 20 }) => (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: "18", height: "18", 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: "20", height: "20", 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 UserIcon = () => (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: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }), jsx("circle", { cx: "12", cy: "7", r: "4" })] }));
47
+ const BotIcon = () => (jsxs("svg", { width: "18", height: "18", 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" })] }));
48
+ const SendIcon = () => (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: "m22 2-7 20-4-9-9-4Z" }), jsx("path", { d: "M22 2 11 13" })] }));
49
+ const PlusIcon = () => (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: "M5 12h14" }), jsx("path", { d: "M12 5v14" })] }));
50
+ const Loader2Icon = () => (jsx("svg", { width: "12", height: "12", 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" }) }));
51
+ const AlertCircleIcon = () => (jsxs("svg", { width: "16", height: "16", 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" })] }));
50
52
  // Visualizer Component
51
53
  const Visualizer = ({ analyser, isActive }) => {
52
54
  const canvasRef = useRef(null);
@@ -100,9 +102,9 @@ const Visualizer = ({ analyser, isActive }) => {
100
102
  return jsx("canvas", { ref: canvasRef, width: 300, height: 50, style: { width: '100%', height: '50px', background: 'transparent' } });
101
103
  };
102
104
  // Text Interface Component
103
- const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction }) => {
105
+ const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction, logoUrl }) => {
104
106
  const [messages, setMessages] = useState([
105
- { id: 'welcome', role: 'assistant', text: `VOX INITIALIZED. GREETINGS. I AM ${agentName}. HOW MAY I ASSIST YOUR INQUIRY?` }
107
+ { id: 'welcome', role: 'assistant', text: `Hello! I'm ${agentName}. How can I help you today?` }
106
108
  ]);
107
109
  const [input, setInput] = useState('');
108
110
  const [isLoading, setIsLoading] = useState(false);
@@ -112,7 +114,6 @@ const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction }) => {
112
114
  scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
113
115
  }, [messages]);
114
116
  const handleSend = async (e) => {
115
- var _a;
116
117
  e === null || e === void 0 ? void 0 : e.preventDefault();
117
118
  if (!input.trim() || isLoading)
118
119
  return;
@@ -133,35 +134,36 @@ const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction }) => {
133
134
  throw new Error(errorData.error || 'API request failed');
134
135
  }
135
136
  const data = await response.json();
136
- const botMsg = { id: (Date.now() + 1).toString(), role: 'assistant', text: ((_a = data.response) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'ERROR: NO RESPONSE' };
137
+ const botMsg = { id: (Date.now() + 1).toString(), role: 'assistant', text: data.response || 'Error: No response' };
137
138
  setMessages(prev => [...prev, botMsg]);
138
139
  }
139
140
  catch (err) {
140
141
  console.error('VOX Chat Error:', err);
141
- const errorMsg = { id: (Date.now() + 1).toString(), role: 'assistant', text: `ERROR: ${err.message || 'SIGNAL LOST. RETRY.'}` };
142
+ const errorMsg = { id: (Date.now() + 1).toString(), role: 'assistant', text: `Error: ${err.message || 'Connection lost. Please try again.'}` };
142
143
  setMessages(prev => [...prev, errorMsg]);
143
144
  }
144
145
  finally {
145
146
  setIsLoading(false);
146
147
  }
147
148
  };
148
- return (jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: '#000000' }, children: [jsxs("div", { ref: scrollRef, style: { flex: 1, overflowY: 'auto', padding: '16px', 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: '8px', maxWidth: '85%' }, children: [jsx("div", { style: {
149
+ return (jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: '#000000', fontFamily: 'Outfit, sans-serif' }, children: [jsxs("div", { ref: scrollRef, style: { flex: 1, overflowY: 'auto', padding: '16px', 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: '8px', maxWidth: '85%' }, children: [jsx("div", { style: {
149
150
  flexShrink: 0, width: '24px', height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center',
150
- border: msg.role === 'user' ? '1px solid #ffffff' : '1px solid #52525b',
151
+ border: msg.role === 'user' ? '1px solid #ffffff' : 'none',
151
152
  background: msg.role === 'user' ? '#ffffff' : 'transparent',
152
153
  color: msg.role === 'user' ? '#000000' : '#ffffff'
153
- }, children: msg.role === 'user' ? jsx(UserIcon, {}) : jsx(VoxLogoIcon, {}) }), jsx("div", { style: {
154
- padding: '8px 12px', fontSize: '10px', fontWeight: 700, letterSpacing: '0.05em', lineHeight: 1.6,
155
- border: '1px solid #3f3f46', background: msg.role === 'user' ? '#27272a' : '#000000',
154
+ }, children: msg.role === 'user' ? jsx(UserIcon, {}) : (logoUrl ? jsx("img", { src: logoUrl, alt: agentName, style: { width: '24px', height: '24px', objectFit: 'contain' } }) : jsx(BotIcon, {})) }), jsx("div", { style: {
155
+ padding: '8px 12px', fontSize: '14px', fontWeight: 500, lineHeight: 1.6,
156
+ border: '1px solid', borderRadius: '8px',
157
+ borderColor: msg.role === 'user' ? '#52525b' : '#3f3f46',
158
+ background: msg.role === 'user' ? '#27272a' : '#000000',
156
159
  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: '8px' }, children: [jsx("div", { style: { flexShrink: 0, width: '24px', height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid #3f3f46', color: '#ffffff', opacity: 0.2 }, children: jsx(VoxLogoIcon, {}) }), jsx("div", { style: { padding: '12px 16px', border: '1px solid #3f3f46', background: '#000000', color: '#71717a' }, children: jsx(Loader2Icon, {}) })] }) }))] }), jsx("div", { style: { padding: '16px' }, children: jsxs("form", { onSubmit: handleSend, style: { display: 'flex', gap: '8px' }, children: [jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: "INPUT COMMAND...", disabled: isLoading, style: {
158
- flex: 1, padding: '12px 16px', border: '1px solid #52525b', background: '#000000', color: '#ffffff',
159
- outline: 'none', fontSize: '9px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.1em'
160
- } }), jsx("button", { type: "submit", disabled: !input.trim() || isLoading, style: {
161
- padding: '12px 16px', background: '#ffffff', color: '#000000', border: 'none', cursor: 'pointer',
162
- fontSize: '9px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.1em',
163
- opacity: !input.trim() || isLoading ? 0.2 : 1
164
- }, children: "EXECUTE" })] }) })] }));
160
+ }, children: msg.text })] }) }, msg.id))), isLoading && (jsx("div", { style: { display: 'flex', justifyContent: 'flex-start' }, children: jsxs("div", { style: { display: 'flex', alignItems: 'flex-start', gap: '8px' }, children: [jsx("div", { style: { flexShrink: 0, width: '24px', height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#ffffff', opacity: 0.2 }, children: logoUrl ? jsx("img", { src: logoUrl, alt: agentName, style: { width: '24px', height: '24px', objectFit: 'contain' } }) : jsx(BotIcon, {}) }), jsx("div", { style: { padding: '12px 16px', border: '1px solid #3f3f46', background: '#000000', borderRadius: '8px' }, children: jsx(Loader2Icon, {}) })] }) }))] }), jsx("div", { style: { padding: '16px' }, children: jsx("form", { onSubmit: handleSend, children: jsxs("div", { style: {
161
+ width: '100%', cursor: 'text', overflow: 'clip', padding: '6px', boxShadow: '0 4px 6px -1px rgba(0,0,0,0.1)', border: '1px solid #3f3f46', borderRadius: '9999px',
162
+ display: 'grid', gridTemplateColumns: 'auto 1fr auto', gridTemplateRows: 'auto', gap: '4px', alignItems: 'center', background: 'rgba(0,0,0,0.5)'
163
+ }, children: [jsx("button", { type: "button", style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '32px', height: '32px', borderRadius: '9999px', outline: 'none', border: 'none', background: 'transparent', cursor: 'pointer', color: '#71717a' }, children: jsx(PlusIcon, {}) }), jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), onKeyDown: (e) => { if (e.key === 'Enter' && !e.shiftKey) {
164
+ e.preventDefault();
165
+ handleSend();
166
+ } }, placeholder: "Type your message...", disabled: isLoading, style: { width: '100%', minHeight: '32px', resize: 'none', border: 0, padding: 0, fontSize: '14px', outline: 'none', background: 'transparent', color: '#ffffff' } }), jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '4px' }, children: [jsx("button", { type: "button", style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '32px', height: '32px', borderRadius: '9999px', outline: 'none', border: 'none', background: 'transparent', cursor: 'pointer', color: '#71717a' }, children: jsx(MicIcon, { size: 16 }) }), input.trim() && (jsx("button", { type: "submit", disabled: isLoading, style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '32px', height: '32px', borderRadius: '9999px', border: 'none', background: '#ffffff', color: '#000000', cursor: 'pointer' }, children: jsx(SendIcon, {}) }))] })] }) }) })] }));
165
167
  };
166
168
  // Voice Interface Component
167
169
  const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall }) => {
@@ -169,30 +171,20 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
169
171
  const [errorMsg, setErrorMsg] = useState('');
170
172
  const [isMuted, setIsMuted] = useState(false);
171
173
  const [callDuration, setCallDuration] = useState(0);
172
- const [isSpeaking, setIsSpeaking] = useState(false);
173
- const [speakingIntensity, setSpeakingIntensity] = useState(0);
174
174
  const callStartTimeRef = useRef(null);
175
175
  const durationIntervalRef = useRef(null);
176
176
  const inputAudioContextRef = useRef(null);
177
177
  const outputAudioContextRef = useRef(null);
178
178
  const inputAnalyserRef = useRef(null);
179
- const outputAnalyserRef = useRef(null);
180
179
  const outputGainRef = useRef(null);
181
180
  const nextStartTimeRef = useRef(0);
182
181
  const sourcesRef = useRef(new Set());
183
- const speakingAnimationRef = useRef(null);
184
182
  const sessionRef = useRef(null);
185
183
  const streamRef = useRef(null);
186
184
  const scriptProcessorRef = useRef(null);
187
185
  const sourceNodeRef = useRef(null);
188
186
  const cleanupAudio = useCallback(() => {
189
187
  var _a, _b, _c, _d;
190
- if (speakingAnimationRef.current) {
191
- cancelAnimationFrame(speakingAnimationRef.current);
192
- speakingAnimationRef.current = null;
193
- }
194
- setIsSpeaking(false);
195
- setSpeakingIntensity(0);
196
188
  sourcesRef.current.forEach(source => { try {
197
189
  source.stop();
198
190
  }
@@ -223,13 +215,6 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
223
215
  catch (e) { }
224
216
  outputGainRef.current = null;
225
217
  }
226
- if (outputAnalyserRef.current) {
227
- try {
228
- outputAnalyserRef.current.disconnect();
229
- }
230
- catch (e) { }
231
- outputAnalyserRef.current = null;
232
- }
233
218
  if (((_a = inputAudioContextRef.current) === null || _a === void 0 ? void 0 : _a.state) !== 'closed') {
234
219
  try {
235
220
  (_b = inputAudioContextRef.current) === null || _b === void 0 ? void 0 : _b.close();
@@ -264,34 +249,16 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
264
249
  const analyser = inputCtx.createAnalyser();
265
250
  analyser.fftSize = 256;
266
251
  inputAnalyserRef.current = analyser;
267
- const outputAnalyser = outputCtx.createAnalyser();
268
- outputAnalyser.fftSize = 256;
269
- outputAnalyserRef.current = outputAnalyser;
270
252
  const outputGain = outputCtx.createGain();
271
253
  outputGain.gain.value = 1;
272
254
  outputGainRef.current = outputGain;
273
- outputGain.connect(outputAnalyser);
274
- outputAnalyser.connect(outputCtx.destination);
275
- const monitorSpeaking = () => {
276
- if (!outputAnalyserRef.current)
277
- return;
278
- const dataArray = new Uint8Array(outputAnalyserRef.current.frequencyBinCount);
279
- outputAnalyserRef.current.getByteFrequencyData(dataArray);
280
- const average = dataArray.reduce((a, b) => a + b, 0) / dataArray.length;
281
- const intensity = Math.min(average / 128, 1);
282
- setIsSpeaking(intensity > 0.05);
283
- setSpeakingIntensity(intensity);
284
- speakingAnimationRef.current = requestAnimationFrame(monitorSpeaking);
285
- };
286
- monitorSpeaking();
255
+ outputGain.connect(outputCtx.destination);
287
256
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
288
257
  streamRef.current = stream;
289
258
  const wsUrl = `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent?key=${geminiApiKey}`;
290
- console.log('[VOX] Connecting to Gemini Live API...');
291
259
  const ws = new WebSocket(wsUrl);
292
260
  sessionRef.current = ws;
293
261
  ws.onopen = () => {
294
- console.log('[VOX] WebSocket connected, sending setup...');
295
262
  const setupMsg = {
296
263
  setup: {
297
264
  model: 'models/gemini-2.5-flash-native-audio-preview-09-2025',
@@ -321,9 +288,7 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
321
288
  return;
322
289
  }
323
290
  }
324
- console.log('[VOX] Received:', data);
325
291
  if (data.setupComplete) {
326
- console.log('[VOX] Setup complete, voice ready!');
327
292
  setConnectionState(ConnectionState.CONNECTED);
328
293
  const source = inputCtx.createMediaStreamSource(stream);
329
294
  sourceNodeRef.current = source;
@@ -364,19 +329,14 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
364
329
  nextStartTimeRef.current = 0;
365
330
  }
366
331
  };
367
- ws.onclose = (event) => {
368
- console.log('[VOX] WebSocket closed:', event.code, event.reason);
369
- setConnectionState(ConnectionState.DISCONNECTED);
370
- };
371
- ws.onerror = (error) => {
372
- console.error('[VOX] WebSocket error:', error);
332
+ ws.onclose = () => setConnectionState(ConnectionState.DISCONNECTED);
333
+ ws.onerror = () => {
373
334
  setConnectionState(ConnectionState.ERROR);
374
335
  setErrorMsg('Connection failed. Check your Gemini API key.');
375
336
  cleanupAudio();
376
337
  };
377
338
  }
378
339
  catch (err) {
379
- console.error('[VOX] Connection error:', err);
380
340
  setConnectionState(ConnectionState.ERROR);
381
341
  setErrorMsg(err.message || 'Failed to access microphone or connect.');
382
342
  cleanupAudio();
@@ -402,15 +362,24 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
402
362
  connect();
403
363
  return () => disconnect();
404
364
  }, []);
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: {
365
+ const toggleMute = () => {
366
+ setIsMuted(!isMuted);
367
+ if (streamRef.current)
368
+ streamRef.current.getAudioTracks().forEach(track => { track.enabled = isMuted; });
369
+ };
370
+ return (jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '32px', height: '100%', padding: '24px', fontFamily: 'Outfit, sans-serif' }, children: [jsxs("div", { style: { position: 'relative' }, children: [jsx("div", { style: {
406
371
  width: '112px', height: '112px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center',
407
372
  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',
409
- transform: isSpeaking ? `scale(${1 + speakingIntensity * 0.08})` : 'scale(1)', transition: 'all 0.3s'
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',
412
- fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.1em', display: 'flex', alignItems: 'center', gap: '8px'
413
- }, children: [jsx(PhoneOffIcon, {}), "End Call"] }))] }));
373
+ boxShadow: connectionState === ConnectionState.CONNECTED ? '0 0 30px rgba(255,255,255,0.15)' : 'none',
374
+ transition: 'all 0.3s'
375
+ }, 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, {}) })) }), 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: 0.75 } }), 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: '14px', fontWeight: 600, color: '#71717a', height: '20px' }, children: [connectionState === ConnectionState.CONNECTING && 'Initializing...', connectionState === ConnectionState.CONNECTED && (isMuted ? '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: '14px', fontWeight: 600, padding: '12px 16px', maxWidth: '280px', textAlign: 'center', color: '#71717a', background: '#18181b', border: '1px solid #3f3f46', borderRadius: '8px', gap: '12px' }, children: [jsx(AlertCircleIcon, {}), jsx("span", { children: errorMsg })] })), connectionState === ConnectionState.CONNECTED && (jsxs("div", { style: { display: 'flex', gap: '16px' }, children: [jsx("button", { onClick: toggleMute, style: {
376
+ padding: '16px', borderRadius: '50%', border: 'none', cursor: 'pointer', transition: 'all 0.2s',
377
+ background: isMuted ? '#27272a' : '#ffffff',
378
+ color: isMuted ? '#71717a' : '#000000'
379
+ }, children: isMuted ? jsx(MicOffIcon, {}) : jsx(MicIcon, {}) }), jsx("button", { onClick: () => { disconnect(); onEndCall === null || onEndCall === void 0 ? void 0 : onEndCall(); }, style: {
380
+ padding: '16px', borderRadius: '50%', border: 'none', cursor: 'pointer', transition: 'all 0.2s',
381
+ background: '#dc2626', color: '#ffffff'
382
+ }, children: jsx(PhoneOffIcon, {}) })] }))] }));
414
383
  };
415
384
  // Main VoxChat Component
416
385
  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, configPollInterval = 10000 }) => {
@@ -418,11 +387,9 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
418
387
  const [isOpen, setIsOpen] = useState(false);
419
388
  const [activeTab, setActiveTab] = useState('text');
420
389
  const [isVoiceActive, setIsVoiceActive] = useState(false);
421
- // Server config state
422
390
  const [serverConfig, setServerConfig] = useState(null);
423
391
  const [configLoaded, setConfigLoaded] = useState(!useServerConfig);
424
392
  const [isInitialLoad, setIsInitialLoad] = useState(true);
425
- // Fetch config from server
426
393
  const fetchConfig = useCallback(() => {
427
394
  if (useServerConfig && apiKey && apiUrl) {
428
395
  fetch(`${apiUrl}/api/v1/config`, {
@@ -443,22 +410,16 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
443
410
  });
444
411
  }
445
412
  }, [useServerConfig, apiKey, apiUrl]);
446
- // Initial fetch and real-time polling (every 10 seconds)
447
413
  useEffect(() => {
448
414
  if (useServerConfig && apiKey && apiUrl) {
449
- // Fetch immediately
450
415
  fetchConfig();
451
- // Poll at specified interval for real-time updates
452
416
  const intervalId = setInterval(fetchConfig, configPollInterval);
453
- // Cleanup interval on unmount
454
417
  return () => clearInterval(intervalId);
455
418
  }
456
419
  }, [useServerConfig, apiKey, apiUrl, fetchConfig, configPollInterval]);
457
- // Use server config if available, otherwise use props
458
420
  const agentName = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.name) || propAgentName;
459
421
  const voiceName = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.voiceName) || propVoiceName;
460
422
  const systemInstruction = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.systemInstruction) || propSystemInstruction;
461
- // Widget style from server or props
462
423
  const finalButtonColor = ((_a = serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.widgetStyle) === null || _a === void 0 ? void 0 : _a.buttonColor) || buttonColor || (theme === 'light' ? '#000000' : '#ffffff');
463
424
  const finalButtonSize = ((_b = serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.widgetStyle) === null || _b === void 0 ? void 0 : _b.buttonSize) || buttonSize || 56;
464
425
  const finalBorderRadius = ((_c = serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.widgetStyle) === null || _c === void 0 ? void 0 : _c.borderRadius) !== undefined ? serverConfig.widgetStyle.borderRadius : (borderRadius !== undefined ? borderRadius : 50);
@@ -484,7 +445,7 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
484
445
  const positionStyles = finalPosition === 'bottom-left'
485
446
  ? { bottom: '16px', left: '16px' }
486
447
  : { bottom: '16px', right: '16px' };
487
- return (jsxs(Fragment, { children: [jsx("style", { children: `
448
+ return (jsxs(Fragment, { children: [jsx("link", { href: "https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;900&display=swap", rel: "stylesheet" }), jsx("style", { children: `
488
449
  @keyframes vox-fade-in { from { opacity: 0; transform: translateY(40px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
489
450
  @keyframes vox-spin { to { transform: rotate(360deg); } }
490
451
  @keyframes vox-ping { 0% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.15); opacity: 0; } 100% { transform: scale(1); opacity: 0; } }
@@ -492,14 +453,17 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
492
453
  ` }), jsxs("div", { style: { position: 'fixed', zIndex: 10000, display: 'flex', flexDirection: 'column', alignItems: finalPosition === 'bottom-left' ? 'flex-start' : 'flex-end', gap: '24px', ...positionStyles }, children: [isOpen && (jsxs("div", { className: "vox-widget", style: {
493
454
  width: 'min(calc(100vw - 32px), 360px)', height: '550px', maxHeight: '550px',
494
455
  background: '#000000', overflow: 'hidden', display: 'flex', flexDirection: 'column',
495
- border: '1px solid #3f3f46', boxShadow: '0 20px 40px -10px rgba(0,0,0,0.5)', borderRadius: '12px'
496
- }, children: [jsxs("div", { style: { height: '48px', 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: '10px' }, children: [jsx("div", { style: { width: '28px', height: '28px', border: '1px solid #52525b', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#ffffff', transition: 'all 0.3s ease' }, children: finalLogoUrl ? (jsx("img", { src: finalLogoUrl, alt: agentName, style: { width: '16px', height: '16px', objectFit: 'contain', transition: 'opacity 0.3s ease' } }, finalLogoUrl)) : (jsx(MessageCircleIcon, {})) }), jsxs("div", { children: [jsx("h2", { style: { color: '#ffffff', fontWeight: 900, fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.15em', margin: 0, transition: 'all 0.3s ease' }, children: agentName }), jsxs("p", { style: { color: '#a1a1aa', fontSize: '8px', 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: '6px' } }), "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: '2px', background: '#18181b', borderBottom: '1px solid #3f3f46', flexShrink: 0 }, children: [jsx("button", { onClick: () => handleTabChange('text'), style: {
497
- flex: 1, padding: '8px', fontSize: '9px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.15em',
498
- background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'text' ? '#ffffff' : '#71717a', transition: 'color 0.2s'
499
- }, children: "Text Interface" }), jsx("div", { style: { width: '1px', background: '#3f3f46' } }), jsx("button", { onClick: () => handleTabChange('voice'), style: {
500
- flex: 1, padding: '8px', fontSize: '9px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.15em',
501
- background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'voice' ? '#ffffff' : '#71717a', transition: 'color 0.2s'
502
- }, 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 && (!useServerConfig || configLoaded) && (jsx("button", { onClick: handleOpen, style: {
456
+ border: '1px solid #3f3f46', boxShadow: '0 20px 40px -10px rgba(0,0,0,0.5)', borderRadius: '12px',
457
+ transition: 'all 0.3s ease'
458
+ }, children: [jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 16px', flexShrink: 0, borderBottom: '1px solid #3f3f46', background: '#000000', fontFamily: 'Outfit, sans-serif' }, children: [jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '10px' }, children: [jsx("div", { style: { width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.3s ease' }, children: finalLogoUrl ? (jsx("img", { src: finalLogoUrl, alt: agentName, style: { width: '28px', height: '28px', objectFit: 'contain', transition: 'opacity 0.3s ease' } }, finalLogoUrl)) : (activeTab === 'voice' ? jsx(MicIcon, {}) : jsx(MessageCircleIcon, {})) }), jsxs("div", { children: [jsx("h2", { style: { color: '#ffffff', fontWeight: 700, fontSize: '14px', margin: 0, transition: 'all 0.3s ease' }, children: agentName }), jsxs("p", { style: { color: '#a1a1aa', fontSize: '12px', fontWeight: 500, margin: 0, display: 'flex', alignItems: 'center' }, children: [jsx("span", { style: { width: '6px', height: '6px', background: '#ffffff', borderRadius: '50%', marginRight: '6px' } }), "Active"] })] })] }), jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '2px', padding: '2px', borderRadius: '8px', background: '#18181b' }, children: [jsx("button", { onClick: () => handleTabChange('text'), style: {
459
+ padding: '6px 12px', fontSize: '12px', fontWeight: 600, borderRadius: '6px',
460
+ background: activeTab === 'text' ? '#27272a' : 'transparent', border: 'none', cursor: 'pointer', color: activeTab === 'text' ? '#ffffff' : '#71717a', transition: 'all 0.2s',
461
+ boxShadow: activeTab === 'text' ? '0 1px 2px rgba(0,0,0,0.1)' : 'none'
462
+ }, children: "Text" }), jsx("button", { onClick: () => handleTabChange('voice'), style: {
463
+ padding: '6px 12px', fontSize: '12px', fontWeight: 600, borderRadius: '6px',
464
+ background: activeTab === 'voice' ? '#27272a' : 'transparent', border: 'none', cursor: 'pointer', color: activeTab === 'voice' ? '#ffffff' : '#71717a', transition: 'all 0.2s',
465
+ boxShadow: activeTab === 'voice' ? '0 1px 2px rgba(0,0,0,0.1)' : 'none'
466
+ }, children: "Voice" })] }), jsx("button", { onClick: handleClose, style: { background: 'none', border: 'none', color: '#a1a1aa', cursor: 'pointer', padding: '4px', transition: 'color 0.2s' }, children: jsx(XIcon, {}) })] }), jsx("div", { style: { flex: 1, overflow: 'hidden', position: 'relative', background: '#000000' }, children: activeTab === 'text' ? (jsx(TextInterface, { apiKey: apiKey, apiUrl: apiUrl, agentName: agentName, systemInstruction: systemInstruction, logoUrl: finalLogoUrl })) : (isVoiceActive ? (jsx(VoiceInterface, { geminiApiKey: geminiApiKey, voiceName: voiceName, systemInstruction: systemInstruction, onEndCall: handleEndCall })) : (jsx(TextInterface, { apiKey: apiKey, apiUrl: apiUrl, agentName: agentName, systemInstruction: systemInstruction, logoUrl: finalLogoUrl }))) })] })), !isOpen && (!useServerConfig || configLoaded) && (jsx("button", { onClick: handleOpen, style: {
503
467
  width: `${finalButtonSize}px`, height: `${finalButtonSize}px`,
504
468
  borderRadius: `${finalBorderRadius}px`,
505
469
  background: finalButtonColor, color: finalIconColor, border: 'none', cursor: 'pointer',