@unctad-ai/voice-agent-ui 5.3.1 → 5.4.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-QXFECZAT.js";
8
+ } from "./chunk-B46B46BD.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-U7O5H5PC.js.map
17
+ //# sourceMappingURL=VoiceSettingsView-SBAX47HO.js.map
@@ -1,5 +1,5 @@
1
1
  // src/components/VoiceSettingsView.tsx
2
- import { useState as useState3, useCallback as useCallback3 } from "react";
2
+ import { useState as useState3, useEffect as useEffect2, useCallback as useCallback3 } from "react";
3
3
  import { motion, AnimatePresence } from "motion/react";
4
4
  import {
5
5
  ArrowLeft,
@@ -14,6 +14,7 @@ import {
14
14
  Activity,
15
15
  Cpu,
16
16
  EyeOff,
17
+ Info,
17
18
  Ear,
18
19
  Clock,
19
20
  Zap,
@@ -26,7 +27,10 @@ import {
26
27
  Headphones,
27
28
  SlidersHorizontal,
28
29
  Wrench,
29
- Globe
30
+ Globe,
31
+ Type,
32
+ TextCursorInput,
33
+ Sparkle
30
34
  } from "lucide-react";
31
35
 
32
36
  // src/contexts/VoiceSettingsContext.tsx
@@ -163,17 +167,6 @@ var RECORDING_PROMPTS = [
163
167
  "Could you please provide your business name and the type of license you need?",
164
168
  "Thank you for your patience. Your application has been submitted successfully."
165
169
  ];
166
- var LANGUAGE_OPTIONS = [
167
- { value: "en", label: "English" },
168
- { value: "fr", label: "French" },
169
- { value: "es", label: "Spanish" },
170
- { value: "sw", label: "Swahili" },
171
- { value: "pt", label: "Portuguese" },
172
- { value: "ar", label: "Arabic" },
173
- { value: "zh", label: "Chinese" },
174
- { value: "hi", label: "Hindi" },
175
- { value: "dz", label: "Dzongkha" }
176
- ];
177
170
  function PersonaSettings({ adminPassword }) {
178
171
  const config = useSiteConfig();
179
172
  if (!config.personaEndpoint) return null;
@@ -231,67 +224,14 @@ function PersonaSettingsInner({ adminPassword }) {
231
224
  onRecord: () => setShowRecording(true)
232
225
  }
233
226
  ),
234
- /* @__PURE__ */ jsxs("div", { style: { borderTop: "1px solid #e5e7eb", paddingTop: 12, display: "flex", flexDirection: "column", gap: 10 }, children: [
235
- /* @__PURE__ */ jsx2("span", { style: { fontSize: 13, fontWeight: 500, color: "#111827" }, children: "Copilot settings" }),
236
- /* @__PURE__ */ jsx2(
237
- ColorSettingRow,
238
- {
239
- label: "Color",
240
- value: data?.copilotColor || config.colors.primary || "#1B5E20",
241
- onSave: (v) => handleSharedSave({ copilotColor: v })
242
- }
243
- ),
244
- /* @__PURE__ */ jsx2(
245
- TextSettingRow,
246
- {
247
- label: "Site title",
248
- value: data?.siteTitle || "",
249
- onSave: (v) => handleSharedSave({ siteTitle: v })
250
- }
251
- ),
252
- /* @__PURE__ */ jsx2(
253
- TextAreaSettingRow,
254
- {
255
- label: "Greeting",
256
- value: data?.greetingMessage || "",
257
- onSave: (v) => handleSharedSave({ greetingMessage: v })
258
- }
259
- ),
260
- /* @__PURE__ */ jsx2(
261
- TextAreaSettingRow,
262
- {
263
- label: "Farewell",
264
- value: data?.farewellMessage || "",
265
- onSave: (v) => handleSharedSave({ farewellMessage: v })
266
- }
267
- ),
268
- /* @__PURE__ */ jsx2(
269
- TextAreaSettingRow,
270
- {
271
- label: "System prompt intro",
272
- value: data?.systemPromptIntro || "",
273
- onSave: (v) => handleSharedSave({ systemPromptIntro: v }),
274
- rows: 4
275
- }
276
- ),
277
- /* @__PURE__ */ jsx2(SettingRow, { label: "Default language", children: /* @__PURE__ */ jsx2(
278
- "select",
279
- {
280
- value: data?.language || "en",
281
- onChange: (e) => handleSharedSave({ language: e.target.value }),
282
- style: {
283
- fontSize: 12,
284
- borderRadius: 6,
285
- border: "1px solid #e5e7eb",
286
- padding: "4px 8px",
287
- outline: "none",
288
- fontFamily: "inherit",
289
- backgroundColor: "#fff"
290
- },
291
- children: LANGUAGE_OPTIONS.map((l) => /* @__PURE__ */ jsx2("option", { value: l.value, children: l.label }, l.value))
292
- }
293
- ) })
294
- ] })
227
+ /* @__PURE__ */ jsx2("div", { style: { borderTop: "1px solid #e5e7eb", paddingTop: 10 }, children: /* @__PURE__ */ jsx2(
228
+ ColorSettingRow,
229
+ {
230
+ label: "Brand color",
231
+ value: data?.copilotColor || config.colors.primary || "#DB2129",
232
+ onSave: (v) => handleSharedSave({ copilotColor: v })
233
+ }
234
+ ) })
295
235
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
296
236
  /* @__PURE__ */ jsx2(AvatarSection, { avatarUrl: config.avatarUrl, name: config.copilotName, disabled: true }),
297
237
  /* @__PURE__ */ jsxs("div", { style: { fontSize: 13, color: "#6b7280", padding: "4px 0" }, children: [
@@ -322,6 +262,7 @@ function SettingRow({ label, children }) {
322
262
  }
323
263
  function ColorSettingRow({ label, value, onSave }) {
324
264
  const [local, setLocal] = useState2(value);
265
+ const [focused, setFocused] = useState2(false);
325
266
  useEffect(() => {
326
267
  setLocal(value);
327
268
  }, [value]);
@@ -331,71 +272,15 @@ function ColorSettingRow({ label, value, onSave }) {
331
272
  type: "color",
332
273
  value: local,
333
274
  onChange: (e) => setLocal(e.target.value),
275
+ onFocus: () => setFocused(true),
334
276
  onBlur: () => {
277
+ setFocused(false);
335
278
  if (local !== value) onSave(local);
336
279
  },
337
- style: { width: 32, height: 26, border: "1px solid #e5e7eb", borderRadius: 4, cursor: "pointer", padding: 0 }
280
+ style: { width: 32, height: 26, border: `1px solid ${focused ? "#9ca3af" : "#e5e7eb"}`, borderRadius: 4, cursor: "pointer", padding: 0, transition: "border-color 0.15s" }
338
281
  }
339
282
  ) });
340
283
  }
341
- function TextSettingRow({ label, value, onSave }) {
342
- const [local, setLocal] = useState2(value);
343
- useEffect(() => {
344
- setLocal(value);
345
- }, [value]);
346
- return /* @__PURE__ */ jsx2(SettingRow, { label, children: /* @__PURE__ */ jsx2(
347
- "input",
348
- {
349
- type: "text",
350
- value: local,
351
- onChange: (e) => setLocal(e.target.value),
352
- onBlur: () => {
353
- if (local !== value) onSave(local);
354
- },
355
- style: {
356
- width: "100%",
357
- fontSize: 12,
358
- padding: "4px 8px",
359
- borderRadius: 6,
360
- border: "1px solid #e5e7eb",
361
- outline: "none",
362
- fontFamily: "inherit",
363
- boxSizing: "border-box"
364
- }
365
- }
366
- ) });
367
- }
368
- function TextAreaSettingRow({ label, value, onSave, rows = 2 }) {
369
- const [local, setLocal] = useState2(value);
370
- useEffect(() => {
371
- setLocal(value);
372
- }, [value]);
373
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
374
- /* @__PURE__ */ jsx2("span", { style: { fontSize: 12, color: "#6b7280" }, children: label }),
375
- /* @__PURE__ */ jsx2(
376
- "textarea",
377
- {
378
- value: local,
379
- onChange: (e) => setLocal(e.target.value),
380
- onBlur: () => {
381
- if (local !== value) onSave(local);
382
- },
383
- rows,
384
- style: {
385
- width: "100%",
386
- fontSize: 12,
387
- padding: "6px 8px",
388
- borderRadius: 6,
389
- border: "1px solid #e5e7eb",
390
- outline: "none",
391
- fontFamily: "inherit",
392
- resize: "vertical",
393
- boxSizing: "border-box"
394
- }
395
- }
396
- )
397
- ] });
398
- }
399
284
  function AvatarSection({ avatarUrl, name, onUpload, disabled }) {
400
285
  const [uploading, setUploading] = useState2(false);
401
286
  const [hovered, setHovered] = useState2(false);
@@ -1212,7 +1097,7 @@ function bargeInLabel(v) {
1212
1097
  if (v >= 0.6) return "Normal";
1213
1098
  return "Easy";
1214
1099
  }
1215
- var LANGUAGE_OPTIONS2 = [
1100
+ var LANGUAGE_OPTIONS = [
1216
1101
  { value: "en", label: "English" },
1217
1102
  { value: "fr", label: "French" },
1218
1103
  { value: "es", label: "Spanish" },
@@ -1424,6 +1309,92 @@ function SelectSetting({
1424
1309
  )
1425
1310
  ] });
1426
1311
  }
