@unctad-ai/voice-agent-ui 3.0.3 → 5.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.
@@ -5,7 +5,7 @@ import {
5
5
  SliderSetting,
6
6
  ToggleSetting,
7
7
  VoiceSettingsView
8
- } from "./chunk-MUM5DNV4.js";
8
+ } from "./chunk-K3JJWWRQ.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-25CXZPWY.js.map
17
+ //# sourceMappingURL=VoiceSettingsView-QY7TEHYZ.js.map
@@ -1,5 +1,5 @@
1
1
  // src/components/VoiceSettingsView.tsx
2
- import { useState as useState3 } from "react";
2
+ import { useState as useState3, useCallback as useCallback3 } from "react";
3
3
  import { motion, AnimatePresence } from "motion/react";
4
4
  import {
5
5
  ArrowLeft,
@@ -152,68 +152,258 @@ function useVoiceSettings() {
152
152
  }
153
153
 
154
154
  // src/components/VoiceSettingsView.tsx
155
- import { VAD, useSiteConfig as useSiteConfig2 } from "@unctad-ai/voice-agent-core";
155
+ import { VAD, useSiteConfig as useSiteConfig2, usePersonaContext as usePersonaContext2 } from "@unctad-ai/voice-agent-core";
156
156
 
157
157
  // src/components/PersonaSettings.tsx
158
- import { useState as useState2, useRef as useRef2, useEffect } from "react";
158
+ import { useState as useState2, useRef as useRef2, useEffect, useCallback as useCallback2 } from "react";
159
159
  import { usePersonaContext, useSiteConfig } from "@unctad-ai/voice-agent-core";
160
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
161
- function PersonaSettings() {
160
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
161
+ var LANGUAGE_OPTIONS = [
162
+ { value: "en", label: "English" },
163
+ { value: "fr", label: "French" },
164
+ { value: "es", label: "Spanish" },
165
+ { value: "sw", label: "Swahili" },
166
+ { value: "pt", label: "Portuguese" },
167
+ { value: "ar", label: "Arabic" },
168
+ { value: "zh", label: "Chinese" },
169
+ { value: "hi", label: "Hindi" },
170
+ { value: "dz", label: "Dzongkha" }
171
+ ];
172
+ function PersonaSettings({ adminPassword }) {
162
173
  const config = useSiteConfig();
163
174
  if (!config.personaEndpoint) return null;
164
- return /* @__PURE__ */ jsx2(PersonaSettingsInner, {});
175
+ return /* @__PURE__ */ jsx2(PersonaSettingsInner, { adminPassword: adminPassword ?? null });
165
176
  }
166
- function PersonaSettingsInner() {
177
+ function PersonaSettingsInner({ adminPassword }) {
167
178
  const config = useSiteConfig();
168
179
  const persona = usePersonaContext();
169
180
  if (!persona) return null;
170
- const { persona: data, isLoaded, updateName, uploadAvatar, uploadVoice, deleteVoice, setActiveVoice, previewVoice } = persona;
181
+ const { persona: data, isLoaded, uploadAvatar, uploadVoice, deleteVoice, setActiveVoice, previewVoice, updateConfig } = persona;
182
+ const isAdmin = adminPassword !== null;
183
+ const handleSharedSave = useCallback2(async (fields) => {
184
+ if (!adminPassword) return;
185
+ try {
186
+ await updateConfig(fields, adminPassword);
187
+ } catch (err) {
188
+ console.error("Settings save failed:", err);
189
+ }
190
+ }, [adminPassword, updateConfig]);
171
191
  if (!isLoaded) {
172
192
  return /* @__PURE__ */ jsx2("div", { style: { padding: 16, fontSize: 13, color: "#9ca3af", fontFamily: "inherit" }, children: "Loading persona settings..." });
173
193
  }
174
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12, fontFamily: "inherit" }, children: [
175
- /* @__PURE__ */ jsx2(AvatarSection, { avatarUrl: config.avatarUrl, name: config.copilotName, onUpload: uploadAvatar }),
176
- /* @__PURE__ */ jsx2(NameSection, { name: config.copilotName, onSave: updateName, primaryColor: config.colors.primary }),
194
+ return /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexDirection: "column", gap: 12, fontFamily: "inherit" }, children: isAdmin ? /* @__PURE__ */ jsxs(Fragment, { children: [
195
+ /* @__PURE__ */ jsx2(AvatarSection, { avatarUrl: config.avatarUrl, name: config.copilotName, onUpload: (f) => uploadAvatar(f, adminPassword) }),
196
+ /* @__PURE__ */ jsx2(NameSection, { name: config.copilotName, onSave: (n) => updateConfig({ copilotName: n }, adminPassword), primaryColor: config.colors.primary }),
177
197
  /* @__PURE__ */ jsx2(
178
198
  VoiceSection,
179
199
  {
180
200
  voices: data?.voices ?? [],
181
201
  activeVoiceId: data?.activeVoiceId ?? "",
182
- onUpload: uploadVoice,
183
- onDelete: deleteVoice,
184
- onSelect: setActiveVoice,
202
+ onUpload: (f, n) => uploadVoice(f, n, adminPassword),
203
+ onDelete: (id) => deleteVoice(id, adminPassword),
204
+ onSelect: (id) => setActiveVoice(id, adminPassword),
185
205
  onPreview: previewVoice,
186
206
  primaryColor: config.colors.primary
187
207
  }
208
+ ),
209
+ /* @__PURE__ */ jsxs("div", { style: { borderTop: "1px solid #e5e7eb", paddingTop: 12, display: "flex", flexDirection: "column", gap: 10 }, children: [
210
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 13, fontWeight: 500, color: "#111827" }, children: "Copilot settings" }),
211
+ /* @__PURE__ */ jsx2(
212
+ ColorSettingRow,
213
+ {
214
+ label: "Color",
215
+ value: data?.copilotColor || config.colors.primary || "#1B5E20",
216
+ onSave: (v) => handleSharedSave({ copilotColor: v })
217
+ }
218
+ ),
219
+ /* @__PURE__ */ jsx2(
220
+ TextSettingRow,
221
+ {
222
+ label: "Site title",
223
+ value: data?.siteTitle || "",
224
+ onSave: (v) => handleSharedSave({ siteTitle: v })
225
+ }
226
+ ),
227
+ /* @__PURE__ */ jsx2(
228
+ TextAreaSettingRow,
229
+ {
230
+ label: "Greeting",
231
+ value: data?.greetingMessage || "",
232
+ onSave: (v) => handleSharedSave({ greetingMessage: v })
233
+ }
234
+ ),
235
+ /* @__PURE__ */ jsx2(
236
+ TextAreaSettingRow,
237
+ {
238
+ label: "Farewell",
239
+ value: data?.farewellMessage || "",
240
+ onSave: (v) => handleSharedSave({ farewellMessage: v })
241
+ }
242
+ ),
243
+ /* @__PURE__ */ jsx2(
244
+ TextAreaSettingRow,
245
+ {
246
+ label: "System prompt intro",
247
+ value: data?.systemPromptIntro || "",
248
+ onSave: (v) => handleSharedSave({ systemPromptIntro: v }),
249
+ rows: 4
250
+ }
251
+ ),
252
+ /* @__PURE__ */ jsx2(SettingRow, { label: "Default language", children: /* @__PURE__ */ jsx2(
253
+ "select",
254
+ {
255
+ value: data?.language || "en",
256
+ onChange: (e) => handleSharedSave({ language: e.target.value }),
257
+ style: {
258
+ fontSize: 12,
259
+ borderRadius: 6,
260
+ border: "1px solid #e5e7eb",
261
+ padding: "4px 8px",
262
+ outline: "none",
263
+ fontFamily: "inherit",
264
+ backgroundColor: "#fff"
265
+ },
266
+ children: LANGUAGE_OPTIONS.map((l) => /* @__PURE__ */ jsx2("option", { value: l.value, children: l.label }, l.value))
267
+ }
268
+ ) })
269
+ ] })
270
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
271
+ /* @__PURE__ */ jsx2(AvatarSection, { avatarUrl: config.avatarUrl, name: config.copilotName, disabled: true }),
272
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 13, color: "#6b7280", padding: "4px 0" }, children: [
273
+ /* @__PURE__ */ jsx2("span", { style: { fontWeight: 500, color: "#111827" }, children: "Name:" }),
274
+ " ",
275
+ config.copilotName
276
+ ] }),
277
+ /* @__PURE__ */ jsx2(
278
+ VoiceSection,
279
+ {
280
+ voices: data?.voices ?? [],
281
+ activeVoiceId: data?.activeVoiceId ?? "",
282
+ onUpload: (f, n) => uploadVoice(f, n),
283
+ onDelete: (id) => deleteVoice(id),
284
+ onSelect: setActiveVoice,
285
+ onPreview: previewVoice,
286
+ primaryColor: config.colors.primary,
287
+ disabled: true
288
+ }
289
+ )
290
+ ] }) });
291
+ }
292
+ function SettingRow({ label, children }) {
293
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
294
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 12, color: "#6b7280", minWidth: 90 }, children: label }),
295
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1 }, children })
296
+ ] });
297
+ }
298
+ function ColorSettingRow({ label, value, onSave }) {
299
+ const [local, setLocal] = useState2(value);
300
+ useEffect(() => {
301
+ setLocal(value);
302
+ }, [value]);
303
+ return /* @__PURE__ */ jsx2(SettingRow, { label, children: /* @__PURE__ */ jsx2(
304
+ "input",
305
+ {
306
+ type: "color",
307
+ value: local,
308
+ onChange: (e) => setLocal(e.target.value),
309
+ onBlur: () => {
310
+ if (local !== value) onSave(local);
311
+ },
312
+ style: { width: 32, height: 26, border: "1px solid #e5e7eb", borderRadius: 4, cursor: "pointer", padding: 0 }
313
+ }
314
+ ) });
315
+ }
316
+ function TextSettingRow({ label, value, onSave }) {
317
+ const [local, setLocal] = useState2(value);
318
+ useEffect(() => {
319
+ setLocal(value);
320
+ }, [value]);
321
+ return /* @__PURE__ */ jsx2(SettingRow, { label, children: /* @__PURE__ */ jsx2(
322
+ "input",
323
+ {
324
+ type: "text",
325
+ value: local,
326
+ onChange: (e) => setLocal(e.target.value),
327
+ onBlur: () => {
328
+ if (local !== value) onSave(local);
329
+ },
330
+ style: {
331
+ width: "100%",
332
+ fontSize: 12,
333
+ padding: "4px 8px",
334
+ borderRadius: 6,
335
+ border: "1px solid #e5e7eb",
336
+ outline: "none",
337
+ fontFamily: "inherit",
338
+ boxSizing: "border-box"
339
+ }
340
+ }
341
+ ) });
342
+ }
343
+ function TextAreaSettingRow({ label, value, onSave, rows = 2 }) {
344
+ const [local, setLocal] = useState2(value);
345
+ useEffect(() => {
346
+ setLocal(value);
347
+ }, [value]);
348
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
349
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 12, color: "#6b7280" }, children: label }),
350
+ /* @__PURE__ */ jsx2(
351
+ "textarea",
352
+ {
353
+ value: local,
354
+ onChange: (e) => setLocal(e.target.value),
355
+ onBlur: () => {
356
+ if (local !== value) onSave(local);
357
+ },
358
+ rows,
359
+ style: {
360
+ width: "100%",
361
+ fontSize: 12,
362
+ padding: "6px 8px",
363
+ borderRadius: 6,
364
+ border: "1px solid #e5e7eb",
365
+ outline: "none",
366
+ fontFamily: "inherit",
367
+ resize: "vertical",
368
+ boxSizing: "border-box"
369
+ }
370
+ }
188
371
  )
