@unctad-ai/voice-agent-ui 5.1.3 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import {
5
5
  SliderSetting,
6
6
  ToggleSetting,
7
7
  VoiceSettingsView
8
- } from "./chunk-LOZIYINE.js";
8
+ } from "./chunk-IMDRPZ6B.js";
9
9
  export {
10
10
  Divider,
11
11
  SelectSetting,
@@ -14,4 +14,4 @@ export {
14
14
  ToggleSetting,
15
15
  VoiceSettingsView as default
16
16
  };
17
- //# sourceMappingURL=VoiceSettingsView-UL3TSETS.js.map
17
+ //# sourceMappingURL=VoiceSettingsView-OPMI5HCP.js.map
@@ -1902,7 +1902,7 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
1902
1902
  ) : /* @__PURE__ */ jsx3("span", {}),
1903
1903
  /* @__PURE__ */ jsxs2("span", { children: [
1904
1904
  "Kit v",
1905
- /* @__PURE__ */ jsx3("span", { style: { fontWeight: 500, color: "#6b7280" }, children: "5.1.3" })
1905
+ /* @__PURE__ */ jsx3("span", { style: { fontWeight: 500, color: "#6b7280" }, children: "5.2.0" })
1906
1906
  ] })
1907
1907
  ] }) })
1908
1908
  ]
@@ -1994,4 +1994,4 @@ export {
1994
1994
  SettingsSection,
1995
1995
  Divider
1996
1996
  };
1997
- //# sourceMappingURL=chunk-LOZIYINE.js.map
1997
+ //# sourceMappingURL=chunk-IMDRPZ6B.js.map
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  VoiceSettingsProvider,
9
9
  VoiceSettingsView,
10
10
  useVoiceSettings
11
- } from "./chunk-LOZIYINE.js";
11
+ } from "./chunk-IMDRPZ6B.js";
12
12
 
13
13
  // src/VoiceAgentProvider.tsx
14
14
  import { SiteConfigProvider } from "@unctad-ai/voice-agent-core";
@@ -32,7 +32,7 @@ import {
32
32
  Component as Component2
33
33
  } from "react";
34
34
  import { createPortal } from "react-dom";
35
- import { motion as motion5, AnimatePresence as AnimatePresence5 } from "motion/react";
35
+ import { motion as motion5, AnimatePresence as AnimatePresence5, useReducedMotion as useReducedMotion2 } from "motion/react";
36
36
  import { ChevronDown as ChevronDown2, X, Mic as Mic3, ArrowUp, Keyboard as Keyboard2, RotateCw, Settings, VolumeX as VolumeX2, Flag as Flag2 } from "lucide-react";
