vox-ai-react 1.4.0 → 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 +59 -96
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +59 -96
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -41,16 +41,18 @@ async function decodeAudioData(arrayBuffer, ctx, sampleRate, numChannels) {
|
|
|
41
41
|
}
|
|
42
42
|
return audioBuffer;
|
|
43
43
|
}
|
|
44
|
-
// Icons
|
|
44
|
+
// Icons
|
|
45
45
|
const MessageCircleIcon = () => (jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z" }) }));
|
|
46
|
-
const MicIcon = ({ size =
|
|
47
|
-
const MicOffIcon = ({ size =
|
|
48
|
-
const XIcon = () => (jsxRuntime.jsxs("svg", { width: "
|
|
49
|
-
const PhoneOffIcon = () => (jsxRuntime.jsxs("svg", { width: "
|
|
50
|
-
const VoxLogoIcon = () => (jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 48 48", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.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" }), jsxRuntime.jsx("path", { d: "M24 42.476L12.938 33.58V5.524L24 16.351" }), jsxRuntime.jsx("path", { d: "M24 42.476L9.125 35.557V7.501L24 16.351" }), jsxRuntime.jsx("path", { d: "M24 42.476L5.5 38.004V9.948L24 16.351m0 26.125l11.062-8.896V5.524L24 16.351" }), jsxRuntime.jsx("path", { d: "m24 42.476l14.875-6.919V7.501L24 16.351" }), jsxRuntime.jsx("path", { d: "m24 42.476l18.5-4.472V9.948L24 16.351" })] }));
|
|
46
|
+
const MicIcon = ({ size = 20 }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }), jsxRuntime.jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }), jsxRuntime.jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }));
|
|
47
|
+
const MicOffIcon = ({ size = 20 }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("line", { x1: "2", x2: "22", y1: "2", y2: "22" }), jsxRuntime.jsx("path", { d: "M18.89 13.23A7.12 7.12 0 0 0 19 12v-2" }), jsxRuntime.jsx("path", { d: "M5 10v2a7 7 0 0 0 12 5" }), jsxRuntime.jsx("path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33" }), jsxRuntime.jsx("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }), jsxRuntime.jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }));
|
|
48
|
+
const XIcon = () => (jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M18 6 6 18" }), jsxRuntime.jsx("path", { d: "m6 6 12 12" })] }));
|
|
49
|
+
const PhoneOffIcon = () => (jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.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" }), jsxRuntime.jsx("line", { x1: "22", x2: "2", y1: "2", y2: "22" })] }));
|
|
51
50
|
const UserIcon = () => (jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }), jsxRuntime.jsx("circle", { cx: "12", cy: "7", r: "4" })] }));
|
|
52
|
-
const
|
|
53
|
-
const
|
|
51
|
+
const BotIcon = () => (jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M12 8V4H8" }), jsxRuntime.jsx("rect", { width: "16", height: "12", x: "4", y: "8", rx: "2" }), jsxRuntime.jsx("path", { d: "M2 14h2" }), jsxRuntime.jsx("path", { d: "M20 14h2" }), jsxRuntime.jsx("path", { d: "M15 13v2" }), jsxRuntime.jsx("path", { d: "M9 13v2" })] }));
|
|
52
|
+
const SendIcon = () => (jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "m22 2-7 20-4-9-9-4Z" }), jsxRuntime.jsx("path", { d: "M22 2 11 13" })] }));
|
|
53
|
+
const PlusIcon = () => (jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M5 12h14" }), jsxRuntime.jsx("path", { d: "M12 5v14" })] }));
|
|
54
|
+
const Loader2Icon = () => (jsxRuntime.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: jsxRuntime.jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }));
|
|
55
|
+
const AlertCircleIcon = () => (jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }), jsxRuntime.jsx("line", { x1: "12", x2: "12", y1: "8", y2: "12" }), jsxRuntime.jsx("line", { x1: "12", x2: "12.01", y1: "16", y2: "16" })] }));
|
|
54
56
|
// Visualizer Component
|
|
55
57
|
const Visualizer = ({ analyser, isActive }) => {
|
|
56
58
|
const canvasRef = react.useRef(null);
|
|
@@ -104,9 +106,9 @@ const Visualizer = ({ analyser, isActive }) => {
|
|
|
104
106
|
return jsxRuntime.jsx("canvas", { ref: canvasRef, width: 300, height: 50, style: { width: '100%', height: '50px', background: 'transparent' } });
|
|
105
107
|
};
|
|
106
108
|
// Text Interface Component
|
|
107
|
-
const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction }) => {
|
|
109
|
+
const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction, logoUrl }) => {
|
|
108
110
|
const [messages, setMessages] = react.useState([
|
|
109
|
-
{ id: 'welcome', role: 'assistant', text: `
|
|
111
|
+
{ id: 'welcome', role: 'assistant', text: `Hello! I'm ${agentName}. How can I help you today?` }
|
|
110
112
|
]);
|
|
111
113
|
const [input, setInput] = react.useState('');
|
|
112
114
|
const [isLoading, setIsLoading] = react.useState(false);
|
|
@@ -116,7 +118,6 @@ const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction }) => {
|
|
|
116
118
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
117
119
|
}, [messages]);
|
|
118
120
|
const handleSend = async (e) => {
|
|
119
|
-
var _a;
|
|
120
121
|
e === null || e === void 0 ? void 0 : e.preventDefault();
|
|
121
122
|
if (!input.trim() || isLoading)
|
|
122
123
|
return;
|
|
@@ -137,36 +138,36 @@ const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction }) => {
|
|
|
137
138
|
throw new Error(errorData.error || 'API request failed');
|
|
138
139
|
}
|
|
139
140
|
const data = await response.json();
|
|
140
|
-
const botMsg = { id: (Date.now() + 1).toString(), role: 'assistant', text:
|
|
141
|
+
const botMsg = { id: (Date.now() + 1).toString(), role: 'assistant', text: data.response || 'Error: No response' };
|
|
141
142
|
setMessages(prev => [...prev, botMsg]);
|
|
142
143
|
}
|
|
143
144
|
catch (err) {
|
|
144
145
|
console.error('VOX Chat Error:', err);
|
|
145
|
-
const errorMsg = { id: (Date.now() + 1).toString(), role: 'assistant', text: `
|
|
146
|
+
const errorMsg = { id: (Date.now() + 1).toString(), role: 'assistant', text: `Error: ${err.message || 'Connection lost. Please try again.'}` };
|
|
146
147
|
setMessages(prev => [...prev, errorMsg]);
|
|
147
148
|
}
|
|
148
149
|
finally {
|
|
149
150
|
setIsLoading(false);
|
|
150
151
|
}
|
|
151
152
|
};
|
|
152
|
-
return (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: '#000000' }, children: [jsxRuntime.jsxs("div", { ref: scrollRef, style: { flex: 1, overflowY: 'auto', padding: '16px', display: 'flex', flexDirection: 'column', gap: '16px' }, children: [messages.map((msg) => (jsxRuntime.jsx("div", { style: { display: 'flex', justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start' }, children: jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: msg.role === 'user' ? 'row-reverse' : 'row', alignItems: 'flex-start', gap: '8px', maxWidth: '85%' }, children: [jsxRuntime.jsx("div", { style: {
|
|
153
|
+
return (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: '#000000', fontFamily: 'Outfit, sans-serif' }, children: [jsxRuntime.jsxs("div", { ref: scrollRef, style: { flex: 1, overflowY: 'auto', padding: '16px', display: 'flex', flexDirection: 'column', gap: '16px' }, children: [messages.map((msg) => (jsxRuntime.jsx("div", { style: { display: 'flex', justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start' }, children: jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: msg.role === 'user' ? 'row-reverse' : 'row', alignItems: 'flex-start', gap: '8px', maxWidth: '85%' }, children: [jsxRuntime.jsx("div", { style: {
|
|
153
154
|
flexShrink: 0, width: '24px', height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
154
155
|
border: msg.role === 'user' ? '1px solid #ffffff' : 'none',
|
|
155
156
|
background: msg.role === 'user' ? '#ffffff' : 'transparent',
|
|
156
157
|
color: msg.role === 'user' ? '#000000' : '#ffffff'
|
|
157
|
-
}, children: msg.role === 'user' ? jsxRuntime.jsx(UserIcon, {}) : jsxRuntime.jsx(
|
|
158
|
-
padding: '8px 12px', fontSize: '
|
|
159
|
-
border: '1px solid
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
158
|
+
}, children: msg.role === 'user' ? jsxRuntime.jsx(UserIcon, {}) : (logoUrl ? jsxRuntime.jsx("img", { src: logoUrl, alt: agentName, style: { width: '24px', height: '24px', objectFit: 'contain' } }) : jsxRuntime.jsx(BotIcon, {})) }), jsxRuntime.jsx("div", { style: {
|
|
159
|
+
padding: '8px 12px', fontSize: '14px', fontWeight: 500, lineHeight: 1.6,
|
|
160
|
+
border: '1px solid', borderRadius: '8px',
|
|
161
|
+
borderColor: msg.role === 'user' ? '#52525b' : '#3f3f46',
|
|
162
|
+
background: msg.role === 'user' ? '#27272a' : '#000000',
|
|
163
|
+
color: msg.role === 'user' ? '#ffffff' : '#d4d4d8'
|
|
164
|
+
}, children: msg.text })] }) }, msg.id))), isLoading && (jsxRuntime.jsx("div", { style: { display: 'flex', justifyContent: 'flex-start' }, children: jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'flex-start', gap: '8px' }, children: [jsxRuntime.jsx("div", { style: { flexShrink: 0, width: '24px', height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#ffffff', opacity: 0.2 }, children: logoUrl ? jsxRuntime.jsx("img", { src: logoUrl, alt: agentName, style: { width: '24px', height: '24px', objectFit: 'contain' } }) : jsxRuntime.jsx(BotIcon, {}) }), jsxRuntime.jsx("div", { style: { padding: '12px 16px', border: '1px solid #3f3f46', background: '#000000', borderRadius: '8px' }, children: jsxRuntime.jsx(Loader2Icon, {}) })] }) }))] }), jsxRuntime.jsx("div", { style: { padding: '16px' }, children: jsxRuntime.jsx("form", { onSubmit: handleSend, children: jsxRuntime.jsxs("div", { style: {
|
|
165
|
+
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',
|
|
166
|
+
display: 'grid', gridTemplateColumns: 'auto 1fr auto', gridTemplateRows: 'auto', gap: '4px', alignItems: 'center', background: 'rgba(0,0,0,0.5)'
|
|
167
|
+
}, children: [jsxRuntime.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: jsxRuntime.jsx(PlusIcon, {}) }), jsxRuntime.jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), onKeyDown: (e) => { if (e.key === 'Enter' && !e.shiftKey) {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
handleSend();
|
|
170
|
+
} }, 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' } }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '4px' }, children: [jsxRuntime.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: jsxRuntime.jsx(MicIcon, { size: 16 }) }), input.trim() && (jsxRuntime.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: jsxRuntime.jsx(SendIcon, {}) }))] })] }) }) })] }));
|
|
170
171
|
};
|
|
171
172
|
// Voice Interface Component
|
|
172
173
|
const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall }) => {
|
|
@@ -174,30 +175,20 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
174
175
|
const [errorMsg, setErrorMsg] = react.useState('');
|
|
175
176
|
const [isMuted, setIsMuted] = react.useState(false);
|
|
176
177
|
const [callDuration, setCallDuration] = react.useState(0);
|
|
177
|
-
const [isSpeaking, setIsSpeaking] = react.useState(false);
|
|
178
|
-
const [speakingIntensity, setSpeakingIntensity] = react.useState(0);
|
|
179
178
|
const callStartTimeRef = react.useRef(null);
|
|
180
179
|
const durationIntervalRef = react.useRef(null);
|
|
181
180
|
const inputAudioContextRef = react.useRef(null);
|
|
182
181
|
const outputAudioContextRef = react.useRef(null);
|
|
183
182
|
const inputAnalyserRef = react.useRef(null);
|
|
184
|
-
const outputAnalyserRef = react.useRef(null);
|
|
185
183
|
const outputGainRef = react.useRef(null);
|
|
186
184
|
const nextStartTimeRef = react.useRef(0);
|
|
187
185
|
const sourcesRef = react.useRef(new Set());
|
|
188
|
-
const speakingAnimationRef = react.useRef(null);
|
|
189
186
|
const sessionRef = react.useRef(null);
|
|
190
187
|
const streamRef = react.useRef(null);
|
|
191
188
|
const scriptProcessorRef = react.useRef(null);
|
|
192
189
|
const sourceNodeRef = react.useRef(null);
|
|
193
190
|
const cleanupAudio = react.useCallback(() => {
|
|
194
191
|
var _a, _b, _c, _d;
|
|
195
|
-
if (speakingAnimationRef.current) {
|
|
196
|
-
cancelAnimationFrame(speakingAnimationRef.current);
|
|
197
|
-
speakingAnimationRef.current = null;
|
|
198
|
-
}
|
|
199
|
-
setIsSpeaking(false);
|
|
200
|
-
setSpeakingIntensity(0);
|
|
201
192
|
sourcesRef.current.forEach(source => { try {
|
|
202
193
|
source.stop();
|
|
203
194
|
}
|
|
@@ -228,13 +219,6 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
228
219
|
catch (e) { }
|
|
229
220
|
outputGainRef.current = null;
|
|
230
221
|
}
|
|
231
|
-
if (outputAnalyserRef.current) {
|
|
232
|
-
try {
|
|
233
|
-
outputAnalyserRef.current.disconnect();
|
|
234
|
-
}
|
|
235
|
-
catch (e) { }
|
|
236
|
-
outputAnalyserRef.current = null;
|
|
237
|
-
}
|
|
238
222
|
if (((_a = inputAudioContextRef.current) === null || _a === void 0 ? void 0 : _a.state) !== 'closed') {
|
|
239
223
|
try {
|
|
240
224
|
(_b = inputAudioContextRef.current) === null || _b === void 0 ? void 0 : _b.close();
|
|
@@ -269,34 +253,16 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
269
253
|
const analyser = inputCtx.createAnalyser();
|
|
270
254
|
analyser.fftSize = 256;
|
|
271
255
|
inputAnalyserRef.current = analyser;
|
|
272
|
-
const outputAnalyser = outputCtx.createAnalyser();
|
|
273
|
-
outputAnalyser.fftSize = 256;
|
|
274
|
-
outputAnalyserRef.current = outputAnalyser;
|
|
275
256
|
const outputGain = outputCtx.createGain();
|
|
276
257
|
outputGain.gain.value = 1;
|
|
277
258
|
outputGainRef.current = outputGain;
|
|
278
|
-
outputGain.connect(
|
|
279
|
-
outputAnalyser.connect(outputCtx.destination);
|
|
280
|
-
const monitorSpeaking = () => {
|
|
281
|
-
if (!outputAnalyserRef.current)
|
|
282
|
-
return;
|
|
283
|
-
const dataArray = new Uint8Array(outputAnalyserRef.current.frequencyBinCount);
|
|
284
|
-
outputAnalyserRef.current.getByteFrequencyData(dataArray);
|
|
285
|
-
const average = dataArray.reduce((a, b) => a + b, 0) / dataArray.length;
|
|
286
|
-
const intensity = Math.min(average / 128, 1);
|
|
287
|
-
setIsSpeaking(intensity > 0.05);
|
|
288
|
-
setSpeakingIntensity(intensity);
|
|
289
|
-
speakingAnimationRef.current = requestAnimationFrame(monitorSpeaking);
|
|
290
|
-
};
|
|
291
|
-
monitorSpeaking();
|
|
259
|
+
outputGain.connect(outputCtx.destination);
|
|
292
260
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
293
261
|
streamRef.current = stream;
|
|
294
262
|
const wsUrl = `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent?key=${geminiApiKey}`;
|
|
295
|
-
console.log('[VOX] Connecting to Gemini Live API...');
|
|
296
263
|
const ws = new WebSocket(wsUrl);
|
|
297
264
|
sessionRef.current = ws;
|
|
298
265
|
ws.onopen = () => {
|
|
299
|
-
console.log('[VOX] WebSocket connected, sending setup...');
|
|
300
266
|
const setupMsg = {
|
|
301
267
|
setup: {
|
|
302
268
|
model: 'models/gemini-2.5-flash-native-audio-preview-09-2025',
|
|
@@ -326,9 +292,7 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
326
292
|
return;
|
|
327
293
|
}
|
|
328
294
|
}
|
|
329
|
-
console.log('[VOX] Received:', data);
|
|
330
295
|
if (data.setupComplete) {
|
|
331
|
-
console.log('[VOX] Setup complete, voice ready!');
|
|
332
296
|
setConnectionState(ConnectionState.CONNECTED);
|
|
333
297
|
const source = inputCtx.createMediaStreamSource(stream);
|
|
334
298
|
sourceNodeRef.current = source;
|
|
@@ -369,19 +333,14 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
369
333
|
nextStartTimeRef.current = 0;
|
|
370
334
|
}
|
|
371
335
|
};
|
|
372
|
-
ws.onclose = (
|
|
373
|
-
|
|
374
|
-
setConnectionState(ConnectionState.DISCONNECTED);
|
|
375
|
-
};
|
|
376
|
-
ws.onerror = (error) => {
|
|
377
|
-
console.error('[VOX] WebSocket error:', error);
|
|
336
|
+
ws.onclose = () => setConnectionState(ConnectionState.DISCONNECTED);
|
|
337
|
+
ws.onerror = () => {
|
|
378
338
|
setConnectionState(ConnectionState.ERROR);
|
|
379
339
|
setErrorMsg('Connection failed. Check your Gemini API key.');
|
|
380
340
|
cleanupAudio();
|
|
381
341
|
};
|
|
382
342
|
}
|
|
383
343
|
catch (err) {
|
|
384
|
-
console.error('[VOX] Connection error:', err);
|
|
385
344
|
setConnectionState(ConnectionState.ERROR);
|
|
386
345
|
setErrorMsg(err.message || 'Failed to access microphone or connect.');
|
|
387
346
|
cleanupAudio();
|
|
@@ -407,15 +366,24 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
407
366
|
connect();
|
|
408
367
|
return () => disconnect();
|
|
409
368
|
}, []);
|
|
410
|
-
|
|
369
|
+
const toggleMute = () => {
|
|
370
|
+
setIsMuted(!isMuted);
|
|
371
|
+
if (streamRef.current)
|
|
372
|
+
streamRef.current.getAudioTracks().forEach(track => { track.enabled = isMuted; });
|
|
373
|
+
};
|
|
374
|
+
return (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '32px', height: '100%', padding: '24px', fontFamily: 'Outfit, sans-serif' }, children: [jsxRuntime.jsxs("div", { style: { position: 'relative' }, children: [jsxRuntime.jsx("div", { style: {
|
|
411
375
|
width: '112px', height: '112px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
412
376
|
border: `1px solid ${connectionState === ConnectionState.CONNECTED ? '#ffffff' : '#52525b'}`,
|
|
413
|
-
boxShadow: connectionState === ConnectionState.CONNECTED ?
|
|
414
|
-
|
|
415
|
-
}, children: connectionState === ConnectionState.CONNECTING ? (jsxRuntime.jsx("div", { style: { width: '40px', height: '40px', border: '2px solid #ffffff', borderTopColor: 'transparent', borderRadius: '50%', animation: 'vox-spin 1s linear infinite' } })) : (jsxRuntime.jsx("div", { style: { padding: '20px', color: isMuted ? '#52525b' : '#ffffff' }, children: isMuted ? jsxRuntime.jsx(MicOffIcon, {}) : jsxRuntime.jsx(MicIcon, {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
377
|
+
boxShadow: connectionState === ConnectionState.CONNECTED ? '0 0 30px rgba(255,255,255,0.15)' : 'none',
|
|
378
|
+
transition: 'all 0.3s'
|
|
379
|
+
}, children: connectionState === ConnectionState.CONNECTING ? (jsxRuntime.jsx("div", { style: { width: '40px', height: '40px', border: '2px solid #ffffff', borderTopColor: 'transparent', borderRadius: '50%', animation: 'vox-spin 1s linear infinite' } })) : (jsxRuntime.jsx("div", { style: { padding: '20px', color: isMuted ? '#52525b' : '#ffffff' }, children: isMuted ? jsxRuntime.jsx(MicOffIcon, {}) : jsxRuntime.jsx(MicIcon, {}) })) }), connectionState === ConnectionState.CONNECTED && (jsxRuntime.jsxs("span", { style: { position: 'absolute', bottom: '-4px', right: '-4px', display: 'flex', width: '12px', height: '12px' }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', display: 'inline-flex', width: '100%', height: '100%', borderRadius: '50%', background: '#ffffff', opacity: 0.75 } }), jsxRuntime.jsx("span", { style: { position: 'relative', display: 'inline-flex', borderRadius: '50%', width: '12px', height: '12px', background: '#ffffff' } })] }))] }), jsxRuntime.jsxs("div", { style: { width: '100%', maxWidth: '280px', display: 'flex', flexDirection: 'column', gap: '16px' }, children: [jsxRuntime.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 && (jsxRuntime.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')] })), jsxRuntime.jsx(Visualizer, { analyser: inputAnalyserRef.current, isActive: connectionState === ConnectionState.CONNECTED && !isMuted })] }), connectionState === ConnectionState.ERROR && (jsxRuntime.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: [jsxRuntime.jsx(AlertCircleIcon, {}), jsxRuntime.jsx("span", { children: errorMsg })] })), connectionState === ConnectionState.CONNECTED && (jsxRuntime.jsxs("div", { style: { display: 'flex', gap: '16px' }, children: [jsxRuntime.jsx("button", { onClick: toggleMute, style: {
|
|
380
|
+
padding: '16px', borderRadius: '50%', border: 'none', cursor: 'pointer', transition: 'all 0.2s',
|
|
381
|
+
background: isMuted ? '#27272a' : '#ffffff',
|
|
382
|
+
color: isMuted ? '#71717a' : '#000000'
|
|
383
|
+
}, children: isMuted ? jsxRuntime.jsx(MicOffIcon, {}) : jsxRuntime.jsx(MicIcon, {}) }), jsxRuntime.jsx("button", { onClick: () => { disconnect(); onEndCall === null || onEndCall === void 0 ? void 0 : onEndCall(); }, style: {
|
|
384
|
+
padding: '16px', borderRadius: '50%', border: 'none', cursor: 'pointer', transition: 'all 0.2s',
|
|
385
|
+
background: '#dc2626', color: '#ffffff'
|
|
386
|
+
}, children: jsxRuntime.jsx(PhoneOffIcon, {}) })] }))] }));
|
|
419
387
|
};
|
|
420
388
|
// Main VoxChat Component
|
|
421
389
|
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 }) => {
|
|
@@ -423,11 +391,9 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
|
|
|
423
391
|
const [isOpen, setIsOpen] = react.useState(false);
|
|
424
392
|
const [activeTab, setActiveTab] = react.useState('text');
|
|
425
393
|
const [isVoiceActive, setIsVoiceActive] = react.useState(false);
|
|
426
|
-
// Server config state
|
|
427
394
|
const [serverConfig, setServerConfig] = react.useState(null);
|
|
428
395
|
const [configLoaded, setConfigLoaded] = react.useState(!useServerConfig);
|
|
429
396
|
const [isInitialLoad, setIsInitialLoad] = react.useState(true);
|
|
430
|
-
// Fetch config from server
|
|
431
397
|
const fetchConfig = react.useCallback(() => {
|
|
432
398
|
if (useServerConfig && apiKey && apiUrl) {
|
|
433
399
|
fetch(`${apiUrl}/api/v1/config`, {
|
|
@@ -448,22 +414,16 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
|
|
|
448
414
|
});
|
|
449
415
|
}
|
|
450
416
|
}, [useServerConfig, apiKey, apiUrl]);
|
|
451
|
-
// Initial fetch and real-time polling (every 10 seconds)
|
|
452
417
|
react.useEffect(() => {
|
|
453
418
|
if (useServerConfig && apiKey && apiUrl) {
|
|
454
|
-
// Fetch immediately
|
|
455
419
|
fetchConfig();
|
|
456
|
-
// Poll at specified interval for real-time updates
|
|
457
420
|
const intervalId = setInterval(fetchConfig, configPollInterval);
|
|
458
|
-
// Cleanup interval on unmount
|
|
459
421
|
return () => clearInterval(intervalId);
|
|
460
422
|
}
|
|
461
423
|
}, [useServerConfig, apiKey, apiUrl, fetchConfig, configPollInterval]);
|
|
462
|
-
// Use server config if available, otherwise use props
|
|
463
424
|
const agentName = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.name) || propAgentName;
|
|
464
425
|
const voiceName = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.voiceName) || propVoiceName;
|
|
465
426
|
const systemInstruction = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.systemInstruction) || propSystemInstruction;
|
|
466
|
-
// Widget style from server or props
|
|
467
427
|
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');
|
|
468
428
|
const finalButtonSize = ((_b = serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.widgetStyle) === null || _b === void 0 ? void 0 : _b.buttonSize) || buttonSize || 56;
|
|
469
429
|
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);
|
|
@@ -489,7 +449,7 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
|
|
|
489
449
|
const positionStyles = finalPosition === 'bottom-left'
|
|
490
450
|
? { bottom: '16px', left: '16px' }
|
|
491
451
|
: { bottom: '16px', right: '16px' };
|
|
492
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { children: `
|
|
452
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("link", { href: "https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;900&display=swap", rel: "stylesheet" }), jsxRuntime.jsx("style", { children: `
|
|
493
453
|
@keyframes vox-fade-in { from { opacity: 0; transform: translateY(40px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
|
494
454
|
@keyframes vox-spin { to { transform: rotate(360deg); } }
|
|
495
455
|
@keyframes vox-ping { 0% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.15); opacity: 0; } 100% { transform: scale(1); opacity: 0; } }
|
|
@@ -497,14 +457,17 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
|
|
|
497
457
|
` }), jsxRuntime.jsxs("div", { style: { position: 'fixed', zIndex: 10000, display: 'flex', flexDirection: 'column', alignItems: finalPosition === 'bottom-left' ? 'flex-start' : 'flex-end', gap: '24px', ...positionStyles }, children: [isOpen && (jsxRuntime.jsxs("div", { className: "vox-widget", style: {
|
|
498
458
|
width: 'min(calc(100vw - 32px), 360px)', height: '550px', maxHeight: '550px',
|
|
499
459
|
background: '#000000', overflow: 'hidden', display: 'flex', flexDirection: 'column',
|
|
500
|
-
border: '1px solid #3f3f46', boxShadow: '0 20px 40px -10px rgba(0,0,0,0.5)', borderRadius: '12px'
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
460
|
+
border: '1px solid #3f3f46', boxShadow: '0 20px 40px -10px rgba(0,0,0,0.5)', borderRadius: '12px',
|
|
461
|
+
transition: 'all 0.3s ease'
|
|
462
|
+
}, children: [jsxRuntime.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: [jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '10px' }, children: [jsxRuntime.jsx("div", { style: { width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.3s ease' }, children: finalLogoUrl ? (jsxRuntime.jsx("img", { src: finalLogoUrl, alt: agentName, style: { width: '28px', height: '28px', objectFit: 'contain', transition: 'opacity 0.3s ease' } }, finalLogoUrl)) : (activeTab === 'voice' ? jsxRuntime.jsx(MicIcon, {}) : jsxRuntime.jsx(MessageCircleIcon, {})) }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("h2", { style: { color: '#ffffff', fontWeight: 700, fontSize: '14px', margin: 0, transition: 'all 0.3s ease' }, children: agentName }), jsxRuntime.jsxs("p", { style: { color: '#a1a1aa', fontSize: '12px', fontWeight: 500, margin: 0, display: 'flex', alignItems: 'center' }, children: [jsxRuntime.jsx("span", { style: { width: '6px', height: '6px', background: '#ffffff', borderRadius: '50%', marginRight: '6px' } }), "Active"] })] })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '2px', padding: '2px', borderRadius: '8px', background: '#18181b' }, children: [jsxRuntime.jsx("button", { onClick: () => handleTabChange('text'), style: {
|
|
463
|
+
padding: '6px 12px', fontSize: '12px', fontWeight: 600, borderRadius: '6px',
|
|
464
|
+
background: activeTab === 'text' ? '#27272a' : 'transparent', border: 'none', cursor: 'pointer', color: activeTab === 'text' ? '#ffffff' : '#71717a', transition: 'all 0.2s',
|
|
465
|
+
boxShadow: activeTab === 'text' ? '0 1px 2px rgba(0,0,0,0.1)' : 'none'
|
|
466
|
+
}, children: "Text" }), jsxRuntime.jsx("button", { onClick: () => handleTabChange('voice'), style: {
|
|
467
|
+
padding: '6px 12px', fontSize: '12px', fontWeight: 600, borderRadius: '6px',
|
|
468
|
+
background: activeTab === 'voice' ? '#27272a' : 'transparent', border: 'none', cursor: 'pointer', color: activeTab === 'voice' ? '#ffffff' : '#71717a', transition: 'all 0.2s',
|
|
469
|
+
boxShadow: activeTab === 'voice' ? '0 1px 2px rgba(0,0,0,0.1)' : 'none'
|
|
470
|
+
}, children: "Voice" })] }), jsxRuntime.jsx("button", { onClick: handleClose, style: { background: 'none', border: 'none', color: '#a1a1aa', cursor: 'pointer', padding: '4px', transition: 'color 0.2s' }, children: jsxRuntime.jsx(XIcon, {}) })] }), jsxRuntime.jsx("div", { style: { flex: 1, overflow: 'hidden', position: 'relative', background: '#000000' }, children: activeTab === 'text' ? (jsxRuntime.jsx(TextInterface, { apiKey: apiKey, apiUrl: apiUrl, agentName: agentName, systemInstruction: systemInstruction, logoUrl: finalLogoUrl })) : (isVoiceActive ? (jsxRuntime.jsx(VoiceInterface, { geminiApiKey: geminiApiKey, voiceName: voiceName, systemInstruction: systemInstruction, onEndCall: handleEndCall })) : (jsxRuntime.jsx(TextInterface, { apiKey: apiKey, apiUrl: apiUrl, agentName: agentName, systemInstruction: systemInstruction, logoUrl: finalLogoUrl }))) })] })), !isOpen && (!useServerConfig || configLoaded) && (jsxRuntime.jsx("button", { onClick: handleOpen, style: {
|
|
508
471
|
width: `${finalButtonSize}px`, height: `${finalButtonSize}px`,
|
|
509
472
|
borderRadius: `${finalBorderRadius}px`,
|
|
510
473
|
background: finalButtonColor, color: finalIconColor, border: 'none', cursor: 'pointer',
|