189
372
  ] });
190
373
  }
191
- function AvatarSection({ avatarUrl, name, onUpload }) {
374
+ function AvatarSection({ avatarUrl, name, onUpload, disabled }) {
192
375
  const [uploading, setUploading] = useState2(false);
193
376
  const [hovered, setHovered] = useState2(false);
194
377
  const [imgError, setImgError] = useState2(false);
378
+ const [uploadError, setUploadError] = useState2(null);
195
379
  const inputRef = useRef2(null);
196
380
  const initial = (name || "?")[0].toUpperCase();
197
381
  const handleFile = async (e) => {
198
382
  const file = e.target.files?.[0];
199
- if (!file) return;
383
+ if (!file || !onUpload) return;
384
+ if (file.size > 5 * 1024 * 1024) {
385
+ setUploadError("Image too large (max 5 MB)");
386
+ return;
387
+ }
200
388
  setUploading(true);
389
+ setUploadError(null);
201
390
  try {
202
391
  await onUpload(file);
203
392
  setImgError(false);
204
393
  } catch (err) {
205
- console.error("Avatar upload failed:", err);
394
+ setUploadError(err instanceof Error ? err.message : "Upload failed");
206
395
  } finally {
207
396
  setUploading(false);
208
397
  }
209
398
  };
210
399
  const showImage = avatarUrl && !imgError;
400
+ const canEdit = !disabled && onUpload;
211
401
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, paddingTop: 4, paddingBottom: 4 }, children: [
212
402
  /* @__PURE__ */ jsxs(
213
403
  "div",
214
404
  {
215
- onClick: () => !uploading && inputRef.current?.click(),
216
- onMouseEnter: () => setHovered(true),
405
+ onClick: () => canEdit && !uploading && inputRef.current?.click(),
406
+ onMouseEnter: () => canEdit && setHovered(true),
217
407
  onMouseLeave: () => setHovered(false),
218
408
  style: {
219
409
  position: "relative",
@@ -223,7 +413,7 @@ function AvatarSection({ avatarUrl, name, onUpload }) {
223
413
  overflow: "hidden",
224
414
  backgroundColor: "#e5e7eb",
225
415
  flexShrink: 0,
226
- cursor: uploading ? "wait" : "pointer"
416
+ cursor: !canEdit ? "default" : uploading ? "wait" : "pointer"
227
417
  },
228
418
  children: [
229
419
  showImage ? /* @__PURE__ */ jsx2(
@@ -244,7 +434,7 @@ function AvatarSection({ avatarUrl, name, onUpload }) {
244
434
  fontSize: 16,
245
435
  fontWeight: 600
246
436
  }, children: initial }),
247
- hovered && !uploading && /* @__PURE__ */ jsx2("div", { style: {
437
+ canEdit && hovered && !uploading && /* @__PURE__ */ jsx2("div", { style: {
248
438
  position: "absolute",
249
439
  inset: 0,
250
440
  backgroundColor: "rgba(0,0,0,0.35)",
@@ -261,9 +451,9 @@ function AvatarSection({ avatarUrl, name, onUpload }) {
261
451
  ),
262
452
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
263
453
  /* @__PURE__ */ jsx2("div", { style: { fontSize: 13, fontWeight: 500, color: "#111827" }, children: "Avatar" }),
264
- /* @__PURE__ */ jsx2("div", { style: { fontSize: 11, color: "#6b7280" }, children: uploading ? "Uploading..." : "Click to change" })
454
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: 11, color: uploadError ? "#DC2626" : "#6b7280" }, children: uploadError ?? (disabled ? "" : uploading ? "Uploading..." : "PNG, JPG or WebP (max 5 MB)") })
265
455
  ] }),
266
- /* @__PURE__ */ jsx2("input", { ref: inputRef, type: "file", accept: "image/png,image/jpeg,image/webp", onChange: handleFile, style: { display: "none" } })
456
+ canEdit && /* @__PURE__ */ jsx2("input", { ref: inputRef, type: "file", accept: "image/png,image/jpeg,image/webp", onChange: handleFile, style: { display: "none" } })
267
457
  ] });
268
458
  }
269
459
  function NameSection({ name, onSave, primaryColor }) {
@@ -342,7 +532,7 @@ function NameSection({ name, onSave, primaryColor }) {
342
532
  ] })
