vox-ai-react 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -2
- package/dist/index.esm.js +429 -201
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +428 -200
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,229 +1,457 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
var ConnectionState;
|
|
5
|
+
(function (ConnectionState) {
|
|
6
|
+
ConnectionState["DISCONNECTED"] = "DISCONNECTED";
|
|
7
|
+
ConnectionState["CONNECTING"] = "CONNECTING";
|
|
8
|
+
ConnectionState["CONNECTED"] = "CONNECTED";
|
|
9
|
+
ConnectionState["ERROR"] = "ERROR";
|
|
10
|
+
})(ConnectionState || (ConnectionState = {}));
|
|
11
|
+
// Audio utilities
|
|
12
|
+
function createBlob(inputData) {
|
|
13
|
+
const buffer = new ArrayBuffer(inputData.length * 2);
|
|
14
|
+
const view = new DataView(buffer);
|
|
15
|
+
for (let i = 0; i < inputData.length; i++) {
|
|
16
|
+
const s = Math.max(-1, Math.min(1, inputData[i]));
|
|
17
|
+
view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
|
18
|
+
}
|
|
19
|
+
const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
|
|
20
|
+
return { mimeType: 'audio/pcm;rate=16000', data: base64 };
|
|
21
|
+
}
|
|
22
|
+
function decode(base64) {
|
|
23
|
+
const binaryString = atob(base64);
|
|
24
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
25
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
26
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
27
|
+
}
|
|
28
|
+
return bytes.buffer;
|
|
29
|
+
}
|
|
30
|
+
async function decodeAudioData(arrayBuffer, ctx, sampleRate, numChannels) {
|
|
31
|
+
const dataView = new DataView(arrayBuffer);
|
|
32
|
+
const numSamples = arrayBuffer.byteLength / 2;
|
|
33
|
+
const audioBuffer = ctx.createBuffer(numChannels, numSamples, sampleRate);
|
|
34
|
+
const channelData = audioBuffer.getChannelData(0);
|
|
35
|
+
for (let i = 0; i < numSamples; i++) {
|
|
36
|
+
const sample = dataView.getInt16(i * 2, true);
|
|
37
|
+
channelData[i] = sample / 32768;
|
|
38
|
+
}
|
|
39
|
+
return audioBuffer;
|
|
40
|
+
}
|
|
41
|
+
// Icons
|
|
42
|
+
const MessageIcon = () => (jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: jsx("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z" }) }));
|
|
43
|
+
const MicIcon = ({ size = 14 }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", children: [jsx("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }), jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }), jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }));
|
|
44
|
+
const MicOffIcon = ({ size = 40 }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", children: [jsx("line", { x1: "2", x2: "22", y1: "2", y2: "22" }), jsx("path", { d: "M18.89 13.23A7.12 7.12 0 0 0 19 12v-2" }), jsx("path", { d: "M5 10v2a7 7 0 0 0 12 5" }), jsx("path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33" }), jsx("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }), jsx("line", { x1: "12", x2: "12", y1: "19", y2: "22" })] }));
|
|
45
|
+
const XIcon = () => (jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { d: "M18 6L6 18M6 6l12 12" }) }));
|
|
46
|
+
const PhoneOffIcon = () => (jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67m-2.67-3.34a19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91" }), jsx("line", { x1: "22", x2: "2", y1: "2", y2: "22" })] }));
|
|
47
|
+
const BotIcon = () => (jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "M12 8V4H8" }), jsx("rect", { width: "16", height: "12", x: "4", y: "8", rx: "2" }), jsx("path", { d: "M2 14h2" }), jsx("path", { d: "M20 14h2" }), jsx("path", { d: "M15 13v2" }), jsx("path", { d: "M9 13v2" })] }));
|
|
48
|
+
const UserIcon = () => (jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }), jsx("circle", { cx: "12", cy: "7", r: "4" })] }));
|
|
49
|
+
const LoaderIcon = () => (jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "vox-spin", children: jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }));
|
|
50
|
+
const AlertIcon = () => (jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }), jsx("path", { d: "M12 9v4" }), jsx("path", { d: "M12 17h.01" })] }));
|
|
51
|
+
// Visualizer Component
|
|
52
|
+
const Visualizer = ({ analyser, isActive, theme }) => {
|
|
53
|
+
const canvasRef = useRef(null);
|
|
54
|
+
const animationRef = useRef();
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const canvas = canvasRef.current;
|
|
57
|
+
if (!canvas)
|
|
58
|
+
return;
|
|
59
|
+
const ctx = canvas.getContext('2d');
|
|
60
|
+
if (!ctx)
|
|
61
|
+
return;
|
|
62
|
+
const draw = () => {
|
|
63
|
+
const width = canvas.width;
|
|
64
|
+
const height = canvas.height;
|
|
65
|
+
ctx.clearRect(0, 0, width, height);
|
|
66
|
+
if (!analyser || !isActive) {
|
|
67
|
+
ctx.strokeStyle = theme === 'light' ? '#a1a1aa' : '#3f3f46';
|
|
68
|
+
ctx.lineWidth = 1;
|
|
69
|
+
ctx.beginPath();
|
|
70
|
+
ctx.moveTo(0, height / 2);
|
|
71
|
+
ctx.lineTo(width, height / 2);
|
|
72
|
+
ctx.stroke();
|
|
73
|
+
animationRef.current = requestAnimationFrame(draw);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const bufferLength = analyser.frequencyBinCount;
|
|
77
|
+
const dataArray = new Uint8Array(bufferLength);
|
|
78
|
+
analyser.getByteTimeDomainData(dataArray);
|
|
79
|
+
ctx.strokeStyle = theme === 'light' ? '#000000' : '#ffffff';
|
|
80
|
+
ctx.lineWidth = 1;
|
|
81
|
+
ctx.beginPath();
|
|
82
|
+
const sliceWidth = width / bufferLength;
|
|
83
|
+
let x = 0;
|
|
84
|
+
for (let i = 0; i < bufferLength; i++) {
|
|
85
|
+
const v = dataArray[i] / 128.0;
|
|
86
|
+
const y = (v * height) / 2;
|
|
87
|
+
if (i === 0)
|
|
88
|
+
ctx.moveTo(x, y);
|
|
89
|
+
else
|
|
90
|
+
ctx.lineTo(x, y);
|
|
91
|
+
x += sliceWidth;
|
|
92
|
+
}
|
|
93
|
+
ctx.lineTo(width, height / 2);
|
|
94
|
+
ctx.stroke();
|
|
95
|
+
animationRef.current = requestAnimationFrame(draw);
|
|
96
|
+
};
|
|
97
|
+
draw();
|
|
98
|
+
return () => {
|
|
99
|
+
if (animationRef.current)
|
|
100
|
+
cancelAnimationFrame(animationRef.current);
|
|
101
|
+
};
|
|
102
|
+
}, [analyser, isActive, theme]);
|
|
103
|
+
return jsx("canvas", { ref: canvasRef, width: 300, height: 50, className: "vox-visualizer" });
|
|
104
|
+
};
|
|
105
|
+
// Text Interface Component
|
|
106
|
+
const TextInterface = ({ apiKey, apiUrl, agentName, systemInstruction, theme }) => {
|
|
107
|
+
const [messages, setMessages] = useState([
|
|
108
|
+
{ id: 'welcome', role: 'assistant', text: `VOX INITIALIZED. GREETINGS. I AM ${agentName}. HOW MAY I ASSIST YOUR INQUIRY?` }
|
|
109
|
+
]);
|
|
7
110
|
const [input, setInput] = useState('');
|
|
8
111
|
const [isLoading, setIsLoading] = useState(false);
|
|
9
112
|
const scrollRef = useRef(null);
|
|
10
|
-
// Determine actual theme
|
|
11
|
-
const actualTheme = theme === 'auto'
|
|
12
|
-
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
|
13
|
-
: theme;
|
|
14
|
-
// Default colors based on theme
|
|
15
|
-
const defaultButtonColor = actualTheme === 'dark' ? '#000000' : '#ffffff';
|
|
16
|
-
const defaultIconColor = actualTheme === 'dark' ? '#ffffff' : '#000000';
|
|
17
|
-
const finalButtonColor = buttonColor || defaultButtonColor;
|
|
18
|
-
const finalIconColor = iconColor || defaultIconColor;
|
|
19
113
|
useEffect(() => {
|
|
20
|
-
if (scrollRef.current)
|
|
114
|
+
if (scrollRef.current)
|
|
21
115
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
22
|
-
}
|
|
23
116
|
}, [messages]);
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
// Add welcome message
|
|
26
|
-
setMessages([{
|
|
27
|
-
id: 'welcome',
|
|
28
|
-
role: 'assistant',
|
|
29
|
-
text: `Hello! I'm ${agentName}. How can I help you today?`,
|
|
30
|
-
timestamp: new Date()
|
|
31
|
-
}]);
|
|
32
|
-
}, [agentName]);
|
|
33
|
-
const handleOpen = () => {
|
|
34
|
-
setIsOpen(true);
|
|
35
|
-
onOpen === null || onOpen === void 0 ? void 0 : onOpen();
|
|
36
|
-
};
|
|
37
|
-
const handleClose = () => {
|
|
38
|
-
setIsOpen(false);
|
|
39
|
-
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
40
|
-
};
|
|
41
117
|
const handleSend = async (e) => {
|
|
118
|
+
var _a;
|
|
42
119
|
e === null || e === void 0 ? void 0 : e.preventDefault();
|
|
43
120
|
if (!input.trim() || isLoading)
|
|
44
121
|
return;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
role: 'user',
|
|
48
|
-
text: input,
|
|
49
|
-
timestamp: new Date()
|
|
50
|
-
};
|
|
51
|
-
setMessages(prev => [...prev, userMessage]);
|
|
52
|
-
onMessage === null || onMessage === void 0 ? void 0 : onMessage(input, 'user');
|
|
122
|
+
const userMsg = { id: Date.now().toString(), role: 'user', text: input };
|
|
123
|
+
setMessages(prev => [...prev, userMsg]);
|
|
53
124
|
setInput('');
|
|
54
125
|
setIsLoading(true);
|
|
55
126
|
try {
|
|
127
|
+
const history = messages.map(m => ({ role: m.role === 'assistant' ? 'model' : 'user', text: m.text }));
|
|
56
128
|
const response = await fetch(`${apiUrl}/api/v1/chat`, {
|
|
57
129
|
method: 'POST',
|
|
58
|
-
headers: {
|
|
59
|
-
|
|
60
|
-
'Authorization': `Bearer ${apiKey}`
|
|
61
|
-
},
|
|
62
|
-
body: JSON.stringify({
|
|
63
|
-
message: userMessage.text,
|
|
64
|
-
history: messages.map(m => ({ role: m.role, content: m.text }))
|
|
65
|
-
})
|
|
130
|
+
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
|
131
|
+
body: JSON.stringify({ message: input, history, systemInstruction })
|
|
66
132
|
});
|
|
133
|
+
if (!response.ok)
|
|
134
|
+
throw new Error('API request failed');
|
|
67
135
|
const data = await response.json();
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
setMessages(prev => [...prev, assistantMessage]);
|
|
75
|
-
onMessage === null || onMessage === void 0 ? void 0 : onMessage(assistantMessage.text, 'assistant');
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
const errorMessage = {
|
|
79
|
-
id: (Date.now() + 1).toString(),
|
|
80
|
-
role: 'assistant',
|
|
81
|
-
text: 'Sorry, I encountered an error. Please try again.',
|
|
82
|
-
timestamp: new Date()
|
|
83
|
-
};
|
|
84
|
-
setMessages(prev => [...prev, errorMessage]);
|
|
136
|
+
const botMsg = { id: (Date.now() + 1).toString(), role: 'assistant', text: ((_a = data.response) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'ERROR: NO RESPONSE' };
|
|
137
|
+
setMessages(prev => [...prev, botMsg]);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const errorMsg = { id: (Date.now() + 1).toString(), role: 'assistant', text: 'ERROR: SIGNAL LOST. RETRY.' };
|
|
141
|
+
setMessages(prev => [...prev, errorMsg]);
|
|
85
142
|
}
|
|
86
143
|
finally {
|
|
87
144
|
setIsLoading(false);
|
|
88
145
|
}
|
|
89
146
|
};
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
147
|
+
const bgColor = theme === 'light' ? '#ffffff' : '#000000';
|
|
148
|
+
const textColor = theme === 'light' ? '#000000' : '#ffffff';
|
|
149
|
+
const borderColor = theme === 'light' ? '#a1a1aa' : '#52525b';
|
|
150
|
+
const mutedColor = theme === 'light' ? '#71717a' : '#a1a1aa';
|
|
151
|
+
return (jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: bgColor }, children: [jsxs("div", { ref: scrollRef, style: { flex: 1, overflowY: 'auto', padding: '24px', display: 'flex', flexDirection: 'column', gap: '16px' }, children: [messages.map((msg) => (jsx("div", { style: { display: 'flex', justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start' }, children: jsxs("div", { style: { display: 'flex', flexDirection: msg.role === 'user' ? 'row-reverse' : 'row', alignItems: 'flex-start', gap: '12px', maxWidth: '85%' }, children: [jsx("div", { style: {
|
|
152
|
+
width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
153
|
+
border: `1px solid ${msg.role === 'user' ? textColor : borderColor}`,
|
|
154
|
+
background: msg.role === 'user' ? textColor : 'transparent', color: msg.role === 'user' ? bgColor : textColor
|
|
155
|
+
}, children: msg.role === 'user' ? jsx(UserIcon, {}) : jsx(BotIcon, {}) }), jsx("div", { style: {
|
|
156
|
+
padding: '12px 16px', fontSize: '11px', fontWeight: 700, letterSpacing: '0.05em', lineHeight: 1.6,
|
|
157
|
+
border: `1px solid ${borderColor}`, background: msg.role === 'user' ? (theme === 'light' ? '#f4f4f5' : '#27272a') : bgColor,
|
|
158
|
+
color: msg.role === 'user' ? textColor : mutedColor
|
|
159
|
+
}, children: msg.text })] }) }, msg.id))), isLoading && (jsx("div", { style: { display: 'flex', justifyContent: 'flex-start' }, children: jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '12px' }, children: [jsx("div", { style: { width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', border: `1px solid ${borderColor}`, color: textColor, opacity: 0.2 }, children: jsx(BotIcon, {}) }), jsx("div", { style: { padding: '16px 24px', border: `1px solid ${borderColor}`, background: bgColor, color: mutedColor }, children: jsx(LoaderIcon, {}) })] }) }))] }), jsx("div", { style: { padding: '24px' }, children: jsxs("form", { onSubmit: handleSend, style: { display: 'flex', gap: '16px' }, children: [jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: "INPUT COMMAND...", disabled: isLoading, style: {
|
|
160
|
+
flex: 1, padding: '16px 24px', border: `1px solid ${borderColor}`, background: bgColor, color: textColor,
|
|
161
|
+
outline: 'none', fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.1em'
|
|
162
|
+
} }), jsx("button", { type: "submit", disabled: !input.trim() || isLoading, style: {
|
|
163
|
+
padding: '16px 24px', background: textColor, color: bgColor, border: 'none', cursor: 'pointer',
|
|
164
|
+
fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.1em',
|
|
165
|
+
opacity: !input.trim() || isLoading ? 0.2 : 1
|
|
166
|
+
}, children: "EXECUTE" })] }) })] }));
|
|
167
|
+
};
|
|
168
|
+
// Voice Interface Component
|
|
169
|
+
const VoiceInterface = ({ geminiApiKey, voiceName, systemInstruction, theme, onEndCall }) => {
|
|
170
|
+
const [connectionState, setConnectionState] = useState(ConnectionState.DISCONNECTED);
|
|
171
|
+
const [errorMsg, setErrorMsg] = useState('');
|
|
172
|
+
const [isMuted, setIsMuted] = useState(false);
|
|
173
|
+
const [callDuration, setCallDuration] = useState(0);
|
|
174
|
+
const [isSpeaking, setIsSpeaking] = useState(false);
|
|
175
|
+
const [speakingIntensity, setSpeakingIntensity] = useState(0);
|
|
176
|
+
const callStartTimeRef = useRef(null);
|
|
177
|
+
const durationIntervalRef = useRef(null);
|
|
178
|
+
const inputAudioContextRef = useRef(null);
|
|
179
|
+
const outputAudioContextRef = useRef(null);
|
|
180
|
+
const inputAnalyserRef = useRef(null);
|
|
181
|
+
const outputAnalyserRef = useRef(null);
|
|
182
|
+
const outputGainRef = useRef(null);
|
|
183
|
+
const nextStartTimeRef = useRef(0);
|
|
184
|
+
const sourcesRef = useRef(new Set());
|
|
185
|
+
const speakingAnimationRef = useRef(null);
|
|
186
|
+
const sessionRef = useRef(null);
|
|
187
|
+
const streamRef = useRef(null);
|
|
188
|
+
const scriptProcessorRef = useRef(null);
|
|
189
|
+
const sourceNodeRef = useRef(null);
|
|
190
|
+
const cleanupAudio = useCallback(() => {
|
|
191
|
+
var _a, _b, _c, _d;
|
|
192
|
+
if (speakingAnimationRef.current) {
|
|
193
|
+
cancelAnimationFrame(speakingAnimationRef.current);
|
|
194
|
+
speakingAnimationRef.current = null;
|
|
195
|
+
}
|
|
196
|
+
setIsSpeaking(false);
|
|
197
|
+
setSpeakingIntensity(0);
|
|
198
|
+
sourcesRef.current.forEach(source => { try {
|
|
199
|
+
source.stop();
|
|
200
|
+
}
|
|
201
|
+
catch (e) { } });
|
|
202
|
+
sourcesRef.current.clear();
|
|
203
|
+
if (streamRef.current) {
|
|
204
|
+
streamRef.current.getTracks().forEach(track => { track.stop(); track.enabled = false; });
|
|
205
|
+
streamRef.current = null;
|
|
206
|
+
}
|
|
207
|
+
if (scriptProcessorRef.current) {
|
|
208
|
+
try {
|
|
209
|
+
scriptProcessorRef.current.disconnect();
|
|
210
|
+
}
|
|
211
|
+
catch (e) { }
|
|
212
|
+
scriptProcessorRef.current = null;
|
|
213
|
+
}
|
|
214
|
+
if (sourceNodeRef.current) {
|
|
215
|
+
try {
|
|
216
|
+
sourceNodeRef.current.disconnect();
|
|
217
|
+
}
|
|
218
|
+
catch (e) { }
|
|
219
|
+
sourceNodeRef.current = null;
|
|
220
|
+
}
|
|
221
|
+
if (outputGainRef.current) {
|
|
222
|
+
try {
|
|
223
|
+
outputGainRef.current.disconnect();
|
|
224
|
+
}
|
|
225
|
+
catch (e) { }
|
|
226
|
+
outputGainRef.current = null;
|
|
227
|
+
}
|
|
228
|
+
if (outputAnalyserRef.current) {
|
|
229
|
+
try {
|
|
230
|
+
outputAnalyserRef.current.disconnect();
|
|
231
|
+
}
|
|
232
|
+
catch (e) { }
|
|
233
|
+
outputAnalyserRef.current = null;
|
|
234
|
+
}
|
|
235
|
+
if (((_a = inputAudioContextRef.current) === null || _a === void 0 ? void 0 : _a.state) !== 'closed') {
|
|
236
|
+
try {
|
|
237
|
+
(_b = inputAudioContextRef.current) === null || _b === void 0 ? void 0 : _b.close();
|
|
238
|
+
}
|
|
239
|
+
catch (e) { }
|
|
240
|
+
}
|
|
241
|
+
if (((_c = outputAudioContextRef.current) === null || _c === void 0 ? void 0 : _c.state) !== 'closed') {
|
|
242
|
+
try {
|
|
243
|
+
(_d = outputAudioContextRef.current) === null || _d === void 0 ? void 0 : _d.close();
|
|
244
|
+
}
|
|
245
|
+
catch (e) { }
|
|
246
|
+
}
|
|
247
|
+
inputAudioContextRef.current = null;
|
|
248
|
+
outputAudioContextRef.current = null;
|
|
249
|
+
inputAnalyserRef.current = null;
|
|
250
|
+
sessionRef.current = null;
|
|
251
|
+
}, []);
|
|
252
|
+
const connect = async () => {
|
|
253
|
+
try {
|
|
254
|
+
setConnectionState(ConnectionState.CONNECTING);
|
|
255
|
+
setErrorMsg('');
|
|
256
|
+
callStartTimeRef.current = Date.now();
|
|
257
|
+
durationIntervalRef.current = setInterval(() => {
|
|
258
|
+
if (callStartTimeRef.current)
|
|
259
|
+
setCallDuration(Math.floor((Date.now() - callStartTimeRef.current) / 1000));
|
|
260
|
+
}, 1000);
|
|
261
|
+
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
262
|
+
const inputCtx = new AudioContextClass({ sampleRate: 16000 });
|
|
263
|
+
const outputCtx = new AudioContextClass({ sampleRate: 24000 });
|
|
264
|
+
inputAudioContextRef.current = inputCtx;
|
|
265
|
+
outputAudioContextRef.current = outputCtx;
|
|
266
|
+
const analyser = inputCtx.createAnalyser();
|
|
267
|
+
analyser.fftSize = 256;
|
|
268
|
+
inputAnalyserRef.current = analyser;
|
|
269
|
+
const outputAnalyser = outputCtx.createAnalyser();
|
|
270
|
+
outputAnalyser.fftSize = 256;
|
|
271
|
+
outputAnalyserRef.current = outputAnalyser;
|
|
272
|
+
const outputGain = outputCtx.createGain();
|
|
273
|
+
outputGain.gain.value = 1;
|
|
274
|
+
outputGainRef.current = outputGain;
|
|
275
|
+
outputGain.connect(outputAnalyser);
|
|
276
|
+
outputAnalyser.connect(outputCtx.destination);
|
|
277
|
+
const monitorSpeaking = () => {
|
|
278
|
+
if (!outputAnalyserRef.current)
|
|
279
|
+
return;
|
|
280
|
+
const dataArray = new Uint8Array(outputAnalyserRef.current.frequencyBinCount);
|
|
281
|
+
outputAnalyserRef.current.getByteFrequencyData(dataArray);
|
|
282
|
+
const average = dataArray.reduce((a, b) => a + b, 0) / dataArray.length;
|
|
283
|
+
const intensity = Math.min(average / 128, 1);
|
|
284
|
+
setIsSpeaking(intensity > 0.05);
|
|
285
|
+
setSpeakingIntensity(intensity);
|
|
286
|
+
speakingAnimationRef.current = requestAnimationFrame(monitorSpeaking);
|
|
287
|
+
};
|
|
288
|
+
monitorSpeaking();
|
|
289
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
290
|
+
streamRef.current = stream;
|
|
291
|
+
// Connect to Gemini Live API via WebSocket
|
|
292
|
+
const wsUrl = `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent?key=${geminiApiKey}`;
|
|
293
|
+
const ws = new WebSocket(wsUrl);
|
|
294
|
+
sessionRef.current = ws;
|
|
295
|
+
ws.onopen = () => {
|
|
296
|
+
// Send setup message
|
|
297
|
+
const setupMsg = {
|
|
298
|
+
setup: {
|
|
299
|
+
model: 'models/gemini-2.5-flash-preview-native-audio-dialog',
|
|
300
|
+
generationConfig: { responseModalities: ['AUDIO'], speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName } } } },
|
|
301
|
+
systemInstruction: { parts: [{ text: systemInstruction }] }
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
ws.send(JSON.stringify(setupMsg));
|
|
305
|
+
};
|
|
306
|
+
ws.onmessage = async (event) => {
|
|
307
|
+
var _a, _b, _c, _d, _e, _f;
|
|
308
|
+
const data = JSON.parse(event.data);
|
|
309
|
+
if (data.setupComplete) {
|
|
310
|
+
setConnectionState(ConnectionState.CONNECTED);
|
|
311
|
+
// Setup audio input pipeline
|
|
312
|
+
const source = inputCtx.createMediaStreamSource(stream);
|
|
313
|
+
sourceNodeRef.current = source;
|
|
314
|
+
const processor = inputCtx.createScriptProcessor(2048, 1, 1);
|
|
315
|
+
scriptProcessorRef.current = processor;
|
|
316
|
+
processor.onaudioprocess = (e) => {
|
|
317
|
+
if (isMuted || ws.readyState !== WebSocket.OPEN)
|
|
318
|
+
return;
|
|
319
|
+
const inputData = e.inputBuffer.getChannelData(0);
|
|
320
|
+
const pcmBlob = createBlob(inputData);
|
|
321
|
+
ws.send(JSON.stringify({ realtimeInput: { mediaChunks: [pcmBlob] } }));
|
|
322
|
+
};
|
|
323
|
+
source.connect(analyser);
|
|
324
|
+
source.connect(processor);
|
|
325
|
+
processor.connect(inputCtx.destination);
|
|
326
|
+
}
|
|
327
|
+
// Handle audio output
|
|
328
|
+
const audioData = (_e = (_d = (_c = (_b = (_a = data.serverContent) === null || _a === void 0 ? void 0 : _a.modelTurn) === null || _b === void 0 ? void 0 : _b.parts) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.inlineData) === null || _e === void 0 ? void 0 : _e.data;
|
|
329
|
+
if (audioData) {
|
|
330
|
+
const ctx = outputAudioContextRef.current;
|
|
331
|
+
if (!ctx)
|
|
332
|
+
return;
|
|
333
|
+
nextStartTimeRef.current = Math.max(nextStartTimeRef.current, ctx.currentTime);
|
|
334
|
+
const audioBuffer = await decodeAudioData(decode(audioData), ctx, 24000, 1);
|
|
335
|
+
const source = ctx.createBufferSource();
|
|
336
|
+
source.buffer = audioBuffer;
|
|
337
|
+
if (outputGainRef.current)
|
|
338
|
+
source.connect(outputGainRef.current);
|
|
339
|
+
else
|
|
340
|
+
source.connect(ctx.destination);
|
|
341
|
+
source.addEventListener('ended', () => sourcesRef.current.delete(source));
|
|
342
|
+
source.start(nextStartTimeRef.current);
|
|
343
|
+
nextStartTimeRef.current += audioBuffer.duration;
|
|
344
|
+
sourcesRef.current.add(source);
|
|
345
|
+
}
|
|
346
|
+
if ((_f = data.serverContent) === null || _f === void 0 ? void 0 : _f.interrupted) {
|
|
347
|
+
sourcesRef.current.forEach(s => s.stop());
|
|
348
|
+
sourcesRef.current.clear();
|
|
349
|
+
nextStartTimeRef.current = 0;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
ws.onclose = () => setConnectionState(ConnectionState.DISCONNECTED);
|
|
353
|
+
ws.onerror = () => {
|
|
354
|
+
setConnectionState(ConnectionState.ERROR);
|
|
355
|
+
setErrorMsg('Connection lost. Please try again.');
|
|
356
|
+
cleanupAudio();
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
setConnectionState(ConnectionState.ERROR);
|
|
361
|
+
setErrorMsg(err.message || 'Failed to access microphone or connect.');
|
|
362
|
+
cleanupAudio();
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
const disconnect = useCallback(() => {
|
|
366
|
+
callStartTimeRef.current = null;
|
|
367
|
+
if (durationIntervalRef.current) {
|
|
368
|
+
clearInterval(durationIntervalRef.current);
|
|
369
|
+
durationIntervalRef.current = null;
|
|
370
|
+
}
|
|
371
|
+
setCallDuration(0);
|
|
372
|
+
if (sessionRef.current) {
|
|
373
|
+
try {
|
|
374
|
+
sessionRef.current.close();
|
|
375
|
+
}
|
|
376
|
+
catch (e) { }
|
|
377
|
+
}
|
|
378
|
+
cleanupAudio();
|
|
379
|
+
setConnectionState(ConnectionState.DISCONNECTED);
|
|
380
|
+
}, [cleanupAudio]);
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
connect();
|
|
383
|
+
return () => disconnect();
|
|
384
|
+
}, []);
|
|
385
|
+
const bgColor = theme === 'light' ? '#ffffff' : '#000000';
|
|
386
|
+
const textColor = theme === 'light' ? '#000000' : '#ffffff';
|
|
387
|
+
const borderColor = theme === 'light' ? '#a1a1aa' : '#52525b';
|
|
388
|
+
const mutedColor = '#71717a';
|
|
389
|
+
return (jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '32px', height: '100%', padding: '24px' }, children: [jsxs("div", { style: { position: 'relative' }, children: [connectionState === ConnectionState.CONNECTED && isSpeaking && (jsx("div", { style: {
|
|
390
|
+
position: 'absolute', inset: 0, borderRadius: '50%', border: `1px solid ${textColor}`,
|
|
391
|
+
transform: `scale(${1 + speakingIntensity * 0.3})`, opacity: 0.2, animation: 'vox-ping 1s infinite'
|
|
392
|
+
} })), jsx("div", { style: {
|
|
393
|
+
width: '112px', height: '112px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
394
|
+
border: `1px solid ${connectionState === ConnectionState.CONNECTED ? textColor : borderColor}`,
|
|
395
|
+
boxShadow: connectionState === ConnectionState.CONNECTED ? `0 0 ${30 + speakingIntensity * 40}px ${theme === 'light' ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.15)'}` : 'none',
|
|
396
|
+
transform: isSpeaking ? `scale(${1 + speakingIntensity * 0.08})` : 'scale(1)', transition: 'all 0.3s'
|
|
397
|
+
}, children: connectionState === ConnectionState.CONNECTING ? (jsx("div", { style: { width: '40px', height: '40px', border: `2px solid ${textColor}`, borderTopColor: 'transparent', borderRadius: '50%', animation: 'vox-spin 1s linear infinite' } })) : (jsx("div", { style: { padding: '20px', color: isMuted ? mutedColor : textColor }, children: isMuted ? jsx(MicOffIcon, {}) : jsx(MicIcon, { size: 40 }) })) }), connectionState === ConnectionState.CONNECTED && (jsx("span", { style: { position: 'absolute', bottom: '-4px', right: '-4px', width: '12px', height: '12px', borderRadius: '50%', background: textColor } }))] }), jsxs("div", { style: { width: '100%', maxWidth: '280px', display: 'flex', flexDirection: 'column', gap: '16px' }, children: [jsxs("p", { style: { textAlign: 'center', fontSize: '10px', fontWeight: 900, color: mutedColor, textTransform: 'uppercase', letterSpacing: '0.2em', height: '20px' }, children: [connectionState === ConnectionState.CONNECTING && 'Initializing...', connectionState === ConnectionState.CONNECTED && (isSpeaking ? 'Agent Speaking...' : isMuted ? 'Signal Muted' : 'Listening...'), connectionState === ConnectionState.ERROR && 'Connection Error', connectionState === ConnectionState.DISCONNECTED && 'Disconnected'] }), connectionState === ConnectionState.CONNECTED && callDuration > 0 && (jsxs("p", { style: { textAlign: 'center', fontSize: '11px', fontFamily: 'monospace', color: textColor }, children: [Math.floor(callDuration / 60).toString().padStart(2, '0'), ":", (callDuration % 60).toString().padStart(2, '0')] })), jsx(Visualizer, { analyser: inputAnalyserRef.current, isActive: connectionState === ConnectionState.CONNECTED && !isMuted, theme: theme })] }), connectionState === ConnectionState.ERROR && (jsxs("div", { style: { display: 'flex', alignItems: 'center', fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.1em', padding: '12px 16px', maxWidth: '280px', textAlign: 'center', color: mutedColor, background: theme === 'light' ? '#f4f4f5' : '#18181b', border: `1px solid ${borderColor}` }, children: [jsx(AlertIcon, {}), jsx("span", { style: { marginLeft: '12px' }, children: errorMsg })] })), connectionState === ConnectionState.CONNECTED && (jsxs("button", { onClick: () => { disconnect(); onEndCall === null || onEndCall === void 0 ? void 0 : onEndCall(); }, style: {
|
|
398
|
+
padding: '12px 24px', border: `1px solid ${borderColor}`, background: bgColor, color: mutedColor, cursor: 'pointer',
|
|
399
|
+
fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.1em', display: 'flex', alignItems: 'center', gap: '8px'
|
|
400
|
+
}, children: [jsx(PhoneOffIcon, {}), "End Call"] }))] }));
|
|
401
|
+
};
|
|
402
|
+
// Main VoxChat Component
|
|
403
|
+
const VoxChat = ({ apiKey, geminiApiKey, apiUrl = 'https://your-server.com', agentName = 'VOX-01', voiceName = 'Kore', systemInstruction = 'You are a helpful AI assistant.', position = 'bottom-right', theme = 'dark', buttonColor, buttonSize = 56, borderRadius = 0, iconColor, onOpen, onClose }) => {
|
|
404
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
405
|
+
const [activeTab, setActiveTab] = useState('text');
|
|
406
|
+
const [isVoiceActive, setIsVoiceActive] = useState(false);
|
|
407
|
+
// Invert theme for widget (black in light mode, white in dark mode)
|
|
408
|
+
const widgetTheme = theme === 'light' ? 'dark' : 'light';
|
|
409
|
+
const bgColor = widgetTheme === 'light' ? '#ffffff' : '#000000';
|
|
410
|
+
const textColor = widgetTheme === 'light' ? '#000000' : '#ffffff';
|
|
411
|
+
const borderColor = widgetTheme === 'light' ? '#a1a1aa' : '#52525b';
|
|
412
|
+
const mutedColor = '#71717a';
|
|
413
|
+
const handleOpen = () => { setIsOpen(true); onOpen === null || onOpen === void 0 ? void 0 : onOpen(); };
|
|
414
|
+
const handleClose = () => { setIsOpen(false); setIsVoiceActive(false); onClose === null || onClose === void 0 ? void 0 : onClose(); };
|
|
415
|
+
const handleTabChange = (tab) => {
|
|
416
|
+
if (tab === 'voice' && activeTab !== 'voice') {
|
|
417
|
+
setActiveTab('voice');
|
|
418
|
+
setIsVoiceActive(true);
|
|
419
|
+
}
|
|
420
|
+
else if (tab === 'text') {
|
|
421
|
+
setIsVoiceActive(false);
|
|
422
|
+
setActiveTab('text');
|
|
423
|
+
}
|
|
109
424
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
fontSize: '10px',
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
textTransform: 'uppercase',
|
|
141
|
-
letterSpacing: '0.1em',
|
|
142
|
-
display: 'flex',
|
|
143
|
-
alignItems: 'center',
|
|
144
|
-
gap: '6px'
|
|
145
|
-
}, children: [jsx("span", { style: {
|
|
146
|
-
width: '6px',
|
|
147
|
-
height: '6px',
|
|
148
|
-
backgroundColor: '#22c55e',
|
|
149
|
-
borderRadius: '50%'
|
|
150
|
-
} }), "Online"] })] }), jsx("button", { onClick: handleClose, style: {
|
|
151
|
-
background: 'none',
|
|
152
|
-
border: 'none',
|
|
153
|
-
color: colors.textSecondary,
|
|
154
|
-
cursor: 'pointer',
|
|
155
|
-
padding: '4px',
|
|
156
|
-
}, children: jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { d: "M18 6L6 18M6 6l12 12" }) }) })] }), jsxs("div", { ref: scrollRef, style: {
|
|
157
|
-
flex: 1,
|
|
158
|
-
overflowY: 'auto',
|
|
159
|
-
padding: '16px',
|
|
160
|
-
display: 'flex',
|
|
161
|
-
flexDirection: 'column',
|
|
162
|
-
gap: '12px',
|
|
163
|
-
}, children: [messages.map((msg) => (jsx("div", { style: {
|
|
164
|
-
display: 'flex',
|
|
165
|
-
justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start',
|
|
166
|
-
}, children: jsx("div", { style: {
|
|
167
|
-
maxWidth: '80%',
|
|
168
|
-
padding: '10px 14px',
|
|
169
|
-
backgroundColor: msg.role === 'user' ? colors.userBubble : colors.assistantBubble,
|
|
170
|
-
border: `1px solid ${colors.border}`,
|
|
171
|
-
color: colors.text,
|
|
172
|
-
fontSize: '13px',
|
|
173
|
-
lineHeight: 1.5,
|
|
174
|
-
}, children: msg.text }) }, msg.id))), isLoading && (jsx("div", { style: { display: 'flex', justifyContent: 'flex-start' }, children: jsx("div", { style: {
|
|
175
|
-
padding: '10px 14px',
|
|
176
|
-
backgroundColor: colors.assistantBubble,
|
|
177
|
-
border: `1px solid ${colors.border}`,
|
|
178
|
-
color: colors.textSecondary,
|
|
179
|
-
fontSize: '13px',
|
|
180
|
-
}, children: "Typing..." }) }))] }), jsxs("form", { onSubmit: handleSend, style: {
|
|
181
|
-
padding: '16px',
|
|
182
|
-
borderTop: `1px solid ${colors.border}`,
|
|
183
|
-
display: 'flex',
|
|
184
|
-
gap: '8px',
|
|
185
|
-
}, children: [jsx("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: "Type a message...", style: {
|
|
186
|
-
flex: 1,
|
|
187
|
-
padding: '10px 14px',
|
|
188
|
-
backgroundColor: colors.bgSecondary,
|
|
189
|
-
border: `1px solid ${colors.border}`,
|
|
190
|
-
color: colors.text,
|
|
191
|
-
fontSize: '13px',
|
|
192
|
-
outline: 'none',
|
|
193
|
-
} }), jsx("button", { type: "submit", disabled: !input.trim() || isLoading, style: {
|
|
194
|
-
padding: '10px 16px',
|
|
195
|
-
backgroundColor: colors.text,
|
|
196
|
-
color: colors.bg,
|
|
197
|
-
border: 'none',
|
|
198
|
-
fontSize: '11px',
|
|
199
|
-
fontWeight: 700,
|
|
200
|
-
textTransform: 'uppercase',
|
|
201
|
-
letterSpacing: '0.05em',
|
|
202
|
-
cursor: input.trim() && !isLoading ? 'pointer' : 'not-allowed',
|
|
203
|
-
opacity: input.trim() && !isLoading ? 1 : 0.5,
|
|
204
|
-
}, children: "Send" })] })] })), !isOpen && (jsx("button", { onClick: handleOpen, style: {
|
|
205
|
-
position: 'fixed',
|
|
206
|
-
bottom: '16px',
|
|
207
|
-
...positionStyles,
|
|
208
|
-
width: `${buttonSize}px`,
|
|
209
|
-
height: `${buttonSize}px`,
|
|
210
|
-
backgroundColor: finalButtonColor,
|
|
211
|
-
borderRadius: `${borderRadius}px`,
|
|
212
|
-
border: 'none',
|
|
213
|
-
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
|
214
|
-
cursor: 'pointer',
|
|
215
|
-
display: 'flex',
|
|
216
|
-
alignItems: 'center',
|
|
217
|
-
justifyContent: 'center',
|
|
218
|
-
zIndex: 9999,
|
|
219
|
-
transition: 'transform 0.2s, box-shadow 0.2s',
|
|
220
|
-
}, onMouseEnter: (e) => {
|
|
221
|
-
e.currentTarget.style.transform = 'scale(1.05)';
|
|
222
|
-
e.currentTarget.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.2)';
|
|
223
|
-
}, onMouseLeave: (e) => {
|
|
224
|
-
e.currentTarget.style.transform = 'scale(1)';
|
|
225
|
-
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
|
|
226
|
-
}, children: jsx("svg", { width: buttonSize * 0.4, height: buttonSize * 0.4, viewBox: "0 0 24 24", fill: "none", stroke: finalIconColor, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z" }) }) }))] }));
|
|
425
|
+
const positionStyles = position === 'bottom-left'
|
|
426
|
+
? { bottom: '16px', left: '16px' }
|
|
427
|
+
: { bottom: '16px', right: '16px' };
|
|
428
|
+
const btnBg = buttonColor || (theme === 'light' ? '#000000' : '#ffffff');
|
|
429
|
+
const btnIcon = iconColor || (theme === 'light' ? '#ffffff' : '#000000');
|
|
430
|
+
return (jsxs(Fragment, { children: [jsx("style", { children: `
|
|
431
|
+
@keyframes vox-fade-in { from { opacity: 0; transform: translateY(40px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
|
432
|
+
@keyframes vox-spin { to { transform: rotate(360deg); } }
|
|
433
|
+
@keyframes vox-ping { 0% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.15); opacity: 0; } 100% { transform: scale(1); opacity: 0; } }
|
|
434
|
+
.vox-widget { animation: vox-fade-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
|
|
435
|
+
.vox-spin { animation: vox-spin 1s linear infinite; }
|
|
436
|
+
.vox-visualizer { width: 100%; height: 50px; background: transparent; }
|
|
437
|
+
.vox-scrollbar::-webkit-scrollbar { display: none; }
|
|
438
|
+
.vox-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
439
|
+
` }), jsxs("div", { style: { position: 'fixed', zIndex: 10000, display: 'flex', flexDirection: 'column', alignItems: position === 'bottom-left' ? 'flex-start' : 'flex-end', gap: '24px', ...positionStyles }, children: [isOpen && (jsxs("div", { className: "vox-widget", style: {
|
|
440
|
+
width: 'min(calc(100vw - 32px), 400px)', height: 'min(calc(100vh - 100px), 640px)',
|
|
441
|
+
background: bgColor, overflow: 'hidden', display: 'flex', flexDirection: 'column',
|
|
442
|
+
border: `1px solid ${borderColor}`, boxShadow: '0 30px 60px -15px rgba(0,0,0,0.5)'
|
|
443
|
+
}, children: [jsxs("div", { style: { height: '56px', background: bgColor, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 24px', flexShrink: 0, borderBottom: `1px solid ${borderColor}` }, children: [jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '12px' }, children: [jsx("div", { style: { width: '32px', height: '32px', border: `1px solid ${borderColor}`, display: 'flex', alignItems: 'center', justifyContent: 'center', color: textColor }, children: jsx(MicIcon, {}) }), jsxs("div", { children: [jsx("h2", { style: { color: textColor, fontWeight: 900, fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.2em', margin: 0 }, children: agentName }), jsxs("p", { style: { color: mutedColor, fontSize: '9px', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', margin: 0, display: 'flex', alignItems: 'center' }, children: [jsx("span", { style: { width: '4px', height: '4px', background: textColor, borderRadius: '50%', marginRight: '8px' } }), "Active"] })] })] }), jsx("button", { onClick: handleClose, style: { background: 'none', border: 'none', color: mutedColor, cursor: 'pointer', padding: '4px' }, children: jsx(XIcon, {}) })] }), jsxs("div", { style: { display: 'flex', padding: '4px', background: widgetTheme === 'light' ? '#f4f4f5' : '#09090b', borderBottom: `1px solid ${borderColor}`, flexShrink: 0 }, children: [jsx("button", { onClick: () => handleTabChange('text'), style: {
|
|
444
|
+
flex: 1, padding: '12px', fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.2em',
|
|
445
|
+
background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'text' ? textColor : mutedColor
|
|
446
|
+
}, children: "Text Interface" }), jsx("div", { style: { width: '1px', background: borderColor } }), jsx("button", { onClick: () => handleTabChange('voice'), style: {
|
|
447
|
+
flex: 1, padding: '12px', fontSize: '10px', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.2em',
|
|
448
|
+
background: 'none', border: 'none', cursor: 'pointer', color: activeTab === 'voice' ? textColor : mutedColor
|
|
449
|
+
}, children: "Voice Protocol" })] }), jsx("div", { style: { flex: 1, overflow: 'hidden', position: 'relative', background: bgColor }, children: activeTab === 'text' ? (jsx(TextInterface, { apiKey: apiKey, apiUrl: apiUrl, agentName: agentName, systemInstruction: systemInstruction, theme: widgetTheme })) : (isVoiceActive && jsx(VoiceInterface, { geminiApiKey: geminiApiKey, voiceName: voiceName, systemInstruction: systemInstruction, theme: widgetTheme, onEndCall: () => setIsVoiceActive(false) })) })] })), jsx("button", { onClick: isOpen ? handleClose : handleOpen, style: {
|
|
450
|
+
width: `${buttonSize}px`, height: `${buttonSize}px`, borderRadius: `${borderRadius}%`,
|
|
451
|
+
background: btnBg, color: btnIcon, border: 'none', cursor: 'pointer',
|
|
452
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
453
|
+
boxShadow: '0 4px 20px rgba(0,0,0,0.3)', transition: 'transform 0.2s'
|
|
454
|
+
}, children: isOpen ? jsx(XIcon, {}) : jsx(MessageIcon, {}) })] })] }));
|
|
227
455
|
};
|
|
228
456
|
|
|
229
457
|
export { VoxChat, VoxChat as default };
|