@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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/VoiceSettingsView.tsx","../src/contexts/VoiceSettingsContext.tsx","../src/components/PersonaSettings.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\nimport {\n ArrowLeft,\n RotateCcw,\n Volume2,\n Gauge,\n AudioLines,\n Sparkles,\n Mic,\n Timer,\n MessageSquare,\n Activity,\n Cpu,\n EyeOff,\n Info,\n Ear,\n Clock,\n Zap,\n Minimize2,\n Signal,\n History,\n ChevronDown,\n User,\n MessageCircle,\n Headphones,\n SlidersHorizontal,\n Wrench,\n Globe,\n} from 'lucide-react';\nimport { useVoiceSettings } from '../contexts/VoiceSettingsContext';\nimport { VAD, useSiteConfig, usePersonaContext } from '@unctad-ai/voice-agent-core';\nimport { PersonaSettings } from './PersonaSettings.js';\nimport { Lock, Unlock } from 'lucide-react';\n\ninterface VoiceSettingsViewProps {\n onBack: () => void;\n onVolumeChange?: (v: number) => void;\n}\n\nfunction expressivenessLabel(v: number): string {\n if (v <= 0.15) return 'Low';\n if (v <= 0.4) return 'Medium';\n return 'High';\n}\n\nfunction speechThresholdLabel(v: number): string {\n if (v >= 0.75) return 'Strict';\n if (v >= 0.5) return 'Balanced';\n return 'Sensitive';\n}\n\nfunction bargeInLabel(v: number): string {\n if (v >= 0.8) return 'Hard';\n if (v >= 0.6) return 'Normal';\n return 'Easy';\n}\n\nconst LANGUAGE_OPTIONS = [\n { value: 'en', label: 'English' },\n { value: 'fr', label: 'French' },\n { value: 'es', label: 'Spanish' },\n { value: 'sw', label: 'Swahili' },\n { value: 'pt', label: 'Portuguese' },\n { value: 'ar', label: 'Arabic' },\n { value: 'zh', label: 'Chinese' },\n { value: 'hi', label: 'Hindi' },\n { value: 'dz', label: 'Dzongkha' },\n];\n\n/** Inline style tag for custom range slider — injected once */\nlet sliderStylesInjected = false;\nfunction ensureSliderStyles() {\n if (sliderStylesInjected || typeof document === 'undefined') return;\n sliderStylesInjected = true;\n const style = document.createElement('style');\n style.textContent = `\n input[type=\"range\"].voice-slider {\n -webkit-appearance: none;\n appearance: none;\n width: 100%;\n height: 4px;\n border-radius: 2px;\n background: #e5e7eb;\n outline: none;\n cursor: pointer;\n margin: 6px 0;\n }\n input[type=\"range\"].voice-slider::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--voice-settings-accent, #DB2129);\n border: 2px solid #fff;\n box-shadow: 0 1px 3px rgba(0,0,0,0.15);\n cursor: pointer;\n transition: transform 0.1s ease;\n margin-top: -6px;\n }\n input[type=\"range\"].voice-slider::-webkit-slider-thumb:hover {\n transform: scale(1.15);\n }\n input[type=\"range\"].voice-slider::-moz-range-thumb {\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--voice-settings-accent, #DB2129);\n border: 2px solid #fff;\n box-shadow: 0 1px 3px rgba(0,0,0,0.15);\n cursor: pointer;\n }\n input[type=\"range\"].voice-slider::-webkit-slider-runnable-track {\n height: 4px;\n border-radius: 2px;\n display: flex;\n align-items: center;\n }\n input[type=\"range\"].voice-slider::-moz-range-track {\n height: 4px;\n border-radius: 2px;\n background: #e5e7eb;\n }\n `;\n document.head.appendChild(style);\n}\n\n/** Slider setting row */\nexport function SliderSetting({\n icon,\n label,\n value,\n displayValue,\n min,\n max,\n step,\n onChange,\n}: {\n icon: React.ReactNode;\n label: string;\n value: number;\n displayValue: string;\n min: number;\n max: number;\n step: number;\n onChange: (v: number) => void;\n}) {\n ensureSliderStyles();\n const pct = ((value - min) / (max - min)) * 100;\n\n return (\n <div style={{ paddingTop: 12, paddingBottom: 12, display: 'flex', flexDirection: 'column', gap: 10 }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>\n {icon}\n <span style={{ flex: 1, fontSize: 13, fontWeight: 500, color: '#111827' }}>{label}</span>\n <span style={{ fontSize: 11, fontWeight: 500, fontVariantNumeric: 'tabular-nums', color: 'var(--voice-settings-accent, #DB2129)' }}>{displayValue}</span>\n </div>\n <input\n type=\"range\"\n className=\"voice-slider\"\n value={value}\n min={min}\n max={max}\n step={step}\n onChange={(e) => onChange(Number(e.target.value))}\n style={{\n background: `linear-gradient(to right, var(--voice-settings-accent, #DB2129) 0%, var(--voice-settings-accent, #DB2129) ${pct}%, #e5e7eb ${pct}%, #e5e7eb 100%)`,\n }}\n />\n </div>\n );\n}\n\n/** Toggle setting row */\nexport function ToggleSetting({\n icon,\n label,\n description,\n checked,\n onChange,\n}: {\n icon: React.ReactNode;\n label: string;\n description: string;\n checked: boolean;\n onChange: (v: boolean) => void;\n}) {\n return (\n <label style={{ paddingTop: 12, paddingBottom: 12, display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer' }}>\n {icon}\n <div style={{ flex: 1, minWidth: 0 }}>\n <div style={{ fontSize: 13, fontWeight: 500, color: '#111827' }}>{label}</div>\n <div style={{ fontSize: 11, color: '#6b7280' }}>{description}</div>\n </div>\n <button\n role=\"switch\"\n aria-checked={checked}\n onClick={() => onChange(!checked)}\n style={{\n position: 'relative',\n display: 'inline-flex',\n alignItems: 'center',\n width: 44,\n height: 24,\n borderRadius: 9999,\n backgroundColor: checked ? 'var(--voice-settings-accent, #DB2129)' : '#d1d5db',\n border: 'none',\n padding: 0,\n cursor: 'pointer',\n transition: 'background-color 0.2s',\n flexShrink: 0,\n }}\n >\n <span\n style={{\n display: 'inline-block',\n width: 20,\n height: 20,\n borderRadius: 9999,\n backgroundColor: '#fff',\n boxShadow: '0 1px 3px rgba(0,0,0,0.2)',\n transition: 'transform 0.2s',\n transform: checked ? 'translateX(22px)' : 'translateX(2px)',\n }}\n />\n </button>\n </label>\n );\n}\n\n/** Select setting row */\nexport function SelectSetting({\n icon,\n label,\n value,\n onChange,\n options,\n}: {\n icon: React.ReactNode;\n label: string;\n value: string;\n onChange: (v: string) => void;\n options: { value: string; label: string }[];\n}) {\n return (\n <div style={{ paddingTop: 12, paddingBottom: 12, display: 'flex', alignItems: 'center', gap: 12 }}>\n {icon}\n <span style={{ flex: 1, fontSize: 13, fontWeight: 500, color: '#111827', minWidth: 0 }}>{label}</span>\n <select\n value={value}\n onChange={(e) => onChange(e.target.value)}\n onMouseEnter={(e) => {\n e.currentTarget.style.borderColor = '#d1d5db';\n e.currentTarget.style.backgroundColor = '#f3f4f6';\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.borderColor = '#e5e7eb';\n e.currentTarget.style.backgroundColor = '#f9fafb';\n }}\n onFocus={(e) => { e.currentTarget.style.borderColor = '#9ca3af'; }}\n onBlur={(e) => { e.currentTarget.style.borderColor = '#e5e7eb'; }}\n style={{\n height: 34,\n lineHeight: '34px',\n fontSize: 12,\n fontWeight: 500,\n color: '#374151',\n borderRadius: 9999,\n border: '1px solid #e5e7eb',\n backgroundColor: '#f9fafb',\n paddingTop: 0,\n paddingBottom: 0,\n paddingLeft: 12,\n paddingRight: 28,\n outline: 'none',\n WebkitAppearance: 'none',\n appearance: 'none',\n cursor: 'pointer',\n flexShrink: 0,\n width: 110,\n transition: 'border-color 0.15s, background-color 0.15s',\n 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\")`,\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'right 8px center',\n }}\n >\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n );\n}\n\nexport default function VoiceSettingsView({ onBack, onVolumeChange }: VoiceSettingsViewProps) {\n const { settings, updateSetting, resetSettings } = useVoiceSettings();\n const config = useSiteConfig();\n const persona = usePersonaContext();\n const { colors } = config;\n const [openSection, setOpenSection] = useState<string | null>(null);\n const iconStyle = { width: 16, height: 16, flexShrink: 0, color: colors.primary };\n const sectionIconStyle = { width: 16, height: 16, flexShrink: 0, color: colors.primary };\n const sectionProps = (id: string) => ({\n open: openSection === id,\n onToggle: () => setOpenSection(openSection === id ? null : id),\n });\n\n // Admin auth state — lifted here so the toggle lives in the footer\n const [adminPassword, setAdminPassword] = useState<string | null>(\n () => sessionStorage.getItem('voice-admin-pw')\n );\n const [showPasswordInput, setShowPasswordInput] = useState(false);\n const [passwordInput, setPasswordInput] = useState('');\n const [authError, setAuthError] = useState('');\n const isAdmin = adminPassword !== null;\n\n const handleAdminLogin = useCallback(async (pw: string) => {\n if (!persona) return;\n try {\n await persona.updateConfig({}, pw);\n sessionStorage.setItem('voice-admin-pw', pw);\n setAdminPassword(pw);\n setAuthError('');\n setShowPasswordInput(false);\n setPasswordInput('');\n } catch {\n setAuthError('Invalid password');\n }\n }, [persona]);\n\n const handleAdminToggle = useCallback(() => {\n if (isAdmin) {\n sessionStorage.removeItem('voice-admin-pw');\n setAdminPassword(null);\n setShowPasswordInput(false);\n setPasswordInput('');\n } else {\n setShowPasswordInput(true);\n }\n }, [isAdmin]);\n\n return (\n <motion.div\n initial={{ opacity: 0, x: 20 }}\n animate={{ opacity: 1, x: 0 }}\n exit={{ opacity: 0, x: 20 }}\n transition={{ duration: 0.2, ease: [0.22, 1, 0.36, 1] }}\n style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n flexDirection: 'column',\n zIndex: 10,\n borderRadius: 'inherit',\n backgroundColor: '#f9fafb',\n fontFamily: 'inherit',\n '--voice-settings-accent': colors.primary,\n } as React.CSSProperties}\n >\n {/* Header */}\n <div\n style={{ display: 'flex', alignItems: 'center', gap: 10, flexShrink: 0, paddingLeft: 16, paddingRight: 16, height: 56, borderBottom: '1px solid #e5e7eb' }}\n >\n <button\n onClick={onBack}\n style={{ width: 32, height: 32, borderRadius: 9999, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', transition: 'color 0.15s', color: '#6b7280', background: 'none', border: 'none' }}\n onMouseEnter={(e) => {\n (e.currentTarget as HTMLElement).style.color = '#111827';\n }}\n onMouseLeave={(e) => {\n (e.currentTarget as HTMLElement).style.color = '#6b7280';\n }}\n aria-label=\"Back to conversation\"\n >\n <ArrowLeft style={{ width: 16, height: 16 }} />\n </button>\n <span style={{ flex: 1, fontSize: 13, fontWeight: 600, color: '#111827' }}>\n Settings\n </span>\n <button\n onClick={resetSettings}\n style={{ width: 32, height: 32, borderRadius: 9999, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', transition: 'color 0.15s', color: '#9ca3af', background: 'none', border: 'none' }}\n onMouseEnter={(e) => {\n (e.currentTarget as HTMLElement).style.color = colors.primary;\n }}\n onMouseLeave={(e) => {\n (e.currentTarget as HTMLElement).style.color = '#9ca3af';\n }}\n aria-label=\"Reset all settings\"\n title=\"Reset to defaults\"\n >\n <RotateCcw style={{ width: 14, height: 14 }} />\n </button>\n </div>\n\n {/* Scrollable content */}\n <div style={{ flex: 1, minHeight: 0, overflowY: 'auto' }}>\n {/* Agent Persona */}\n {config.personaEndpoint && (\n <SettingsSection title=\"Persona\" icon={<User style={sectionIconStyle} />} {...sectionProps('persona')}>\n <PersonaSettings adminPassword={adminPassword} />\n </SettingsSection>\n )}\n\n {/* Conversation */}\n <SettingsSection title=\"Conversation\" icon={<MessageCircle style={sectionIconStyle} />} {...sectionProps('conversation')}>\n <SelectSetting\n icon={<MessageSquare style={iconStyle} />}\n label=\"Response length\"\n value={String(settings.responseLength)}\n onChange={(v) => updateSetting('responseLength', Number(v))}\n options={[\n { value: '30', label: 'Brief' },\n { value: '60', label: 'Normal' },\n { value: '100', label: 'Detailed' },\n ]}\n />\n <Divider />\n <SelectSetting\n icon={<History style={iconStyle} />}\n label=\"Chat memory\"\n value={String(settings.maxHistoryMessages)}\n onChange={(v) => updateSetting('maxHistoryMessages', Number(v))}\n options={[\n { value: '10', label: '10 msgs' },\n { value: '20', label: '20 msgs' },\n { value: '30', label: '30 msgs' },\n { value: '40', label: '40 msgs' },\n ]}\n />\n </SettingsSection>\n\n {/* Listening */}\n <SettingsSection title=\"Listening\" icon={<Headphones style={sectionIconStyle} />} {...sectionProps('listening')}>\n <SelectSetting\n icon={<Globe style={iconStyle} />}\n label=\"Language\"\n value={settings.language}\n onChange={(v) => updateSetting('language', v)}\n options={LANGUAGE_OPTIONS}\n />\n <Divider />\n <ToggleSetting\n icon={<Mic style={iconStyle} />}\n label=\"Auto-listen\"\n description=\"Start mic when panel opens\"\n checked={settings.autoListen}\n onChange={(v) => updateSetting('autoListen', v)}\n />\n <Divider />\n <SliderSetting\n icon={<Ear style={iconStyle} />}\n label=\"Speech threshold\"\n value={settings.speechThreshold * 100}\n displayValue={speechThresholdLabel(settings.speechThreshold)}\n min={30}\n max={90}\n step={5}\n onChange={(v) => updateSetting('speechThreshold', v / 100)}\n />\n <Divider />\n <SelectSetting\n icon={<Clock style={iconStyle} />}\n label=\"Pause tolerance\"\n value={String(settings.pauseToleranceMs)}\n onChange={(v) => updateSetting('pauseToleranceMs', Number(v))}\n options={[\n { value: '400', label: 'Fast' },\n { value: '600', label: 'Default' },\n { value: '800', label: 'Relaxed' },\n { value: '1000', label: 'Patient' },\n ]}\n />\n <Divider />\n <SliderSetting\n icon={<Zap style={iconStyle} />}\n label=\"Barge-in threshold\"\n value={settings.bargeInThreshold * 100}\n displayValue={bargeInLabel(settings.bargeInThreshold)}\n min={40}\n max={90}\n step={5}\n onChange={(v) => updateSetting('bargeInThreshold', v / 100)}\n />\n </SettingsSection>\n\n {/* Speaking */}\n <SettingsSection title=\"Speaking\" icon={<Volume2 style={sectionIconStyle} />} {...sectionProps('speaking')}>\n <ToggleSetting\n icon={<AudioLines style={iconStyle} />}\n label=\"Text-to-speech\"\n description=\"Speak responses aloud\"\n checked={settings.ttsEnabled}\n onChange={(v) => updateSetting('ttsEnabled', v)}\n />\n <Divider />\n <SliderSetting\n icon={<Volume2 style={iconStyle} />}\n label=\"Volume\"\n value={settings.volume * 100}\n displayValue={`${Math.round(settings.volume * 100)}%`}\n min={0}\n max={100}\n step={1}\n onChange={(v) => {\n updateSetting('volume', v / 100);\n onVolumeChange?.(v / 100);\n }}\n />\n <Divider />\n <SliderSetting\n icon={<Gauge style={iconStyle} />}\n label=\"Speed\"\n value={settings.playbackSpeed * 100}\n displayValue={`${settings.playbackSpeed.toFixed(2)}x`}\n min={75}\n max={150}\n step={5}\n onChange={(v) => updateSetting('playbackSpeed', v / 100)}\n />\n <Divider />\n <SliderSetting\n icon={<Sparkles style={iconStyle} />}\n label=\"Expressiveness\"\n value={settings.expressiveness * 100}\n displayValue={expressivenessLabel(settings.expressiveness)}\n min={10}\n max={60}\n step={5}\n onChange={(v) => updateSetting('expressiveness', v / 100)}\n />\n </SettingsSection>\n\n {/* Behavior */}\n <SettingsSection title=\"Behavior\" icon={<SlidersHorizontal style={sectionIconStyle} />} {...sectionProps('behavior')}>\n <SelectSetting\n icon={<Timer style={iconStyle} />}\n label=\"Idle timeout\"\n value={String(settings.idleTimeoutMs)}\n onChange={(v) => updateSetting('idleTimeoutMs', Number(v))}\n options={[\n { value: '30000', label: '30s' },\n { value: '60000', label: '1 min' },\n { value: '120000', label: '2 min' },\n { value: '300000', label: '5 min' },\n ]}\n />\n <Divider />\n <SelectSetting\n icon={<Minimize2 style={iconStyle} />}\n label=\"Auto-collapse\"\n value={String(settings.panelCollapseTimeoutMs)}\n onChange={(v) => updateSetting('panelCollapseTimeoutMs', Number(v))}\n options={[\n { value: '120000', label: '2 min' },\n { value: '300000', label: '5 min' },\n { value: '600000', label: '10 min' },\n { value: '0', label: 'Never' },\n ]}\n />\n </SettingsSection>\n\n {/* Developer */}\n <SettingsSection title=\"Developer\" icon={<Wrench style={sectionIconStyle} />} {...sectionProps('developer')} last>\n <ToggleSetting\n icon={<Activity style={iconStyle} />}\n label=\"Pipeline metrics\"\n description=\"Show STT / LLM / TTS timings\"\n checked={settings.showPipelineMetrics}\n onChange={(v) => updateSetting('showPipelineMetrics', v)}\n />\n <Divider />\n <SelectSetting\n icon={<EyeOff style={iconStyle} />}\n label=\"Auto-hide metrics\"\n value={String(settings.pipelineMetricsAutoHideMs)}\n onChange={(v) => updateSetting('pipelineMetricsAutoHideMs', Number(v))}\n options={[\n { value: '5000', label: '5s' },\n { value: '8000', label: '8s' },\n { value: '15000', label: '15s' },\n { value: '0', label: 'Never' },\n ]}\n />\n <Divider />\n <SelectSetting\n icon={<Mic style={iconStyle} />}\n label=\"STT timeout\"\n value={String(settings.sttTimeoutMs)}\n onChange={(v) => updateSetting('sttTimeoutMs', Number(v))}\n options={[\n { value: '10000', label: '10s' },\n { value: '15000', label: '15s' },\n { value: '30000', label: '30s' },\n ]}\n />\n <Divider />\n <SelectSetting\n icon={<AudioLines style={iconStyle} />}\n label=\"TTS timeout\"\n value={String(settings.ttsTimeoutMs)}\n onChange={(v) => updateSetting('ttsTimeoutMs', Number(v))}\n options={[\n { value: '30000', label: '30s' },\n { value: '55000', label: '55s' },\n { value: '90000', label: '90s' },\n ]}\n />\n <Divider />\n <SelectSetting\n icon={<Cpu style={iconStyle} />}\n label=\"LLM timeout\"\n value={String(settings.llmTimeoutMs)}\n onChange={(v) => updateSetting('llmTimeoutMs', Number(v))}\n options={[\n { value: '10000', label: '10s' },\n { value: '20000', label: '20s' },\n { value: '30000', label: '30s' },\n { value: '60000', label: '60s' },\n ]}\n />\n <Divider />\n <SelectSetting\n icon={<Signal style={iconStyle} />}\n label=\"Min audio level\"\n value={String(settings.minAudioRms)}\n onChange={(v) => updateSetting('minAudioRms', Number(v))}\n options={[\n { value: '0.01', label: 'Sensitive' },\n { value: '0.02', label: 'Default' },\n { value: '0.035', label: 'Moderate' },\n { value: '0.05', label: 'Strict' },\n ]}\n />\n <Divider />\n <div style={{ paddingTop: 8, paddingBottom: 4, fontSize: 11, color: '#9ca3af' }}>\n VAD Threshold: <span style={{ fontWeight: 500, color: '#6b7280' }}>{VAD.positiveSpeechThreshold}</span>\n </div>\n </SettingsSection>\n </div>\n\n {/* Footer — admin toggle + kit version */}\n <div style={{ flexShrink: 0, padding: '8px 16px', fontSize: 11, color: '#9ca3af', borderTop: '1px solid #f3f4f6' }}>\n {showPasswordInput ? (\n <div style={{ display: 'flex', gap: 6, alignItems: 'center', justifyContent: 'center', padding: '4px 0' }}>\n <input\n type=\"password\"\n placeholder=\"Admin password\"\n value={passwordInput}\n onChange={e => { setPasswordInput(e.target.value); setAuthError(''); }}\n onKeyDown={e => {\n if (e.key === 'Enter') handleAdminLogin(passwordInput);\n if (e.key === 'Escape') { setShowPasswordInput(false); setPasswordInput(''); setAuthError(''); }\n }}\n autoFocus\n style={{\n fontSize: 11, padding: '3px 8px', borderRadius: 6,\n border: `1px solid ${authError ? '#ef4444' : '#e5e7eb'}`,\n outline: 'none', fontFamily: 'inherit', width: 110,\n }}\n />\n <button\n onClick={() => handleAdminLogin(passwordInput)}\n style={{\n fontSize: 10, fontWeight: 500, padding: '3px 8px', borderRadius: 6,\n border: 'none', backgroundColor: '#1f2937', color: '#fff',\n cursor: 'pointer', fontFamily: 'inherit',\n }}\n >OK</button>\n <button\n onClick={() => { setShowPasswordInput(false); setPasswordInput(''); setAuthError(''); }}\n style={{ fontSize: 10, color: '#9ca3af', background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit' }}\n >Cancel</button>\n {authError && <span style={{ fontSize: 10, color: '#ef4444' }}>{authError}</span>}\n </div>\n ) : (\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n {config.personaEndpoint ? (\n <button\n onClick={handleAdminToggle}\n style={{\n display: 'flex', alignItems: 'center', gap: 4,\n fontSize: 11, color: isAdmin ? colors.primary : '#9ca3af',\n background: 'none', border: 'none', cursor: 'pointer',\n fontFamily: 'inherit', padding: 0, transition: 'color 0.15s',\n }}\n >\n {isAdmin\n ? <><Unlock style={{ width: 12, height: 12 }} /> Admin mode</>\n : <><Lock style={{ width: 12, height: 12 }} /> Admin mode</>\n }\n </button>\n ) : <span />}\n <span>Kit v<span style={{ fontWeight: 500, color: '#6b7280' }}>{__KIT_VERSION__}</span></span>\n </div>\n )}\n </div>\n </motion.div>\n );\n}\n\nexport function SettingsSection({\n title,\n icon,\n children,\n last,\n open = false,\n onToggle,\n}: {\n title: string;\n icon?: React.ReactNode;\n children: React.ReactNode;\n last?: boolean;\n open?: boolean;\n onToggle?: () => void;\n}) {\n const [hovered, setHovered] = useState(false);\n\n return (\n <div style={{ borderBottom: last ? 'none' : '1px solid #e5e7eb' }}>\n <button\n onClick={onToggle}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={{\n width: '100%',\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n paddingLeft: 16,\n paddingRight: 16,\n paddingTop: 14,\n paddingBottom: 14,\n backgroundColor: hovered ? '#edf0f3' : open ? '#f0f2f5' : 'transparent',\n border: 'none',\n cursor: 'pointer',\n fontFamily: 'inherit',\n transition: 'background-color 0.15s',\n }}\n >\n {icon}\n <span style={{\n flex: 1,\n textAlign: 'left',\n fontSize: 13,\n fontWeight: 600,\n color: '#374151',\n }}>{title}</span>\n <ChevronDown style={{\n width: 14,\n height: 14,\n color: '#9ca3af',\n transition: 'transform 0.2s ease',\n transform: open ? 'rotate(0deg)' : 'rotate(-90deg)',\n }} />\n </button>\n <AnimatePresence initial={false}>\n {open && (\n <motion.div\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: 'auto', opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: 0.2, ease: [0.22, 1, 0.36, 1] }}\n style={{ overflow: 'hidden' }}\n >\n <div style={{\n paddingLeft: 16,\n paddingRight: 16,\n paddingTop: 4,\n paddingBottom: 12,\n backgroundColor: '#fff',\n borderTop: '1px solid #e5e7eb',\n }}>\n {children}\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n );\n}\n\nexport function Divider() {\n return <div style={{ height: 1, backgroundColor: '#f3f4f6' }} />;\n}\n","import { createContext, useContext, useState, useRef, useCallback, type ReactNode } from 'react';\nimport {\n DEFAULT_VOLUME,\n DEFAULT_PLAYBACK_SPEED,\n DEFAULT_TTS_ENABLED,\n DEFAULT_AUTO_LISTEN,\n DEFAULT_IDLE_TIMEOUT_MS,\n DEFAULT_EXPRESSIVENESS,\n DEFAULT_RESPONSE_LENGTH,\n DEFAULT_SHOW_PIPELINE_METRICS,\n DEFAULT_PIPELINE_METRICS_AUTO_HIDE_MS,\n DEFAULT_SPEECH_THRESHOLD,\n DEFAULT_PAUSE_TOLERANCE_MS,\n DEFAULT_BARGE_IN_THRESHOLD,\n DEFAULT_PANEL_COLLAPSE_TIMEOUT_MS,\n DEFAULT_STT_TIMEOUT_MS,\n DEFAULT_TTS_TIMEOUT_MS,\n DEFAULT_LLM_TIMEOUT_MS,\n DEFAULT_MIN_AUDIO_RMS,\n DEFAULT_MAX_HISTORY_MESSAGES,\n DEFAULT_LANGUAGE,\n} from '@unctad-ai/voice-agent-core';\n\nexport interface VoiceSettings {\n volume: number;\n playbackSpeed: number;\n ttsEnabled: boolean;\n autoListen: boolean;\n idleTimeoutMs: number;\n expressiveness: number;\n responseLength: number;\n showPipelineMetrics: boolean;\n pipelineMetricsAutoHideMs: number;\n speechThreshold: number;\n pauseToleranceMs: number;\n bargeInThreshold: number;\n panelCollapseTimeoutMs: number;\n sttTimeoutMs: number;\n ttsTimeoutMs: number;\n llmTimeoutMs: number;\n minAudioRms: number;\n maxHistoryMessages: number;\n language: string;\n}\n\nconst DEFAULTS: VoiceSettings = {\n volume: DEFAULT_VOLUME,\n playbackSpeed: DEFAULT_PLAYBACK_SPEED,\n ttsEnabled: DEFAULT_TTS_ENABLED,\n autoListen: DEFAULT_AUTO_LISTEN,\n idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,\n expressiveness: DEFAULT_EXPRESSIVENESS,\n responseLength: DEFAULT_RESPONSE_LENGTH,\n showPipelineMetrics: DEFAULT_SHOW_PIPELINE_METRICS,\n pipelineMetricsAutoHideMs: DEFAULT_PIPELINE_METRICS_AUTO_HIDE_MS,\n speechThreshold: DEFAULT_SPEECH_THRESHOLD,\n pauseToleranceMs: DEFAULT_PAUSE_TOLERANCE_MS,\n bargeInThreshold: DEFAULT_BARGE_IN_THRESHOLD,\n panelCollapseTimeoutMs: DEFAULT_PANEL_COLLAPSE_TIMEOUT_MS,\n sttTimeoutMs: DEFAULT_STT_TIMEOUT_MS,\n ttsTimeoutMs: DEFAULT_TTS_TIMEOUT_MS,\n llmTimeoutMs: DEFAULT_LLM_TIMEOUT_MS,\n minAudioRms: DEFAULT_MIN_AUDIO_RMS,\n maxHistoryMessages: DEFAULT_MAX_HISTORY_MESSAGES,\n language: DEFAULT_LANGUAGE,\n};\n\nconst STORAGE_KEY = 'voice-settings';\n\nfunction loadSettings(): VoiceSettings {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return { ...DEFAULTS };\n const parsed = JSON.parse(raw);\n // Merge with defaults to handle missing keys from older versions\n return { ...DEFAULTS, ...parsed };\n } catch {\n return { ...DEFAULTS };\n }\n}\n\nfunction persistSettings(settings: VoiceSettings) {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));\n } catch {\n // localStorage full or unavailable — silently skip\n }\n}\n\ninterface VoiceSettingsContextType {\n settings: VoiceSettings;\n volumeRef: React.RefObject<number>;\n speedRef: React.RefObject<number>;\n updateSetting: <K extends keyof VoiceSettings>(key: K, value: VoiceSettings[K]) => void;\n resetSettings: () => void;\n}\n\nconst VoiceSettingsContext = createContext<VoiceSettingsContextType | undefined>(undefined);\n\ninterface VoiceSettingsProviderProps {\n children: ReactNode;\n siteLanguage?: string;\n}\n\nexport function VoiceSettingsProvider({ children, siteLanguage }: VoiceSettingsProviderProps) {\n const [settings, setSettings] = useState<VoiceSettings>(() => {\n const loaded = loadSettings();\n if (siteLanguage) {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n const hasPersisted = raw ? Object.prototype.hasOwnProperty.call(JSON.parse(raw), 'language') : false;\n if (!hasPersisted) loaded.language = siteLanguage;\n } catch {\n loaded.language = siteLanguage;\n }\n }\n return loaded;\n });\n\n // Refs for hot-path audio — avoids re-renders on volume/speed drag\n const volumeRef = useRef(settings.volume);\n const speedRef = useRef(settings.playbackSpeed);\n const siteLanguageRef = useRef(siteLanguage);\n siteLanguageRef.current = siteLanguage;\n\n const updateSetting = useCallback(\n <K extends keyof VoiceSettings>(key: K, value: VoiceSettings[K]) => {\n setSettings((prev) => {\n const next = { ...prev, [key]: value };\n persistSettings(next);\n // Keep refs in sync\n if (key === 'volume') volumeRef.current = value as number;\n if (key === 'playbackSpeed') speedRef.current = value as number;\n return next;\n });\n },\n []\n );\n\n const resetSettings = useCallback(() => {\n const defaults = { ...DEFAULTS, language: siteLanguageRef.current ?? DEFAULT_LANGUAGE };\n setSettings(defaults);\n persistSettings(defaults);\n volumeRef.current = defaults.volume;\n speedRef.current = defaults.playbackSpeed;\n }, []);\n\n return (\n <VoiceSettingsContext.Provider\n value={{ settings, volumeRef, speedRef, updateSetting, resetSettings }}\n >\n {children}\n </VoiceSettingsContext.Provider>\n );\n}\n\n/**\n * Access voice settings. Returns defaults if Provider is absent\n * (needed for error boundary paths where the tree may be partial).\n */\nexport function useVoiceSettings(): VoiceSettingsContextType {\n const context = useContext(VoiceSettingsContext);\n if (context) return context;\n\n // Fallback — return static defaults (no persistence, no-op updates)\n return {\n settings: DEFAULTS,\n volumeRef: { current: DEFAULTS.volume },\n speedRef: { current: DEFAULTS.playbackSpeed },\n updateSetting: () => {},\n resetSettings: () => {},\n };\n}\n","import { useState, useRef, useEffect, useCallback } from 'react';\nimport { usePersonaContext, useSiteConfig } from '@unctad-ai/voice-agent-core';\n\nconst LANGUAGE_OPTIONS = [\n { value: 'en', label: 'English' },\n { value: 'fr', label: 'French' },\n { value: 'es', label: 'Spanish' },\n { value: 'sw', label: 'Swahili' },\n { value: 'pt', label: 'Portuguese' },\n { value: 'ar', label: 'Arabic' },\n { value: 'zh', label: 'Chinese' },\n { value: 'hi', label: 'Hindi' },\n { value: 'dz', label: 'Dzongkha' },\n];\n\nexport function PersonaSettings({ adminPassword }: { adminPassword?: string | null }) {\n const config = useSiteConfig();\n if (!config.personaEndpoint) return null;\n\n return <PersonaSettingsInner adminPassword={adminPassword ?? null} />;\n}\n\nfunction PersonaSettingsInner({ adminPassword }: { adminPassword: string | null }) {\n const config = useSiteConfig();\n const persona = usePersonaContext();\n if (!persona) return null;\n const { persona: data, isLoaded, uploadAvatar, uploadVoice, deleteVoice, setActiveVoice, previewVoice, updateConfig } = persona;\n\n const isAdmin = adminPassword !== null;\n\n const handleSharedSave = useCallback(async (fields: Record<string, string>) => {\n if (!adminPassword) return;\n try {\n await updateConfig(fields, adminPassword);\n } catch (err) {\n console.error('Settings save failed:', err);\n }\n }, [adminPassword, updateConfig]);\n\n if (!isLoaded) {\n return (\n <div style={{ padding: 16, fontSize: 13, color: '#9ca3af', fontFamily: 'inherit' }}>\n Loading persona settings...\n </div>\n );\n }\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 12, fontFamily: 'inherit' }}>\n {isAdmin ? (\n <>\n <AvatarSection avatarUrl={config.avatarUrl} name={config.copilotName} onUpload={(f) => uploadAvatar(f, adminPassword)} />\n <NameSection name={config.copilotName} onSave={(n) => updateConfig({ copilotName: n }, adminPassword)} primaryColor={config.colors.primary} />\n <VoiceSection\n voices={data?.voices ?? []}\n activeVoiceId={data?.activeVoiceId ?? ''}\n onUpload={(f, n) => uploadVoice(f, n, adminPassword)}\n onDelete={(id) => deleteVoice(id, adminPassword)}\n onSelect={(id) => setActiveVoice(id, adminPassword)}\n onPreview={previewVoice}\n primaryColor={config.colors.primary}\n />\n\n {/* Shared settings */}\n <div style={{ borderTop: '1px solid #e5e7eb', paddingTop: 12, display: 'flex', flexDirection: 'column', gap: 10 }}>\n <span style={{ fontSize: 13, fontWeight: 500, color: '#111827' }}>Copilot settings</span>\n\n <ColorSettingRow\n label=\"Color\"\n value={data?.copilotColor || config.colors.primary || '#1B5E20'}\n onSave={v => handleSharedSave({ copilotColor: v })}\n />\n\n <TextSettingRow\n label=\"Site title\"\n value={data?.siteTitle || ''}\n onSave={v => handleSharedSave({ siteTitle: v })}\n />\n\n <TextAreaSettingRow\n label=\"Greeting\"\n value={data?.greetingMessage || ''}\n onSave={v => handleSharedSave({ greetingMessage: v })}\n />\n\n <TextAreaSettingRow\n label=\"Farewell\"\n value={data?.farewellMessage || ''}\n onSave={v => handleSharedSave({ farewellMessage: v })}\n />\n\n <TextAreaSettingRow\n label=\"System prompt intro\"\n value={data?.systemPromptIntro || ''}\n onSave={v => handleSharedSave({ systemPromptIntro: v })}\n rows={4}\n />\n\n <SettingRow label=\"Default language\">\n <select\n value={data?.language || 'en'}\n onChange={e => handleSharedSave({ language: e.target.value })}\n style={{\n fontSize: 12, borderRadius: 6, border: '1px solid #e5e7eb',\n padding: '4px 8px', outline: 'none', fontFamily: 'inherit',\n backgroundColor: '#fff',\n }}\n >\n {LANGUAGE_OPTIONS.map(l => (\n <option key={l.value} value={l.value}>{l.label}</option>\n ))}\n </select>\n </SettingRow>\n </div>\n\n </>\n ) : (\n <>\n {/* Read-only view */}\n <AvatarSection avatarUrl={config.avatarUrl} name={config.copilotName} disabled />\n <div style={{ fontSize: 13, color: '#6b7280', padding: '4px 0' }}>\n <span style={{ fontWeight: 500, color: '#111827' }}>Name:</span> {config.copilotName}\n </div>\n <VoiceSection\n voices={data?.voices ?? []}\n activeVoiceId={data?.activeVoiceId ?? ''}\n onUpload={(f, n) => uploadVoice(f, n)}\n onDelete={(id) => deleteVoice(id)}\n onSelect={setActiveVoice}\n onPreview={previewVoice}\n primaryColor={config.colors.primary}\n disabled\n />\n </>\n )}\n </div>\n );\n}\n\nfunction SettingRow({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n <span style={{ fontSize: 12, color: '#6b7280', minWidth: 90 }}>{label}</span>\n <div style={{ flex: 1 }}>{children}</div>\n </div>\n );\n}\n\nfunction ColorSettingRow({ label, value, onSave }: { label: string; value: string; onSave: (v: string) => void }) {\n const [local, setLocal] = useState(value);\n useEffect(() => { setLocal(value); }, [value]);\n\n return (\n <SettingRow label={label}>\n <input type=\"color\"\n value={local}\n onChange={e => setLocal(e.target.value)}\n onBlur={() => { if (local !== value) onSave(local); }}\n style={{ width: 32, height: 26, border: '1px solid #e5e7eb', borderRadius: 4, cursor: 'pointer', padding: 0 }}\n />\n </SettingRow>\n );\n}\n\nfunction TextSettingRow({ label, value, onSave }: { label: string; value: string; onSave: (v: string) => void }) {\n const [local, setLocal] = useState(value);\n useEffect(() => { setLocal(value); }, [value]);\n\n return (\n <SettingRow label={label}>\n <input\n type=\"text\"\n value={local}\n onChange={e => setLocal(e.target.value)}\n onBlur={() => { if (local !== value) onSave(local); }}\n style={{\n width: '100%', fontSize: 12, padding: '4px 8px', borderRadius: 6,\n border: '1px solid #e5e7eb', outline: 'none', fontFamily: 'inherit',\n boxSizing: 'border-box',\n }}\n />\n </SettingRow>\n );\n}\n\nfunction TextAreaSettingRow({ label, value, onSave, rows = 2 }: {\n label: string; value: string; onSave: (v: string) => void; rows?: number;\n}) {\n const [local, setLocal] = useState(value);\n useEffect(() => { setLocal(value); }, [value]);\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>\n <span style={{ fontSize: 12, color: '#6b7280' }}>{label}</span>\n <textarea\n value={local}\n onChange={e => setLocal(e.target.value)}\n onBlur={() => { if (local !== value) onSave(local); }}\n rows={rows}\n style={{\n width: '100%', fontSize: 12, padding: '6px 8px', borderRadius: 6,\n border: '1px solid #e5e7eb', outline: 'none', fontFamily: 'inherit',\n resize: 'vertical', boxSizing: 'border-box',\n }}\n />\n </div>\n );\n}\n\nfunction AvatarSection({ avatarUrl, name, onUpload, disabled }: {\n avatarUrl?: string;\n name: string;\n onUpload?: (file: File) => Promise<void>;\n disabled?: boolean;\n}) {\n const [uploading, setUploading] = useState(false);\n const [hovered, setHovered] = useState(false);\n const [imgError, setImgError] = useState(false);\n const [uploadError, setUploadError] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const initial = (name || '?')[0].toUpperCase();\n\n const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file || !onUpload) return;\n if (file.size > 5 * 1024 * 1024) {\n setUploadError('Image too large (max 5 MB)');\n return;\n }\n setUploading(true);\n setUploadError(null);\n try { await onUpload(file); setImgError(false); }\n catch (err) { setUploadError(err instanceof Error ? err.message : 'Upload failed'); }\n finally { setUploading(false); }\n };\n\n const showImage = avatarUrl && !imgError;\n const canEdit = !disabled && onUpload;\n\n return (\n <div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingTop: 4, paddingBottom: 4 }}>\n <div\n onClick={() => canEdit && !uploading && inputRef.current?.click()}\n onMouseEnter={() => canEdit && setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={{\n position: 'relative',\n width: 44,\n height: 44,\n borderRadius: 9999,\n overflow: 'hidden',\n backgroundColor: '#e5e7eb',\n flexShrink: 0,\n cursor: !canEdit ? 'default' : uploading ? 'wait' : 'pointer',\n }}\n >\n {showImage ? (\n <img\n src={avatarUrl}\n alt=\"\"\n onError={() => setImgError(true)}\n style={{ width: '100%', height: '100%', objectFit: 'cover' }}\n />\n ) : (\n <div style={{\n width: '100%',\n height: '100%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n color: '#6b7280',\n fontSize: 16,\n fontWeight: 600,\n }}>{initial}</div>\n )}\n {/* hover overlay */}\n {canEdit && hovered && !uploading && (\n <div style={{\n position: 'absolute',\n inset: 0,\n backgroundColor: 'rgba(0,0,0,0.35)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n color: '#fff',\n fontSize: 9,\n fontWeight: 600,\n letterSpacing: '0.02em',\n }}>Edit</div>\n )}\n </div>\n <div style={{ flex: 1, minWidth: 0 }}>\n <div style={{ fontSize: 13, fontWeight: 500, color: '#111827' }}>Avatar</div>\n <div style={{ fontSize: 11, color: uploadError ? '#DC2626' : '#6b7280' }}>\n {uploadError ?? (disabled ? '' : uploading ? 'Uploading...' : 'PNG, JPG or WebP (max 5 MB)')}\n </div>\n </div>\n {canEdit && <input ref={inputRef} type=\"file\" accept=\"image/png,image/jpeg,image/webp\" onChange={handleFile} style={{ display: 'none' }} />}\n </div>\n );\n}\n\nfunction NameSection({ name, onSave, primaryColor }: {\n name: string;\n onSave: (name: string) => Promise<void>;\n primaryColor: string;\n}) {\n const [value, setValue] = useState(name);\n const [saving, setSaving] = useState(false);\n const [inputFocused, setInputFocused] = useState(false);\n const [saveHovered, setSaveHovered] = useState(false);\n const dirty = value !== name;\n\n useEffect(() => { setValue(name); }, [name]);\n\n const handleSave = async () => {\n setSaving(true);\n try { await onSave(value); }\n catch (err) { console.error('Name update failed:', err); }\n finally { setSaving(false); }\n };\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 6, paddingTop: 4, paddingBottom: 4 }}>\n <span style={{ fontSize: 13, fontWeight: 500, color: '#111827' }}>Name</span>\n <div style={{ display: 'flex', gap: 8 }}>\n <input\n type=\"text\"\n value={value}\n onChange={e => setValue(e.target.value)}\n onFocus={() => setInputFocused(true)}\n onBlur={() => setInputFocused(false)}\n maxLength={30}\n style={{\n flex: 1,\n fontSize: 13,\n paddingLeft: 10,\n paddingRight: 10,\n paddingTop: 6,\n paddingBottom: 6,\n borderRadius: 8,\n border: `1px solid ${inputFocused ? '#9ca3af' : '#e5e7eb'}`,\n backgroundColor: '#fff',\n outline: 'none',\n fontFamily: 'inherit',\n transition: 'border-color 0.15s',\n }}\n />\n {dirty && (\n <button\n onClick={handleSave}\n disabled={saving}\n onMouseEnter={() => setSaveHovered(true)}\n onMouseLeave={() => setSaveHovered(false)}\n style={{\n fontSize: 12,\n fontWeight: 500,\n paddingLeft: 12,\n paddingRight: 12,\n paddingTop: 6,\n paddingBottom: 6,\n borderRadius: 8,\n border: 'none',\n backgroundColor: saveHovered ? primaryColor : '#1f2937',\n color: '#fff',\n cursor: saving ? 'default' : 'pointer',\n opacity: saving ? 0.5 : 1,\n transition: 'background-color 0.15s',\n fontFamily: 'inherit',\n }}\n >\n {saving ? 'Saving...' : 'Save'}\n </button>\n )}\n </div>\n </div>\n );\n}\n\nfunction VoiceSection({ voices, activeVoiceId, onUpload, onDelete, onSelect, onPreview, primaryColor, disabled }: {\n voices: { id: string; name: string }[];\n activeVoiceId: string;\n onUpload: (file: File, name: string) => Promise<any>;\n onDelete: (id: string) => Promise<void>;\n onSelect: (id: string) => Promise<void>;\n onPreview: (id: string, text: string) => Promise<ArrayBuffer>;\n primaryColor: string;\n disabled?: boolean;\n}) {\n const [uploading, setUploading] = useState(false);\n const [uploadName, setUploadName] = useState('');\n const [showUpload, setShowUpload] = useState(false);\n const [previewing, setPreviewing] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const fileRef = useRef<File | null>(null);\n\n const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file) return;\n fileRef.current = file;\n setUploadName(file.name.replace(/\\.wav$/i, ''));\n setShowUpload(true);\n };\n\n const handleUpload = async () => {\n if (!fileRef.current || !uploadName) return;\n setUploading(true);\n try {\n await onUpload(fileRef.current, uploadName);\n setShowUpload(false);\n setUploadName('');\n fileRef.current = null;\n } catch (err) {\n console.error('Voice upload failed:', err);\n } finally {\n setUploading(false);\n }\n };\n\n const handlePreview = async (voiceId: string) => {\n setPreviewing(voiceId);\n try {\n const buffer = await onPreview(voiceId, 'Hello, I am your AI assistant. How can I help you today?');\n const blob = new Blob([buffer], { type: 'audio/wav' });\n const url = URL.createObjectURL(blob);\n const audio = new Audio(url);\n audio.onended = () => { URL.revokeObjectURL(url); setPreviewing(null); };\n audio.play();\n } catch (err) {\n console.error('Preview failed:', err);\n setPreviewing(null);\n }\n };\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 8, paddingTop: 4, paddingBottom: 4 }}>\n <span style={{ fontSize: 13, fontWeight: 500, color: '#111827' }}>Voice</span>\n\n {voices.length === 0 && (\n <p style={{ fontSize: 11, color: '#9ca3af', margin: 0 }}>\n No voices configured. Upload a WAV sample to enable voice cloning.\n </p>\n )}\n\n {voices.map(v => (\n <VoiceRow\n key={v.id}\n voice={v}\n isActive={v.id === activeVoiceId}\n isPreviewing={previewing === v.id}\n onSelect={() => onSelect(v.id)}\n onPreview={() => handlePreview(v.id)}\n onDelete={disabled ? undefined : () => onDelete(v.id)}\n disabled={disabled}\n primaryColor={primaryColor}\n />\n ))}\n\n {!disabled && (\n showUpload ? (\n <UploadForm\n uploadName={uploadName}\n uploading={uploading}\n onNameChange={setUploadName}\n onUpload={handleUpload}\n onCancel={() => { setShowUpload(false); fileRef.current = null; }}\n primaryColor={primaryColor}\n />\n ) : (\n <UploadButton\n disabled={voices.length >= 10}\n onClick={() => inputRef.current?.click()}\n />\n )\n )}\n\n {!disabled && <input ref={inputRef} type=\"file\" accept=\"audio/wav\" onChange={handleFileSelect} style={{ display: 'none' }} />}\n </div>\n );\n}\n\nfunction VoiceRow({ voice, isActive, isPreviewing, onSelect, onPreview, onDelete, primaryColor, disabled }: {\n voice: { id: string; name: string };\n isActive: boolean;\n isPreviewing: boolean;\n onSelect: () => void;\n onPreview: () => void;\n onDelete?: () => void;\n primaryColor: string;\n disabled?: boolean;\n}) {\n const [previewHovered, setPreviewHovered] = useState(false);\n const [deleteHovered, setDeleteHovered] = useState(false);\n\n return (\n <div style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n paddingLeft: 10,\n paddingRight: 10,\n paddingTop: 8,\n paddingBottom: 8,\n borderRadius: 8,\n border: `1px solid ${isActive ? '#9ca3af' : '#e5e7eb'}`,\n backgroundColor: isActive ? '#f9fafb' : '#fff',\n fontSize: 13,\n }}>\n <input\n type=\"radio\"\n name=\"active-voice\"\n checked={isActive}\n onChange={onSelect}\n disabled={disabled}\n style={{ accentColor: primaryColor }}\n />\n <span style={{ flex: 1 }}>{voice.name}</span>\n <button\n onClick={onPreview}\n disabled={isPreviewing}\n onMouseEnter={() => setPreviewHovered(true)}\n onMouseLeave={() => setPreviewHovered(false)}\n style={{\n fontSize: 11,\n color: previewHovered ? '#374151' : '#6b7280',\n background: 'none',\n border: 'none',\n cursor: isPreviewing ? 'default' : 'pointer',\n opacity: isPreviewing ? 0.5 : 1,\n fontFamily: 'inherit',\n transition: 'color 0.15s',\n }}\n >\n {isPreviewing ? 'Playing...' : 'Preview'}\n </button>\n {onDelete && (\n <button\n onClick={onDelete}\n onMouseEnter={() => setDeleteHovered(true)}\n onMouseLeave={() => setDeleteHovered(false)}\n style={{\n fontSize: 11,\n color: deleteHovered ? '#b91c1c' : '#ef4444',\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n fontFamily: 'inherit',\n transition: 'color 0.15s',\n }}\n >\n Delete\n </button>\n )}\n </div>\n );\n}\n\nfunction UploadForm({ uploadName, uploading, onNameChange, onUpload, onCancel, primaryColor }: {\n uploadName: string;\n uploading: boolean;\n onNameChange: (v: string) => void;\n onUpload: () => void;\n onCancel: () => void;\n primaryColor: string;\n}) {\n const [uploadHovered, setUploadHovered] = useState(false);\n const [cancelHovered, setCancelHovered] = useState(false);\n\n return (\n <div style={{\n display: 'flex',\n flexDirection: 'column',\n gap: 8,\n padding: 10,\n borderRadius: 6,\n border: '1px solid #e5e7eb',\n backgroundColor: '#f9fafb',\n }}>\n <input\n type=\"text\"\n value={uploadName}\n onChange={e => onNameChange(e.target.value)}\n placeholder=\"Voice name\"\n style={{\n fontSize: 13,\n paddingLeft: 10,\n paddingRight: 10,\n paddingTop: 6,\n paddingBottom: 6,\n borderRadius: 6,\n border: '1px solid #e5e7eb',\n outline: 'none',\n fontFamily: 'inherit',\n }}\n />\n <div style={{ display: 'flex', gap: 8 }}>\n <button\n onClick={onUpload}\n disabled={uploading || !uploadName}\n onMouseEnter={() => setUploadHovered(true)}\n onMouseLeave={() => setUploadHovered(false)}\n style={{\n fontSize: 11,\n paddingLeft: 10,\n paddingRight: 10,\n paddingTop: 4,\n paddingBottom: 4,\n borderRadius: 6,\n border: 'none',\n backgroundColor: uploadHovered ? primaryColor : '#1f2937',\n color: '#fff',\n cursor: uploading || !uploadName ? 'default' : 'pointer',\n opacity: uploading || !uploadName ? 0.5 : 1,\n fontFamily: 'inherit',\n transition: 'background-color 0.15s',\n }}\n >\n {uploading ? 'Processing (~8s)...' : 'Upload'}\n </button>\n <button\n onClick={onCancel}\n onMouseEnter={() => setCancelHovered(true)}\n onMouseLeave={() => setCancelHovered(false)}\n style={{\n fontSize: 11,\n paddingLeft: 10,\n paddingRight: 10,\n paddingTop: 4,\n paddingBottom: 4,\n borderRadius: 6,\n border: '1px solid #e5e7eb',\n backgroundColor: cancelHovered ? '#f9fafb' : 'transparent',\n cursor: 'pointer',\n fontFamily: 'inherit',\n transition: 'background-color 0.15s',\n }}\n >\n Cancel\n </button>\n </div>\n </div>\n );\n}\n\nfunction UploadButton({ disabled, onClick }: { disabled: boolean; onClick: () => void }) {\n const [hovered, setHovered] = useState(false);\n\n return (\n <button\n onClick={onClick}\n disabled={disabled}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={{\n fontSize: 11,\n paddingLeft: 10,\n paddingRight: 10,\n paddingTop: 6,\n paddingBottom: 6,\n borderRadius: 8,\n border: '1px dashed #d1d5db',\n backgroundColor: hovered && !disabled ? '#f9fafb' : '#fff',\n cursor: disabled ? 'not-allowed' : 'pointer',\n opacity: disabled ? 0.5 : 1,\n transition: 'background-color 0.15s',\n fontFamily: 'inherit',\n }}\n >\n + Upload voice sample (WAV, max 30s)\n </button>\n );\n}\n"],"mappings":"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","names":["useState","useCallback","useSiteConfig","usePersonaContext","useState","useRef","useCallback","jsx","Fragment","jsx","jsxs","LANGUAGE_OPTIONS","useSiteConfig","usePersonaContext","useState","useCallback"]}
package/dist/index.d.ts CHANGED
@@ -197,7 +197,9 @@ declare function SettingsSection({ title, icon, children, last, open, onToggle,
197
197
  }): react_jsx_runtime.JSX.Element;
198
198
  declare function Divider(): react_jsx_runtime.JSX.Element;
199
199
 
200
- declare function PersonaSettings(): react_jsx_runtime.JSX.Element | null;
200
+ declare function PersonaSettings({ adminPassword }: {
201
+ adminPassword?: string | null;
202
+ }): react_jsx_runtime.JSX.Element | null;
201
203
 
202
204
  interface VoiceToolCardProps {
203
205
  result: VoiceToolResult | null;
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  VoiceSettingsProvider,
9
9
  VoiceSettingsView,
10
10
  useVoiceSettings
11
- } from "./chunk-MUM5DNV4.js";
11
+ } from "./chunk-K3JJWWRQ.js";
12
12
 
