// ─── Agent Settings Page ───────────────────────────────────────────────────── const GEMINI_DEFAULT_MODEL = 'gemini-3.1-flash-live-preview'; const GEMINI_TTS_DEFAULT_MODEL = 'gemini-3.1-flash-tts-preview'; const GEMINI_LANGUAGE_OPTIONS = [ { value: '', label: 'Auto-detect' }, { value: 'en-IN', label: 'English (India)' }, { value: 'hi-IN', label: 'Hindi' }, { value: 'mr-IN', label: 'Marathi' }, { value: 'ta-IN', label: 'Tamil' }, { value: 'te-IN', label: 'Telugu' }, ]; const CFG_DEFAULTS = { first_line: "Namaste! This is Aryan from SPX AI. We help businesses automate with AI. Hmm, may I ask what kind of business you run?", agent_instructions: "", stt_min_endpointing_delay: 0.15, max_endpointing_delay: 0.9, turn_detection_mode: 'stt', endpointing_mode: 'dynamic', allow_interruptions: true, interruption_mode: 'adaptive', discard_audio_if_uninterruptible: true, min_interruption_duration: 0.18, min_interruption_words: 0, resume_false_interruption: true, false_interruption_timeout: 1.2, preemptive_generation: true, preemptive_generation_enable_tts: false, preemptive_generation_max_speech_duration: 8.0, preemptive_generation_max_retries: 2, use_tts_aligned_transcript: true, user_away_timeout: 15.0, session_close_transcript_timeout: 2.0, max_turns: 25, voice_mode: 'pipeline', llm_provider: 'groq', llm_model: 'llama-3.3-70b-versatile', stt_provider: 'sarvam', stt_language: 'unknown', stt_model: 'saaras:v3', stt_mode: 'translate', stt_sample_rate: 16000, tts_provider: 'cartesia', tts_language: 'hi-IN', tts_voice: 'kavya', xai_tts_voice: 'eve', xai_tts_style_preset: 'spirited', cartesia_voice_id: '', cartesia_tts_model: 'sonic-3', sarvam_tts_model: 'bulbul:v3', cartesia_sample_rate: 24000, xai_tts_sample_rate: 24000, gemini_live_model: GEMINI_DEFAULT_MODEL, gemini_live_voice: 'Puck', gemini_live_temperature: 0.8, gemini_live_language: '', gemini_tts_model: GEMINI_TTS_DEFAULT_MODEL, lang_preset: 'multilingual', livekit_url: '', sip_trunk_id: '', livekit_api_key: '', livekit_api_secret: '', groq_api_key: '', openrouter_api_key: '', google_api_key: '', sarvam_api_key: '', cartesia_api_key: '', x_ai_api_key: '', supabase_url: '', supabase_key: '', }; function GeneralTab({ cfg, set }) { return (
set('stt_min_endpointing_delay')(parseFloat(v))} hint="Min endpointing delay before agent responds." /> set('max_turns')(parseInt(v))} hint="Maximum dialogue turns per session." />
); } function TurnHandlingTab({ cfg, set }) { return (
Turn Detection
({ value: v, label: v.toUpperCase() }))} /> ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }))} />
set('max_endpointing_delay')(parseFloat(v))} /> ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }))} />
Session
set('max_turns')(parseInt(v))} /> set('user_away_timeout')(parseFloat(v))} />
); } function LatencyTab({ cfg, set }) { return (
Latency Toggles Timing Thresholds
set('min_interruption_duration')(parseFloat(v))} /> set('false_interruption_timeout')(parseFloat(v))} /> set('preemptive_generation_max_retries')(parseInt(v))} /> set('session_close_transcript_timeout')(parseFloat(v))} /> set('min_interruption_words')(parseInt(v))} /> set('preemptive_generation_max_speech_duration')(parseFloat(v))} /> set('user_away_timeout')(parseFloat(v))} />
); } function ModelsTab({ cfg, set }) { const vm = cfg.voice_mode || 'pipeline'; const geminiModel = cfg.gemini_live_model || GEMINI_DEFAULT_MODEL; const geminiLimited = /3\.1/i.test(geminiModel); return (
Conversation Mode
{[ { value: 'pipeline', label: 'Pipeline', sub: 'STT → LLM → TTS' }, { value: 'gemini_live', label: 'Gemini Live', sub: 'Google native audio' }, ].map(m => (
set('voice_mode')(m.value)} style={{ border: `1px solid ${vm === m.value ? '#5a7ef5' : 'rgba(255,255,255,0.08)'}`, borderRadius: 10, padding: '12px 14px', cursor: 'pointer', background: vm === m.value ? 'rgba(90,126,245,0.08)' : 'transparent', transition: 'all 0.15s' }}>
{m.label}
{m.sub}
))}
{vm === 'pipeline' && <> LLM Provider
Speech-to-Text (STT)
({ value: v, label: v }))} /> set('stt_sample_rate')(parseInt(v))} />
Text-to-Speech (TTS)
{cfg.tts_provider === 'xai' && <> ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }))} /> set('xai_tts_sample_rate')(parseInt(v))} /> } {cfg.tts_provider === 'cartesia' && <> set('cartesia_sample_rate')(parseInt(v))} /> } {cfg.tts_provider === 'sarvam' && <> ({ value: v, label: v }))} /> ({ value: v, label: v }))} /> }
} {vm === 'gemini_live' && <> Gemini Live Settings
set('gemini_live_temperature')(parseFloat(v))} /> {geminiLimited && (
Gemini 3.1 preview has limited LiveKit scripted-speech support. This app uses Gemini TTS with the same selected voice for fixed greetings and wrap-ups.
)}
}
); } function CredentialsTab({ cfg, set }) { const [show, setShow] = React.useState({}); const toggleShow = k => setShow(p => ({ ...p, [k]: !p[k] })); const SecretInput = ({ label, k, hint }) => (
set(k)(e.target.value)} style={{ flex: 1, background: '#1a1e28', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 8, color: '#e8eaef', fontSize: 13, padding: '9px 12px', fontFamily: 'monospace', outline: 'none' }} />
{hint && {hint}}
); return (
LiveKit
AI Provider Secrets
Supabase
); } function AgentSettingsPage() { const [tab, setTab] = React.useState('General'); const [cfg, setCfg] = React.useState({ ...CFG_DEFAULTS }); const [loading, setLoading] = React.useState(true); const [saveState, setSaveState] = React.useState('idle'); // idle | saving | saved | error const tabs = ['General', 'Turn Handling', 'Latency', 'Models & Voice', 'API Credentials']; const set = k => v => setCfg(p => ({ ...p, [k]: v })); React.useEffect(() => { fetch('/api/config') .then(r => r.json()) .then(data => { setCfg(p => ({ ...p, ...data })); setLoading(false); }) .catch(() => setLoading(false)); }, []); const handleSave = async () => { setSaveState('saving'); try { const res = await fetch('/api/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cfg), }); if (!res.ok) throw new Error('Save failed'); setSaveState('saved'); setTimeout(() => setSaveState('idle'), 2500); } catch { setSaveState('error'); setTimeout(() => setSaveState('idle'), 3000); } }; const saveLabel = { idle: 'Save Changes', saving: 'Saving…', saved: '✓ Saved', error: '✗ Error' }[saveState]; const saveVariant = saveState === 'saved' ? 'success' : saveState === 'error' ? 'danger' : 'primary'; return (
{saveLabel}} />
{loading ? : ( {tab === 'General' && } {tab === 'Turn Handling' && } {tab === 'Latency' && } {tab === 'Models & Voice' && } {tab === 'API Credentials' && } )}
); } Object.assign(window, { AgentSettingsPage });