1312
+ function TextInputSetting({
1313
+ icon,
1314
+ label,
1315
+ description,
1316
+ value,
1317
+ onSave,
1318
+ multiline,
1319
+ rows = 2
1320
+ }) {
1321
+ const [local, setLocal] = useState3(value);
1322
+ const [focused, setFocused] = useState3(false);
1323
+ const [saveStatus, setSaveStatus] = useState3("idle");
1324
+ useEffect2(() => {
1325
+ setLocal(value);
1326
+ }, [value]);
1327
+ useEffect2(() => {
1328
+ if (saveStatus === "saved") {
1329
+ const t = setTimeout(() => setSaveStatus("idle"), 1500);
1330
+ return () => clearTimeout(t);
1331
+ }
1332
+ }, [saveStatus]);
1333
+ const handleBlur = async () => {
1334
+ setFocused(false);
1335
+ if (local !== value) {
1336
+ const ok = await onSave(local);
1337
+ if (typeof ok === "boolean") setSaveStatus(ok ? "saved" : "error");
1338
+ }
1339
+ };
1340
+ const inputStyle = {
1341
+ width: "100%",
1342
+ fontSize: 12,
1343
+ fontWeight: 500,
1344
+ color: "#374151",
1345
+ padding: "6px 10px",
1346
+ borderRadius: 8,
1347
+ border: `1px solid ${focused ? "#9ca3af" : "#e5e7eb"}`,
1348
+ backgroundColor: "#f9fafb",
1349
+ outline: "none",
1350
+ fontFamily: "inherit",
1351
+ boxSizing: "border-box",
1352
+ resize: multiline ? "vertical" : "none",
1353
+ transition: "border-color 0.15s"
1354
+ };
1355
+ return /* @__PURE__ */ jsxs2("div", { style: { paddingTop: 10, paddingBottom: 10, display: "flex", flexDirection: "column", gap: 8 }, children: [
1356
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "flex-start", gap: 12 }, children: [
1357
+ /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", width: 16, height: 20, flexShrink: 0 }, children: icon }),
1358
+ /* @__PURE__ */ jsxs2("div", { style: { flex: 1 }, children: [
1359
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "baseline", gap: 6, minHeight: 20 }, children: [
1360
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: 13, fontWeight: 500, color: "#111827" }, children: label }),
1361
+ /* @__PURE__ */ jsxs2(AnimatePresence, { children: [
1362
+ saveStatus === "saved" && /* @__PURE__ */ jsx3(motion.span, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, style: { fontSize: 11, color: "#22c55e", fontWeight: 400 }, children: "Saved" }, "saved"),
1363
+ saveStatus === "error" && /* @__PURE__ */ jsx3(motion.span, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, style: { fontSize: 11, color: "#ef4444", fontWeight: 400 }, children: "Save failed" }, "error")
1364
+ ] })
1365
+ ] }),
1366
+ description && /* @__PURE__ */ jsx3("p", { style: { fontSize: 11, color: "#9ca3af", margin: "2px 0 0", lineHeight: 1.4 }, children: description })
1367
+ ] })
1368
+ ] }),
1369
+ multiline ? /* @__PURE__ */ jsx3(
1370
+ "textarea",
1371
+ {
1372
+ value: local,
1373
+ onChange: (e) => {
1374
+ setLocal(e.target.value);
1375
+ if (saveStatus === "error") setSaveStatus("idle");
1376
+ },
1377
+ onFocus: () => setFocused(true),
1378
+ onBlur: handleBlur,
1379
+ rows,
1380
+ style: inputStyle
1381
+ }
1382
+ ) : /* @__PURE__ */ jsx3(
1383
+ "input",
1384
+ {
1385
+ type: "text",
1386
+ value: local,
1387
+ onChange: (e) => {
1388
+ setLocal(e.target.value);
1389
+ if (saveStatus === "error") setSaveStatus("idle");
1390
+ },
1391
+ onFocus: () => setFocused(true),
1392
+ onBlur: handleBlur,
1393
+ style: inputStyle
1394
+ }
1395
+ )
1396
+ ] });
1397
+ }
1427
1398
  function VoiceSettingsView({ onBack, onVolumeChange }) {
1428
1399
  const { settings, updateSetting, resetSettings } = useVoiceSettings();
1429
1400
  const config = useSiteConfig2();
@@ -1443,6 +1414,17 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
1443
1414
  const [passwordInput, setPasswordInput] = useState3("");
1444
1415
  const [authError, setAuthError] = useState3("");
1445
1416
  const isAdmin = adminPassword !== null;
1417
+ const updateConfigFn = persona?.updateConfig;
1418
+ const handleSharedSave = useCallback3(async (fields) => {
1419
+ if (!adminPassword || !updateConfigFn) return false;
1420
+ try {
1421
+ await updateConfigFn(fields, adminPassword);
1422
+ return true;
1423
+ } catch (err) {
1424
+ console.error("Settings save failed:", err);
1425
+ return false;
1426
+ }
1427
+ }, [adminPassword, updateConfigFn]);
1446
1428
  const handleAdminLogin = useCallback3(async (pw) => {
1447
1429
  if (!persona) return;
1448
1430
  try {
@@ -1527,6 +1509,66 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
1527
1509
  ),
1528
1510
  /* @__PURE__ */ jsxs2("div", { style: { flex: 1, minHeight: 0, overflowY: "auto" }, children: [
1529
1511
  config.personaEndpoint && /* @__PURE__ */ jsx3(SettingsSection, { title: "Persona", icon: /* @__PURE__ */ jsx3(User, { style: sectionIconStyle }), ...sectionProps("persona"), children: /* @__PURE__ */ jsx3(PersonaSettings, { adminPassword }) }),
1512
+ config.personaEndpoint && isAdmin && persona?.persona && /* @__PURE__ */ jsxs2(SettingsSection, { title: "Agent", icon: /* @__PURE__ */ jsx3(Sparkle, { style: sectionIconStyle }), ...sectionProps("agent"), children: [
1513
+ /* @__PURE__ */ jsx3(
1514
+ TextInputSetting,
1515
+ {
1516
+ icon: /* @__PURE__ */ jsx3(Type, { style: iconStyle }),
1517
+ label: "Portal context",
1518
+ description: "What this portal offers \u2014 helps the AI understand the domain",
1519
+ value: persona.persona.siteTitle || config.siteTitle || "",
1520
+ onSave: (v) => handleSharedSave({ siteTitle: v })
1521
+ }
1522
+ ),
1523
+ /* @__PURE__ */ jsx3(Divider, {}),
1524
+ /* @__PURE__ */ jsx3(
1525
+ TextInputSetting,
1526
+ {
1527
+ icon: /* @__PURE__ */ jsx3(MessageSquare, { style: iconStyle }),
1528
+ label: "Greeting",
1529
+ description: "Shown when the panel opens with no conversation",
1530
+ value: persona.persona.greetingMessage || config.greetingMessage || "",
1531
+ onSave: (v) => handleSharedSave({ greetingMessage: v })
1532
+ }
1533
+ ),
1534
+ /* @__PURE__ */ jsx3(Divider, {}),
1535
+ /* @__PURE__ */ jsx3(
1536
+ TextInputSetting,
1537
+ {
1538
+ icon: /* @__PURE__ */ jsx3(TextCursorInput, { style: iconStyle }),
1539
+ label: "Suggested prompts",
1540
+ description: "Tappable chips in the empty state (one per line)",
1541
+ value: persona.persona.suggestedPrompts || config.suggestedPrompts?.join("\n") || "",
1542
+ onSave: (v) => handleSharedSave({ suggestedPrompts: v }),
1543
+ multiline: true,
1544
+ rows: 3
1545
+ }
1546
+ ),
1547
+ /* @__PURE__ */ jsx3(Divider, {}),
1548
+ /* @__PURE__ */ jsx3(
1549
+ TextInputSetting,
1550
+ {
1551
+ icon: /* @__PURE__ */ jsx3(Info, { style: iconStyle }),
1552
+ label: "System prompt intro",
1553
+ description: "Prefixed to every LLM conversation. Use {name} for the agent's name.",
1554
+ value: persona.persona.systemPromptIntro || config.systemPromptIntro || "",
1555
+ onSave: (v) => handleSharedSave({ systemPromptIntro: v }),
1556
+ multiline: true,
1557
+ rows: 4
1558
+ }
1559
+ ),
1560
+ /* @__PURE__ */ jsx3(Divider, {}),
1561
+ /* @__PURE__ */ jsx3(
1562
+ SelectSetting,
1563
+ {
1564
+ icon: /* @__PURE__ */ jsx3(Globe, { style: iconStyle }),
1565
+ label: "Default language",
1566
+ value: persona.persona.language || config.language || "en",
1567
+ onChange: (v) => handleSharedSave({ language: v }),
1568
+ options: LANGUAGE_OPTIONS
1569
+ }
1570
+ )
1571
+ ] }),
1530
1572
  /* @__PURE__ */ jsxs2(SettingsSection, { title: "Conversation", icon: /* @__PURE__ */ jsx3(MessageCircle, { style: sectionIconStyle }), ...sectionProps("conversation"), children: [
1531
1573
  /* @__PURE__ */ jsx3(
1532
1574
  SelectSetting,
@@ -1567,7 +1609,7 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
1567
1609
  label: "Language",
1568
1610
  value: settings.language,
1569
1611
  onChange: (v) => updateSetting("language", v),
1570
- options: LANGUAGE_OPTIONS2
1612
+ options: LANGUAGE_OPTIONS
1571
1613
  }
1572
1614
  ),
1573
1615
  /* @__PURE__ */ jsx3(Divider, {}),
@@ -1816,7 +1858,7 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
1816
1858
  /* @__PURE__ */ jsx3(
1817
1859
  "input",
1818
1860
  {
1819
- type: "password",
1861
+ type: "text",
1820
1862
  placeholder: "Admin password",
1821
1863
  value: passwordInput,
1822
1864
  onChange: (e) => {
@@ -1832,6 +1874,9 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
1832
1874
  }
1833
1875
  },
1834
1876
  autoFocus: true,
1877
+ autoComplete: "off",
1878
+ "data-1p-ignore": true,
1879
+ "data-lpignore": "true",
1835
1880
  style: {
1836
1881
  fontSize: 11,
1837
1882
  padding: "3px 8px",
@@ -1839,7 +1884,8 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
1839
1884
  border: `1px solid ${authError ? "#ef4444" : "#e5e7eb"}`,
1840
1885
  outline: "none",
1841
1886
  fontFamily: "inherit",
1842
- width: 110
1887
+ width: 110,
1888
+ WebkitTextSecurity: "disc"
1843
1889
  }
1844
1890
  }
1845
1891
  ),
@@ -1903,7 +1949,7 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
1903
1949
  ) : /* @__PURE__ */ jsx3("span", {}),
1904
1950
  /* @__PURE__ */ jsxs2("span", { children: [
1905
1951
  "Kit v",
1906
- /* @__PURE__ */ jsx3("span", { style: { fontWeight: 500, color: "#6b7280" }, children: "5.3.1" })
1952
+ /* @__PURE__ */ jsx3("span", { style: { fontWeight: 500, color: "#6b7280" }, children: "5.4.0" })
1907
1953
  ] })
1908
1954
  ] }) })
1909
1955
  ]
@@ -1995,4 +2041,4 @@ export {
1995
2041
  SettingsSection,
1996
2042
  Divider
1997
2043
  };
1998
- //# sourceMappingURL=chunk-QXFECZAT.js.map
2044
+ //# sourceMappingURL=chunk-B46B46BD.js.map