document.addEventListener('DOMContentLoaded', () => {
// DOM Elements
const chatBox = document.getElementById('chat-box');
const textInput = document.getElementById('text-input');
const sendBtn = document.getElementById('send-btn');
const micBtn = document.getElementById('mic-btn');
const loadingIndicator = document.getElementById('loading-indicator');
const statusIndicator = document.getElementById('status-indicator');
// Mode Buttons
const modeTextBtn = document.getElementById('mode-text');
const modeVoiceBtn = document.getElementById('mode-voice');
const modeVideoBtn = document.getElementById('mode-video');
// Voice Controls
const voiceControls = document.getElementById('voice-controls');
const continuousToggle = document.getElementById('continuous-toggle');
const rateSlider = document.getElementById('rate');
const pitchSlider = document.getElementById('pitch');
// Video Elements
const videoFeed = document.getElementById('video-feed');
const canvas = document.getElementById('canvas');
const imageModal = document.getElementById('image-capture-modal');
const closeModalBtn = document.getElementById('close-modal-btn');
// Upload Elements (Optional - if you want file upload capability)
const fileUploadBtn = document.getElementById('file-upload-btn');
const fileInput = document.getElementById('file-input');
// State Variables
let sessionId = null;
let currentMode = 'text';
let isListening = false;
let isContinuousMode = false;
let videoStream = null;
let systemStatus = 'disconnected';
// Updated API Base URL for local development
const API_BASE_URL = 'https://nitinbot001-medbot-backend.hf.space';
// Speech Recognition (STT) Setup
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
let recognition;
if (SpeechRecognition) {
recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.interimResults = false;
recognition.lang = 'en-US';
} else {
console.warn('Speech Recognition not supported');
if (micBtn) micBtn.disabled = true;
}
// Speech Synthesis (TTS) Setup
const synth = window.speechSynthesis;
// --- INITIALIZATION ---
function initializeApp() {
console.log('MediBot initializing...');
checkSystemHealth();
loadHistory();
setupEventListeners();
updateStatusIndicator('ready', 'Ready');
addMessageToUI('ai', 'Hello! I am your MediBot Assistant. I can help you with medical information, analyze images of medicines, and answer questions about diseases. How can I assist you today?');
console.log('MediBot initialization complete');
}
async function checkSystemHealth() {
console.log('Checking system health...');
try {
const response = await fetch(`${API_BASE_URL}/health`);
const data = await response.json();
if (response.ok && data.status.includes('Running')) {
systemStatus = 'connected';
updateStatusIndicator('connected', 'System Ready');
console.log('System health check passed:', data);
// Display system info only if there are loaded files
if (data.disease_fact_sheets > 0 || data.medicine_knowledge_files > 0) {
console.log(`System loaded with ${data.disease_fact_sheets} disease fact sheets and ${data.medicine_knowledge_files} medicine knowledge files`);
}
} else {
throw new Error('System health check failed');
}
} catch (error) {
systemStatus = 'disconnected';
updateStatusIndicator('error', 'System Offline');
console.error('System health check failed:', error);
addMessageToUI('error', 'Unable to connect to the medical system. Please ensure the backend server is running on localhost:5000.');
}
}
async function startNewSession() {
if (systemStatus !== 'connected') {
console.error('Cannot start session - system not connected');
addMessageToUI('error', 'System is not connected. Please refresh the page and try again.');
return false;
}
console.log('Starting new session...');
updateStatusIndicator('connecting', 'Starting session...');
try {
const response = await fetch(`${API_BASE_URL}/start_session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to start session');
}
const data = await response.json();
sessionId = data.session_id;
updateStatusIndicator('connected', 'Session Active');
console.log('New session started:', sessionId);
return true;
} catch (error) {
console.error('Session start error:', error);
sessionId = null;
updateStatusIndicator('error', 'Session Failed');
addMessageToUI('error', `Could not start session: ${error.message}`);
return false;
}
}
// --- EVENT LISTENERS ---
function setupEventListeners() {
console.log('Setting up event listeners...');
// Text input handling
if (sendBtn) sendBtn.addEventListener('click', handleTextInput);
if (textInput) {
textInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleTextInput();
}
});
}
// Mode switching
if (modeTextBtn) modeTextBtn.addEventListener('click', () => switchMode('text'));
if (modeVoiceBtn) modeVoiceBtn.addEventListener('click', () => switchMode('voice'));
if (modeVideoBtn) modeVideoBtn.addEventListener('click', () => switchMode('video'));
// Voice controls
if (micBtn) micBtn.addEventListener('click', toggleListening);
if (continuousToggle) {
continuousToggle.addEventListener('change', (e) => {
isContinuousMode = e.target.checked;
console.log('Continuous mode toggled:', isContinuousMode);
if (recognition) {
recognition.continuous = isContinuousMode;
}
});
}
// File upload (optional)
if (fileUploadBtn && fileInput) {
fileUploadBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileUpload);
}
// Speech recognition events
if (recognition) {
recognition.onstart = () => {
console.log('Speech recognition started');
isListening = true;
if (micBtn) {
micBtn.classList.add('listening');
micBtn.innerHTML = '';
}
updateStatusIndicator('listening', 'Listening...');
};
recognition.onend = () => {
console.log('Speech recognition ended');
isListening = false;
if (micBtn) {
micBtn.classList.remove('listening');
micBtn.innerHTML = '';
}
updateStatusIndicator('connected', 'Session Active');
if (isContinuousMode && currentMode !== 'text') {
console.log('Restarting speech recognition in continuous mode');
setTimeout(() => recognition.start(), 1000);
}
};
recognition.onresult = (event) => {
const transcript = event.results[event.results.length - 1][0].transcript.trim();
console.log('Speech recognition result:', transcript);
if (textInput) textInput.value = transcript;
if (transcript) processUserQuery(transcript);
};
recognition.onerror = (event) => {
console.error('Speech recognition error:', event.error);
addMessageToUI('error', `Speech recognition error: ${event.error}`);
updateStatusIndicator('error', 'Speech Error');
};
}
// Image capture modal
if (closeModalBtn) {
closeModalBtn.addEventListener('click', () => {
console.log('Image capture modal closed, starting capture...');
if (imageModal) imageModal.classList.add('hidden');
setTimeout(captureAndSendImage, 2000);
});
}
// System health check interval
setInterval(checkSystemHealth, 30000); // Check every 30 seconds
console.log('Event listeners setup complete');
}
// --- CORE LOGIC ---
function handleTextInput() {
const query = textInput ? textInput.value.trim() : '';
if (query) {
console.log('Processing text input:', query);
processUserQuery(query);
if (textInput) textInput.value = '';
}
}
async function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
console.log('File selected for upload:', file.name, file.type, file.size);
// Validate file type
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
console.error('Invalid file type:', file.type);
addMessageToUI('error', 'Please select a valid image file (JPEG, PNG, GIF, BMP, or WebP).');
return;
}
// Check file size (16MB limit)
if (file.size > 16 * 1024 * 1024) {
console.error('File size too large:', file.size);
addMessageToUI('error', 'File size too large. Please select an image under 16MB.');
return;
}
const query = prompt('Please describe what you want to know about this image:');
if (!query) return;
console.log('Processing file upload with query:', query);
await processImageQuery(query, file);
}
async function processUserQuery(query) {
console.log('Processing user query:', query);
addMessageToUI('user', query);
showLoading(true);
// Start a new session for every query
const sessionStarted = await startNewSession();
if (!sessionStarted) {
showLoading(false);
return;
}
try {
console.log('Sending query to API...');
const response = await fetch(`${API_BASE_URL}/process_query`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: sessionId,
query: query
})
});
const data = await response.json();
console.log('API response received:', data);
if (!response.ok) {
throw new Error(data.error || `Server error: ${response.status}`);
}
if (data.status === 'image_required') {
console.log('Image required for query');
handleImageRequest(data.message, data.category);
} else if (data.status === 'success') {
console.log('Query processed successfully');
handleApiResponse(data.response, data.category);
} else {
throw new Error('Unexpected response format');
}
} catch (error) {
console.error('Error processing query:', error);
handleApiResponse(`Sorry, I encountered an error: ${error.message}`, null, true);
} finally {
showLoading(false);
}
}
async function processImageQuery(query, imageFile) {
console.log('Processing image query:', query, 'with file:', imageFile.name);
addMessageToUI('user', query);
showLoading(true);
const sessionStarted = await startNewSession();
if (!sessionStarted) {
showLoading(false);
return;
}
try {
console.log('Sending image to API...');
const formData = new FormData();
formData.append('session_id', sessionId);
formData.append('photo', imageFile);
const response = await fetch(`${API_BASE_URL}/process_with_image`, {
method: 'POST',
body: formData
});
const data = await response.json();
console.log('Image API response received:', data);
if (!response.ok) {
throw new Error(data.error || `Server error: ${response.status}`);
}
if (data.status === 'success') {
console.log('Image processed successfully');
handleApiResponse(data.response, data.category);
} else {
throw new Error('Unexpected response format');
}
} catch (error) {
console.error('Error processing image query:', error);
handleApiResponse(`Sorry, I couldn't process the image: ${error.message}`, null, true);
} finally {
showLoading(false);
}
}
function handleApiResponse(responseData, category, isError = false) {
let message = '';
if (isError) {
message = responseData;
console.error('API Error:', responseData);
} else {
console.log('Handling API response:', responseData, 'Category:', category);
// Handle different response formats from the integrated backend
if (typeof responseData === 'string') {
message = responseData;
} else if (responseData && responseData.response) {
message = responseData.response;
} else if (responseData && responseData.data) {
message = responseData.data;
} else {
message = JSON.stringify(responseData, null, 2);
}
}
const type = isError ? 'error' : 'ai';
addMessageToUI(type, message, category);
// Add category info if available
if (category && !isError) {
console.log('Response category:', category);
}
// Text-to-speech for voice/video modes
if (!isError && (currentMode === 'voice' || currentMode === 'video')) {
console.log('Speaking response in', currentMode, 'mode');
speak(message);
}
}
function getCategoryInfo(category) {
const categoryMap = {
'disease_query': 'Category: General Disease Information',
'medicine_info': 'Category: Medicine Information & Analysis',
'skin_disease': 'Category: Skin Condition Analysis',
'report_reading': 'Category: Medical Report Interpretation'
};
return categoryMap[category] || null;
}
// --- UI & STATE MANAGEMENT ---
function updateStatusIndicator(status, message) {
if (!statusIndicator) return;
const colors = {
'ready': '#cccccc',
'connected': '#76ff03',
'connecting': '#ffeb3b',
'listening': '#2196f3',
'error': '#ff4d4d',
'disconnected': '#ff9800'
};
statusIndicator.textContent = `● ${message}`;
statusIndicator.style.color = colors[status] || '#cccccc';
console.log('Status updated:', status, message);
}
function switchMode(newMode) {
if (currentMode === newMode) return;
console.log('Switching mode from', currentMode, 'to', newMode);
// Cleanup current mode
if (currentMode === 'video') stopCamera();
if (isListening && recognition) recognition.stop();
currentMode = newMode;
// Update UI
document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active'));
const newModeBtn = document.getElementById(`mode-${newMode}`);
if (newModeBtn) newModeBtn.classList.add('active');
// Mode-specific setup
switch (newMode) {
case 'text':
document.body.classList.remove('body-video-mode');
if (voiceControls) voiceControls.classList.add('hidden');
if (micBtn) micBtn.classList.add('hidden');
if (sendBtn) sendBtn.classList.remove('hidden');
if (textInput) textInput.classList.remove('hidden');
if (videoFeed) videoFeed.style.display = 'none';
break;
case 'voice':
document.body.classList.remove('body-video-mode');
if (voiceControls) voiceControls.classList.remove('hidden');
if (micBtn) micBtn.classList.remove('hidden');
if (sendBtn) sendBtn.classList.add('hidden');
if (textInput) textInput.classList.add('hidden');
if (videoFeed) videoFeed.style.display = 'none';
break;
case 'video':
document.body.classList.add('body-video-mode');
if (voiceControls) voiceControls.classList.remove('hidden');
if (micBtn) micBtn.classList.remove('hidden');
if (sendBtn) sendBtn.classList.add('hidden');
if (textInput) textInput.classList.add('hidden');
if (videoFeed) videoFeed.style.display = 'block';
startCamera();
break;
}
console.log('Mode switch completed to:', newMode);
}
function addMessageToUI(sender, text, category = null) {
if (!chatBox) return;
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', `${sender}-message`);
// Add category class if provided
if (category) {
messageDiv.classList.add(`category-${category}`);
}
// Handle different message types
if (sender === 'system') {
messageDiv.style.fontStyle = 'italic';
messageDiv.style.color = '#888';
messageDiv.style.fontSize = '0.9em';
}
messageDiv.textContent = text;
chatBox.appendChild(messageDiv);
chatBox.scrollTop = chatBox.scrollHeight;
saveHistory();
}
function showLoading(show) {
if (loadingIndicator) {
loadingIndicator.style.display = show ? 'flex' : 'none';
}
console.log('Loading indicator:', show ? 'shown' : 'hidden');
}
// --- VOICE & VIDEO ---
function toggleListening() {
if (!recognition) {
console.error('Speech recognition not supported');
addMessageToUI('error', 'Speech recognition is not supported in this browser.');
return;
}
console.log('Toggling listening, current state:', isListening);
if (isListening) {
recognition.stop();
} else {
recognition.start();
}
}
function speak(text) {
if (!synth || synth.speaking) {
console.warn('Speech synthesis not available or already speaking');
return;
}
if (text && text.trim() !== '') {
console.log('Speaking text:', text.substring(0, 50) + '...');
const utterance = new SpeechSynthesisUtterance(text);
// Try to find a female voice
const voices = synth.getVoices();
const femaleVoice = voices.find(voice =>
voice.name.toLowerCase().includes('female') ||
voice.gender === 'female' ||
voice.name.toLowerCase().includes('zira') ||
voice.name.toLowerCase().includes('hazel')
);
if (femaleVoice) {
utterance.voice = femaleVoice;
console.log('Using voice:', femaleVoice.name);
}
// Apply voice settings
if (pitchSlider) utterance.pitch = parseFloat(pitchSlider.value);
if (rateSlider) utterance.rate = parseFloat(rateSlider.value);
utterance.onerror = (event) => {
console.error('Speech synthesis error:', event.error);
};
synth.speak(utterance);
}
}
async function startCamera() {
console.log('Starting camera...');
try {
videoStream = await navigator.mediaDevices.getUserMedia({
video: { width: 640, height: 480 },
audio: false
});
if (videoFeed) videoFeed.srcObject = videoStream;
console.log('Camera started successfully');
} catch (err) {
console.error("Error accessing camera:", err);
addMessageToUI('error', 'Could not access the camera. Please grant permission and try again.');
switchMode('voice');
}
}
function stopCamera() {
console.log('Stopping camera...');
if (videoStream) {
videoStream.getTracks().forEach(track => track.stop());
if (videoFeed) videoFeed.srcObject = null;
videoStream = null;
console.log('Camera stopped');
}
}
function handleImageRequest(message, category) {
console.log('Image request received:', message, 'Category:', category);
addMessageToUI('ai', message);
if (currentMode !== 'video') {
addMessageToUI('ai', "Please switch to Video mode to capture an image, or use the file upload option in Text mode.");
} else {
if (imageModal) imageModal.classList.remove('hidden');
}
}
async function captureAndSendImage() {
console.log('Capturing image...');
if (!videoStream || !sessionId) {
console.error('Cannot capture image - missing video stream or session');
addMessageToUI('error', 'Cannot capture image. Video stream or session is not active.');
return;
}
if (!videoFeed || !canvas) {
console.error('Video capture elements not found');
addMessageToUI('error', 'Video capture elements not found.');
return;
}
const videoTrack = videoStream.getVideoTracks()[0];
const settings = videoTrack.getSettings();
canvas.width = settings.width || 640;
canvas.height = settings.height || 480;
const context = canvas.getContext('2d');
context.drawImage(videoFeed, 0, 0, canvas.width, canvas.height);
canvas.toBlob(async (blob) => {
if (!blob) {
console.error('Failed to create image blob');
addMessageToUI('error', 'Failed to capture image.');
return;
}
console.log('Image captured, sending to server...');
const formData = new FormData();
formData.append('session_id', sessionId);
formData.append('photo', blob, 'capture.jpg');
showLoading(true);
try {
const response = await fetch(`${API_BASE_URL}/process_with_image`, {
method: 'POST',
body: formData
});
const data = await response.json();
console.log('Captured image processed:', data);
if (!response.ok) {
throw new Error(data.error || 'Image processing failed');
}
if (data.status === 'success') {
handleApiResponse(data.response, data.category);
} else {
throw new Error('Unexpected response format');
}
} catch (error) {
console.error('Error processing captured image:', error);
handleApiResponse(`Sorry, I couldn't process the captured image: ${error.message}`, null, true);
} finally {
showLoading(false);
}
}, 'image/jpeg', 0.8);
}
// --- LOCAL STORAGE ---
function saveHistory() {
if (chatBox) {
try {
localStorage.setItem('medibotChatHistory', chatBox.innerHTML);
} catch (error) {
console.warn('Could not save chat history:', error);
}
}
}
function loadHistory() {
try {
const history = localStorage.getItem('medibotChatHistory');
if (history && chatBox) {
chatBox.innerHTML = history;
chatBox.scrollTop = chatBox.scrollHeight;
console.log('Chat history loaded');
}
} catch (error) {
console.warn('Could not load chat history:', error);
}
}
function clearHistory() {
if (chatBox) {
chatBox.innerHTML = '';
localStorage.removeItem('medibotChatHistory');
console.log('Chat history cleared');
}
}
// --- UTILITY FUNCTIONS ---
function downloadKnowledgeBase() {
console.log('Knowledge base download requested');
}
// --- EXPOSE FUNCTIONS TO GLOBAL SCOPE (for debugging) ---
window.medibotDebug = {
clearHistory,
checkSystemHealth,
switchMode,
getCurrentMode: () => currentMode,
getSessionId: () => sessionId,
getSystemStatus: () => systemStatus
};
// --- START THE APP ---
initializeApp();
});