vox-ai-react 1.4.0 → 1.6.1
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 +92 -100
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +92 -100
- package/dist/index.js.map +1 -1
- package/package.json +4 -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 = ({ size = 20 }) => (jsxRuntime.jsxs("svg", { width: size, height: size, 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,58 @@ 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: {
|
|
153
|
+
return (jsxRuntime.jsxs("div", { style: {
|
|
154
|
+
display: 'flex', flexDirection: 'column', height: '100%', background: '#000000', fontFamily: '"Plus Jakarta Sans", sans-serif',
|
|
155
|
+
WebkitFontSmoothing: 'antialiased', MozOsxFontSmoothing: 'grayscale'
|
|
156
|
+
}, 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
157
|
flexShrink: 0, width: '24px', height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
154
158
|
border: msg.role === 'user' ? '1px solid #ffffff' : 'none',
|
|
155
159
|
background: msg.role === 'user' ? '#ffffff' : 'transparent',
|
|
156
160
|
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
|
-
|
|
161
|
+
}, 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: {
|
|
162
|
+
padding: '8px 12px', fontSize: '12px', fontWeight: 400, lineHeight: 1.625,
|
|
163
|
+
border: '1px solid', borderRadius: '8px',
|
|
164
|
+
borderColor: msg.role === 'user' ? '#52525b' : '#3f3f46',
|
|
165
|
+
background: msg.role === 'user' ? '#27272a' : '#000000',
|
|
166
|
+
color: msg.role === 'user' ? '#ffffff' : '#d4d4d8'
|
|
167
|
+
}, 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, style: { width: '100%' }, children: jsxRuntime.jsxs("div", { style: {
|
|
168
|
+
width: '100%', cursor: 'text', overflow: 'clip', backgroundClip: 'padding-box',
|
|
169
|
+
padding: '6px', boxShadow: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)',
|
|
170
|
+
border: '1px solid #3f3f46', transition: 'border-radius 200ms ease-out',
|
|
171
|
+
borderRadius: '9999px', display: 'grid',
|
|
172
|
+
gridTemplateColumns: 'auto 1fr auto', gridTemplateRows: 'auto 1fr auto',
|
|
173
|
+
gridTemplateAreas: '"header header header" "leading primary trailing" ". footer ."',
|
|
174
|
+
background: 'rgba(0,0,0,0.5)', boxSizing: 'border-box'
|
|
175
|
+
}, children: [jsxRuntime.jsx("div", { style: { gridArea: 'primary', display: 'flex', minHeight: '32px', alignItems: 'center', overflowX: 'hidden', paddingLeft: '4px', paddingRight: '4px', marginTop: '-6px', marginBottom: '-6px' }, children: jsxRuntime.jsx("div", { style: { flex: '1 1 0%', overflow: 'auto', maxHeight: '13rem' }, children: jsxRuntime.jsx("input", { type: "text", placeholder: "Type your message...", value: input, onChange: (e) => setInput(e.target.value), onKeyDown: (e) => { if (e.key === 'Enter' && !e.shiftKey) {
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
handleSend();
|
|
178
|
+
} }, disabled: isLoading, style: {
|
|
179
|
+
width: '100%', minHeight: 0, resize: 'none', border: 'none', padding: 0,
|
|
180
|
+
fontSize: '12px', outline: 'none', background: 'transparent',
|
|
181
|
+
color: '#ffffff', margin: 0, fontFamily: 'inherit', lineHeight: 'normal'
|
|
182
|
+
} }) }) }), jsxRuntime.jsx("div", { style: { gridArea: 'leading', display: 'flex' }, children: jsxRuntime.jsx("button", { type: "button", "aria-label": "Add attachments", style: {
|
|
183
|
+
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
184
|
+
width: '32px', height: '32px', borderRadius: '9999px', outline: 'none',
|
|
185
|
+
border: 'none', background: 'transparent', cursor: 'pointer', color: '#71717a',
|
|
186
|
+
transition: 'all 0.15s ease'
|
|
187
|
+
}, children: jsxRuntime.jsx(PlusIcon, { size: 20 }) }) }), jsxRuntime.jsx("div", { style: { gridArea: 'trailing', display: 'flex', alignItems: 'center', gap: '4px' }, children: jsxRuntime.jsxs("div", { style: { marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: '4px' }, children: [jsxRuntime.jsx("button", { type: "button", "aria-label": "Record audio message", style: {
|
|
188
|
+
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
|
189
|
+
width: '32px', height: '32px', borderRadius: '9999px', outline: 'none',
|
|
190
|
+
border: 'none', background: 'transparent', cursor: 'pointer', color: '#71717a',
|
|
191
|
+
transition: 'all 0.15s ease'
|
|
192
|
+
}, 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
193
|
};
|
|
171
194
|
// Voice Interface Component
|
|
172
195
|
const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall }) => {
|
|
@@ -174,30 +197,20 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
174
197
|
const [errorMsg, setErrorMsg] = react.useState('');
|
|
175
198
|
const [isMuted, setIsMuted] = react.useState(false);
|
|
176
199
|
const [callDuration, setCallDuration] = react.useState(0);
|
|
177
|
-
const [isSpeaking, setIsSpeaking] = react.useState(false);
|
|
178
|
-
const [speakingIntensity, setSpeakingIntensity] = react.useState(0);
|
|
179
200
|
const callStartTimeRef = react.useRef(null);
|
|
180
201
|
const durationIntervalRef = react.useRef(null);
|
|
181
202
|
const inputAudioContextRef = react.useRef(null);
|
|
182
203
|
const outputAudioContextRef = react.useRef(null);
|
|
183
204
|
const inputAnalyserRef = react.useRef(null);
|
|
184
|
-
const outputAnalyserRef = react.useRef(null);
|
|
185
205
|
const outputGainRef = react.useRef(null);
|
|
186
206
|
const nextStartTimeRef = react.useRef(0);
|
|
187
207
|
const sourcesRef = react.useRef(new Set());
|
|
188
|
-
const speakingAnimationRef = react.useRef(null);
|
|
189
208
|
const sessionRef = react.useRef(null);
|
|
190
209
|
const streamRef = react.useRef(null);
|
|
191
210
|
const scriptProcessorRef = react.useRef(null);
|
|
192
211
|
const sourceNodeRef = react.useRef(null);
|
|
193
212
|
const cleanupAudio = react.useCallback(() => {
|
|
194
213
|
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
214
|
sourcesRef.current.forEach(source => { try {
|
|
202
215
|
source.stop();
|
|
203
216
|
}
|
|
@@ -228,13 +241,6 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
228
241
|
catch (e) { }
|
|
229
242
|
outputGainRef.current = null;
|
|
230
243
|
}
|
|
231
|
-
if (outputAnalyserRef.current) {
|
|
232
|
-
try {
|
|
233
|
-
outputAnalyserRef.current.disconnect();
|
|
234
|
-
}
|
|
235
|
-
catch (e) { }
|
|
236
|
-
outputAnalyserRef.current = null;
|
|
237
|
-
}
|
|
238
244
|
if (((_a = inputAudioContextRef.current) === null || _a === void 0 ? void 0 : _a.state) !== 'closed') {
|
|
239
245
|
try {
|
|
240
246
|
(_b = inputAudioContextRef.current) === null || _b === void 0 ? void 0 : _b.close();
|
|
@@ -269,34 +275,16 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
269
275
|
const analyser = inputCtx.createAnalyser();
|
|
270
276
|
analyser.fftSize = 256;
|
|
271
277
|
inputAnalyserRef.current = analyser;
|
|
272
|
-
const outputAnalyser = outputCtx.createAnalyser();
|
|
273
|
-
outputAnalyser.fftSize = 256;
|
|
274
|
-
outputAnalyserRef.current = outputAnalyser;
|
|
275
278
|
const outputGain = outputCtx.createGain();
|
|
276
279
|
outputGain.gain.value = 1;
|
|
277
280
|
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();
|
|
281
|
+
outputGain.connect(outputCtx.destination);
|
|
292
282
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
293
283
|
streamRef.current = stream;
|
|
294
284
|
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
285
|
const ws = new WebSocket(wsUrl);
|
|
297
286
|
sessionRef.current = ws;
|
|
298
287
|
ws.onopen = () => {
|
|
299
|
-
console.log('[VOX] WebSocket connected, sending setup...');
|
|
300
288
|
const setupMsg = {
|
|
301
289
|
setup: {
|
|
302
290
|
model: 'models/gemini-2.5-flash-native-audio-preview-09-2025',
|
|
@@ -326,9 +314,7 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
326
314
|
return;
|
|
327
315
|
}
|
|
328
316
|
}
|
|
329
|
-
console.log('[VOX] Received:', data);
|
|
330
317
|
if (data.setupComplete) {
|
|
331
|
-
console.log('[VOX] Setup complete, voice ready!');
|
|
332
318
|
setConnectionState(ConnectionState.CONNECTED);
|
|
333
319
|
const source = inputCtx.createMediaStreamSource(stream);
|
|
334
320
|
sourceNodeRef.current = source;
|
|
@@ -369,19 +355,14 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
369
355
|
nextStartTimeRef.current = 0;
|
|
370
356
|
}
|
|
371
357
|
};
|
|
372
|
-
ws.onclose = (
|
|
373
|
-
|
|
374
|
-
setConnectionState(ConnectionState.DISCONNECTED);
|
|
375
|
-
};
|
|
376
|
-
ws.onerror = (error) => {
|
|
377
|
-
console.error('[VOX] WebSocket error:', error);
|
|
358
|
+
ws.onclose = () => setConnectionState(ConnectionState.DISCONNECTED);
|
|
359
|
+
ws.onerror = () => {
|
|
378
360
|
setConnectionState(ConnectionState.ERROR);
|
|
379
361
|
setErrorMsg('Connection failed. Check your Gemini API key.');
|
|
380
362
|
cleanupAudio();
|
|
381
363
|
};
|
|
382
364
|
}
|
|
383
365
|
catch (err) {
|
|
384
|
-
console.error('[VOX] Connection error:', err);
|
|
385
366
|
setConnectionState(ConnectionState.ERROR);
|
|
386
367
|
setErrorMsg(err.message || 'Failed to access microphone or connect.');
|
|
387
368
|
cleanupAudio();
|
|
@@ -407,15 +388,24 @@ const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, onEndCall
|
|
|
407
388
|
connect();
|
|
408
389
|
return () => disconnect();
|
|
409
390
|
}, []);
|
|
410
|
-
|
|
391
|
+
const toggleMute = () => {
|
|
392
|
+
setIsMuted(!isMuted);
|
|
393
|
+
if (streamRef.current)
|
|
394
|
+
streamRef.current.getAudioTracks().forEach(track => { track.enabled = isMuted; });
|
|
395
|
+
};
|
|
396
|
+
return (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '32px', height: '100%', padding: '24px' }, children: [jsxRuntime.jsxs("div", { style: { position: 'relative' }, children: [jsxRuntime.jsx("div", { style: {
|
|
411
397
|
width: '112px', height: '112px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
412
398
|
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
|
-
|
|
399
|
+
boxShadow: connectionState === ConnectionState.CONNECTED ? '0 0 30px rgba(255,255,255,0.15)' : 'none',
|
|
400
|
+
transition: 'all 0.3s'
|
|
401
|
+
}, 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: {
|
|
402
|
+
padding: '16px', borderRadius: '50%', border: 'none', cursor: 'pointer', transition: 'all 0.2s',
|
|
403
|
+
background: isMuted ? '#27272a' : '#ffffff',
|
|
404
|
+
color: isMuted ? '#71717a' : '#000000'
|
|
405
|
+
}, children: isMuted ? jsxRuntime.jsx(MicOffIcon, {}) : jsxRuntime.jsx(MicIcon, {}) }), jsxRuntime.jsx("button", { onClick: () => { disconnect(); onEndCall === null || onEndCall === void 0 ? void 0 : onEndCall(); }, style: {
|
|
406
|
+
padding: '16px', borderRadius: '50%', border: 'none', cursor: 'pointer', transition: 'all 0.2s',
|
|
407
|
+
background: '#dc2626', color: '#ffffff'
|
|
408
|
+
}, children: jsxRuntime.jsx(PhoneOffIcon, {}) })] }))] }));
|
|
419
409
|
};
|
|
420
410
|
// Main VoxChat Component
|
|
421
411
|
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 +413,10 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
|
|
|
423
413
|
const [isOpen, setIsOpen] = react.useState(false);
|
|
424
414
|
const [activeTab, setActiveTab] = react.useState('text');
|
|
425
415
|
const [isVoiceActive, setIsVoiceActive] = react.useState(false);
|
|
426
|
-
// Server config state
|
|
427
416
|
const [serverConfig, setServerConfig] = react.useState(null);
|
|
417
|
+
const [serverGeminiApiKey, setServerGeminiApiKey] = react.useState();
|
|
428
418
|
const [configLoaded, setConfigLoaded] = react.useState(!useServerConfig);
|
|
429
419
|
const [isInitialLoad, setIsInitialLoad] = react.useState(true);
|
|
430
|
-
// Fetch config from server
|
|
431
420
|
const fetchConfig = react.useCallback(() => {
|
|
432
421
|
if (useServerConfig && apiKey && apiUrl) {
|
|
433
422
|
fetch(`${apiUrl}/api/v1/config`, {
|
|
@@ -438,6 +427,9 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
|
|
|
438
427
|
if (data.agentConfig) {
|
|
439
428
|
setServerConfig(data.agentConfig);
|
|
440
429
|
}
|
|
430
|
+
if (data.geminiApiKey) {
|
|
431
|
+
setServerGeminiApiKey(data.geminiApiKey);
|
|
432
|
+
}
|
|
441
433
|
setConfigLoaded(true);
|
|
442
434
|
setIsInitialLoad(false);
|
|
443
435
|
})
|
|
@@ -448,22 +440,16 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
|
|
|
448
440
|
});
|
|
449
441
|
}
|
|
450
442
|
}, [useServerConfig, apiKey, apiUrl]);
|
|
451
|
-
// Initial fetch and real-time polling (every 10 seconds)
|
|
452
443
|
react.useEffect(() => {
|
|
453
444
|
if (useServerConfig && apiKey && apiUrl) {
|
|
454
|
-
// Fetch immediately
|
|
455
445
|
fetchConfig();
|
|
456
|
-
// Poll at specified interval for real-time updates
|
|
457
446
|
const intervalId = setInterval(fetchConfig, configPollInterval);
|
|
458
|
-
// Cleanup interval on unmount
|
|
459
447
|
return () => clearInterval(intervalId);
|
|
460
448
|
}
|
|
461
449
|
}, [useServerConfig, apiKey, apiUrl, fetchConfig, configPollInterval]);
|
|
462
|
-
// Use server config if available, otherwise use props
|
|
463
450
|
const agentName = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.name) || propAgentName;
|
|
464
451
|
const voiceName = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.voiceName) || propVoiceName;
|
|
465
452
|
const systemInstruction = (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.systemInstruction) || propSystemInstruction;
|
|
466
|
-
// Widget style from server or props
|
|
467
453
|
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
454
|
const finalButtonSize = ((_b = serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.widgetStyle) === null || _b === void 0 ? void 0 : _b.buttonSize) || buttonSize || 56;
|
|
469
455
|
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,22 +475,28 @@ const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', age
|
|
|
489
475
|
const positionStyles = finalPosition === 'bottom-left'
|
|
490
476
|
? { bottom: '16px', left: '16px' }
|
|
491
477
|
: { bottom: '16px', right: '16px' };
|
|
492
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { children: `
|
|
493
|
-
@keyframes vox-fade-in { from { opacity: 0; transform: translateY(40px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
|
494
|
-
@keyframes vox-spin { to { transform: rotate(360deg); } }
|
|
495
|
-
@keyframes vox-ping { 0% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.15); opacity: 0; } 100% { transform: scale(1); opacity: 0; } }
|
|
496
|
-
.vox-widget { animation: vox-fade-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
|
|
478
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("link", { href: "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap", rel: "stylesheet" }), jsxRuntime.jsx("style", { children: `
|
|
479
|
+
@keyframes vox-fade-in { from { opacity: 0; transform: translateY(40px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
|
480
|
+
@keyframes vox-spin { to { transform: rotate(360deg); } }
|
|
481
|
+
@keyframes vox-ping { 0% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.15); opacity: 0; } 100% { transform: scale(1); opacity: 0; } }
|
|
482
|
+
.vox-widget { animation: vox-fade-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
|
|
497
483
|
` }), 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
484
|
width: 'min(calc(100vw - 32px), 360px)', height: '550px', maxHeight: '550px',
|
|
499
485
|
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: '
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
486
|
+
border: '1px solid #3f3f46', boxShadow: '0 20px 40px -10px rgba(0,0,0,0.5)', borderRadius: '8px',
|
|
487
|
+
transition: 'all 0.3s ease',
|
|
488
|
+
fontFamily: '"Plus Jakarta Sans", sans-serif',
|
|
489
|
+
WebkitFontSmoothing: 'antialiased', MozOsxFontSmoothing: 'grayscale',
|
|
490
|
+
textAlign: 'left', boxSizing: 'border-box'
|
|
491
|
+
}, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 16px', flexShrink: 0, borderBottom: '1px solid #3f3f46', background: '#000000', fontFamily: '"Plus Jakarta Sans", 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', color: '#ffffff' }, 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: 600, fontSize: '12px', margin: 0, transition: 'all 0.3s ease', lineHeight: '1.2', fontFamily: 'inherit' }, children: agentName }), jsxRuntime.jsxs("p", { style: { color: '#a1a1aa', fontSize: '11px', fontWeight: 400, margin: 0, display: 'flex', alignItems: 'center', lineHeight: '1.2', fontFamily: 'inherit' }, 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: {
|
|
492
|
+
padding: '6px 12px', fontSize: '11px', fontWeight: 500, borderRadius: '6px',
|
|
493
|
+
background: activeTab === 'text' ? '#27272a' : 'transparent', border: 'none', cursor: 'pointer', color: activeTab === 'text' ? '#ffffff' : '#71717a', transition: 'all 0.2s',
|
|
494
|
+
boxShadow: activeTab === 'text' ? '0 1px 2px rgba(0,0,0,0.1)' : 'none'
|
|
495
|
+
}, children: "Text" }), jsxRuntime.jsx("button", { onClick: () => handleTabChange('voice'), style: {
|
|
496
|
+
padding: '6px 12px', fontSize: '11px', fontWeight: 500, borderRadius: '6px',
|
|
497
|
+
background: activeTab === 'voice' ? '#27272a' : 'transparent', border: 'none', cursor: 'pointer', color: activeTab === 'voice' ? '#ffffff' : '#71717a', transition: 'all 0.2s',
|
|
498
|
+
boxShadow: activeTab === 'voice' ? '0 1px 2px rgba(0,0,0,0.1)' : 'none'
|
|
499
|
+
}, 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 || serverGeminiApiKey || '', 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
500
|
width: `${finalButtonSize}px`, height: `${finalButtonSize}px`,
|
|
509
501
|
borderRadius: `${finalBorderRadius}px`,
|
|
510
502
|
background: finalButtonColor, color: finalIconColor, border: 'none', cursor: 'pointer',
|