import React, { useState, useEffect } from 'react'; import Header from './components/Header'; import PatientSetupForm from './components/PatientSetupForm'; import VoiceChat from './components/VoiceChat'; import DocumentUploader from './components/DocumentUploader'; import SummaryModal from './components/SummaryModal'; import { checkHealth, startVoiceSession, startTextSession, sendVoiceTurn, sendTextMessage, } from './services/api'; export default function App() { const [healthStatus, setHealthStatus] = useState(null); const [activeSession, setActiveSession] = useState(null); const [isVoiceMode, setIsVoiceMode] = useState(true); const [isLoadingSession, setIsLoadingSession] = useState(false); const [isProcessingTurn, setIsProcessingTurn] = useState(false); // Modals visibility const [showDocumentUploader, setShowDocumentUploader] = useState(false); const [showSummaryModal, setShowSummaryModal] = useState(false); const [uploadedDocuments, setUploadedDocuments] = useState([]); // Check Backend Health Status on Mount and periodically useEffect(() => { async function pingHealth() { const health = await checkHealth(); setHealthStatus(health); } pingHealth(); const interval = setInterval(pingHealth, 15000); return () => clearInterval(interval); }, []); // Ingest Patient Data from URL Search Params or Parent postMessage (WebView Bridge) useEffect(() => { if (typeof window === 'undefined') return; // Method 1: Check URL Query Parameters (e.g. ?first_name=Ramesh&phone=9876543210&autostart=true) const urlParams = new URLSearchParams(window.location.search); const autostart = urlParams.get('autostart') === 'true' || urlParams.get('autoStart') === 'true'; const firstName = urlParams.get('first_name') || urlParams.get('firstName') || urlParams.get('name'); if (autostart && firstName) { const patient = { first_name: firstName, last_name: urlParams.get('last_name') || urlParams.get('lastName') || '', age: urlParams.get('age') || '30', gender: urlParams.get('gender') || 'Male', phone: urlParams.get('phone') || '', email: urlParams.get('email') || '', }; const category = { type: 'general', code: urlParams.get('category') || 'general_medicine', name: urlParams.get('category_name') || 'General Medicine / Primary Care', }; const language = urlParams.get('language') || urlParams.get('lang') || 'ta-IN'; const mode = urlParams.get('mode') || 'voice'; handleStartSession({ patient, category, language, mode }); } // Method 2: Listen for postMessage from Parent App (Web iFrame / WebView) const handleParentBridgeMessage = (event) => { if (!event.data) return; let payload = event.data; if (typeof payload === 'string') { try { payload = JSON.parse(payload); } catch (_) { return; } } if (payload.type === 'INIT_PATIENT_ONBOARDING' && payload.data) { const { patient, category, language, mode } = payload.data; if (patient) { handleStartSession({ patient, category: category || { type: 'general', code: 'general_medicine', name: 'General Medicine / Primary Care' }, language: language || 'ta-IN', mode: mode || 'voice', }); } } }; window.addEventListener('message', handleParentBridgeMessage); return () => window.removeEventListener('message', handleParentBridgeMessage); }, []); // Handle Session Initialization (Start Voice / Text Session) const handleStartSession = async ({ patient, category, language, mode }) => { // Prime audio context synchronously inside user click gesture event if (typeof window !== 'undefined') { try { const silentAudio = new Audio("data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA="); silentAudio.play().then(() => silentAudio.pause()).catch(() => {}); } catch (_) {} } setIsLoadingSession(true); const useVoice = mode ? mode === 'voice' : true; setIsVoiceMode(useVoice); try { let sessionRes; if (useVoice) { sessionRes = await startVoiceSession({ patient, category, language }); } else { sessionRes = await startTextSession({ patient, category, language }); } setActiveSession({ ...sessionRes, patient, category: sessionRes?.category || category, language: sessionRes?.language || language, greeting_text: sessionRes?.greeting_text || `Hello ${patient.first_name || patient.name || 'Patient'}, welcome to CureSelect Healthcare. I am your AI clinical assistant. What symptoms or medical concerns bring you in today?`, }); } catch (err) { console.error('Failed to start session:', err); // Fallback mock session if backend server is unreachable setActiveSession({ success: true, session_id: `VOICE-${Math.random().toString(36).substring(2, 9).toUpperCase()}`, patient_id: patient.id, patient, category, language, greeting_text: `Hello ${patient.first_name || patient.name || 'Patient'}, welcome to CureSelect Healthcare. I am your AI clinical assistant. What symptoms or medical concerns bring you in today?`, status: 'in_progress', stage: 'greeting', completed: false, }); } finally { setIsLoadingSession(false); } }; // Handle Turn Submission (Voice Blob or Text) const handleSendTurn = async ({ audioBlob, textFallback }) => { if (!activeSession) return null; setIsProcessingTurn(true); try { let turnRes; if (isVoiceMode) { turnRes = await sendVoiceTurn(activeSession.session_id, audioBlob, textFallback); } else { turnRes = await sendTextMessage(activeSession.session_id, textFallback); } if (turnRes) { setActiveSession((prev) => ({ ...prev, stage: turnRes.stage || prev.stage, completed: Boolean(turnRes.completed || turnRes.stage === 'completed' || turnRes.stage === 'finished'), })); } return turnRes; } catch (err) { console.error('Error submitting turn:', err); // Fallback local mock response for seamless UI demo if network error return { success: true, session_id: activeSession.session_id, patient_transcript: textFallback || 'Audio turn recorded', ai_text: 'Thank you for providing that detail. Do you currently experience any shortness of breath or radiating pain?', stage: 'symptoms_inquiry', completed: false, audio_url: null, }; } finally { setIsProcessingTurn(false); } }; // Document Upload Callback const handleDocumentUploaded = (docResult) => { setUploadedDocuments((prev) => [...prev, docResult]); }; // Reset Session const handleResetSession = () => { if (window.confirm('Are you sure you want to end this session and start a new patient onboarding?')) { setActiveSession(null); setUploadedDocuments([]); setShowDocumentUploader(false); setShowSummaryModal(false); } }; return (