343
533
  ] });
344
534
  }
345
- function VoiceSection({ voices, activeVoiceId, onUpload, onDelete, onSelect, onPreview, primaryColor }) {
535
+ function VoiceSection({ voices, activeVoiceId, onUpload, onDelete, onSelect, onPreview, primaryColor, disabled }) {
346
536
  const [uploading, setUploading] = useState2(false);
347
537
  const [uploadName, setUploadName] = useState2("");
348
538
  const [showUpload, setShowUpload] = useState2(false);
@@ -398,12 +588,13 @@ function VoiceSection({ voices, activeVoiceId, onUpload, onDelete, onSelect, onP
398
588
  isPreviewing: previewing === v.id,
399
589
  onSelect: () => onSelect(v.id),
400
590
  onPreview: () => handlePreview(v.id),
401
- onDelete: () => onDelete(v.id),
591
+ onDelete: disabled ? void 0 : () => onDelete(v.id),
592
+ disabled,
402
593
  primaryColor
403
594
  },
404
595
  v.id
405
596
  )),
406
- showUpload ? /* @__PURE__ */ jsx2(
597
+ !disabled && (showUpload ? /* @__PURE__ */ jsx2(
407
598
  UploadForm,
408
599
  {
409
600
  uploadName,
@@ -422,11 +613,11 @@ function VoiceSection({ voices, activeVoiceId, onUpload, onDelete, onSelect, onP
422
613
  disabled: voices.length >= 10,
423
614
  onClick: () => inputRef.current?.click()
424
615
  }
425
- ),
426
- /* @__PURE__ */ jsx2("input", { ref: inputRef, type: "file", accept: "audio/wav", onChange: handleFileSelect, style: { display: "none" } })
616
+ )),
617
+ !disabled && /* @__PURE__ */ jsx2("input", { ref: inputRef, type: "file", accept: "audio/wav", onChange: handleFileSelect, style: { display: "none" } })
427
618
  ] });
