import React, { useState, useCallback } from 'react'; import { Scenario, UserSettings, AnalysisResult } from '../types'; import { useLiveSession } from '../hooks/useLiveSession'; import { AlertCircle, Settings2, X, AlertTriangle } from 'lucide-react'; import { Button } from './ui/button'; import { generateSessionSummaryReport } from '../services/openRouterService'; import { saveSession } from '../services/sessionStorage'; // Sub-components import AudioVisualizer from './AudioVisualizer'; import TranscriptPanel from './TranscriptPanel'; import LiveInsightPanel from './LiveInsightPanel'; import SessionReport from './SessionReport'; import ConnectionState from './VoiceSession/ConnectionState'; import AnalyzingState from './VoiceSession/AnalyzingState'; import VoiceHeader from './VoiceSession/VoiceHeader'; import VoiceControls from './VoiceSession/VoiceControls'; import AudioDeviceSelector from './AudioDeviceSelector'; interface VoiceSessionProps { scenario: Scenario; onExit: () => void; voiceSettings: UserSettings; onUpdateSettings: (settings: UserSettings) => void; } const VoiceSession: React.FC = ({ scenario, onExit, voiceSettings, onUpdateSettings }) => { const [showTranscript, setShowTranscript] = useState(false); const [showDeviceSelector, setShowDeviceSelector] = useState(false); const [hasStarted, setHasStarted] = useState(false); const [finalReport, setFinalReport] = useState(null); const { status, setStatus, errorMessage, isMuted, toggleMute, audioLevel, aiAudioLevel, isAiSpeaking, isUserSpeaking, messages, liveInsight, isAnalyzingTurn, isSilenceDetected, startSession } = useLiveSession(scenario, voiceSettings); const activeLevel = isAiSpeaking ? aiAudioLevel : audioLevel; const handleStart = useCallback(() => { setHasStarted(true); startSession(); }, [startSession]); const handleFinishSession = useCallback(async () => { setStatus('analyzing'); try { const reportData = await generateSessionSummaryReport( messages, scenario.title, scenario.description, { apiKey: voiceSettings.openRouterApiKey, model: voiceSettings.supervisorModel } ); setFinalReport(reportData); saveSession(scenario, 'voice', messages, reportData); setStatus('finished'); } catch (err) { console.error("Post-session analysis failure", err); onExit(); } }, [messages, scenario, voiceSettings.openRouterApiKey, voiceSettings.supervisorModel, setStatus, onExit]); if (status === 'connecting') return ; if (status === 'analyzing') return ; if (status === 'finished' && finalReport) { return ( ); } return (
{/* Voice Session Header */} setShowDeviceSelector(true)} /> {/* Main Interaction Stage */}
{/* Error Banner */} {status === 'error' && (

Connection Warning

{errorMessage || 'Unable to connect to live voice session. Please verify your OpenRouter API key.'}

)} {/* Silence Warning Notice */} {isSilenceDetected && !isMuted && status === 'active' && (
Low microphone input detected. Please speak closer to your mic.
)} {/* Audio Visualizer Orb */}
{!hasStarted && status === 'active' && ( )}
{/* Status Indicators */}
{isAiSpeaking ? `${scenario.characterName} is speaking` : isUserSpeaking ? 'Listening to you...' : 'Ready for dialogue'}

{voiceSettings.voiceName} Vocal Persona Active

{/* Live Insight Drawer */}
{/* Persistent Audio Controls Footer */} setShowTranscript(prev => !prev)} showTranscript={showTranscript} messageCount={messages.length} /> {/* Transcript Drawer Slideout */} {showTranscript && ( setShowTranscript(false)} /> )} {/* Audio Hardware Selector Modal */} {showDeviceSelector && (

Select Audio Input Device

{ onUpdateSettings({ ...voiceSettings, preferredAudioDeviceId: deviceId }); setShowDeviceSelector(false); }} />
)}
); }; export default React.memo(VoiceSession);