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 +61 -97
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +61 -97
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
41
|
-
const MessageCircleIcon = () => (jsx("svg", { width: "
|
|
42
|
-
const MicIcon = ({ size =
|
|
43
|
-
const MicOffIcon = ({ size =
|
|
44
|
-
const XIcon = () => (jsxs("svg", { width: "
|
|
45
|
-
const PhoneOffIcon = () => (jsxs("svg", { width: "
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
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: `
|
|
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:
|
|
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: `
|
|
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' : '
|
|
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(
|
|
154
|
-
padding: '8px 12px', fontSize: '
|
|
155
|
-
border: '1px solid
|
|
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',
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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(
|
|
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 = (
|
|
368
|
-
|
|
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
|
-
|
|
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 ?
|
|
409
|
-
|
|
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, {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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',
|