@unctad-ai/voice-agent-ui 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import {
2
+ Divider,
3
+ PersonaSettings,
4
+ SelectSetting,
5
+ SettingsSection,
6
+ SliderSetting,
7
+ ToggleSetting,
2
8
  VoiceSettingsProvider,
3
9
  VoiceSettingsView,
4
10
  useVoiceSettings
5
- } from "./chunk-IYKRYBBO.js";
11
+ } from "./chunk-BFDEQ5XG.js";
6
12
 
7
13
  // src/VoiceAgentProvider.tsx
8
14
  import { SiteConfigProvider } from "@unctad-ai/voice-agent-core";
@@ -27,7 +33,7 @@ import {
27
33
  } from "react";
28
34
  import { createPortal } from "react-dom";
29
35
  import { motion as motion5, AnimatePresence as AnimatePresence5 } from "motion/react";
30
- import { ChevronDown as ChevronDown2, X, Mic as Mic2, ArrowUp, Keyboard, RotateCw, Settings, VolumeX as VolumeX2 } from "lucide-react";
36
+ import { ChevronDown as ChevronDown2, X, Mic as Mic3, ArrowUp, Keyboard as Keyboard2, RotateCw, Settings, VolumeX as VolumeX2 } from "lucide-react";
31
37
  import {
32
38
  useVoiceAgent,
33
39
  voiceStateToOrbState,
@@ -657,7 +663,7 @@ import {
657
663
  DEFAULT_FONT_FAMILY,
658
664
  useSiteConfig as useSiteConfig3
659
665
  } from "@unctad-ai/voice-agent-core";
660
- import { ArrowRight, PenLine, MousePointerClick, Search, Info, ChevronDown } from "lucide-react";
666
+ import { ArrowRight, PenLine, MousePointerClick, Search, Info, ChevronDown, Mic, Keyboard } from "lucide-react";
661
667
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
662
668
  function cleanForDisplay(text) {
663
669
  return text.replace(/\[(laugh|chuckle|sigh|gasp|cough|clear throat|sniff|groan|shush)\]/gi, "").replace(/\*\*(.*?)\*\*/g, "$1").replace(/^\s*\*\s+/gm, "- ").replace(/\*(.*?)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "").replace(/\|[-:\s|]+\|/g, "").replace(/\|/g, "\n").replace(/\p{Emoji_Presentation}|\p{Extended_Pictographic}/gu, "").replace(/[ \t]{3,}/g, " ").replace(/\n{3,}/g, "\n\n").replace(/^[\s-]{3,}$/gm, "").trim();
@@ -830,10 +836,14 @@ function VoiceTranscript({
830
836
  messages,
831
837
  isTyping,
832
838
  variant = "overlay",
833
- voiceError
839
+ voiceError,
840
+ voiceState,
841
+ onStartMic,
842
+ onSwitchToKeyboard
834
843
  }) {
835
844
  const config = useSiteConfig3();
836
845
  const fontFamily = config.fontFamily ?? DEFAULT_FONT_FAMILY;
846
+ const assistantLabel = config.copilotName || "Assistant";
837
847
  const containerRef = useRef3(null);
838
848
  const isPanel = variant === "panel";
839
849
  const maxVisible = isPanel ? PANEL_MAX_VISIBLE_MESSAGES : MAX_VISIBLE_MESSAGES;
@@ -891,18 +901,21 @@ function VoiceTranscript({
891
901
  }
892
902
  };
893
903
  if (isPanel) {
894
- return /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-h-0 relative", style: { fontFamily }, children: [
904
+ return /* @__PURE__ */ jsxs3("div", { style: { flex: 1, minHeight: 0, position: "relative", fontFamily }, children: [
895
905
  /* @__PURE__ */ jsx4(
896
906
  "div",
897
907
  {
898
908
  ref: containerRef,
899
- className: "absolute inset-0 overflow-y-auto overscroll-contain",
900
909
  style: {
910
+ position: "absolute",
911
+ inset: 0,
912
+ overflowY: "auto",
913
+ overscrollBehavior: "contain",
901
914
  padding: "16px 16px 12px",
902
915
  maskImage: "linear-gradient(to bottom, transparent 0%, black 8px, black calc(100% - 8px), transparent 100%)",
903
916
  WebkitMaskImage: "linear-gradient(to bottom, transparent 0%, black 8px, black calc(100% - 8px), transparent 100%)"
904
917
  },
905
- children: /* @__PURE__ */ jsxs3("div", { ref: contentRef, className: "flex flex-col", style: { paddingBottom: "24px" }, children: [
918
+ children: /* @__PURE__ */ jsxs3("div", { ref: contentRef, style: { display: "flex", flexDirection: "column", paddingBottom: 24 }, children: [
906
919
  /* @__PURE__ */ jsx4(AnimatePresence, { mode: "popLayout", children: groupDisplayItems(visible).map((item, idx, arr) => {
907
920
  if (item.type === "action") {
908
921
  const prevIsAction = idx > 0 && arr[idx - 1].type === "action";
@@ -932,7 +945,7 @@ function VoiceTranscript({
932
945
  color: "rgba(0,0,0,0.35)",
933
946
  marginBottom: "4px"
934
947
  },
935
- children: "Copilot"
948
+ children: assistantLabel
936
949
  }
937
950
  ),
938
951
  /* @__PURE__ */ jsx4(ActionBadge, { msg: item.msg, count: item.count })
@@ -971,7 +984,7 @@ function VoiceTranscript({
971
984
  color: isAI ? "rgba(0,0,0,0.35)" : "rgba(219,33,41,0.5)",
972
985
  marginBottom: "4px"
973
986
  },
974
- children: isAI ? "Copilot" : "You"
987
+ children: isAI ? assistantLabel : "You"
975
988
  }
976
989
  ),
977
990
  /* @__PURE__ */ jsx4(
@@ -1086,17 +1099,7 @@ function VoiceTranscript({
1086
1099
  )
1087
1100
  ]
1088
1101
  }
1089
- ) : /* @__PURE__ */ jsx4(
1090
- motion.p,
1091
- {
1092
- initial: { opacity: 0, y: 4 },
1093
- animate: { opacity: 1, y: 0 },
1094
- transition: { duration: 0.4, delay: 0.2 },
1095
- className: "text-center italic",
1096
- style: { fontSize: "14px", color: "rgba(0,0,0,0.4)", paddingTop: "60px" },
1097
- children: "Ask me anything"
1098
- }
1099
- )),
1102
+ ) : /* @__PURE__ */ jsx4("div", { style: { paddingTop: 40 }, children: /* @__PURE__ */ jsx4(EmptyStateGraphic, { primaryColor: config.colors.primary }) })),
1100
1103
  isTyping && visible.length === 0 && /* @__PURE__ */ jsx4(
1101
1104
  motion.p,
1102
1105
  {
@@ -1213,14 +1216,12 @@ function VoiceTranscript({
1213
1216
  );
1214
1217
  }) }),
1215
1218
  visible.length === 0 && !isTyping && /* @__PURE__ */ jsx4(
1216
- motion.p,
1219
+ EmptyStateGraphic,
1217
1220
  {
1218
- initial: { opacity: 0, y: 8 },
1219
- animate: { opacity: 1, y: 0 },
1220
- transition: { duration: 0.5, delay: 0.3 },
1221
- className: "text-lg italic",
1222
- style: { color: "rgba(0,0,0,0.5)" },
1223
- children: "Ask me anything"
1221
+ primaryColor: config.colors.primary,
1222
+ voiceState,
1223
+ onStartMic,
1224
+ onSwitchToKeyboard
1224
1225
  }
1225
1226
  ),
1226
1227
  isTyping && visible.length === 0 && /* @__PURE__ */ jsx4(
@@ -1237,6 +1238,114 @@ function VoiceTranscript({
1237
1238
  }
1238
1239
  );
1239
1240
  }
1241
+ function EmptyStateGraphic({ primaryColor, voiceState, onStartMic, onSwitchToKeyboard }) {
1242
+ const isListening = voiceState === "LISTENING" || voiceState === "USER_SPEAKING";
1243
+ if (isListening) {
1244
+ return /* @__PURE__ */ jsxs3(
1245
+ motion.div,
1246
+ {
1247
+ initial: { opacity: 0 },
1248
+ animate: { opacity: 1 },
1249
+ transition: { duration: 0.3 },
1250
+ style: {
1251
+ display: "flex",
1252
+ flexDirection: "column",
1253
+ alignItems: "center",
1254
+ justifyContent: "center",
1255
+ gap: 6,
1256
+ paddingTop: 60
1257
+ },
1258
+ children: [
1259
+ /* @__PURE__ */ jsx4("p", { style: { fontSize: 16, fontWeight: 500, color: primaryColor, margin: 0, opacity: 0.7 }, children: "Listening..." }),
1260
+ /* @__PURE__ */ jsx4("p", { style: { fontSize: 12, color: "rgba(0,0,0,0.3)", margin: 0 }, children: "Go ahead, I can hear you" })
1261
+ ]
1262
+ }
1263
+ );
1264
+ }
1265
+ return /* @__PURE__ */ jsxs3(
1266
+ motion.div,
1267
+ {
1268
+ initial: { opacity: 0, y: 6 },
1269
+ animate: { opacity: 1, y: 0 },
1270
+ transition: { duration: 0.5, delay: 0.15 },
1271
+ style: {
1272
+ display: "flex",
1273
+ flexDirection: "column",
1274
+ alignItems: "center",
1275
+ justifyContent: "center",
1276
+ gap: 16,
1277
+ paddingTop: 60
1278
+ },
1279
+ children: [
1280
+ /* @__PURE__ */ jsx4("p", { style: { fontSize: 18, fontWeight: 500, color: primaryColor, margin: 0, opacity: 0.6 }, children: "How can I help?" }),
1281
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1282
+ onStartMic && /* @__PURE__ */ jsxs3(
1283
+ "button",
1284
+ {
1285
+ onClick: onStartMic,
1286
+ style: {
1287
+ display: "flex",
1288
+ alignItems: "center",
1289
+ gap: 8,
1290
+ padding: "8px 16px",
1291
+ borderRadius: 20,
1292
+ border: `1px solid ${primaryColor}22`,
1293
+ backgroundColor: `${primaryColor}08`,
1294
+ cursor: "pointer",
1295
+ fontFamily: "inherit",
1296
+ fontSize: 13,
1297
+ color: "rgba(0,0,0,0.5)",
1298
+ transition: "background-color 0.15s, border-color 0.15s"
1299
+ },
1300
+ onMouseEnter: (e) => {
1301
+ e.currentTarget.style.backgroundColor = `${primaryColor}14`;
1302
+ e.currentTarget.style.borderColor = `${primaryColor}33`;
1303
+ },
1304
+ onMouseLeave: (e) => {
1305
+ e.currentTarget.style.backgroundColor = `${primaryColor}08`;
1306
+ e.currentTarget.style.borderColor = `${primaryColor}22`;
1307
+ },
1308
+ children: [
1309
+ /* @__PURE__ */ jsx4(Mic, { style: { width: 14, height: 14, color: primaryColor, opacity: 0.7 } }),
1310
+ "Start talking"
1311
+ ]
1312
+ }
1313
+ ),
1314
+ onSwitchToKeyboard && /* @__PURE__ */ jsxs3(
1315
+ "button",
1316
+ {
1317
+ onClick: onSwitchToKeyboard,
1318
+ style: {
1319
+ display: "flex",
1320
+ alignItems: "center",
1321
+ gap: 8,
1322
+ padding: "8px 16px",
1323
+ borderRadius: 20,
1324
+ border: "1px solid rgba(0,0,0,0.06)",
1325
+ backgroundColor: "transparent",
1326
+ cursor: "pointer",
1327
+ fontFamily: "inherit",
1328
+ fontSize: 13,
1329
+ color: "rgba(0,0,0,0.35)",
1330
+ transition: "background-color 0.15s"
1331
+ },
1332
+ onMouseEnter: (e) => {
1333
+ e.currentTarget.style.backgroundColor = "rgba(0,0,0,0.03)";
1334
+ },
1335
+ onMouseLeave: (e) => {
1336
+ e.currentTarget.style.backgroundColor = "transparent";
1337
+ },
1338
+ children: [
1339
+ /* @__PURE__ */ jsx4(Keyboard, { style: { width: 14, height: 14, opacity: 0.5 } }),
1340
+ "Type a message"
1341
+ ]
1342
+ }
1343
+ )
1344
+ ] })
1345
+ ]
1346
+ }
1347
+ );
1348
+ }
1240
1349
 
1241
1350
  // src/components/VoiceToolCard.tsx
1242
1351
  import { useEffect as useEffect4 } from "react";
@@ -1355,7 +1464,7 @@ var VoiceErrorBoundary = class extends Component {
1355
1464
 
1356
1465
  // src/components/VoiceErrorDisplay.tsx
1357
1466
  import { motion as motion3, AnimatePresence as AnimatePresence3 } from "motion/react";
1358
- import { AlertTriangle as AlertTriangle2, Hourglass, Mic, MicOff, VolumeX, Wifi, WifiOff } from "lucide-react";
1467
+ import { AlertTriangle as AlertTriangle2, Hourglass, Mic as Mic2, MicOff, VolumeX, Wifi, WifiOff } from "lucide-react";
1359
1468
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1360
1469
  var SEVERITY_STYLES = {
1361
1470
  error: {
@@ -1397,7 +1506,7 @@ var ERROR_CONFIG = {
1397
1506
  severity: "warning"
1398
1507
  },
1399
1508
  stt_failed: {
1400
- icon: Mic,
1509
+ icon: Mic2,
1401
1510
  title: "Didn't catch that",
1402
1511
  severity: "info"
1403
1512
  },
@@ -1417,7 +1526,7 @@ var ERROR_CONFIG = {
1417
1526
  severity: "error"
1418
1527
  },
1419
1528
  speech_too_short: {
1420
- icon: Mic,
1529
+ icon: Mic2,
1421
1530
  title: "Didn't catch that",
1422
1531
  severity: "info"
1423
1532
  },
@@ -1556,7 +1665,7 @@ function PipelineMetricsBar({
1556
1665
 
1557
1666
  // src/components/GlassCopilotPanel.tsx
1558
1667
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1559
- var VoiceSettingsView2 = lazy(() => import("./VoiceSettingsView-J4OIRJ23.js"));
1668
+ var VoiceSettingsView2 = lazy(() => import("./VoiceSettingsView-LVFUTFOG.js"));
1560
1669
  var STATE_LABELS = {
1561
1670
  IDLE: "Tap mic to speak",
1562
1671
  LISTENING: "Listening...",
@@ -1759,7 +1868,7 @@ function CollapsedBar({
1759
1868
  style: { backgroundColor: `${colors.primary}40` }
1760
1869
  }
1761
1870
  ),
1762
- /* @__PURE__ */ jsx9(Mic2, { style: { width: 16, height: 16, position: "relative", zIndex: 1 } })
1871
+ /* @__PURE__ */ jsx9(Mic3, { style: { width: 16, height: 16, position: "relative", zIndex: 1 } })
1763
1872
  ]
1764
1873
  }
1765
1874
  ),
@@ -1807,12 +1916,16 @@ function ComposerBar({
1807
1916
  micPaused = false,
1808
1917
  onTextSubmit,
1809
1918
  onMicToggle,
1810
- disabled = false
1919
+ disabled = false,
1920
+ switchToTextRef
1811
1921
  }) {
1812
1922
  const { colors } = useSiteConfig4();
1813
1923
  const [mode, setMode] = useState5("voice");
1814
1924
  const [text, setText] = useState5("");
1815
1925
  const inputRef = useRef4(null);
1926
+ useEffect6(() => {
1927
+ if (switchToTextRef) switchToTextRef.current = () => setMode("text");
1928
+ }, [switchToTextRef]);
1816
1929
  useEffect6(() => {
1817
1930
  if (mode === "text") {
1818
1931
  requestAnimationFrame(() => inputRef.current?.focus());
@@ -1852,7 +1965,7 @@ function ComposerBar({
1852
1965
  color: "rgba(0,0,0,0.2)",
1853
1966
  opacity: 0.5
1854
1967
  },
1855
- children: /* @__PURE__ */ jsx9(Mic2, { style: { width: 18, height: 18 } })
1968
+ children: /* @__PURE__ */ jsx9(Mic3, { style: { width: 18, height: 18 } })
1856
1969
  }
1857
1970
  ),
1858
1971
  /* @__PURE__ */ jsx9(
@@ -1908,7 +2021,7 @@ function ComposerBar({
1908
2021
  style: { backgroundColor: `${colors.primary}33`, filter: "blur(4px)" }
1909
2022
  }
1910
2023
  ),
1911
- /* @__PURE__ */ jsx9(Mic2, { style: { width: 18, height: 18, position: "relative", zIndex: 1 } })
2024
+ /* @__PURE__ */ jsx9(Mic3, { style: { width: 18, height: 18, position: "relative", zIndex: 1 } })
1912
2025
  ]
1913
2026
  }
1914
2027
  ),
@@ -1947,7 +2060,7 @@ function ComposerBar({
1947
2060
  },
1948
2061
  "aria-label": "Type a message",
1949
2062
  "data-testid": "voice-agent-keyboard",
1950
- children: /* @__PURE__ */ jsx9(Keyboard, { style: { width: 18, height: 18 } })
2063
+ children: /* @__PURE__ */ jsx9(Keyboard2, { style: { width: 18, height: 18 } })
1951
2064
  }
1952
2065
  )
1953
2066
  ]
@@ -2052,7 +2165,7 @@ function ComposerBar({
2052
2165
  },
2053
2166
  "aria-label": "Back to voice mode",
2054
2167
  "data-testid": "voice-agent-voice-mode",
2055
- children: /* @__PURE__ */ jsx9(Mic2, { style: { width: 18, height: 18 } })
2168
+ children: /* @__PURE__ */ jsx9(Mic3, { style: { width: 18, height: 18 } })
2056
2169
  }
2057
2170
  )
2058
2171
  ]
@@ -2088,7 +2201,10 @@ function ExpandedContent({
2088
2201
  onSettingsToggle,
2089
2202
  ttsEnabled = true,
2090
2203
  copilotName,
2091
- portraitSrc
2204
+ portraitSrc,
2205
+ onStartMic,
2206
+ onSwitchToKeyboard,
2207
+ switchToTextRef
2092
2208
  }) {
2093
2209
  const { colors } = useSiteConfig4();
2094
2210
  const isListening = voiceState === "LISTENING" || voiceState === "USER_SPEAKING";
@@ -2096,7 +2212,7 @@ function ExpandedContent({
2096
2212
  return /* @__PURE__ */ jsxs8(
2097
2213
  "div",
2098
2214
  {
2099
- className: "flex flex-col h-full overflow-hidden",
2215
+ style: { display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" },
2100
2216
  onClick: onInteraction,
2101
2217
  onKeyDown: onInteraction,
2102
2218
  children: [
@@ -2172,9 +2288,9 @@ function ExpandedContent({
2172
2288
  ]
2173
2289
  }
2174
2290
  ),
2175
- /* @__PURE__ */ jsxs8("div", { className: "flex-1 min-h-0 flex flex-col overflow-hidden", children: [
2176
- /* @__PURE__ */ jsx9("div", { "data-testid": "voice-agent-transcript", children: /* @__PURE__ */ jsx9(VoiceTranscript, { messages, isTyping, variant: "panel", voiceError }) }),
2177
- /* @__PURE__ */ jsxs8("div", { className: "shrink-0", children: [
2291
+ /* @__PURE__ */ jsxs8("div", { style: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column", overflow: "hidden" }, children: [
2292
+ /* @__PURE__ */ jsx9("div", { "data-testid": "voice-agent-transcript", style: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }, children: /* @__PURE__ */ jsx9(VoiceTranscript, { messages, isTyping, variant: "panel", voiceError, voiceState, onStartMic, onSwitchToKeyboard }) }),
2293
+ /* @__PURE__ */ jsxs8("div", { style: { flexShrink: 0 }, children: [
2178
2294
  /* @__PURE__ */ jsx9(PipelineMetricsBar, { timings: lastTimings ?? null, show: showPipelineMetrics, autoHideMs: pipelineMetricsAutoHideMs }),
2179
2295
  isOffline && onRetry && /* @__PURE__ */ jsx9("div", { style: { padding: "0 16px 8px", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsxs8(motion5.button, { whileHover: { scale: 1.04 }, whileTap: { scale: 0.96 }, onClick: onRetry, disabled: isRetrying, className: "inline-flex items-center gap-2 rounded-full cursor-pointer transition-colors", style: { padding: "8px 18px", fontSize: "13px", fontWeight: 500, color: isRetrying ? "rgba(0,0,0,0.35)" : "rgba(220,38,38,0.8)", backgroundColor: isRetrying ? "rgba(0,0,0,0.04)" : "rgba(220,38,38,0.08)", border: "1px solid", borderColor: isRetrying ? "rgba(0,0,0,0.06)" : "rgba(220,38,38,0.15)" }, "aria-label": "Retry connection", children: [
2180
2296
  /* @__PURE__ */ jsx9(motion5.span, { animate: isRetrying ? { rotate: 360 } : { rotate: 0 }, transition: isRetrying ? { duration: 0.8, repeat: Infinity, ease: "linear" } : { duration: 0.3 }, style: { display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx9(RotateCw, { style: { width: 14, height: 14 } }) }),
@@ -2184,7 +2300,7 @@ function ExpandedContent({
2184
2300
  /* @__PURE__ */ jsx9("div", { style: { padding: "0 16px 8px" }, children: /* @__PURE__ */ jsx9(VoiceToolCard, { result: toolResult, onDismiss: onToolDismiss, variant: "capsule" }) })
2185
2301
  ] })
2186
2302
  ] }),
2187
- /* @__PURE__ */ jsx9(motion5.div, { initial: { opacity: 0, y: 12 }, animate: { opacity: 1, y: 0 }, transition: { ...SPRING_MICRO, delay: 0.2 }, className: "shrink-0", children: /* @__PURE__ */ jsx9(ComposerBar, { voiceState, isListening, micPaused, onTextSubmit, onMicToggle, disabled: voiceError === "network_error" }) })
2303
+ /* @__PURE__ */ jsx9(motion5.div, { initial: { opacity: 0, y: 12 }, animate: { opacity: 1, y: 0 }, transition: { ...SPRING_MICRO, delay: 0.2 }, className: "shrink-0", children: /* @__PURE__ */ jsx9(ComposerBar, { voiceState, isListening, micPaused, onTextSubmit, onMicToggle, disabled: voiceError === "network_error", switchToTextRef }) })
2188
2304
  ]
2189
2305
  }
2190
2306
  );
@@ -2261,8 +2377,8 @@ function WiredPanelInner({
2261
2377
  const { settings: voiceSettings, volumeRef, speedRef } = useVoiceSettings();
2262
2378
  const { state, start, stop, messages, isLLMLoading, getAmplitude, analyser, sendTextMessage, voiceError, dismissError, sessionEnded, lastTimings, applyVolume, settings } = useVoiceAgent({ settings: voiceSettings, volumeRef, speedRef });
2263
2379
  useEffect6(() => {
2264
- if (sessionEnded) onClose();
2265
- }, [sessionEnded, onClose]);
2380
+ if (sessionEnded) onCollapse();
2381
+ }, [sessionEnded, onCollapse]);
2266
2382
  const [toolResult, setToolResult] = useState5(null);
2267
2383
  const orbState = voiceStateToOrbState(state);
2268
2384
  const [backendDown, setBackendDown] = useState5(false);
@@ -2385,13 +2501,26 @@ function WiredPanelInner({
2385
2501
  }
2386
2502
  }).catch(() => setIsRetrying(false));
2387
2503
  }, [isRetrying, runHealthCheck, dismissError, settings.autoListen]);
2504
+ const onExpandRef = useRef4(onExpand);
2505
+ useEffect6(() => {
2506
+ onExpandRef.current = onExpand;
2507
+ });
2508
+ useEffect6(() => {
2509
+ if (panelState === "collapsed" && messages.length > 0) {
2510
+ onExpandRef.current();
2511
+ }
2512
+ }, [panelState, messages.length]);
2513
+ const switchToTextRef = useRef4(null);
2514
+ const handleSwitchToKeyboard = useCallback3(() => {
2515
+ switchToTextRef.current?.();
2516
+ }, []);
2388
2517
  const [showSettings, setShowSettings] = useState5(false);
2389
2518
  const toggleSettings = useCallback3(() => setShowSettings((p) => !p), []);
2390
2519
  if (panelState === "collapsed") {
2391
2520
  return /* @__PURE__ */ jsx9(CollapsedBar, { orbState, getAmplitude, analyser, voiceState: state, onExpand, onClose, onRetry: handleRetryClick, isRetrying, voiceError: effectiveError, micPaused, onMicToggle: handleMicToggle, ttsEnabled: settings.ttsEnabled, copilotName: config.copilotName, portraitSrc: resolvedPortrait });
2392
2521
  }
2393
2522
  return /* @__PURE__ */ jsxs8("div", { className: "relative h-full", children: [
2394
- /* @__PURE__ */ jsx9(ExpandedContent, { orbState, getAmplitude, analyser, voiceState: state, messages, isTyping, toolResult, voiceError: effectiveError, dismissError, onCollapse, onClose, onTextSubmit: handleTextSubmit, onMicToggle: handleMicToggle, micPaused, onToolDismiss: () => setToolResult(null), onInteraction: bumpActivity, onRetry: handleRetryClick, isRetrying, lastTimings, showPipelineMetrics: settings.showPipelineMetrics, pipelineMetricsAutoHideMs: settings.pipelineMetricsAutoHideMs, showSettings, onSettingsToggle: toggleSettings, ttsEnabled: settings.ttsEnabled, copilotName: config.copilotName, portraitSrc: resolvedPortrait }),
2523
+ /* @__PURE__ */ jsx9(ExpandedContent, { orbState, getAmplitude, analyser, voiceState: state, messages, isTyping, toolResult, voiceError: effectiveError, dismissError, onCollapse, onClose, onTextSubmit: handleTextSubmit, onMicToggle: handleMicToggle, micPaused, onToolDismiss: () => setToolResult(null), onInteraction: bumpActivity, onRetry: handleRetryClick, isRetrying, lastTimings, showPipelineMetrics: settings.showPipelineMetrics, pipelineMetricsAutoHideMs: settings.pipelineMetricsAutoHideMs, showSettings, onSettingsToggle: toggleSettings, ttsEnabled: settings.ttsEnabled, copilotName: config.copilotName, portraitSrc: resolvedPortrait, onStartMic: handleMicToggle, onSwitchToKeyboard: handleSwitchToKeyboard, switchToTextRef }),
2395
2524
  /* @__PURE__ */ jsx9(AnimatePresence5, { children: showSettings && /* @__PURE__ */ jsx9(Suspense, { fallback: null, children: /* @__PURE__ */ jsx9(VoiceSettingsView2, { onBack: toggleSettings, onVolumeChange: applyVolume }) }) })
2396
2525
  ] });
2397
2526
  }
@@ -2511,7 +2640,7 @@ function VoiceA11yAnnouncer({ isOpen, orbState }) {
2511
2640
 
2512
2641
  // src/components/VoiceCopilotFAB.tsx
2513
2642
  import { motion as motion6, AnimatePresence as AnimatePresence6, useReducedMotion as useReducedMotion2 } from "motion/react";
2514
- import { Mic as Mic3 } from "lucide-react";
2643
+ import { Mic as Mic4 } from "lucide-react";
2515
2644
  import { useSiteConfig as useSiteConfig5 } from "@unctad-ai/voice-agent-core";
2516
2645
  import { jsx as jsx11 } from "react/jsx-runtime";
2517
2646
  function VoiceCopilotFAB({
@@ -2542,7 +2671,7 @@ function VoiceCopilotFAB({
2542
2671
  className: "relative rounded-full p-3 shadow-lg text-white transition-all cursor-pointer",
2543
2672
  style: { backgroundColor: colors.primary },
2544
2673
  "aria-label": "Open voice assistant",
2545
- children: isActive ? /* @__PURE__ */ jsx11("span", { className: "h-3 w-3 rounded-full bg-white animate-pulse" }) : /* @__PURE__ */ jsx11(Mic3, { className: "w-6 h-6" })
2674
+ children: isActive ? /* @__PURE__ */ jsx11("span", { className: "h-3 w-3 rounded-full bg-white animate-pulse" }) : /* @__PURE__ */ jsx11(Mic4, { className: "w-6 h-6" })
2546
2675
  }
2547
2676
  )
2548
2677
  },
@@ -2553,7 +2682,7 @@ function VoiceCopilotFAB({
2553
2682
  // src/components/VoiceControls.tsx
2554
2683
  import { useState as useState6, useRef as useRef6, useEffect as useEffect8 } from "react";
2555
2684
  import { motion as motion7, AnimatePresence as AnimatePresence7 } from "motion/react";
2556
- import { Keyboard as Keyboard2, Send, X as X2 } from "lucide-react";
2685
+ import { Keyboard as Keyboard3, Send, X as X2 } from "lucide-react";
2557
2686
  import { useSiteConfig as useSiteConfig6 } from "@unctad-ai/voice-agent-core";
2558
2687
  import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
2559
2688
  var STATE_LABELS2 = {
@@ -2722,7 +2851,7 @@ function VoiceControls({ state, onTextSubmit, isListening }) {
2722
2851
  "transition-all duration-200 cursor-pointer"
2723
2852
  ),
2724
2853
  "aria-label": "Type a message",
2725
- children: /* @__PURE__ */ jsx12(Keyboard2, { className: "h-4 w-4" })
2854
+ children: /* @__PURE__ */ jsx12(Keyboard3, { className: "h-4 w-4" })
2726
2855
  }
2727
2856
  )
2728
2857
  ]
@@ -3364,8 +3493,14 @@ function VoiceOverlay({ isOpen, onClose, onStateChange }) {
3364
3493
  }
3365
3494
  export {
3366
3495
  AgentAvatar,
3496
+ Divider,
3367
3497
  GlassCopilotPanel,
3498
+ PersonaSettings,
3368
3499
  PipelineMetricsBar,
3500
+ SelectSetting,
3501
+ SettingsSection,
3502
+ SliderSetting,
3503
+ ToggleSetting,
3369
3504
  VoiceA11yAnnouncer,
3370
3505
  VoiceAgentProvider,
3371
3506
  VoiceControls,