document.addEventListener('DOMContentLoaded', function() {
console.log('DOM Content Loaded');
const chatMessages = document.getElementById('chat-messages');
const userInput = document.getElementById('user-input');
const sendButton = document.getElementById('send-button');
const avatar = document.getElementById('avatar');
const statusContainer = document.getElementById('status-container');
// Base API URL detection
const DEFAULT_API_BASE = 'http://127.0.0.1:7860';
const origin = window.location.origin;
const API_BASE_URL = origin && origin !== 'null' ? origin : DEFAULT_API_BASE;
const buildApiUrl = (path) => {
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
return `${API_BASE_URL}${normalizedPath}`;
};
let initializationComplete = false;
let initializationPollingActive = false;
// Debug: Log if elements are found
console.log('Elements found:', {
chatMessages: !!chatMessages,
userInput: !!userInput,
sendButton: !!sendButton,
avatar: !!avatar,
statusContainer: !!statusContainer
});
// Emotion bars
const joyBar = document.getElementById('joy-bar');
const sadnessBar = document.getElementById('sadness-bar');
const angerBar = document.getElementById('anger-bar');
const fearBar = document.getElementById('fear-bar');
const curiosityBar = document.getElementById('curiosity-bar');
// Check server health on load
checkServerHealth();
// Determine availability state
checkAvailability();
// Auto-resize textarea as user types
if (userInput) {
userInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
// Reset height if empty
if (this.value.length === 0) {
this.style.height = '';
}
});
// Send message when Enter key is pressed (without Shift)
userInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
console.log('Enter key pressed, sending message');
sendMessage();
}
});
}
// Add keyboard shortcut for sending messages
document.addEventListener('keydown', function(e) {
// Command+Enter or Ctrl+Enter to send
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
e.preventDefault();
console.log('Cmd/Ctrl+Enter pressed, sending message');
sendMessage();
}
});
if (sendButton) {
sendButton.addEventListener('click', function() {
console.log('Send button clicked');
sendMessage();
});
} else {
console.error('Send button not found!');
}
function checkServerHealth() {
fetch(buildApiUrl('/health'))
.then(response => response.json())
.then(data => {
if (data.status === 'ok') {
if (data.missing_deepseek_key) {
showStatusMessage('DEEPSEEK_API_KEY missing. Chat will remain unavailable until it is configured.', true);
return;
}
if (data.deepseek_available) {
showStatusMessage('Connected to DeepSeek API', false);
} else {
showStatusMessage('Warning: DeepSeek API not available. Using fallback responses.', true);
}
} else {
showStatusMessage('Server health check failed', true);
}
})
.catch(error => {
showStatusMessage('Failed to connect to server', true);
console.error('Health check error:', error);
});
}
function showStatusMessage(message, isError = false) {
statusContainer.textContent = message;
statusContainer.className = isError ?
'status-container error' : 'status-container success';
statusContainer.style.display = 'block';
setTimeout(() => {
statusContainer.style.display = 'none';
}, 5000);
}
function ensureLoadingScreenVisible() {
const loadingScreen = document.getElementById('loading-screen');
const chatContainer = document.getElementById('chat-container');
if (loadingScreen) {
loadingScreen.style.display = 'flex';
loadingScreen.classList.remove('fade-out');
}
if (chatContainer) {
chatContainer.style.display = 'none';
}
}
function onAppReady() {
if (initializationComplete) {
return;
}
initializationComplete = true;
initializationPollingActive = false;
const loadingScreen = document.getElementById('loading-screen');
const chatContainer = document.getElementById('chat-container');
const loadingStatus = document.getElementById('loading-status');
const loadingProgress = document.getElementById('loading-progress');
if (loadingProgress) {
loadingProgress.style.width = '100%';
}
if (loadingStatus) {
loadingStatus.textContent = 'Ready! Welcome to Galatea AI';
}
if (loadingScreen) {
loadingScreen.classList.add('fade-out');
setTimeout(() => {
loadingScreen.style.display = 'none';
if (chatContainer) {
chatContainer.style.display = 'flex';
}
}, 500);
} else if (chatContainer) {
chatContainer.style.display = 'flex';
}
startAvatarPolling();
}
function checkAvailability() {
fetch(buildApiUrl('/api/availability'))
.then(response => response.json())
.then(data => {
if (!data.available) {
if (data.status === 'missing_deepseek_key') {
const errorPath = data.error_page || '/error';
window.location.href = `${API_BASE_URL}${errorPath}`;
return;
}
if (data.status === 'initializing') {
ensureLoadingScreenVisible();
if (!initializationPollingActive) {
initializationPollingActive = true;
checkInitializationStatus();
}
return;
}
}
onAppReady();
})
.catch(error => {
console.error('Error checking availability:', error);
setTimeout(checkAvailability, 3000);
});
}
function sendMessage() {
console.log('sendMessage called');
if (!userInput) {
console.error('userInput element not found');
return;
}
const message = userInput.value.trim();
console.log('Message to send:', message);
if (message.length === 0) {
console.log('Empty message, not sending');
return;
}
// Add user message to chat
addMessage(message, 'user');
// Clear input
userInput.value = '';
userInput.style.height = '';
// Show typing indicator
addTypingIndicator();
// Send to backend
console.log('Sending to backend...');
fetch(buildApiUrl('/api/chat'), {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ message: message })
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
// Remove typing indicator
removeTypingIndicator();
// Add bot response
addMessage(data.response, 'bot');
// Update avatar
updateAvatar(data.avatar_shape);
// Update emotion bars
updateEmotionBars(data.emotions);
})
.catch(error => {
console.error('Error:', error);
removeTypingIndicator();
addMessage('Sorry, I encountered an error processing your request.', 'system');
showStatusMessage('Error connecting to server: ' + error.message, true);
});
}
function addMessage(text, type) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}`;
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';
contentDiv.textContent = text;
messageDiv.appendChild(contentDiv);
chatMessages.appendChild(messageDiv);
// Scroll to bottom
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function addTypingIndicator() {
const typingDiv = document.createElement('div');
typingDiv.className = 'message bot typing-indicator';
typingDiv.innerHTML = '';
typingDiv.id = 'typing-indicator';
chatMessages.appendChild(typingDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function removeTypingIndicator() {
const typingIndicator = document.getElementById('typing-indicator');
if (typingIndicator) {
typingIndicator.remove();
}
}
function updateAvatar(shape) {
// Remove all shape classes first
avatar.classList.remove('circle', 'triangle', 'square');
// Add the new shape class
if (shape === 'Circle' || shape === 'Triangle' || shape === 'Square') {
avatar.classList.add(shape.toLowerCase());
}
}
function updateEmotionBars(emotions) {
if (!emotions) return;
// Update each emotion bar
joyBar.style.width = `${emotions.joy * 100}%`;
sadnessBar.style.width = `${emotions.sadness * 100}%`;
angerBar.style.width = `${emotions.anger * 100}%`;
fearBar.style.width = `${emotions.fear * 100}%`;
curiosityBar.style.width = `${emotions.curiosity * 100}%`;
}
// Add initialization status check with loading screen
let initCheckCount = 0;
function checkInitializationStatus() {
if (initializationComplete) {
return;
}
const loadingScreen = document.getElementById('loading-screen');
const chatContainer = document.getElementById('chat-container');
const loadingStatus = document.getElementById('loading-status');
const loadingProgress = document.getElementById('loading-progress');
initCheckCount++;
const progress = Math.min(initCheckCount * 10, 90); // Cap at 90% until actually ready
if (loadingProgress) {
loadingProgress.style.width = `${progress}%`;
}
fetch(buildApiUrl('/api/is_initialized'))
.then(response => response.json())
.then(data => {
if (data.missing_deepseek_key) {
const errorPath = data.error_page || '/error';
window.location.href = `${API_BASE_URL}${errorPath}`;
return;
}
if (data.is_initialized) {
onAppReady();
return;
}
if (data.initializing) {
if (loadingStatus) {
loadingStatus.textContent = 'Initializing AI components...';
}
setTimeout(checkInitializationStatus, 2000); // Check again in 2 seconds
return;
}
if (loadingStatus) {
loadingStatus.textContent = 'Initialization taking longer than expected...';
}
setTimeout(checkInitializationStatus, 3000);
})
.catch(error => {
console.error('Error checking status:', error);
if (loadingStatus) {
loadingStatus.textContent = 'Error connecting to server. Retrying...';
}
setTimeout(checkInitializationStatus, 3000);
});
}
// Add avatar polling functionality for more responsive updates
let avatarPollInterval;
let lastAvatarShape = '';
function startAvatarPolling() {
// Clear any existing interval
if (avatarPollInterval) {
clearInterval(avatarPollInterval);
}
// Poll every 1 second
avatarPollInterval = setInterval(pollAvatarState, 1000);
console.log("Avatar polling started");
}
function pollAvatarState() {
fetch(buildApiUrl('/api/avatar'))
.then(response => response.json())
.then(data => {
if (data.avatar_shape && data.is_initialized) {
// Only update if shape has changed
if (lastAvatarShape !== data.avatar_shape) {
console.log(`Avatar shape changed: ${lastAvatarShape} -> ${data.avatar_shape}`);
updateAvatar(data.avatar_shape);
lastAvatarShape = data.avatar_shape;
}
// Update sentiment display if available
if (data.sentiment) {
updateSentiment(data.sentiment);
}
// Update emotion bars if available
if (data.emotions) {
updateEmotionBars(data.emotions);
}
}
})
.catch(error => {
console.error('Error polling avatar:', error);
});
}
// Add this new function to update sentiment visualization
function updateSentiment(sentimentData) {
const avatar = document.getElementById('avatar');
// Remove existing sentiment classes
avatar.classList.remove('sentiment-positive', 'sentiment-negative', 'sentiment-neutral', 'sentiment-angry');
// Add the appropriate sentiment class
if (sentimentData.sentiment === "positive") {
avatar.classList.add('sentiment-positive');
} else if (sentimentData.sentiment === "negative") {
avatar.classList.add('sentiment-negative');
} else if (sentimentData.sentiment === "angry") {
avatar.classList.add('sentiment-angry');
} else {
avatar.classList.add('sentiment-neutral');
}
console.log(`Updated sentiment display: ${sentimentData.sentiment}`);
}
});