13
13
  // src/VoiceAgentProvider.tsx
14
14
  import { SiteConfigProvider } from "@unctad-ai/voice-agent-core";
@@ -1507,8 +1507,8 @@ var ERROR_CONFIG = {
1507
1507
  },
1508
1508
  stt_failed: {
1509
1509
  icon: Mic2,
1510
- title: "Didn't catch that",
1511
- severity: "info"
1510
+ title: "Voice input unavailable \u2014 use text",
1511
+ severity: "warning"
1512
1512
  },
1513
1513
  tts_failed: {
1514
1514
  icon: AlertTriangle2,
@@ -1665,7 +1665,9 @@ function PipelineMetricsBar({
1665
1665
 
1666
1666
  // src/components/GlassCopilotPanel.tsx
1667
1667
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1668
- var VoiceSettingsView2 = lazy(() => import("./VoiceSettingsView-25CXZPWY.js"));
1668
+ var VoiceSettingsView2 = lazy(() => import("./VoiceSettingsView-QY7TEHYZ.js"));
1669
+ var RETRY_INITIAL_MS = 3e3;
1670
+ var RETRY_MAX_MS = 3e4;
1669
1671
  var STATE_LABELS = {
1670
1672
  IDLE: "Tap mic to speak",
1671
1673
  LISTENING: "Listening...",
@@ -1680,7 +1682,7 @@ var ARIA_LIVE_LABELS = {
1680
1682
  speaking: "Playing response",
1681
1683
  error: "An error occurred"
1682
1684
  };
1683
- function CopilotFAB({ onClick, portraitSrc }) {
1685
+ function CopilotFAB({ onClick, portraitSrc, isOffline = false }) {
1684
1686
  const { colors } = useSiteConfig4();
1685
1687
  return /* @__PURE__ */ jsx9(
1686
1688
  motion5.button,
@@ -1699,7 +1701,7 @@ function CopilotFAB({ onClick, portraitSrc }) {
1699
1701
  },
1700
1702
  "aria-label": "Open voice assistant",
1701
1703
  "data-testid": "voice-agent-fab",
1702
- children: /* @__PURE__ */ jsx9("div", { className: "agent-fab-border", style: { width: 54, height: 54, "--agent-primary": colors.primary }, children: /* @__PURE__ */ jsx9("div", { className: "agent-fab-border-inner", children: portraitSrc ? /* @__PURE__ */ jsx9(
1704
+ 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(
1703
1705
  "img",
1704
1706
  {
1705
1707
  src: portraitSrc,
@@ -1736,6 +1738,7 @@ function CollapsedBar({
1736
1738
  onClose,
1737
1739
  onRetry,
1738
1740
  isRetrying = false,
1741
+ retryCountdown,
1739
1742
  voiceError,
1740
1743
  micPaused = false,
1741
1744
  onMicToggle,
@@ -1807,8 +1810,8 @@ function CollapsedBar({
1807
1810
  letterSpacing: "0.01em"
1808
1811
  },
1809
1812
  children: isOffline ? /* @__PURE__ */ jsxs8("span", { className: "inline-flex items-center gap-1", children: [
1810
- "Offline",
1811
- onRetry && /* @__PURE__ */ jsx9(
1813
+ retryCountdown ? `Retrying in ${retryCountdown}s` : "Offline",
1814
+ onRetry && !retryCountdown && /* @__PURE__ */ jsx9(
1812
1815
  "button",
1813
1816
  {
1814
1817
  onClick: (e) => {
@@ -1920,66 +1923,35 @@ function ComposerBar({
1920
1923
  switchToTextRef
1921
1924
  }) {
1922
1925
  const { colors } = useSiteConfig4();
1923
- const [mode, setMode] = useState5("voice");
1926
+ const [mode, setMode] = useState5(disabled ? "text" : "voice");
1924
1927
  const [text, setText] = useState5("");
1925
1928
  const inputRef = useRef4(null);
1926
1929
  useEffect6(() => {
1927
1930
  if (switchToTextRef) switchToTextRef.current = () => setMode("text");
1928
1931
  }, [switchToTextRef]);
1932
+ useEffect6(() => {
1933
+ if (disabled) setMode("text");
1934
+ }, [disabled]);
1929
1935
  useEffect6(() => {
1930
1936
  if (mode === "text") {
1931
- requestAnimationFrame(() => inputRef.current?.focus());
1937
+ const timer = setTimeout(() => inputRef.current?.focus(), 200);
1938
+ return () => clearTimeout(timer);
1932
1939
  }
1933
1940
  }, [mode]);
1934
1941
  const handleSubmit = () => {
1942
+ if (disabled) return;
1935
1943
  const trimmed = text.trim();
1936
1944
  if (!trimmed) return;
1937
1945
  onTextSubmit(trimmed);
1938
1946
  setText("");
1939
- setMode("voice");
1947
+ requestAnimationFrame(() => inputRef.current?.focus());
1940
1948
  };
1941
1949
  const handleCancel = () => {
1942
1950
  setText("");
1951
+ if (disabled) return;
1943
1952
  setMode("voice");
1944
1953
  onMicToggle();
1945
1954
  };
1946
- if (disabled) {
1947
- return /* @__PURE__ */ jsxs8(
1948
- "div",
1949
- {
1950
- className: "flex items-center gap-3 shrink-0",
1951
- style: {
1952
- height: 56,
1953
- padding: "10px 14px",
1954
- borderTop: "1px solid rgba(0,0,0,0.08)"
1955
- },
1956
- children: [
1957
- /* @__PURE__ */ jsx9(
1958
- "div",
1959
- {
1960
- className: "shrink-0 rounded-full flex items-center justify-center",
1961
- style: {
1962
- width: 36,
1963
- height: 36,
1964
- backgroundColor: "rgba(0,0,0,0.04)",
1965
- color: "rgba(0,0,0,0.2)",
1966
- opacity: 0.5
1967
- },
1968
- children: /* @__PURE__ */ jsx9(Mic3, { style: { width: 18, height: 18 } })
1969
- }
1970
- ),
1971
- /* @__PURE__ */ jsx9(
1972
- "span",
1973
- {
1974
- className: "italic",
1975
- style: { fontSize: "13px", color: "rgba(0,0,0,0.3)" },
1976
- children: "Service unavailable"
1977
- }
1978
- )
1979
- ]
1980
- }
1981
- );
1982
- }
1983
1955
  return /* @__PURE__ */ jsx9(
1984
1956
  "div",
1985
1957
  {
@@ -2095,6 +2067,7 @@ function ComposerBar({
2095
2067
  type: "text",
2096
2068
  value: text,
2097
2069
  onChange: (e) => setText(e.target.value),
2070
+ disabled,
2098
2071
  onKeyDown: (e) => {
2099
2072
  if (e.key === "Enter") handleSubmit();
2100
2073
  if (e.key === "Escape") handleCancel();
@@ -2113,7 +2086,7 @@ function ComposerBar({
2113
2086
  pill.style.boxShadow = "none";
2114
2087
  }
2115
2088
  },
2116
- placeholder: "Ask about services...",
2089
+ placeholder: disabled ? "Reconnecting..." : "Ask about services...",
2117
2090
  "aria-label": "Type your question",
2118
2091
  "data-testid": "voice-agent-input",
2119
2092
  className: "w-full",
@@ -2123,7 +2096,9 @@ function ComposerBar({
2123
2096
  background: "transparent",
2124
2097
  border: "none",
2125
2098
  outline: "none",
2126
- padding: 0
2099
+ padding: 0,
2100
+ opacity: disabled ? 0.5 : 1,
2101
+ cursor: disabled ? "not-allowed" : void 0
2127
2102
  }
2128
2103
  }
2129
2104
  )
@@ -2150,7 +2125,7 @@ function ComposerBar({
2150
2125
  children: /* @__PURE__ */ jsx9(ArrowUp, { style: { width: 18, height: 18 } })
2151
2126
  }
2152
2127
  ) }),
2153
- /* @__PURE__ */ jsx9(
2128
+ !disabled && /* @__PURE__ */ jsx9(
2154
2129
  motion5.button,
2155
2130
  {
2156
2131
  whileHover: { scale: 1.08 },
@@ -2194,6 +2169,7 @@ function ExpandedContent({
2194
2169
  onInteraction,
2195
2170
  onRetry,
2196
2171
  isRetrying = false,
2172
+ retryCountdown,
2197
2173
  lastTimings,
2198
2174
  showPipelineMetrics,
2199
2175
  pipelineMetricsAutoHideMs,
@@ -2217,20 +2193,15 @@ function ExpandedContent({
2217
2193
  onKeyDown: onInteraction,
2218
2194
  children: [
2219
2195
  /* @__PURE__ */ jsxs8(
2220
- motion5.div,
2196
+ "div",
2221
2197
  {
2222
- initial: { opacity: 0 },
2223
- animate: { opacity: 1 },
2224
- transition: { duration: 0.18, ease: [0.25, 0.1, 0.25, 1], delay: 0.12 },
2225
2198
  className: "flex items-center gap-3 shrink-0",
2226
2199
  style: { height: 64, padding: "12px 16px", borderBottom: "1px solid rgba(0,0,0,0.06)" },
2227
2200
  children: [
2228
2201
  /* @__PURE__ */ jsxs8(
2229
2202
  motion5.div,
2230
2203
  {
2231
- initial: { scale: 0.8 },
2232
- animate: { scale: 1, opacity: isOffline ? 0.35 : 1 },
2233
- transition: { ...SPRING_MICRO, delay: 0.14 },
2204
+ animate: { opacity: isOffline ? 0.35 : 1 },
2234
2205
  className: "relative shrink-0",
2235
2206
  style: { width: 46, height: 46, filter: isOffline ? "grayscale(0.8)" : "none" },
2236
2207
  children: [
@@ -2294,13 +2265,13 @@ function ExpandedContent({
2294
2265
  /* @__PURE__ */ jsx9(PipelineMetricsBar, { timings: lastTimings ?? null, show: showPipelineMetrics, autoHideMs: pipelineMetricsAutoHideMs }),
2295
2266
  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: [
2296
2267
  /* @__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 } }) }),
2297
- isRetrying ? "Checking..." : "Retry connection"
2268
+ isRetrying ? "Checking..." : retryCountdown ? `Retrying in ${retryCountdown}s...` : "Retry connection"
2298
2269
  ] }) }),
2299
2270
  voiceError !== "network_error" && /* @__PURE__ */ jsx9("div", { style: { padding: "0 16px 8px" }, children: /* @__PURE__ */ jsx9(VoiceErrorDisplay, { error: voiceError, onDismiss: dismissError }) }),
2300
2271
  /* @__PURE__ */ jsx9("div", { style: { padding: "0 16px 8px" }, children: /* @__PURE__ */ jsx9(VoiceToolCard, { result: toolResult, onDismiss: onToolDismiss, variant: "capsule" }) })
2301
2272
  ] })
2302
2273
  ] }),
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 }) })
2274
+ /* @__PURE__ */ jsx9("div", { className: "shrink-0", children: /* @__PURE__ */ jsx9(ComposerBar, { voiceState, isListening, micPaused, onTextSubmit, onMicToggle, disabled: voiceError === "network_error" || voiceError === "stt_failed", switchToTextRef }) })
2304
2275
  ]
2305
2276
  }
2306
2277
  );
@@ -2382,28 +2353,70 @@ function WiredPanelInner({
2382
2353
  const autoStartedRef = useRef4(false);
2383
2354
  const pollTimerRef = useRef4(null);
2384
2355
  const cancelledRef = useRef4(false);
2356
+ const [retryCountdown, setRetryCountdown] = useState5(null);
2357
+ const backoffDelayRef = useRef4(RETRY_INITIAL_MS);
2358
+ const countdownTimerRef = useRef4(null);
2359
+ const dismissErrorRef = useRef4(dismissError);
2360
+ useEffect6(() => {
2361
+ dismissErrorRef.current = dismissError;
2362
+ }, [dismissError]);
2363
+ const autoListenRef = useRef4(settings.autoListen);
2364
+ useEffect6(() => {
2365
+ autoListenRef.current = settings.autoListen;
2366
+ }, [settings.autoListen]);
2367
+ const clearCountdown = useCallback3(() => {
2368
+ if (countdownTimerRef.current) {
2369
+ clearInterval(countdownTimerRef.current);
2370
+ countdownTimerRef.current = null;
2371
+ }
2372
+ setRetryCountdown(null);
2373
+ }, []);
2374
+ const runHealthCheckRef = useRef4(() => {
2375
+ });
2376
+ const scheduleRetry = useCallback3((delayMs) => {
2377
+ if (pollTimerRef.current) clearTimeout(pollTimerRef.current);
2378
+ clearCountdown();
2379
+ const seconds = Math.ceil(delayMs / 1e3);
2380
+ setRetryCountdown(seconds);
2381
+ let remaining = seconds;
2382
+ countdownTimerRef.current = setInterval(() => {
2383
+ remaining -= 1;
2384
+ if (remaining <= 0) {
2385
+ clearCountdown();
2386
+ return;
2387
+ }
2388
+ setRetryCountdown(remaining);
2389
+ }, 1e3);
2390
+ pollTimerRef.current = setTimeout(() => runHealthCheckRef.current(), delayMs);
2391
+ backoffDelayRef.current = Math.min(delayMs * 2, RETRY_MAX_MS);
2392
+ }, [clearCountdown]);
2385
2393
  const runHealthCheck = useCallback3(() => {
2394
+ clearCountdown();
2386
2395
  checkBackendHealth().then(({ available }) => {
2387
2396
  if (cancelledRef.current) return;
2388
2397
  setBackendDown(!available);
2389
2398
  if (available) {
2390
- dismissError();
2391
- if (!autoStartedRef.current && settings.autoListen) {
2399
+ backoffDelayRef.current = RETRY_INITIAL_MS;
2400
+ dismissErrorRef.current();
2401
+ if (!autoStartedRef.current && autoListenRef.current) {
2392
2402
  autoStartedRef.current = true;
2393
2403
  startRef.current();
2394
2404
  }
2395
2405
  } else {
2396
- if (pollTimerRef.current) clearTimeout(pollTimerRef.current);
2397
- pollTimerRef.current = setTimeout(runHealthCheck, RECOVERY_POLL_MS);
2406
+ scheduleRetry(backoffDelayRef.current);
2398
2407
  }
2399
2408
  });
2400
- }, [dismissError, settings.autoListen]);
2409
+ }, [scheduleRetry, clearCountdown]);
2410
+ useEffect6(() => {
2411
+ runHealthCheckRef.current = runHealthCheck;
2412
+ }, [runHealthCheck]);
2401
2413
  useEffect6(() => {
2402
2414
  cancelledRef.current = false;
2403
2415
  runHealthCheck();
2404
2416
  return () => {
2405
2417
  cancelledRef.current = true;
2406
2418
  if (pollTimerRef.current) clearTimeout(pollTimerRef.current);
2419
+ if (countdownTimerRef.current) clearInterval(countdownTimerRef.current);
2407
2420
  };
2408
2421
  }, [runHealthCheck]);
2409
2422
  const onStateChangeRef = useRef4(onStateChange);
@@ -2481,23 +2494,23 @@ function WiredPanelInner({
2481
2494
  const handleRetryClick = useCallback3(() => {
2482
2495
  if (isRetrying) return;
2483
2496
  setIsRetrying(true);
2497
+ backoffDelayRef.current = RETRY_INITIAL_MS;
2498
+ clearCountdown();
2484
2499
  checkBackendHealth().then(({ available }) => {
2485
2500
  if (cancelledRef.current) return;
2486
2501
  setIsRetrying(false);
2487
2502
  setBackendDown(!available);
2488
2503
  if (available) {
2489
- dismissError();
2490
- if (!autoStartedRef.current && settings.autoListen) {
2504
+ dismissErrorRef.current();
2505
+ if (!autoStartedRef.current && autoListenRef.current) {
2491
2506
  autoStartedRef.current = true;
2492
2507
  startRef.current();
2493
2508
  }
2494
- }
2495
- if (!available) {
2496
- if (pollTimerRef.current) clearTimeout(pollTimerRef.current);
2497
- pollTimerRef.current = setTimeout(runHealthCheck, RECOVERY_POLL_MS);
2509
+ } else {
2510
+ scheduleRetry(backoffDelayRef.current);
2498
2511
  }
2499
2512
  }).catch(() => setIsRetrying(false));
2500
- }, [isRetrying, runHealthCheck, dismissError, settings.autoListen]);
2513
+ }, [isRetrying, clearCountdown, scheduleRetry]);
2501
2514
  const onExpandRef = useRef4(onExpand);
2502
2515
  useEffect6(() => {
2503
2516
  onExpandRef.current = onExpand;
@@ -2514,10 +2527,10 @@ function WiredPanelInner({
2514
2527
  const [showSettings, setShowSettings] = useState5(false);
2515
2528
  const toggleSettings = useCallback3(() => setShowSettings((p) => !p), []);
2516
2529
  if (panelState === "collapsed") {
2517
- 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 });
2530
+ return /* @__PURE__ */ jsx9(CollapsedBar, { orbState, getAmplitude, analyser, voiceState: state, onExpand, onClose, onRetry: handleRetryClick, isRetrying, retryCountdown, voiceError: effectiveError, micPaused, onMicToggle: handleMicToggle, ttsEnabled: settings.ttsEnabled, copilotName: config.copilotName, portraitSrc: resolvedPortrait });
2518
2531
  }
2519
2532
  return /* @__PURE__ */ jsxs8("div", { className: "relative h-full", children: [
2520
- /* @__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 }),
2533
+ /* @__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, retryCountdown, lastTimings, showPipelineMetrics: settings.showPipelineMetrics, pipelineMetricsAutoHideMs: settings.pipelineMetricsAutoHideMs, showSettings, onSettingsToggle: toggleSettings, ttsEnabled: settings.ttsEnabled, copilotName: config.copilotName, portraitSrc: resolvedPortrait, onStartMic: handleMicToggle, onSwitchToKeyboard: handleSwitchToKeyboard, switchToTextRef }),
2521
2534
  /* @__PURE__ */ jsx9(AnimatePresence5, { children: showSettings && /* @__PURE__ */ jsx9(Suspense, { fallback: null, children: /* @__PURE__ */ jsx9(VoiceSettingsView2, { onBack: toggleSettings, onVolumeChange: applyVolume }) }) })
2522
2535
  ] });
2523
2536
  }
@@ -2544,6 +2557,22 @@ function GlassCopilotPanel({ isOpen: isOpenProp, onOpen: onOpenProp, onClose: on
2544
2557
  const handleExpand = useCallback3(() => {
2545
2558
  setInternalState("expanded");
2546
2559
  }, []);
2560
+ const [fabOffline, setFabOffline] = useState5(false);
2561
+ useEffect6(() => {
2562
+ if (isOpen) return;
2563
+ let cancelled = false;
2564
+ const check = () => {
2565
+ checkBackendHealth().then(({ available }) => {
2566
+ if (!cancelled) setFabOffline(!available);
2567
+ });
2568
+ };
2569
+ check();
2570
+ const timer = setInterval(check, RECOVERY_POLL_MS);
2571
+ return () => {
2572
+ cancelled = true;
2573
+ clearInterval(timer);
2574
+ };
2575
+ }, [isOpen]);
2547
2576
  const isVisible = panelState !== "hidden";
2548
2577
  const isExpanded = panelState === "expanded";
2549
2578
  const targetHeight = isExpanded ? PANEL_EXPANDED_HEIGHT : PANEL_COLLAPSED_HEIGHT;
@@ -2575,7 +2604,7 @@ function GlassCopilotPanel({ isOpen: isOpenProp, onOpen: onOpenProp, onClose: on
2575
2604
  ] }) }) }),
2576
2605
  /* @__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 }),
2577
2606
  /* @__PURE__ */ jsxs8(AnimatePresence5, { children: [
2578
- !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 }) }, "copilot-fab"),
2607
+ !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"),
2579
2608
  isVisible && /* @__PURE__ */ jsxs8(
2580
2609
  motion5.div,
2581
2610
  {