428
619
  }
429
- function VoiceRow({ voice, isActive, isPreviewing, onSelect, onPreview, onDelete, primaryColor }) {
620
+ function VoiceRow({ voice, isActive, isPreviewing, onSelect, onPreview, onDelete, primaryColor, disabled }) {
430
621
  const [previewHovered, setPreviewHovered] = useState2(false);
431
622
  const [deleteHovered, setDeleteHovered] = useState2(false);
432
623
  return /* @__PURE__ */ jsxs("div", { style: {
@@ -449,6 +640,7 @@ function VoiceRow({ voice, isActive, isPreviewing, onSelect, onPreview, onDelete
449
640
  name: "active-voice",
450
641
  checked: isActive,
451
642
  onChange: onSelect,
643
+ disabled,
452
644
  style: { accentColor: primaryColor }
453
645
  }
454
646
  ),
@@ -473,7 +665,7 @@ function VoiceRow({ voice, isActive, isPreviewing, onSelect, onPreview, onDelete
473
665
  children: isPreviewing ? "Playing..." : "Preview"
474
666
  }
475
667
  ),
476
- /* @__PURE__ */ jsx2(
668
+ onDelete && /* @__PURE__ */ jsx2(
477
669
  "button",
478
670
  {
479
671
  onClick: onDelete,
@@ -605,7 +797,8 @@ function UploadButton({ disabled, onClick }) {
605
797
  }
606
798
 
607
799
  // src/components/VoiceSettingsView.tsx
608
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
800
+ import { Lock, Unlock } from "lucide-react";
801
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
609
802
  function expressivenessLabel(v) {
610
803
  if (v <= 0.15) return "Low";
611
804
  if (v <= 0.4) return "Medium";
@@ -621,7 +814,7 @@ function bargeInLabel(v) {
621
814
  if (v >= 0.6) return "Normal";
622
815
  return "Easy";
623
816
  }
624
- var LANGUAGE_OPTIONS = [
817
+ var LANGUAGE_OPTIONS2 = [
625
818
  { value: "en", label: "English" },
626
819
  { value: "fr", label: "French" },
627
820
  { value: "es", label: "Spanish" },
@@ -784,7 +977,7 @@ function SelectSetting({
784
977
  }) {
785
978
  return /* @__PURE__ */ jsxs2("div", { style: { paddingTop: 12, paddingBottom: 12, display: "flex", alignItems: "center", gap: 12 }, children: [
786
979
  icon,
787
- /* @__PURE__ */ jsx3("span", { style: { flex: 1, fontSize: 13, fontWeight: 500, color: "#111827" }, children: label }),
980
+ /* @__PURE__ */ jsx3("span", { style: { flex: 1, fontSize: 13, fontWeight: 500, color: "#111827", minWidth: 0 }, children: label }),
788
981
  /* @__PURE__ */ jsx3(
789
982
  "select",
790
983
  {
@@ -805,19 +998,24 @@ function SelectSetting({
805
998
  e.currentTarget.style.borderColor = "#e5e7eb";
806
999
  },
807
1000
  style: {
808
- height: 32,
1001
+ height: 34,
1002
+ lineHeight: "34px",
809
1003
  fontSize: 12,
810
1004
  fontWeight: 500,
811
1005
  color: "#374151",
812
1006
  borderRadius: 9999,
813
1007
  border: "1px solid #e5e7eb",
814
1008
  backgroundColor: "#f9fafb",
1009
+ paddingTop: 0,
1010
+ paddingBottom: 0,
815
1011
  paddingLeft: 12,
816
1012
  paddingRight: 28,
817
1013
  outline: "none",
818
1014
  WebkitAppearance: "none",
819
1015
  appearance: "none",
820
1016
  cursor: "pointer",
1017
+ flexShrink: 0,
1018
+ width: 110,
821
1019
  transition: "border-color 0.15s, background-color 0.15s",
822
1020
  backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")`,
823
1021
  backgroundRepeat: "no-repeat",
@@ -831,6 +1029,7 @@ function SelectSetting({
831
1029
  function VoiceSettingsView({ onBack, onVolumeChange }) {
832
1030
  const { settings, updateSetting, resetSettings } = useVoiceSettings();
833
1031
  const config = useSiteConfig2();
1032
+ const persona = usePersonaContext2();
834
1033
  const { colors } = config;
835
1034
  const [openSection, setOpenSection] = useState3(null);
836
1035
  const iconStyle = { width: 16, height: 16, flexShrink: 0, color: colors.primary };
@@ -839,6 +1038,36 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
839
1038
  open: openSection === id,
840
1039
  onToggle: () => setOpenSection(openSection === id ? null : id)
841
1040
  });
1041
+ const [adminPassword, setAdminPassword] = useState3(
1042
+ () => sessionStorage.getItem("voice-admin-pw")
1043
+ );
1044
+ const [showPasswordInput, setShowPasswordInput] = useState3(false);
1045
+ const [passwordInput, setPasswordInput] = useState3("");
1046
+ const [authError, setAuthError] = useState3("");
1047
+ const isAdmin = adminPassword !== null;
1048
+ const handleAdminLogin = useCallback3(async (pw) => {
1049
+ if (!persona) return;
1050
+ try {
1051
+ await persona.updateConfig({}, pw);
1052
+ sessionStorage.setItem("voice-admin-pw", pw);
1053
+ setAdminPassword(pw);
1054
+ setAuthError("");
1055
+ setShowPasswordInput(false);
1056
+ setPasswordInput("");
1057
+ } catch {
1058
+ setAuthError("Invalid password");
1059
+ }
1060
+ }, [persona]);
1061
+ const handleAdminToggle = useCallback3(() => {
1062
+ if (isAdmin) {
1063
+ sessionStorage.removeItem("voice-admin-pw");
1064
+ setAdminPassword(null);
1065
+ setShowPasswordInput(false);
1066
+ setPasswordInput("");
1067
+ } else {
1068
+ setShowPasswordInput(true);
1069
+ }
1070
+ }, [isAdmin]);
842
1071
  return /* @__PURE__ */ jsxs2(
843
1072
  motion.div,
844
1073
  {
@@ -899,7 +1128,7 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
899
1128
  }
900
1129
  ),
901
1130
  /* @__PURE__ */ jsxs2("div", { style: { flex: 1, minHeight: 0, overflowY: "auto" }, children: [
902
- config.personaEndpoint && /* @__PURE__ */ jsx3(SettingsSection, { title: "Persona", icon: /* @__PURE__ */ jsx3(User, { style: sectionIconStyle }), ...sectionProps("persona"), children: /* @__PURE__ */ jsx3(PersonaSettings, {}) }),
1131
+ config.personaEndpoint && /* @__PURE__ */ jsx3(SettingsSection, { title: "Persona", icon: /* @__PURE__ */ jsx3(User, { style: sectionIconStyle }), ...sectionProps("persona"), children: /* @__PURE__ */ jsx3(PersonaSettings, { adminPassword }) }),
903
1132
  /* @__PURE__ */ jsxs2(SettingsSection, { title: "Conversation", icon: /* @__PURE__ */ jsx3(MessageCircle, { style: sectionIconStyle }), ...sectionProps("conversation"), children: [
904
1133
  /* @__PURE__ */ jsx3(
905
1134
  SelectSetting,
@@ -940,7 +1169,7 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
940
1169
  label: "Language",
941
1170
  value: settings.language,
942
1171
  onChange: (v) => updateSetting("language", v),
943
- options: LANGUAGE_OPTIONS
1172
+ options: LANGUAGE_OPTIONS2
944
1173
  }
945
1174
  ),
946
1175
  /* @__PURE__ */ jsx3(Divider, {}),
@@ -1185,10 +1414,100 @@ function VoiceSettingsView({ onBack, onVolumeChange }) {
1185
1414
  ] })
1186
1415
  ] })
1187
1416
  ] }),
1188
- /* @__PURE__ */ jsxs2("div", { style: { flexShrink: 0, textAlign: "center", padding: "8px 16px", fontSize: 11, color: "#9ca3af" }, children: [
1189
- "Kit v",
1190
- /* @__PURE__ */ jsx3("span", { style: { fontWeight: 500, color: "#6b7280" }, children: "3.0.3" })
1191
- ] })
1417
+ /* @__PURE__ */ jsx3("div", { style: { flexShrink: 0, padding: "8px 16px", fontSize: 11, color: "#9ca3af", borderTop: "1px solid #f3f4f6" }, children: showPasswordInput ? /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 6, alignItems: "center", justifyContent: "center", padding: "4px 0" }, children: [
1418
+ /* @__PURE__ */ jsx3(
1419
+ "input",
1420
+ {
1421
+ type: "password",
1422
+ placeholder: "Admin password",
1423
+ value: passwordInput,
1424
+ onChange: (e) => {
1425
+ setPasswordInput(e.target.value);
1426
+ setAuthError("");
1427
+ },
1428
+ onKeyDown: (e) => {
1429
+ if (e.key === "Enter") handleAdminLogin(passwordInput);
1430
+ if (e.key === "Escape") {
1431
+ setShowPasswordInput(false);
1432
+ setPasswordInput("");
1433
+ setAuthError("");
1434
+ }
1435
+ },
1436
+ autoFocus: true,
1437
+ style: {
1438
+ fontSize: 11,
1439
+ padding: "3px 8px",
1440
+ borderRadius: 6,
1441
+ border: `1px solid ${authError ? "#ef4444" : "#e5e7eb"}`,
1442
+ outline: "none",
1443
+ fontFamily: "inherit",
1444
+ width: 110
1445
+ }
1446
+ }
1447
+ ),
1448
+ /* @__PURE__ */ jsx3(
1449
+ "button",
1450
+ {
1451
+ onClick: () => handleAdminLogin(passwordInput),
1452
+ style: {
1453
+ fontSize: 10,
1454
+ fontWeight: 500,
1455
+ padding: "3px 8px",
1456
+ borderRadius: 6,
1457
+ border: "none",
1458
+ backgroundColor: "#1f2937",
1459
+ color: "#fff",
1460
+ cursor: "pointer",
1461
+ fontFamily: "inherit"
1462
+ },
1463
+ children: "OK"
1464
+ }
1465
+ ),
1466
+ /* @__PURE__ */ jsx3(
1467
+ "button",
1468
+ {
1469
+ onClick: () => {
1470
+ setShowPasswordInput(false);
1471
+ setPasswordInput("");
1472
+ setAuthError("");
1473
+ },
1474
+ style: { fontSize: 10, color: "#9ca3af", background: "none", border: "none", cursor: "pointer", fontFamily: "inherit" },
1475
+ children: "Cancel"
1476
+ }
1477
+ ),
1478
+ authError && /* @__PURE__ */ jsx3("span", { style: { fontSize: 10, color: "#ef4444" }, children: authError })
1479
+ ] }) : /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1480
+ config.personaEndpoint ? /* @__PURE__ */ jsx3(
1481
+ "button",
1482
+ {
1483
+ onClick: handleAdminToggle,
1484
+ style: {
1485
+ display: "flex",
1486
+ alignItems: "center",
1487
+ gap: 4,
1488
+ fontSize: 11,
1489
+ color: isAdmin ? colors.primary : "#9ca3af",
1490
+ background: "none",
1491
+ border: "none",
1492
+ cursor: "pointer",
1493
+ fontFamily: "inherit",
1494
+ padding: 0,
1495
+ transition: "color 0.15s"
1496
+ },
1497
+ children: isAdmin ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
1498
+ /* @__PURE__ */ jsx3(Unlock, { style: { width: 12, height: 12 } }),
1499
+ " Admin mode"
1500
+ ] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1501
+ /* @__PURE__ */ jsx3(Lock, { style: { width: 12, height: 12 } }),
1502
+ " Admin mode"
1503
+ ] })
1504
+ }
1505
+ ) : /* @__PURE__ */ jsx3("span", {}),
1506
+ /* @__PURE__ */ jsxs2("span", { children: [
1507
+ "Kit v",
1508
+ /* @__PURE__ */ jsx3("span", { style: { fontWeight: 500, color: "#6b7280" }, children: "5.0.1" })
1509
+ ] })
1510
+ ] }) })
1192
1511
  ]
1193
1512
  }
1194
1513
  );
@@ -1278,4 +1597,4 @@ export {
1278
1597
  SettingsSection,
1279
1598
  Divider
1280
1599
  };
1281
- //# sourceMappingURL=chunk-MUM5DNV4.js.map
1600
+ //# sourceMappingURL=chunk-K3JJWWRQ.js.map