37
37
  import {
38
38
  useVoiceAgent,
@@ -1764,7 +1764,7 @@ function PipelineMetricsBar({
1764
1764
 
1765
1765
  // src/components/GlassCopilotPanel.tsx
1766
1766
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1767
- var VoiceSettingsView2 = lazy(() => import("./VoiceSettingsView-UL3TSETS.js"));
1767
+ var VoiceSettingsView2 = lazy(() => import("./VoiceSettingsView-OPMI5HCP.js"));
1768
1768
  var RETRY_INITIAL_MS = 3e3;
1769
1769
  var RETRY_MAX_MS = 3e4;
1770
1770
  var STATE_LABELS = {
@@ -1795,12 +1795,12 @@ function CopilotFAB({ onClick, portraitSrc, isOffline = false }) {
1795
1795
  border: "none",
1796
1796
  padding: 0,
1797
1797
  borderRadius: "50%",
1798
- width: 54,
1799
- height: 54
1798
+ width: 68,
1799
+ height: 68
1800
1800
  },
1801
1801
  "aria-label": "Open voice assistant",
1802
1802
  "data-testid": "voice-agent-fab",
1803
- children: /* @__PURE__ */ jsx9("div", { className: "agent-fab-border", style: { width: 54, height: 54, "--agent-primary": isOffline ? "#9ca3af" : colors.primary, animation: isOffline ? "none" : void 0 }, children: /* @__PURE__ */ jsx9("div", { className: "agent-fab-border-inner", children: portraitSrc ? /* @__PURE__ */ jsx9(
1803
+ children: /* @__PURE__ */ jsx9("div", { className: "agent-fab-border", style: { width: 68, height: 68, "--agent-primary": isOffline ? "#9ca3af" : colors.primary, animation: isOffline ? "none" : void 0 }, children: /* @__PURE__ */ jsx9("div", { className: "agent-fab-border-inner", children: portraitSrc ? /* @__PURE__ */ jsx9(
1804
1804
  "img",
1805
1805
  {
1806
1806
  src: portraitSrc,
@@ -1819,7 +1819,7 @@ function CopilotFAB({ onClick, portraitSrc, isOffline = false }) {
1819
1819
  justifyContent: "center",
1820
1820
  backgroundColor: "#6b7280",
1821
1821
  color: "white",
1822
- fontSize: 20,
1822
+ fontSize: 24,
1823
1823
  fontWeight: 700
1824
1824
  },
1825
1825
  children: "AI"
@@ -1828,6 +1828,176 @@ function CopilotFAB({ onClick, portraitSrc, isOffline = false }) {
1828
1828
  }
1829
1829
  );
1830
1830
  }
1831
+ var FAB_GREETED_KEY = (name) => `voice-fab-greeted:${name}`;
1832
+ var FAB_LAST_SHOWN_KEY = (name) => `voice-fab-last-shown:${name}`;
1833
+ var FAB_COOLDOWN_MS = 30 * 60 * 1e3;
1834
+ var FAB_TOOLTIP_NARROW_QUERY = "(max-width: 479px)";
1835
+ function safeStorage(op) {
1836
+ try {
1837
+ return op();
1838
+ } catch {
1839
+ return null;
1840
+ }
1841
+ }
1842
+ function CopilotFABTooltip({ onClick }) {
1843
+ const { copilotName, colors, fabTooltip } = useSiteConfig4();
1844
+ const prefersReduced = useReducedMotion2();
1845
+ const [visible, setVisible] = useState5(false);
1846
+ const [dismissed, setDismissed] = useState5(false);
1847
+ const [isFirstVisit] = useState5(() => !safeStorage(() => localStorage.getItem(FAB_GREETED_KEY(copilotName))));
1848
+ useEffect6(() => {
1849
+ if (typeof window === "undefined") return;
1850
+ const mql = window.matchMedia(FAB_TOOLTIP_NARROW_QUERY);
1851
+ if (mql.matches) {
1852
+ setDismissed(true);
1853
+ return;
1854
+ }
1855
+ const handler = (e) => {
1856
+ if (e.matches) setDismissed(true);
1857
+ };
1858
+ mql.addEventListener("change", handler);
1859
+ return () => mql.removeEventListener("change", handler);
1860
+ }, []);
1861
+ useEffect6(() => {
1862
+ if (isFirstVisit) {
1863
+ const timer2 = setTimeout(() => {
1864
+ setVisible(true);
1865
+ safeStorage(() => localStorage.setItem(FAB_GREETED_KEY(copilotName), "true"));
1866
+ safeStorage(() => localStorage.setItem(FAB_LAST_SHOWN_KEY(copilotName), (/* @__PURE__ */ new Date()).toISOString()));
1867
+ }, 2e3);
1868
+ return () => clearTimeout(timer2);
1869
+ }
1870
+ const lastShown = safeStorage(() => localStorage.getItem(FAB_LAST_SHOWN_KEY(copilotName)));
1871
+ if (lastShown && Date.now() - new Date(lastShown).getTime() < FAB_COOLDOWN_MS) return;
1872
+ const timer = setTimeout(() => {
1873
+ setVisible(true);
1874
+ safeStorage(() => localStorage.setItem(FAB_LAST_SHOWN_KEY(copilotName), (/* @__PURE__ */ new Date()).toISOString()));
1875
+ }, 5e3);
1876
+ return () => clearTimeout(timer);
1877
+ }, [copilotName, isFirstVisit]);
1878
+ useEffect6(() => {
1879
+ if (!visible || isFirstVisit) return;
1880
+ const timer = setTimeout(() => setDismissed(true), 5e3);
1881
+ return () => clearTimeout(timer);
1882
+ }, [visible, isFirstVisit]);
1883
+ useEffect6(() => {
1884
+ if (!visible || dismissed || isFirstVisit) return;
1885
+ const handleScroll = () => setDismissed(true);
1886
+ window.addEventListener("scroll", handleScroll, { passive: true, once: true });
1887
+ return () => window.removeEventListener("scroll", handleScroll);
1888
+ }, [visible, dismissed, isFirstVisit]);
1889
+ const handleTryNow = () => {
1890
+ setDismissed(true);
1891
+ onClick();
1892
+ };
1893
+ const handleDismiss = (e) => {
1894
+ e.stopPropagation();
1895
+ setDismissed(true);
1896
+ };
1897
+ const firstVisitText = fabTooltip?.firstVisit ? fabTooltip.firstVisit.replaceAll("{name}", copilotName) : `I'm ${copilotName}, your virtual civil servant.
1898
+ How may I help you?`;
1899
+ const returnVisitText = fabTooltip?.returnVisit ?? "How may I help you?";
1900
+ const show = visible && !dismissed;
1901
+ return /* @__PURE__ */ jsx9(AnimatePresence5, { children: show && /* @__PURE__ */ jsxs8(
1902
+ motion5.div,
1903
+ {
1904
+ role: "status",
1905
+ "aria-live": "polite",
1906
+ "data-testid": "voice-agent-fab-tooltip",
1907
+ initial: prefersReduced ? { opacity: 0 } : { opacity: 0, x: 20 },
1908
+ animate: prefersReduced ? { opacity: 1 } : { opacity: 1, x: 0 },
1909
+ exit: prefersReduced ? { opacity: 0 } : { opacity: 0, x: 10 },
1910
+ transition: prefersReduced ? { duration: 0.2 } : { type: "spring", stiffness: 300, damping: 25 },
1911
+ onClick: isFirstVisit ? void 0 : handleTryNow,
1912
+ style: {
1913
+ display: "flex",
1914
+ alignItems: "center",
1915
+ gap: 0,
1916
+ cursor: isFirstVisit ? "default" : "pointer"
1917
+ },
1918
+ children: [
1919
+ /* @__PURE__ */ jsxs8(
1920
+ "div",
1921
+ {
1922
+ style: {
1923
+ background: "white",
1924
+ borderRadius: 20,
1925
+ padding: isFirstVisit ? "12px 20px" : "10px 18px",
1926
+ boxShadow: "0 2px 12px rgba(0,0,0,0.10), 0 0 0 1px rgba(0,0,0,0.04)",
1927
+ maxWidth: 340
1928
+ },
1929
+ children: [
1930
+ /* @__PURE__ */ jsx9(
1931
+ "div",
1932
+ {
1933
+ style: {
1934
+ fontSize: isFirstVisit ? 14 : 13,
1935
+ color: "#1a1a1a",
1936
+ fontWeight: 500,
1937
+ lineHeight: 1.4,
1938
+ whiteSpace: "pre-line"
1939
+ },
1940
+ children: isFirstVisit ? firstVisitText : returnVisitText
1941
+ }
1942
+ ),
1943
+ isFirstVisit && /* @__PURE__ */ jsxs8("div", { style: { display: "flex", gap: 8, marginTop: 8 }, children: [
1944
+ /* @__PURE__ */ jsx9(
1945
+ "button",
1946
+ {
1947
+ "data-testid": "voice-agent-fab-tooltip-cta",
1948
+ "aria-label": `Open ${copilotName} voice assistant`,
1949
+ onClick: handleTryNow,
1950
+ style: {
1951
+ padding: "6px 16px",
1952
+ background: colors.primary,
1953
+ color: "white",
1954
+ border: "none",
1955
+ borderRadius: 12,
1956
+ fontSize: 12,
1957
+ fontWeight: 600,
1958
+ cursor: "pointer"
1959
+ },
1960
+ children: "Try it now"
1961
+ }
1962
+ ),
1963
+ /* @__PURE__ */ jsx9(
1964
+ "button",
1965
+ {
1966
+ onClick: handleDismiss,
1967
+ style: {
1968
+ padding: "6px 12px",
1969
+ background: "transparent",
1970
+ color: "#6b7280",
1971
+ border: "none",
1972
+ borderRadius: 12,
1973
+ fontSize: 12,
1974
+ cursor: "pointer"
1975
+ },
1976
+ children: "Maybe later"
1977
+ }
1978
+ )
1979
+ ] })
1980
+ ]
1981
+ }
1982
+ ),
1983
+ /* @__PURE__ */ jsx9(
1984
+ "div",
1985
+ {
1986
+ style: {
1987
+ width: 0,
1988
+ height: 0,
1989
+ borderTop: "8px solid transparent",
1990
+ borderBottom: "8px solid transparent",
1991
+ borderLeft: "8px solid white",
1992
+ flexShrink: 0,
1993
+ filter: "drop-shadow(2px 0 1px rgba(0,0,0,0.06))"
1994
+ }
1995
+ }
1996
+ )
1997
+ ]
1998
+ }
1999
+ ) });
2000
+ }
1831
2001
  function CollapsedBar({
1832
2002
  orbState,
1833
2003
  getAmplitude,
@@ -2732,7 +2902,7 @@ function WiredPanelInner({
2732
2902
  timings: lastTimings ?? void 0,
2733
2903
  route: window.location.pathname,
2734
2904
  copilotName: config.copilotName,
2735
- kitVersion: "5.1.3"
2905
+ kitVersion: "5.2.0"
2736
2906
  })
2737
2907
  });
2738
2908
  const body = await res.json().catch(() => ({ ticketId: void 0 }));
@@ -2870,7 +3040,10 @@ function GlassCopilotPanel({ isOpen: isOpenProp, onOpen: onOpenProp, onClose: on
2870
3040
  ] }) }) }),
2871
3041
  /* @__PURE__ */ jsx9("span", { "aria-live": "polite", "aria-atomic": "true", style: { position: "absolute", width: 1, height: 1, padding: 0, margin: -1, overflow: "hidden", clip: "rect(0,0,0,0)", whiteSpace: "nowrap", borderWidth: 0 }, children: ariaAnnouncement }),
2872
3042
  /* @__PURE__ */ jsxs8(AnimatePresence5, { children: [
2873
- !isVisible && /* @__PURE__ */ jsx9(motion5.div, { ref: fabRef, initial: { scale: 0, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0, opacity: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: "fixed", style: { bottom: PANEL_BOTTOM, right: PANEL_RIGHT, zIndex: PANEL_Z_INDEX }, children: /* @__PURE__ */ jsx9(CopilotFAB, { onClick: handleOpen, portraitSrc: resolvedPortrait, isOffline: fabOffline }) }, "copilot-fab"),
3043
+ !isVisible && /* @__PURE__ */ jsxs8(motion5.div, { ref: fabRef, initial: { scale: 0, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0, opacity: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: "fixed", style: { bottom: PANEL_BOTTOM, right: PANEL_RIGHT, zIndex: PANEL_Z_INDEX, display: "flex", alignItems: "flex-end", gap: 12 }, children: [
3044
+ /* @__PURE__ */ jsx9(CopilotFABTooltip, { onClick: handleOpen }),
3045
+ /* @__PURE__ */ jsx9(CopilotFAB, { onClick: handleOpen, portraitSrc: resolvedPortrait, isOffline: fabOffline })
3046
+ ] }, "copilot-fab"),
2874
3047
  isVisible && /* @__PURE__ */ jsxs8(
2875
3048
  motion5.div,
2876
3049
  {
@@ -2880,9 +3053,9 @@ function GlassCopilotPanel({ isOpen: isOpenProp, onOpen: onOpenProp, onClose: on
2880
3053
  "aria-label": "Voice Assistant",
2881
3054
  "aria-modal": "false",
2882
3055
  "data-testid": "voice-agent-panel",
2883
- initial: { width: 48, height: 48, borderRadius: 24, opacity: 0, scale: 0.9 },
3056
+ initial: { width: 68, height: 68, borderRadius: 34, opacity: 0, scale: 0.9 },
2884
3057
  animate: { width: PANEL_WIDTH, height: Math.min(targetHeight, window.innerHeight - 48), borderRadius: PANEL_BORDER_RADIUS, opacity: 1, scale: 1, transition: SPRING_PANEL },
2885
- exit: { width: 48, height: 48, borderRadius: 24, opacity: 0, scale: 0.95, transition: SPRING_PANEL_EXIT },
3058
+ exit: { width: 68, height: 68, borderRadius: 34, opacity: 0, scale: 0.95, transition: SPRING_PANEL_EXIT },
2886
3059
  className: "fixed",
2887
3060
  style: { bottom: PANEL_BOTTOM, right: PANEL_RIGHT, zIndex: PANEL_Z_INDEX, transformOrigin: "bottom right", maxWidth: "calc(100vw - 32px)", outline: "none", fontFamily: config.fontFamily ?? DEFAULT_FONT_FAMILY2 },
2888
3061
  children: [
@@ -2939,7 +3112,7 @@ function VoiceA11yAnnouncer({ isOpen, orbState }) {
2939
3112
  }
2940
3113
 
2941
3114
  // src/components/VoiceCopilotFAB.tsx
2942
- import { motion as motion6, AnimatePresence as AnimatePresence6, useReducedMotion as useReducedMotion2 } from "motion/react";
3115
+ import { motion as motion6, AnimatePresence as AnimatePresence6, useReducedMotion as useReducedMotion3 } from "motion/react";
2943
3116
  import { Mic as Mic4 } from "lucide-react";
2944
3117
  import { useSiteConfig as useSiteConfig5 } from "@unctad-ai/voice-agent-core";
2945
3118
  import { jsx as jsx11 } from "react/jsx-runtime";
@@ -2949,7 +3122,7 @@ function VoiceCopilotFAB({
2949
3122
  isOverlayOpen,
2950
3123
  onMouseEnter
2951
3124
  }) {
2952
- const prefersReduced = useReducedMotion2();
3125
+ const prefersReduced = useReducedMotion3();
2953
3126
  const { colors } = useSiteConfig5();
2954
3127
  return /* @__PURE__ */ jsx11(AnimatePresence6, { children: !isOverlayOpen && /* @__PURE__ */ jsx11(
2955
3128
  motion6.div,