/** * Settings Modal Component * * System settings modal for HomePilot Voice UI. * Contains advanced audio settings and content preferences. * * Features: * - Audio meter toggle * - Browser voice selection * - Language settings * - Adult content gating (18+) */ import React, { useState, useEffect } from 'react'; import { X, Activity, Volume2, Globe, Settings, Shield, AlertTriangle, Users, Link2, } from 'lucide-react'; import { isAdultContentEnabled, isAgeConfirmed, setAdultContentEnabled, setAgeConfirmed, isPersonasEnabled, setPersonasEnabled, isVoiceLinkedToProject, setVoiceLinkedToProject, LS_PERSONA_CACHE, } from './personalityGating'; import TtsEngineSection from '../components/TtsEngineSection'; // Side-effect import: ensures the TTS plugin registry is populated the // first time the System Settings modal mounts — same registry the // Enterprise Settings voice block already uses, so both UIs are in // sync automatically (single active-engine pointer in localStorage, // scoped per user). import '../tts'; import { getActiveTtsEngineId, onActiveTtsEngineChange, } from '../tts'; export default function SettingsModal({ isOpen, onClose, showAudioMeter, setShowAudioMeter, browserVoices, selectedBrowserVoice, setSelectedBrowserVoice, }) { // Adult content state const [adultEnabled, setAdultEnabled] = useState(false); const [ageConfirmed, setAgeConfirmedState] = useState(false); const [showAgeConfirmation, setShowAgeConfirmation] = useState(false); // Personas in Voice state const [personasEnabled, setPersonasEnabledState] = useState(false); // Linked-to-project state const [linkedToProject, setLinkedToProjectState] = useState(false); // Track active TTS engine so we can hide the legacy "System Voice" // dropdown when the user picks a non-default engine (Piper etc.) — // prevents the confusing two-voice-pickers state. const [ttsEngineId, setTtsEngineId] = useState(() => getActiveTtsEngineId()); useEffect(() => onActiveTtsEngineChange(setTtsEngineId), []); // Load settings on mount useEffect(() => { setAdultEnabled(isAdultContentEnabled()); setAgeConfirmedState(isAgeConfirmed()); setPersonasEnabledState(isPersonasEnabled()); setLinkedToProjectState(isVoiceLinkedToProject()); }, [isOpen]); // Handle personas toggle const handlePersonasToggle = () => { const next = !personasEnabled; setPersonasEnabled(next); setPersonasEnabledState(next); if (!next) { // Clear persona cache, unlink, and reset personality if it was a persona localStorage.removeItem(LS_PERSONA_CACHE); const pid = localStorage.getItem('homepilot_personality_id'); if (pid?.startsWith('persona:')) { localStorage.removeItem('homepilot_personality_id'); } setVoiceLinkedToProject(false); setLinkedToProjectState(false); } }; // Handle linked-to-project toggle const handleLinkedToggle = () => { const next = !linkedToProject; setVoiceLinkedToProject(next); setLinkedToProjectState(next); }; // Handle adult content toggle const handleAdultToggle = () => { if (!adultEnabled) { // Trying to enable - check age confirmation if (ageConfirmed) { setAdultContentEnabled(true); setAdultEnabled(true); } else { // Show age confirmation dialog setShowAgeConfirmation(true); } } else { // Disabling setAdultContentEnabled(false); setAdultEnabled(false); } }; // Handle age confirmation const handleAgeConfirm = () => { setAgeConfirmed(true); setAgeConfirmedState(true); setAdultContentEnabled(true); setAdultEnabled(true); setShowAgeConfirmation(false); }; if (!isOpen) return null; const toggleOn = 'bg-white/15 border border-white/20'; const toggleOff = 'bg-[#1F1F1F] border border-transparent'; // Filter English voices for cleaner display const englishVoices = browserVoices?.filter((v) => v.lang.startsWith('en')) || []; // Group voices by language const voicesByLang = browserVoices?.reduce((acc, voice) => { const lang = voice.lang.split('-')[0]; if (!acc[lang]) acc[lang] = []; acc[lang].push(voice); return acc; }, {}) || {}; return (
HomePilot Voice Configuration
{browserVoices.length} system voices available
Voice recognition uses your browser's default language settings. To change the recognition language, update your browser's language preferences.
Adult personalities are now visible in the personality selector. These modes may contain mature themes, strong language, and explicit content.
Your Personas from Settings & Projects are now available in the Personality selector.
Linked mode: Voice sessions use the persona's full project context — memory, documents, and tools persist across sessions.
Unlinked: Voice is fast & ephemeral — no conversation history saved.
Adult content requires confirmation
You are about to enable access to adult-only personalities that may contain:
By continuing, you confirm that you are 18 years or older and consent to accessing this content.
HomePilot Voice uses adaptive noise suppression, echo cancellation, and automatic gain control for crystal-clear audio capture.