@shvm/vani-client 0.0.2
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/README.md +196 -0
- package/dist/headless/index.d.ts +205 -0
- package/dist/headless/index.js +624 -0
- package/dist/headless/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +651 -0
- package/dist/index.js.map +1 -0
- package/dist/shared/index.d.ts +49 -0
- package/dist/shared/index.js +30 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/ui/index.d.ts +39 -0
- package/dist/ui/index.js +559 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/voice-BwU4C7fN.d.ts +51 -0
- package/package.json +68 -0
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import { Maximize2, X, Settings, Minimize2, Save, Radio, Loader2, Volume2, AlertCircle, WifiOff, Mic, Square, Terminal, FileAudio, Speaker, Activity } from 'lucide-react';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { lazy, useState, useEffect, Suspense, useRef, useMemo } from 'react';
|
|
4
|
+
import { TTS_MODELS, TTS_MODEL_VOICES, LLM_MODELS, STT_MODELS } from '@shvm/vani-client/shared';
|
|
5
|
+
import { useVoiceSession } from '@shvm/vani-client/headless';
|
|
6
|
+
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
function VoiceDebugSidebar({ isOpen, onClose, events }) {
|
|
17
|
+
if (!isOpen) return null;
|
|
18
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-y-0 right-0 w-96 bg-zinc-900 border-l border-zinc-800 shadow-2xl flex flex-col z-50 animate-in slide-in-from-right duration-300", children: [
|
|
19
|
+
/* @__PURE__ */ jsxs("div", { className: "bg-zinc-950 px-4 py-3 border-b border-zinc-800 flex items-center justify-between shrink-0", children: [
|
|
20
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-zinc-100", children: [
|
|
21
|
+
/* @__PURE__ */ jsx(Terminal, { className: "w-4 h-4 text-zinc-400" }),
|
|
22
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-sm font-semibold", children: "Debug Console" })
|
|
23
|
+
] }),
|
|
24
|
+
/* @__PURE__ */ jsx(
|
|
25
|
+
"button",
|
|
26
|
+
{
|
|
27
|
+
onClick: onClose,
|
|
28
|
+
className: "p-1 hover:bg-zinc-800 rounded-md text-zinc-400 hover:text-white transition-colors",
|
|
29
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
] }),
|
|
33
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-3 font-mono text-xs", children: [
|
|
34
|
+
events.length === 0 && /* @__PURE__ */ jsx("div", { className: "text-zinc-500 text-center py-8", children: "No events logged yet." }),
|
|
35
|
+
events.map((event) => /* @__PURE__ */ jsx(EventItem, { event }, event.id))
|
|
36
|
+
] }),
|
|
37
|
+
/* @__PURE__ */ jsxs("div", { className: "bg-zinc-950 px-4 py-2 border-t border-zinc-800 text-[10px] text-zinc-500 flex justify-between shrink-0", children: [
|
|
38
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
39
|
+
events.length,
|
|
40
|
+
" events"
|
|
41
|
+
] }),
|
|
42
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
43
|
+
"Session ID: ",
|
|
44
|
+
events[0]?.id?.slice(0, 4) || "N/A"
|
|
45
|
+
] })
|
|
46
|
+
] })
|
|
47
|
+
] });
|
|
48
|
+
}
|
|
49
|
+
function EventItem({ event }) {
|
|
50
|
+
const time = new Date(event.timestamp).toLocaleTimeString([], {
|
|
51
|
+
hour12: false,
|
|
52
|
+
hour: "2-digit",
|
|
53
|
+
minute: "2-digit",
|
|
54
|
+
second: "2-digit",
|
|
55
|
+
fractionalSecondDigits: 3
|
|
56
|
+
});
|
|
57
|
+
const getIcon = () => {
|
|
58
|
+
switch (event.type) {
|
|
59
|
+
case "state_change":
|
|
60
|
+
return /* @__PURE__ */ jsx(Activity, { className: "w-3 h-3 text-blue-400" });
|
|
61
|
+
case "socket_event":
|
|
62
|
+
return /* @__PURE__ */ jsx(Terminal, { className: "w-3 h-3 text-zinc-500" });
|
|
63
|
+
case "audio_input":
|
|
64
|
+
return /* @__PURE__ */ jsx(Mic, { className: "w-3 h-3 text-green-400" });
|
|
65
|
+
case "audio_output":
|
|
66
|
+
return /* @__PURE__ */ jsx(Speaker, { className: "w-3 h-3 text-purple-400" });
|
|
67
|
+
case "error":
|
|
68
|
+
return /* @__PURE__ */ jsx(AlertCircle, { className: "w-3 h-3 text-red-500" });
|
|
69
|
+
case "transcript":
|
|
70
|
+
return /* @__PURE__ */ jsx(FileAudio, { className: "w-3 h-3 text-yellow-400" });
|
|
71
|
+
default:
|
|
72
|
+
return /* @__PURE__ */ jsx(Terminal, { className: "w-3 h-3" });
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const isAudio = event.type === "audio_input" || event.type === "audio_output";
|
|
76
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-zinc-800/50 rounded border border-zinc-800 p-2 flex gap-3 hover:bg-zinc-800 transition-colors group", children: [
|
|
77
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 pt-0.5 opacity-70 group-hover:opacity-100", children: getIcon() }),
|
|
78
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 overflow-hidden", children: [
|
|
79
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-0.5", children: [
|
|
80
|
+
/* @__PURE__ */ jsx("span", { className: "text-zinc-400 font-bold uppercase tracking-wider text-[10px]", children: event.type.replace("_", " ") }),
|
|
81
|
+
/* @__PURE__ */ jsx("span", { className: "text-zinc-600 text-[10px]", children: time })
|
|
82
|
+
] }),
|
|
83
|
+
/* @__PURE__ */ jsx("div", { className: "text-zinc-300 break-words whitespace-pre-wrap", children: renderDetails(event) }),
|
|
84
|
+
isAudio && event.blobUrl && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
|
|
85
|
+
"audio",
|
|
86
|
+
{
|
|
87
|
+
controls: true,
|
|
88
|
+
src: event.blobUrl,
|
|
89
|
+
className: "w-full h-6 rounded bg-transparent opacity-80 hover:opacity-100"
|
|
90
|
+
}
|
|
91
|
+
) })
|
|
92
|
+
] })
|
|
93
|
+
] });
|
|
94
|
+
}
|
|
95
|
+
function renderDetails(event) {
|
|
96
|
+
if (event.type === "state_change") {
|
|
97
|
+
return /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
|
|
98
|
+
/* @__PURE__ */ jsx("span", { className: "text-zinc-500", children: event.details.from || "void" }),
|
|
99
|
+
/* @__PURE__ */ jsx("span", { className: "text-zinc-600", children: "\u2192" }),
|
|
100
|
+
/* @__PURE__ */ jsx("span", { className: `${getStateColor(event.details.to)} font-semibold`, children: event.details.to }),
|
|
101
|
+
/* @__PURE__ */ jsxs("span", { className: "text-zinc-600 ml-1 text-[10px]", children: [
|
|
102
|
+
"(",
|
|
103
|
+
event.details.source,
|
|
104
|
+
")"
|
|
105
|
+
] })
|
|
106
|
+
] });
|
|
107
|
+
}
|
|
108
|
+
if (event.type === "transcript") {
|
|
109
|
+
return /* @__PURE__ */ jsxs("span", { children: [
|
|
110
|
+
/* @__PURE__ */ jsxs("span", { className: event.details.role === "user" ? "text-green-400" : "text-purple-400", children: [
|
|
111
|
+
event.details.role,
|
|
112
|
+
":"
|
|
113
|
+
] }),
|
|
114
|
+
" ",
|
|
115
|
+
event.details.text
|
|
116
|
+
] });
|
|
117
|
+
}
|
|
118
|
+
return JSON.stringify(event.details, null, 0).replace(/[{}"]/g, "").replace(/:/g, ": ");
|
|
119
|
+
}
|
|
120
|
+
function getStateColor(state) {
|
|
121
|
+
switch (state) {
|
|
122
|
+
case "listening":
|
|
123
|
+
return "text-green-400";
|
|
124
|
+
case "processing":
|
|
125
|
+
return "text-yellow-400";
|
|
126
|
+
case "speaking":
|
|
127
|
+
return "text-blue-400";
|
|
128
|
+
case "idle":
|
|
129
|
+
return "text-zinc-400";
|
|
130
|
+
case "error":
|
|
131
|
+
return "text-red-400";
|
|
132
|
+
default:
|
|
133
|
+
return "text-zinc-300";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
var init_VoiceDebugSidebar = __esm({
|
|
137
|
+
"src/ui/components/VoiceDebugSidebar.tsx"() {
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
function FullScreenMode({
|
|
141
|
+
status,
|
|
142
|
+
transcript,
|
|
143
|
+
history,
|
|
144
|
+
error,
|
|
145
|
+
connect,
|
|
146
|
+
cancel,
|
|
147
|
+
vadLoading,
|
|
148
|
+
onTogglePip,
|
|
149
|
+
config,
|
|
150
|
+
setConfig,
|
|
151
|
+
feedback
|
|
152
|
+
}) {
|
|
153
|
+
const [isDebugOpen, setIsDebugOpen] = useState(false);
|
|
154
|
+
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
|
155
|
+
const transcriptEndRef = useRef(null);
|
|
156
|
+
const [localConfig, setLocalConfig] = useState(config || {});
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (config) setLocalConfig(config);
|
|
159
|
+
}, [config]);
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
transcriptEndRef.current?.scrollIntoView?.({ behavior: "smooth" });
|
|
162
|
+
}, [transcript]);
|
|
163
|
+
const availableVoices = useMemo(() => {
|
|
164
|
+
const model = localConfig.tts?.model || TTS_MODELS[0];
|
|
165
|
+
return TTS_MODEL_VOICES[model] || [];
|
|
166
|
+
}, [localConfig.tts?.model]);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (localConfig.tts?.speaker && !availableVoices.includes(localConfig.tts.speaker)) {
|
|
169
|
+
setLocalConfig({
|
|
170
|
+
...localConfig,
|
|
171
|
+
tts: { ...localConfig.tts, speaker: availableVoices[0] }
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}, [availableVoices, localConfig]);
|
|
175
|
+
const handleSaveConfig = () => {
|
|
176
|
+
setConfig?.(localConfig);
|
|
177
|
+
setIsSettingsOpen(false);
|
|
178
|
+
};
|
|
179
|
+
const isListening = status === "listening";
|
|
180
|
+
const isThinking = status === "processing";
|
|
181
|
+
const isSpeaking = status === "speaking";
|
|
182
|
+
const isConnecting = status === "connecting";
|
|
183
|
+
const isDisconnected = status === "disconnected";
|
|
184
|
+
const isError = status === "error";
|
|
185
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 min-h-screen bg-black text-zinc-100 flex flex-col items-center justify-center p-4 selection:bg-accent/20 overflow-hidden", children: [
|
|
186
|
+
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 pointer-events-none", children: /* @__PURE__ */ jsx(
|
|
187
|
+
"div",
|
|
188
|
+
{
|
|
189
|
+
className: `absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[60vh] h-[60vh] blur-[120px] rounded-full opacity-20 transition-all duration-700
|
|
190
|
+
${isListening ? "bg-green-500 scale-125" : isThinking ? "bg-yellow-500 scale-110" : isSpeaking ? "bg-blue-600 scale-125" : isError ? "bg-red-600 scale-110" : isDisconnected ? "bg-zinc-800 scale-90" : "bg-zinc-800 scale-100"}`
|
|
191
|
+
}
|
|
192
|
+
) }),
|
|
193
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute top-4 right-4 z-50 flex gap-2", children: [
|
|
194
|
+
/* @__PURE__ */ jsx(
|
|
195
|
+
"button",
|
|
196
|
+
{
|
|
197
|
+
onClick: () => setIsSettingsOpen(true),
|
|
198
|
+
className: "p-2 rounded-full hover:bg-zinc-900 text-zinc-500 hover:text-zinc-300 transition-colors",
|
|
199
|
+
title: "Settings",
|
|
200
|
+
children: /* @__PURE__ */ jsx(Settings, { className: "w-5 h-5" })
|
|
201
|
+
}
|
|
202
|
+
),
|
|
203
|
+
onTogglePip && /* @__PURE__ */ jsx(
|
|
204
|
+
"button",
|
|
205
|
+
{
|
|
206
|
+
onClick: onTogglePip,
|
|
207
|
+
className: "p-2 rounded-full hover:bg-zinc-900 text-zinc-500 hover:text-zinc-300 transition-colors",
|
|
208
|
+
title: "Enter Picture-in-Picture Mode",
|
|
209
|
+
children: /* @__PURE__ */ jsx(Minimize2, { className: "w-5 h-5" })
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
] }),
|
|
213
|
+
isSettingsOpen && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[60] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4", children: /* @__PURE__ */ jsxs("div", { className: "bg-zinc-900 border border-zinc-700 rounded-2xl w-full max-w-md p-6 shadow-2xl animate-in fade-in zoom-in-95 duration-200", children: [
|
|
214
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-6", children: [
|
|
215
|
+
/* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-white", children: "Voice Settings" }),
|
|
216
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setIsSettingsOpen(false), className: "text-zinc-500 hover:text-white", children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" }) })
|
|
217
|
+
] }),
|
|
218
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
219
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
220
|
+
/* @__PURE__ */ jsx("label", { className: "block text-xs font-mono text-zinc-500 mb-1 uppercase", children: "LLM Model" }),
|
|
221
|
+
/* @__PURE__ */ jsx(
|
|
222
|
+
"select",
|
|
223
|
+
{
|
|
224
|
+
value: localConfig.llmModel || LLM_MODELS[0],
|
|
225
|
+
onChange: (e) => setLocalConfig({ ...localConfig, llmModel: e.target.value }),
|
|
226
|
+
className: "w-full bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm focus:border-blue-500 outline-none appearance-none",
|
|
227
|
+
children: LLM_MODELS.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: m }, m))
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
] }),
|
|
231
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
232
|
+
/* @__PURE__ */ jsx("label", { className: "block text-xs font-mono text-zinc-500 mb-1 uppercase", children: "STT Model" }),
|
|
233
|
+
/* @__PURE__ */ jsx(
|
|
234
|
+
"select",
|
|
235
|
+
{
|
|
236
|
+
value: localConfig.sttModel || STT_MODELS[0],
|
|
237
|
+
onChange: (e) => setLocalConfig({ ...localConfig, sttModel: e.target.value }),
|
|
238
|
+
className: "w-full bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm focus:border-blue-500 outline-none appearance-none",
|
|
239
|
+
children: STT_MODELS.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: m }, m))
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
] }),
|
|
243
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
244
|
+
/* @__PURE__ */ jsx("label", { className: "block text-xs font-mono text-zinc-500 mb-1 uppercase", children: "TTS Model" }),
|
|
245
|
+
/* @__PURE__ */ jsx(
|
|
246
|
+
"select",
|
|
247
|
+
{
|
|
248
|
+
value: localConfig.tts?.model || TTS_MODELS[0],
|
|
249
|
+
onChange: (e) => setLocalConfig({ ...localConfig, tts: { ...localConfig.tts, model: e.target.value } }),
|
|
250
|
+
className: "w-full bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm focus:border-blue-500 outline-none appearance-none",
|
|
251
|
+
children: TTS_MODELS.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: m }, m))
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
] }),
|
|
255
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
256
|
+
/* @__PURE__ */ jsx("label", { className: "block text-xs font-mono text-zinc-500 mb-1 uppercase", children: "Voice" }),
|
|
257
|
+
/* @__PURE__ */ jsx(
|
|
258
|
+
"select",
|
|
259
|
+
{
|
|
260
|
+
value: localConfig.tts?.speaker || availableVoices[0],
|
|
261
|
+
onChange: (e) => setLocalConfig({ ...localConfig, tts: { ...localConfig.tts, speaker: e.target.value } }),
|
|
262
|
+
className: "w-full bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm focus:border-blue-500 outline-none appearance-none",
|
|
263
|
+
children: availableVoices.map((v) => /* @__PURE__ */ jsx("option", { value: v, children: v }, v))
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
] }),
|
|
267
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
268
|
+
/* @__PURE__ */ jsx("label", { className: "block text-xs font-mono text-zinc-500 mb-1 uppercase", children: "MCP Server Proxy URL" }),
|
|
269
|
+
/* @__PURE__ */ jsx(
|
|
270
|
+
"input",
|
|
271
|
+
{
|
|
272
|
+
type: "text",
|
|
273
|
+
placeholder: "https://shvm.in/mcp",
|
|
274
|
+
value: localConfig.mcpServer || "",
|
|
275
|
+
onChange: (e) => setLocalConfig({ ...localConfig, mcpServer: e.target.value }),
|
|
276
|
+
className: "w-full bg-zinc-950 border border-zinc-800 rounded px-3 py-2 text-sm focus:border-blue-500 outline-none"
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
] })
|
|
280
|
+
] }),
|
|
281
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-2", children: [
|
|
282
|
+
/* @__PURE__ */ jsx(
|
|
283
|
+
"button",
|
|
284
|
+
{
|
|
285
|
+
onClick: () => setIsSettingsOpen(false),
|
|
286
|
+
className: "px-4 py-2 text-sm font-mono text-zinc-400 hover:text-white transition-colors",
|
|
287
|
+
children: "Cancel"
|
|
288
|
+
}
|
|
289
|
+
),
|
|
290
|
+
/* @__PURE__ */ jsxs(
|
|
291
|
+
"button",
|
|
292
|
+
{
|
|
293
|
+
onClick: handleSaveConfig,
|
|
294
|
+
className: "px-4 py-2 text-sm font-mono bg-blue-600 hover:bg-blue-500 text-white rounded flex items-center gap-2 transition-colors",
|
|
295
|
+
children: [
|
|
296
|
+
/* @__PURE__ */ jsx(Save, { className: "w-4 h-4" }),
|
|
297
|
+
" Save"
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
)
|
|
301
|
+
] })
|
|
302
|
+
] }) }),
|
|
303
|
+
feedback && /* @__PURE__ */ jsx("div", { className: "fixed top-4 left-1/2 -translate-x-1/2 z-[70]", children: /* @__PURE__ */ jsx("div", { className: "text-yellow-400 text-sm font-mono bg-yellow-900/20 px-3 py-1 rounded border border-yellow-900/50 animate-in fade-in slide-in-from-top-2 max-w-xs text-center break-words", children: feedback }) }),
|
|
304
|
+
isError && error && /* @__PURE__ */ jsx("div", { className: "fixed top-4 left-1/2 -translate-x-1/2 z-[70]", children: /* @__PURE__ */ jsx("div", { className: "text-red-400 text-sm font-mono bg-red-900/20 px-3 py-1 rounded border border-red-900/50 animate-in fade-in slide-in-from-top-2 max-w-xs text-center break-words", children: error }) }),
|
|
305
|
+
/* @__PURE__ */ jsxs("div", { className: "relative z-10 flex flex-col items-center gap-6", children: [
|
|
306
|
+
/* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
307
|
+
/* @__PURE__ */ jsx("h1", { className: "text-3xl md:text-4xl font-display font-semibold tracking-tight", children: isListening ? "Listening" : isThinking ? "Processing" : isSpeaking ? "Speaking" : isConnecting ? "Connecting" : isError ? "Error" : isDisconnected ? "Ready" : "Ready" }),
|
|
308
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3 flex justify-center", children: /* @__PURE__ */ jsx(
|
|
309
|
+
"div",
|
|
310
|
+
{
|
|
311
|
+
className: `h-1.5 rounded-full transition-all duration-500 ${isListening ? "w-16 bg-green-500" : isThinking ? "w-16 bg-yellow-500 animate-pulse" : isSpeaking ? "w-24 bg-blue-500" : isConnecting ? "w-8 bg-zinc-500 animate-pulse" : isError ? "w-16 bg-red-500 conversation-shake" : "w-2 bg-zinc-700"}`
|
|
312
|
+
}
|
|
313
|
+
) })
|
|
314
|
+
] }),
|
|
315
|
+
/* @__PURE__ */ jsxs(
|
|
316
|
+
"button",
|
|
317
|
+
{
|
|
318
|
+
disabled: isConnecting || isError || isDisconnected,
|
|
319
|
+
onClick: () => (isThinking || isSpeaking) && cancel(),
|
|
320
|
+
className: `group relative w-48 h-48 rounded-full flex items-center justify-center border-4 transition-all duration-300 outline-none
|
|
321
|
+
${isListening ? "border-green-500/50 bg-green-500/10 scale-105 shadow-[0_0_40px_rgba(34,197,94,0.3)]" : isThinking ? "border-yellow-500/50 bg-yellow-500/10 shadow-[0_0_40px_rgba(234,179,8,0.3)] animate-pulse cursor-pointer hover:border-red-500/50 hover:bg-red-500/10" : isSpeaking ? "border-blue-500/50 bg-blue-500/10 shadow-[0_0_40px_rgba(59,130,246,0.3)] cursor-pointer hover:border-red-500/50 hover:bg-red-500/10" : isError ? "border-red-500/50 bg-red-500/10 hover:border-red-400 cursor-not-allowed" : isDisconnected ? "border-zinc-800/50 bg-black/50 cursor-not-allowed" : "border-zinc-800 bg-zinc-900 hover:border-zinc-700 hover:bg-zinc-800"}`,
|
|
322
|
+
children: [
|
|
323
|
+
isListening ? /* @__PURE__ */ jsx(Radio, { className: "w-16 h-16 text-green-500 animate-pulse" }) : isThinking ? /* @__PURE__ */ jsx(Loader2, { className: "w-16 h-16 text-yellow-500 animate-spin group-hover:hidden" }) : isSpeaking ? /* @__PURE__ */ jsx(Volume2, { className: "w-16 h-16 text-blue-500 animate-bounce group-hover:hidden" }) : isError ? /* @__PURE__ */ jsx(AlertCircle, { className: "w-16 h-16 text-red-500" }) : isDisconnected ? /* @__PURE__ */ jsx(WifiOff, { className: "w-16 h-16 text-zinc-600" }) : /* @__PURE__ */ jsx(Mic, { className: "w-16 h-16 text-zinc-500" }),
|
|
324
|
+
(isThinking || isSpeaking) && /* @__PURE__ */ jsx(Square, { className: "w-16 h-16 text-red-500 absolute hidden group-hover:block fill-current" })
|
|
325
|
+
]
|
|
326
|
+
}
|
|
327
|
+
),
|
|
328
|
+
isError || isDisconnected ? /* @__PURE__ */ jsx(
|
|
329
|
+
"button",
|
|
330
|
+
{
|
|
331
|
+
onClick: () => connect(),
|
|
332
|
+
className: "text-zinc-400 hover:text-white text-sm font-mono border border-zinc-700 px-4 py-2 rounded hover:bg-zinc-800 transition-colors",
|
|
333
|
+
children: isError ? "Retry Connection" : "Start Voice Session"
|
|
334
|
+
}
|
|
335
|
+
) : isThinking || isSpeaking ? /* @__PURE__ */ jsxs(
|
|
336
|
+
"button",
|
|
337
|
+
{
|
|
338
|
+
onClick: cancel,
|
|
339
|
+
className: "text-zinc-400 hover:text-red-400 text-sm font-mono border border-zinc-700 px-4 py-2 rounded hover:bg-zinc-800 transition-colors flex items-center gap-2",
|
|
340
|
+
children: [
|
|
341
|
+
/* @__PURE__ */ jsx(Square, { className: "w-3 h-3 fill-current" }),
|
|
342
|
+
" Stop"
|
|
343
|
+
]
|
|
344
|
+
}
|
|
345
|
+
) : /* @__PURE__ */ jsx("p", { className: "text-zinc-500 text-sm font-mono text-center h-6", children: vadLoading ? "Loading voice activity detection\u2026" : "Just start talking" }),
|
|
346
|
+
/* @__PURE__ */ jsxs("div", { className: "w-full min-h-[150px] max-h-[30vh] overflow-y-auto space-y-4 mask-gradient-b flex flex-col pb-4 px-2 scrollbar-thin scrollbar-thumb-zinc-800 scrollbar-track-transparent", children: [
|
|
347
|
+
transcript && transcript.map((msg) => /* @__PURE__ */ jsxs(
|
|
348
|
+
"div",
|
|
349
|
+
{
|
|
350
|
+
className: `flex flex-col space-y-1 animate-in slide-in-from-bottom-2 fade-in duration-300 ${msg.role === "user" ? "items-end" : "items-start"}`,
|
|
351
|
+
children: [
|
|
352
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-zinc-600 uppercase tracking-widest", children: msg.role }),
|
|
353
|
+
/* @__PURE__ */ jsx(
|
|
354
|
+
"div",
|
|
355
|
+
{
|
|
356
|
+
className: `px-4 py-2 rounded-2xl max-w-[90%] text-sm leading-relaxed shadow-lg break-words
|
|
357
|
+
${msg.role === "user" ? "bg-zinc-800/80 text-zinc-200 rounded-tr-sm border border-zinc-700/50" : "bg-blue-900/20 text-blue-100 border border-blue-500/20 rounded-tl-sm backdrop-blur-sm"}`,
|
|
358
|
+
children: msg.content
|
|
359
|
+
}
|
|
360
|
+
)
|
|
361
|
+
]
|
|
362
|
+
},
|
|
363
|
+
msg.id
|
|
364
|
+
)),
|
|
365
|
+
/* @__PURE__ */ jsx("div", { ref: transcriptEndRef })
|
|
366
|
+
] })
|
|
367
|
+
] }),
|
|
368
|
+
/* @__PURE__ */ jsx(
|
|
369
|
+
"button",
|
|
370
|
+
{
|
|
371
|
+
onClick: () => setIsDebugOpen(!isDebugOpen),
|
|
372
|
+
className: `fixed bottom-4 right-4 p-2 rounded-full transition-all duration-300 z-50
|
|
373
|
+
${isDebugOpen ? "bg-zinc-800 text-white shadow-xl" : "bg-transparent text-zinc-700 hover:text-zinc-400 hover:bg-zinc-900"}`,
|
|
374
|
+
children: /* @__PURE__ */ jsx(Terminal, { className: "w-5 h-5" })
|
|
375
|
+
}
|
|
376
|
+
),
|
|
377
|
+
isDebugOpen && /* @__PURE__ */ jsx("div", { className: "fixed inset-y-0 right-0 z-40", children: /* @__PURE__ */ jsx(VoiceDebugSidebar, { isOpen: isDebugOpen, onClose: () => setIsDebugOpen(false), events: history }) })
|
|
378
|
+
] });
|
|
379
|
+
}
|
|
380
|
+
var init_FullScreenMode = __esm({
|
|
381
|
+
"src/ui/modes/FullScreenMode.tsx"() {
|
|
382
|
+
init_VoiceDebugSidebar();
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
function PipMode({ status, transcript, error, connect, cancel, onTogglePip }) {
|
|
386
|
+
const [isExpanded, setIsExpanded] = useState(true);
|
|
387
|
+
const isListening = status === "listening";
|
|
388
|
+
const isThinking = status === "processing";
|
|
389
|
+
const isSpeaking = status === "speaking";
|
|
390
|
+
const isDisconnected = status === "disconnected";
|
|
391
|
+
const isError = status === "error";
|
|
392
|
+
const getStatusColor = () => {
|
|
393
|
+
if (isListening) return "bg-green-500";
|
|
394
|
+
if (isThinking) return "bg-yellow-500";
|
|
395
|
+
if (isSpeaking) return "bg-blue-500";
|
|
396
|
+
if (isError) return "bg-red-500";
|
|
397
|
+
if (isDisconnected) return "bg-zinc-500";
|
|
398
|
+
return "bg-zinc-700";
|
|
399
|
+
};
|
|
400
|
+
const getStatusIcon = () => {
|
|
401
|
+
if (isListening) return /* @__PURE__ */ jsx(Radio, { className: "w-4 h-4 text-white animate-pulse" });
|
|
402
|
+
if (isThinking) return /* @__PURE__ */ jsx(Loader2, { className: "w-4 h-4 text-white animate-spin" });
|
|
403
|
+
if (isSpeaking) return /* @__PURE__ */ jsx(Volume2, { className: "w-4 h-4 text-white animate-bounce" });
|
|
404
|
+
if (isError) return /* @__PURE__ */ jsx(AlertCircle, { className: "w-4 h-4 text-white" });
|
|
405
|
+
if (isDisconnected) return /* @__PURE__ */ jsx(WifiOff, { className: "w-4 h-4 text-white" });
|
|
406
|
+
return /* @__PURE__ */ jsx(Mic, { className: "w-4 h-4 text-white" });
|
|
407
|
+
};
|
|
408
|
+
if (!isExpanded) {
|
|
409
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed bottom-4 right-4 z-50 animate-in slide-in-from-bottom-4 fade-in duration-300", children: /* @__PURE__ */ jsx(
|
|
410
|
+
"button",
|
|
411
|
+
{
|
|
412
|
+
onClick: () => setIsExpanded(true),
|
|
413
|
+
className: `h-12 w-12 rounded-full shadow-lg flex items-center justify-center transition-all hover:scale-110 active:scale-95 ${getStatusColor()}`,
|
|
414
|
+
title: "Click to expand",
|
|
415
|
+
children: getStatusIcon()
|
|
416
|
+
}
|
|
417
|
+
) });
|
|
418
|
+
}
|
|
419
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed bottom-4 right-4 z-50 w-80 bg-zinc-900/95 backdrop-blur-md border border-zinc-800 rounded-xl shadow-2xl flex flex-col overflow-hidden animate-in slide-in-from-bottom-2 fade-in duration-300", children: [
|
|
420
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-2 bg-zinc-950/50 border-b border-zinc-800", children: [
|
|
421
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
422
|
+
/* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${getStatusColor()} ${isListening || isSpeaking ? "animate-pulse" : ""}` }),
|
|
423
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-zinc-400 font-medium", children: isListening ? "Listening" : isThinking ? "Processing" : isSpeaking ? "Speaking" : "Idle" })
|
|
424
|
+
] }),
|
|
425
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
426
|
+
onTogglePip && /* @__PURE__ */ jsx(
|
|
427
|
+
"button",
|
|
428
|
+
{
|
|
429
|
+
onClick: onTogglePip,
|
|
430
|
+
className: "p-1.5 hover:bg-zinc-800 rounded text-zinc-500 hover:text-zinc-300 transition-colors",
|
|
431
|
+
title: "Maximize to Full Screen",
|
|
432
|
+
children: /* @__PURE__ */ jsx(Maximize2, { className: "w-3.5 h-3.5" })
|
|
433
|
+
}
|
|
434
|
+
),
|
|
435
|
+
/* @__PURE__ */ jsx(
|
|
436
|
+
"button",
|
|
437
|
+
{
|
|
438
|
+
onClick: () => setIsExpanded(false),
|
|
439
|
+
className: "p-1.5 hover:bg-zinc-800 rounded text-zinc-500 hover:text-zinc-300 transition-colors",
|
|
440
|
+
title: "Minimize",
|
|
441
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-3.5 h-3.5" })
|
|
442
|
+
}
|
|
443
|
+
)
|
|
444
|
+
] })
|
|
445
|
+
] }),
|
|
446
|
+
/* @__PURE__ */ jsxs("div", { className: "p-4 flex flex-col gap-4 min-h-[120px]", children: [
|
|
447
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsx(
|
|
448
|
+
"button",
|
|
449
|
+
{
|
|
450
|
+
onClick: () => {
|
|
451
|
+
if (isDisconnected || isError) connect();
|
|
452
|
+
else if (isThinking || isSpeaking) cancel();
|
|
453
|
+
},
|
|
454
|
+
className: `w-16 h-16 rounded-full flex items-center justify-center transition-all duration-300
|
|
455
|
+
${isListening ? "bg-green-500/20 shadow-[0_0_20px_rgba(34,197,94,0.3)]" : isSpeaking ? "bg-blue-500/20 shadow-[0_0_20px_rgba(59,130,246,0.3)] hover:bg-red-500/20 hover:text-red-500 cursor-pointer" : "bg-zinc-800"}`,
|
|
456
|
+
title: isThinking || isSpeaking ? "Stop" : isError && error ? error : void 0,
|
|
457
|
+
children: getStatusIcon()
|
|
458
|
+
}
|
|
459
|
+
) }),
|
|
460
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 space-y-2 max-h-[150px] overflow-y-auto noscrollbar mask-gradient-b", children: !transcript || transcript.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center text-zinc-600 text-xs py-2 italic", children: isDisconnected ? "Disconnected" : "Conversation will appear here..." }) : transcript.slice(-2).map((msg) => /* @__PURE__ */ jsx("div", { className: `flex flex-col ${msg.role === "user" ? "items-end" : "items-start"}`, children: /* @__PURE__ */ jsx(
|
|
461
|
+
"div",
|
|
462
|
+
{
|
|
463
|
+
className: `px-2 py-1.5 rounded-lg max-w-[90%] text-xs leading-relaxed
|
|
464
|
+
${msg.role === "user" ? "bg-zinc-800 text-zinc-300" : "bg-blue-900/10 text-blue-100 border border-blue-500/10"}`,
|
|
465
|
+
children: msg.content
|
|
466
|
+
}
|
|
467
|
+
) }, msg.id)) })
|
|
468
|
+
] })
|
|
469
|
+
] });
|
|
470
|
+
}
|
|
471
|
+
var init_PipMode = __esm({
|
|
472
|
+
"src/ui/modes/PipMode.tsx"() {
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// src/ui/components/VaniClient.tsx
|
|
477
|
+
var VaniClient_exports = {};
|
|
478
|
+
__export(VaniClient_exports, {
|
|
479
|
+
default: () => VaniClient
|
|
480
|
+
});
|
|
481
|
+
function VaniClient({
|
|
482
|
+
onError,
|
|
483
|
+
onMessage,
|
|
484
|
+
initialTranscript,
|
|
485
|
+
defaultMode = "full",
|
|
486
|
+
mode: controlledMode,
|
|
487
|
+
onModeChange,
|
|
488
|
+
initialConfig,
|
|
489
|
+
serverUrl
|
|
490
|
+
}) {
|
|
491
|
+
const [internalMode, setInternalMode] = useState(defaultMode);
|
|
492
|
+
const [config, setConfig] = useState(
|
|
493
|
+
initialConfig || {
|
|
494
|
+
sttModel: "@cf/openai/whisper-tiny-en",
|
|
495
|
+
llmModel: "@cf/meta/llama-3.1-8b-instruct",
|
|
496
|
+
tts: { model: "@cf/deepgram/aura-2-en", speaker: "luna" },
|
|
497
|
+
mcpServer: "https://shvm.in/mcp"
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
const [feedback, setFeedback] = useState(null);
|
|
501
|
+
const currentMode = controlledMode ?? internalMode;
|
|
502
|
+
const handleFeedback = (message) => {
|
|
503
|
+
setFeedback(message);
|
|
504
|
+
setTimeout(() => setFeedback(null), 3e3);
|
|
505
|
+
};
|
|
506
|
+
const session = useVoiceSession({
|
|
507
|
+
onError,
|
|
508
|
+
onMessage,
|
|
509
|
+
initialTranscript,
|
|
510
|
+
config,
|
|
511
|
+
onFeedback: handleFeedback,
|
|
512
|
+
serverUrl
|
|
513
|
+
});
|
|
514
|
+
const handleToggleMode = () => {
|
|
515
|
+
const newMode = currentMode === "full" ? "pip" : "full";
|
|
516
|
+
if (controlledMode === void 0) {
|
|
517
|
+
setInternalMode(newMode);
|
|
518
|
+
}
|
|
519
|
+
onModeChange?.(newMode);
|
|
520
|
+
};
|
|
521
|
+
return /* @__PURE__ */ jsx("div", { className: "vani-root", children: currentMode === "pip" ? /* @__PURE__ */ jsx(
|
|
522
|
+
PipMode,
|
|
523
|
+
{
|
|
524
|
+
...session,
|
|
525
|
+
onTogglePip: handleToggleMode,
|
|
526
|
+
config,
|
|
527
|
+
setConfig,
|
|
528
|
+
feedback
|
|
529
|
+
}
|
|
530
|
+
) : /* @__PURE__ */ jsx(
|
|
531
|
+
FullScreenMode,
|
|
532
|
+
{
|
|
533
|
+
...session,
|
|
534
|
+
onTogglePip: handleToggleMode,
|
|
535
|
+
config,
|
|
536
|
+
setConfig,
|
|
537
|
+
feedback
|
|
538
|
+
}
|
|
539
|
+
) });
|
|
540
|
+
}
|
|
541
|
+
var init_VaniClient = __esm({
|
|
542
|
+
"src/ui/components/VaniClient.tsx"() {
|
|
543
|
+
init_FullScreenMode();
|
|
544
|
+
init_PipMode();
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
var VaniClient2 = lazy(() => Promise.resolve().then(() => (init_VaniClient(), VaniClient_exports)));
|
|
548
|
+
function Vani(props) {
|
|
549
|
+
const [mounted, setMounted] = useState(false);
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
setMounted(true);
|
|
552
|
+
}, []);
|
|
553
|
+
if (!mounted) return null;
|
|
554
|
+
return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(VaniClient2, { ...props }) });
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export { Vani };
|
|
558
|
+
//# sourceMappingURL=index.js.map
|
|
559
|
+
//# sourceMappingURL=index.js.map
|