@hoosei/voxweave-react
Version:
A customizable and interactive voice UI component for React applications
425 lines (424 loc) • 16.9 kB
JavaScript
import { useRef as w, useState as x, useCallback as $, useEffect as Y } from "react";
import { Button as Se } from "./index6.js";
import { cn as m } from "./index8.js";
import { motion as P, AnimatePresence as at } from "framer-motion";
import { PrivacyDialog as st } from "./index9.js";
import { LanguageSelector as lt, languages as ct } from "./index10.js";
/* empty css */
import { VoxweaveMinimal as ut } from "./index11.js";
import { ErrorBoundary as xt } from "./index12.js";
import { ThreeWrapper as vt } from "./index13.js";
import { validateLicenseKey as dt } from "./index14.js";
import { ClientOnly as ft } from "./index15.js";
import { getConfig as mt } from "./index5.js";
import gt from "./index16.js";
import { jsx as n, jsxs as y, Fragment as ht } from "react/jsx-runtime";
import Le from "./index17.js";
import ke from "./index18.js";
import pt from "./index19.js";
function yt({
animation: l,
validatedSize: D,
isListening: i,
audioData: b,
isVertical: T,
agentAudioData: g,
finalListeningColors: c,
finalNotListeningColors: C,
agentStatus: S,
darkMode: L,
containerStyle: h
}) {
return l || console.warn("No animation specified for Voxweave component. Using BlobAnimation as default."), /* @__PURE__ */ n(vt, {
size: D,
animation: l || gt,
isListening: i,
audioData: b,
isVertical: T,
agentAudioData: g,
listeningColors: c,
notListeningColors: C,
agentStatus: S,
darkMode: L,
containerPosition: h
});
}
const Ee = "voxweave-privacy-consent", Z = ({
message: l,
isDev: D,
onClose: i
}) => /* @__PURE__ */ n("div", {
className: "vox:fixed vox:bottom-4 vox:left-4 vox:z-50",
children: /* @__PURE__ */ y("div", {
className: "vox:flex vox:items-start vox:gap-2 vox:p-3 vox:rounded-lg vox:bg-red-50 vox:text-red-900 vox:shadow-lg vox:border vox:border-red-100 vox:max-w-[365px]",
children: [/* @__PURE__ */ n(pt, {
className: "vox:w-4 vox:h-4 vox:text-red-500 vox:mt-0.5"
}), /* @__PURE__ */ y("div", {
className: "vox:flex-1",
children: [/* @__PURE__ */ n("p", {
className: "vox:text-sm vox:font-medium",
children: l
}), D && /* @__PURE__ */ n("p", {
className: "vox:text-xs vox:mt-1 vox:text-red-600",
children: "Check the console for more details"
})]
}), i && /* @__PURE__ */ n("button", {
onClick: i,
className: "vox:text-red-500 hover:vox:text-red-700 vox:mt-0.5",
children: /* @__PURE__ */ n(ke, {
className: "vox:w-4 vox:h-4"
})
})]
})
}), bt = ["#4F46E5", "#818CF8"], wt = ["#6B7280", "#9CA3AF"], Mt = (l) => {
const {
className: D,
isListening: i = !1,
agentStatus: b = "idle",
statusLabel: T,
size: g = 250,
useAsButton: c = !1,
showControls: C = !0,
listeningColors: S,
notListeningColors: L,
theme: h = "default",
animation: J,
darkMode: f = !1,
hideCloseButton: De = !1,
backgroundColor: V,
disableBackground: M = !1,
disableShadow: Ve = !1,
isVisible: F = !1,
isAnimated: Q = !0,
disableFixed: r = !1,
isExpanded: p = !1,
isCompact: ee = !1,
privacyMode: W = !1,
privacyPolicyUrl: Be = "#",
companyName: ze = "our service",
languageMode: Re = !1,
defaultLanguage: $e = "en",
startCallLabel: q = "Call",
endCallLabel: j = "End",
onConsentAccept: Te,
onLanguageChange: Fe,
containerStyle: o = "center",
position: t = "right",
isVertical: A = !1,
expandLabel: Ie = "Chat with AI"
} = l, u = g !== void 0 && !(r === !0 && C === !1) && (g < 75 || g > 250) ? (console.warn(`Voxweave: When disableFixed is not true or showControls is true, size must be between 75 and 250. Current values: disableFixed=${r}, showControls=${C}, size=${g}. Defaulting to ${g < 75 ? 75 : 250}.`), g < 75 ? 75 : 250) : g || 180, a = w(null), N = w(null), B = w(null), z = w(null), s = w(null), I = w(null), [G, te] = x(!1), [oe, ne] = x(!0), [O, re] = x(null), [Oe, ie] = x(!1), [Ue, K] = x(!1), [ae, _e] = x(() => typeof window > "u" || !W ? !0 : localStorage.getItem(Ee) === "true"), [Pe, Me] = x($e), [H, se] = x(i), [le, ce] = x(null), [ue, X] = x(null), [, xe] = x(!1), We = w(null), [E, qe] = x(!1), ve = w(null), de = 250, fe = 82, je = () => u <= de ? "4rem" : `${4 * (de / u)}rem`, Ge = () => {
qe(!E);
}, me = $(async () => {
if (!z.current)
try {
const e = await navigator.mediaDevices.getUserMedia({
audio: !0
});
if (z.current = e, a.current || (a.current = new (window.AudioContext || window.webkitAudioContext)()), !a.current) return;
const v = a.current.createMediaStreamSource(e);
N.current = a.current.createAnalyser(), N.current.fftSize = 256, v.connect(N.current), B.current = new Uint8Array(N.current.frequencyBinCount), ge();
} catch (e) {
console.error("Error accessing microphone:", e), alert("Unable to access microphone. Please check your browser settings and try again.");
}
}, []), ge = $(() => {
if (N.current && B.current) {
const e = performance.now(), v = ve.current || 0;
e - v > 33 && (N.current.getByteFrequencyData(B.current), ce(new Uint8Array(B.current)), ve.current = e);
}
I.current = requestAnimationFrame(ge);
}, []), he = $(() => {
if (!a.current) return;
const e = a.current.createAnalyser();
e.fftSize = 256, s.current = a.current.createBufferSource();
const v = a.current.createBuffer(1, a.current.sampleRate * 3, a.current.sampleRate), d = v.getChannelData(0);
for (let k = 0; k < d.length; k++)
d[k] = Math.random() * 2 - 1;
s.current.buffer = v, s.current.loop = !0, s.current.connect(e), s.current.start();
const R = new Uint8Array(e.frequencyBinCount);
let Ae = 0;
const Ne = () => {
const k = performance.now();
k - Ae > 33 && (e.getByteFrequencyData(R), X(new Uint8Array(R)), Ae = k), requestAnimationFrame(Ne);
};
Ne();
}, []), pe = $(() => {
z.current && (z.current.getTracks().forEach((e) => e.stop()), z.current = null), a.current && (a.current.close(), a.current = null), s.current && (s.current.stop(), s.current.disconnect(), s.current = null), I.current && (cancelAnimationFrame(I.current), I.current = null), N.current = null, B.current = null, ce(null), X(null);
}, []);
Y(() => {
let e = !0;
async function v() {
try {
const d = mt(), R = await dt(d.licenseKey);
e && (te(R), R || re("Voxweave widget is currently unavailable."), ne(!1));
} catch (d) {
console.error("License validation failed:", d), e && (te(!1), re("Unable to initialize the widget."), ne(!1));
}
}
return v(), () => {
e = !1;
};
}, []), Y(() => {
se(i), i ? me() : pe();
}, [i, me, pe]), Y(() => {
b === "speaking" ? he() : s.current && (s.current.stop(), s.current.disconnect(), s.current = null, X(null));
}, [b, he]);
const U = () => {
xe(!1), setTimeout(() => {
l.onClose();
}, 500);
}, _ = () => {
xe(!0), l.onStartCall();
}, ye = () => l.onButtonClick(), be = $(() => {
if (W && !ae) {
K(!0);
return;
}
c ? l.onButtonClick?.() : H ? U?.() : _?.(), se(!H);
}, [H, W, ae, c, _, U]), Ke = () => {
K(!1), _e(!0), localStorage.setItem(Ee, "true"), Te?.(), _();
}, He = () => {
K(!1);
}, we = () => {
"useAsButton" in l && l.useAsButton && ye ? ye() : be();
}, Xe = (e) => {
Me(e), Fe?.(e);
}, Ye = m("vox:flex vox:items-center vox:gap-2 vox:rounded-2xl", r ? "vox:relative" : "vox:fixed", r ? "" : "vox:p-2", D, {
"vox:bg-white": !f && !V && !M,
"vox:bg-gray-900": f && !V && !M,
"vox:shadow-lg": !Ve,
"vox:flex-row": o === "bottom",
"vox:flex-col": o === "center" || A,
"vox:bottom-4": !r && o === "bottom" && (t === "left" || t === "right"),
"vox:left-14": !r && o === "bottom" && t === "left",
"vox:right-14": !r && o === "bottom" && t === "right",
"vox:left-3": !r && o !== "bottom" && t === "left",
"vox:right-3": !r && o !== "bottom" && t === "right",
"vox:top-[5%] vox:-translate-y-[10%]": !r && o !== "bottom" && (t === "left" || t === "right")
}), Ze = m("vox:flex vox:items-center vox:transform-none", {
"vox:flex-row": o === "bottom" && A,
"vox:flex-col": o === "center",
"vox:bottom-4": !r && o === "bottom" && (t === "left" || t === "right"),
"vox:left-3": !r && t?.includes("left") || !r && t === "left",
"vox:right-3": !r && t?.includes("right") || !r && t === "right",
"vox:top-[38%] vox:-translate-y-[38%]": !r && o !== "bottom" && (t === "left" || t === "right")
}), Je = {
...V && !M ? {
backgroundColor: V
} : {},
width: h === "minimal" ? "auto" : `${o === "bottom" ? `calc(${u}px * 1.6)` : `${u}px`}`,
height: h === "minimal" || A ? void 0 : "auto"
}, Qe = {
width: o !== "center" && t === "right" || o === "center" && t === "right" ? void 0 : `calc(${u}px * 2 ${o === "bottom" && !p ? "+ 3rem" : ""})`,
height: h === "minimal" ? void 0 : o === "center" ? `calc(${u}px * 1.15 + ${je()})` : `calc(${u}px * 2 + 2rem)`,
minHeight: h === "minimal" ? void 0 : u
}, et = {
marginTop: o === "center" ? `${ee ? fe - 24 : fe}px` : "1rem",
position: "relative",
zIndex: 50
}, tt = m("vox:inline-flex vox:justify-center vox:grow vox:items-center vox:w-full vox:gap-1 vox:px-3! vox:py-2! vox:rounded-full! vox:w-full vox:text-xs vow-max-w-[320px] vox:transition vox:duration-300 vox:ease-in-out", {
"vox:bg-black! vox:text-white! vox:border-b-4! vox:border-gray-700! vox:hover:bg-gray-700!": !f,
"vox:bg-[#383838]! vox:border-b-4! vox:border-[#292929]! vox:hover:bg-[#292929]! vox:text-white!": f
}), ot = (() => {
const e = o === "center" && (t === "left" || t === "right"), v = t === "left" ? -1 : 1, d = {
type: "spring",
stiffness: 1300,
damping: 120,
duration: 1.5,
mass: 1.2
};
return e ? {
hidden: {
x: `${100 * v}%`,
opacity: 0,
transition: d
},
visible: {
x: 0,
opacity: 1,
transition: d
}
} : {
hidden: {
y: "100%",
opacity: 0,
transition: d
},
visible: {
y: 0,
opacity: 1,
transition: d
}
};
})(), nt = Array.isArray(S) && S.length >= 2 ? [S[0], S[1]] : bt, rt = Array.isArray(L) && L.length >= 2 ? [L[0], L[1]] : wt, it = p ? E : F;
if (oe)
return /* @__PURE__ */ n("div", {
className: "vox:sr-only",
children: "Voxweave is loading..."
});
if (!G || O) {
const e = process.env.NODE_ENV === "development" || !1;
return /* @__PURE__ */ n(Z, {
message: O || "Widget unavailable",
isDev: e
});
}
const Ce = process.env.NODE_ENV === "development" || !1;
return /* @__PURE__ */ n(xt, {
FallbackComponent: ({}) => /* @__PURE__ */ n(Z, {
message: "Something went wrong",
isDev: Ce,
onClose: () => ie(!1)
}),
children: /* @__PURE__ */ y(ft, {
children: [(!G || O) && Oe && /* @__PURE__ */ n(Z, {
message: O || "Voxweave widget unavailable",
isDev: Ce,
onClose: () => ie(!1)
}), /* @__PURE__ */ y(P.div, {
animate: !Q,
id: "voxweave-primary-container",
className: m(Ze, r ? "" : "vox:fixed"),
style: Qe,
children: [/* @__PURE__ */ n(P.div, {
className: m(r ? "vox:relative" : "vox:fixed vox:overflow-hidden", " vox:relative"),
initial: {
height: h === "minimal" ? void 0 : `calc(${u}px ${o === "bottom" ? "/ 1.6" : "* 2"})`
},
animate: {
height: u
},
transition: {
duration: 0.6,
ease: [0.4, 0, 0.2, 1]
},
children: it && /* @__PURE__ */ y(P.div, {
ref: We,
className: m(Ye, c && "vox:cursor-pointer"),
style: Je,
onClick: c ? we : void 0,
role: c ? "button" : "region",
"aria-label": c ? "Open Voxweave" : "Voxweave audio visualization",
tabIndex: c ? 0 : void 0,
onKeyDown: (e) => {
c && (e.key === "Enter" || e.key === " ") && (e.preventDefault(), we());
},
initial: "hidden",
animate: Q ? "visible" : !1,
exit: "hidden",
variants: ot,
children: [Re && /* @__PURE__ */ n(lt, {
value: Pe,
onValueChange: Xe,
languages: ct,
className: "vox:mr-2",
darkMode: f,
backgroundColor: V
}), h === "minimal" ? h === "minimal" && C && /* @__PURE__ */ n(ut, {
isListening: i,
onStartCall: _,
onClose: U,
audioData: le,
agentAudioData: ue,
agentStatus: b,
darkMode: f,
startCallLabel: q,
endCallLabel: j
}) : /* @__PURE__ */ y(ht, {
children: [/* @__PURE__ */ n(yt, {
animation: J,
validatedSize: u,
isListening: i,
audioData: le,
isVertical: A,
agentAudioData: ue,
finalListeningColors: nt,
finalNotListeningColors: rt,
agentStatus: b,
darkMode: f,
containerStyle: o
}), C && !c && /* @__PURE__ */ y("div", {
style: {
width: p && !A ? u : "100%"
},
className: m(A ? "vox:px-1.5! vox:pb-1!" : "", "vox:relative vox:flex vox:flex-col vox:items-center vox:justify-start vox:z-10 vox:flex vox:w-full", {
"vox:gap-2": o === "bottom" || A,
"vox:gap-1": o === "center"
}),
children: [!ee && /* @__PURE__ */ n("div", {
className: m(f ? "vox:text-white/90" : "vox:text-black/90", "vox:relative vox:z-10 vox:px-2! vox:pb-1! vox:text-sm vox:font-light vox:text-left"),
children: i && b === "speaking" ? "Talk to interrupt" : i && b === "listening" ? "Listening" : `${T || "Need help?"}`
}), /* @__PURE__ */ y(Se, {
className: tt,
size: "sm",
onClick: be,
"aria-label": i ? j : q,
disabled: !G,
children: [/* @__PURE__ */ n(Le, {
className: "vox:w-3 vox:h-3",
"aria-hidden": "true"
}), i ? j : q]
})]
})]
})]
})
}), /* @__PURE__ */ n(at, {
children: (!F && p || F) && !oe && C && !De && !c && /* @__PURE__ */ n(P.div, {
initial: {
opacity: 0,
x: t === "left" ? -20 : t === "right" ? 20 : 0,
y: 20
},
animate: {
opacity: 1,
x: 0,
y: 0
},
exit: {
opacity: 0,
x: t === "left" ? -20 : t === "right" ? 20 : 0,
y: 20
},
transition: {
duration: 0.3,
ease: "easeInOut"
},
style: et,
className: m("vox:relative vox:z-50", {
"vox:self-start": o !== "center" && t === "left" || o === "center",
"vox:self-end": o !== "center" && (t === "left" || t === "right")
}),
children: /* @__PURE__ */ y(Se, {
className: m("vox:p-3! vox:rounded-full! vox:transition vox:duration-300 vox:ease-in-out", {
"vox:bg-gray-900/90! vox:hover:bg-gray-300/80! vox:text-gray-800!": !f,
"vox:bg-gray-900/90! vox:hover:bg-gray-900/80! vox:text-white!": f,
"vox:flex vox:items-center vox:gap-1": p && !E
}),
size: "icon",
onClick: p ? Ge : U,
"aria-label": p && E ? "Close Voxweave" : "Expand Voxweave",
children: [p && E || F ? /* @__PURE__ */ n(ke, {
className: "vox:w-3 vox:h-3",
"aria-hidden": "true"
}) : /* @__PURE__ */ n(Le, {
className: "vox:w-3 vox:h-3",
"aria-hidden": "true"
}), " ", p && !E ? /* @__PURE__ */ n("span", {
className: "vox:text-sm vox:font-light",
children: Ie
}) : null]
})
}, "close-button")
})]
}), Ue && /* @__PURE__ */ n(st, {
onAccept: Ke,
onCancel: He,
privacyPolicyUrl: Be,
companyName: ze
})]
})
});
};
export {
Mt as default
};
//# sourceMappingURL=index4.js.map