Spaces:
Sleeping
Sleeping
// Chat functionality | |
let conversations = []; | |
let messages = {}; | |
let pollingInterval; | |
// Mobile sidebar functionality | |
function toggleMobileSidebar() { | |
const sidebar = document.getElementById('sidebar'); | |
const overlay = document.getElementById('sidebarOverlay'); | |
if (sidebar && overlay) { | |
sidebar.classList.toggle('show'); | |
overlay.classList.toggle('show'); | |
// Prevent body scroll when sidebar is open | |
if (sidebar.classList.contains('show')) { | |
document.body.style.overflow = 'hidden'; | |
} else { | |
document.body.style.overflow = ''; | |
} | |
} | |
} | |
// Close mobile sidebar when clicking outside | |
function closeMobileSidebar() { | |
const sidebar = document.getElementById('sidebar'); | |
const overlay = document.getElementById('sidebarOverlay'); | |
if (sidebar && overlay) { | |
sidebar.classList.remove('show'); | |
overlay.classList.remove('show'); | |
document.body.style.overflow = ''; | |
} | |
} | |
// Initialize chat functionality | |
document.addEventListener('DOMContentLoaded', () => { | |
if (!document.body.classList.contains('chat-page')) { | |
return; | |
} | |
initializeChat(); | |
}); | |
async function initializeChat() { | |
try { | |
console.log('Initializing chat...'); | |
// Clear any existing data | |
conversations = []; | |
messages = {}; | |
window.currentConversation = null; | |
// Load fresh data | |
await loadConversations(); | |
startPolling(); | |
setupEventListeners(); | |
console.log('Chat initialized successfully'); | |
} catch (error) { | |
console.error('Failed to initialize chat:', error); | |
MainJS.showError('Failed to initialize chat'); | |
} | |
} | |
function setupEventListeners() { | |
// Message form | |
const messageForm = document.getElementById('messageForm'); | |
if (messageForm) { | |
messageForm.addEventListener('submit', handleSendMessage); | |
} | |
// Private chat form | |
const privateChatForm = document.getElementById('privateChatForm'); | |
if (privateChatForm) { | |
privateChatForm.addEventListener('submit', handleStartPrivateChat); | |
} | |
// Group chat form | |
const groupChatForm = document.getElementById('groupChatForm'); | |
if (groupChatForm) { | |
groupChatForm.addEventListener('submit', handleCreateGroup); | |
} | |
} | |
async function loadConversations() { | |
try { | |
const response = await MainJS.apiRequest('/api/conversations'); | |
if (response.success) { | |
conversations = response.conversations || []; | |
renderConversations(); | |
} else { | |
console.warn('Failed to load conversations:', response.message); | |
// Show empty state instead of error for unauthenticated users | |
conversations = []; | |
renderConversations(); | |
} | |
} catch (error) { | |
console.error('Failed to load conversations:', error); | |
conversations = []; | |
renderConversations(); | |
} | |
} | |
function renderConversations() { | |
const conversationsList = document.getElementById('conversationsList'); | |
if (!conversationsList) { | |
console.error('Conversations list element not found'); | |
return; | |
} | |
console.log('Rendering conversations:', conversations); | |
// FORCE CLEAR the conversations list first | |
conversationsList.innerHTML = ''; | |
if (conversations.length === 0) { | |
conversationsList.innerHTML = ` | |
<div class="text-center p-4 text-muted"> | |
<i class="fas fa-comments mb-3" style="font-size: 2rem;"></i> | |
<p>No conversations yet</p> | |
<small>Start a new chat to begin messaging</small> | |
</div> | |
`; | |
console.log('No conversations to display'); | |
return; | |
} | |
conversationsList.innerHTML = conversations.map(conv => { | |
const lastMessage = conv.last_message; | |
const isActive = window.currentConversation === conv.id; | |
return ` | |
<div class="conversation-item ${isActive ? 'active' : ''}" onclick="selectConversation('${conv.id}')"> | |
<div class="d-flex align-items-center"> | |
<div class="avatar bg-success me-3 position-relative"> | |
${conv.type === 'group' ? '<i class="fas fa-users"></i>' : conv.name[0].toUpperCase()} | |
${conv.type === 'private' && conv.online ? '<div class="online-indicator"></div>' : ''} | |
</div> | |
<div class="flex-grow-1"> | |
<div class="d-flex justify-content-between align-items-start"> | |
<div class="fw-bold">${MainJS.escapeHtml(conv.name)}</div> | |
${lastMessage ? `<small class="text-muted">${MainJS.formatTime(lastMessage.timestamp)}</small>` : ''} | |
</div> | |
${lastMessage ? ` | |
<div class="text-muted small text-truncate"> | |
${conv.type === 'group' ? `${MainJS.escapeHtml(lastMessage.sender_name)}: ` : ''} | |
${MainJS.escapeHtml(lastMessage.content)} | |
</div> | |
` : '<div class="text-muted small">No messages yet</div>'} | |
${conv.type === 'private' && !conv.online ? '<div class="text-offline small">offline</div>' : ''} | |
</div> | |
</div> | |
</div> | |
`; | |
}).join(''); | |
} | |
async function selectConversation(conversationId) { | |
try { | |
console.log('Selecting conversation:', conversationId); | |
// Validate that conversation exists | |
const conversation = conversations.find(c => c.id === conversationId); | |
if (!conversation) { | |
console.error('Conversation not found:', conversationId); | |
MainJS.showError('Conversation not found. Please refresh and try again.'); | |
return; | |
} | |
window.currentConversation = conversationId; | |
// Update UI | |
document.querySelectorAll('.conversation-item').forEach(item => { | |
item.classList.remove('active'); | |
}); | |
// Find and activate the clicked conversation | |
const clickedItem = document.querySelector(`[onclick*="${conversationId}"]`); | |
if (clickedItem) { | |
clickedItem.classList.add('active'); | |
} | |
// Show chat container | |
const welcomeScreen = document.getElementById('welcomeScreen'); | |
const chatContainer = document.getElementById('chatContainer'); | |
if (welcomeScreen) welcomeScreen.style.display = 'none'; | |
if (chatContainer) { | |
chatContainer.style.display = 'flex'; | |
console.log('Chat container displayed'); | |
} else { | |
console.error('Chat container not found'); | |
} | |
// Close mobile sidebar when conversation is selected | |
if (window.innerWidth < 768) { | |
closeMobileSidebar(); | |
} | |
// Update chat header first | |
updateChatHeader(conversationId); | |
// Load conversation details | |
console.log('Loading messages for conversation:', conversationId); | |
await loadMessages(conversationId); | |
// Mark messages as seen | |
markMessagesAsSeen(conversationId); | |
console.log('Conversation selected successfully'); | |
} catch (error) { | |
console.error('Error selecting conversation:', error); | |
MainJS.showError('Failed to load conversation'); | |
} | |
} | |
async function loadMessages(conversationId) { | |
try { | |
console.log('Loading messages for conversation ID:', conversationId); | |
const response = await MainJS.apiRequest(`/api/messages/${conversationId}`); | |
console.log('Messages API response:', response); | |
if (response.success) { | |
messages[conversationId] = response.messages || []; | |
console.log('Messages loaded:', response.messages.length); | |
renderMessages(conversationId); | |
} else { | |
console.error('API error:', response.message); | |
// Even if API fails, show the chat interface with empty state | |
messages[conversationId] = []; | |
renderMessages(conversationId); | |
MainJS.showError('Failed to load messages: ' + response.message); | |
} | |
} catch (error) { | |
console.error('Failed to load messages:', error); | |
// Show empty chat interface even on error | |
messages[conversationId] = []; | |
renderMessages(conversationId); | |
MainJS.showError('Connection error while loading messages'); | |
} | |
} | |
function renderMessages(conversationId) { | |
console.log('Rendering messages for conversation:', conversationId); | |
const chatMessages = document.getElementById('chatMessages'); | |
console.log('Chat messages element:', chatMessages); | |
console.log('Messages data:', messages[conversationId]); | |
if (!chatMessages) { | |
console.error('Chat messages element not found!'); | |
return; | |
} | |
if (!messages[conversationId]) { | |
console.error('No messages found for conversation:', conversationId); | |
return; | |
} | |
const conversationMessages = messages[conversationId]; | |
console.log('Number of messages to render:', conversationMessages.length); | |
if (conversationMessages.length === 0) { | |
console.log('No messages, showing empty state'); | |
chatMessages.innerHTML = ` | |
<div class="text-center text-muted"> | |
<i class="fas fa-comment-dots mb-2" style="font-size: 2rem;"></i> | |
<p>No messages yet</p> | |
<small>Send the first message to start the conversation</small> | |
</div> | |
`; | |
return; | |
} | |
chatMessages.innerHTML = conversationMessages.map(msg => { | |
const isCurrentUser = msg.sender_id === getCurrentUserId(); | |
const messageClass = isCurrentUser ? 'sent' : 'received'; | |
return ` | |
<div class="message-item ${messageClass}"> | |
<div class="message-content"> | |
${!isCurrentUser && getConversationType(conversationId) === 'group' ? | |
`<div class="small text-muted mb-1">${MainJS.escapeHtml(msg.sender_name)}</div>` : ''} | |
<div class="message-bubble"> | |
${MainJS.escapeHtml(msg.content)} | |
</div> | |
<div class="message-time"> | |
${MainJS.formatMessageTime(msg.timestamp)} | |
${isCurrentUser ? getMessageStatusIcon(msg) : ''} | |
</div> | |
</div> | |
</div> | |
`; | |
}).join(''); | |
// Scroll to bottom | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
} | |
function getMessageStatusIcon(message) { | |
const seenCount = message.seen_by ? message.seen_by.length : 0; | |
const conversation = conversations.find(c => c.id === window.currentConversation); | |
const totalParticipants = conversation ? conversation.participants.length : 1; | |
if (seenCount >= totalParticipants) { | |
return '<span class="message-status seen"><i class="fas fa-check-double"></i></span>'; | |
} else if (seenCount > 1) { | |
return '<span class="message-status delivered"><i class="fas fa-check-double"></i></span>'; | |
} else { | |
return '<span class="message-status sent"><i class="fas fa-check"></i></span>'; | |
} | |
} | |
function updateChatHeader(conversationId) { | |
const chatHeader = document.getElementById('chatHeader'); | |
const conversation = conversations.find(c => c.id === conversationId); | |
if (!chatHeader || !conversation) return; | |
chatHeader.innerHTML = ` | |
<div class="d-flex align-items-center"> | |
<div class="avatar bg-success me-3 position-relative"> | |
${conversation.type === 'group' ? '<i class="fas fa-users"></i>' : conversation.name[0].toUpperCase()} | |
${conversation.type === 'private' && conversation.online ? '<div class="online-indicator"></div>' : ''} | |
</div> | |
<div> | |
<div class="fw-bold">${MainJS.escapeHtml(conversation.name)}</div> | |
<small> | |
${conversation.type === 'group' | |
? `${conversation.participants.length} members` | |
: conversation.online ? 'online' : 'offline' | |
} | |
</small> | |
</div> | |
</div> | |
`; | |
} | |
async function handleSendMessage(event) { | |
event.preventDefault(); | |
if (!window.currentConversation) { | |
MainJS.showError('Please select a conversation first'); | |
return; | |
} | |
const messageInput = document.getElementById('messageInput'); | |
const content = messageInput.value.trim(); | |
if (!content) return; | |
try { | |
const response = await MainJS.apiRequest('/api/send_message', { | |
method: 'POST', | |
body: JSON.stringify({ | |
conversation_id: window.currentConversation, | |
content: content | |
}) | |
}); | |
if (response.success) { | |
messageInput.value = ''; | |
// Add message to local state | |
if (!messages[window.currentConversation]) { | |
messages[window.currentConversation] = []; | |
} | |
messages[window.currentConversation].push(response.message); | |
// Re-render messages | |
renderMessages(window.currentConversation); | |
// Update conversations list | |
await loadConversations(); | |
} else { | |
throw new Error(response.message); | |
} | |
} catch (error) { | |
console.error('Failed to send message:', error); | |
MainJS.showError('Failed to send message'); | |
} | |
} | |
async function markMessagesAsSeen(conversationId) { | |
try { | |
await MainJS.apiRequest('/api/mark_seen', { | |
method: 'POST', | |
body: JSON.stringify({ | |
conversation_id: conversationId | |
}) | |
}); | |
} catch (error) { | |
console.error('Failed to mark messages as seen:', error); | |
} | |
} | |
function startPolling() { | |
// Poll for new messages every 2 seconds | |
pollingInterval = setInterval(async () => { | |
try { | |
// Reload conversations to get latest messages | |
await loadConversations(); | |
// If a conversation is selected, reload its messages | |
if (window.currentConversation) { | |
await loadMessages(window.currentConversation); | |
markMessagesAsSeen(window.currentConversation); | |
} | |
} catch (error) { | |
console.error('Polling error:', error); | |
} | |
}, 2000); | |
} | |
function stopPolling() { | |
if (pollingInterval) { | |
clearInterval(pollingInterval); | |
pollingInterval = null; | |
} | |
} | |
// Modal functions | |
function startPrivateChat() { | |
const modal = new bootstrap.Modal(document.getElementById('newChatModal')); | |
modal.hide(); | |
setTimeout(() => { | |
const privateChatModal = new bootstrap.Modal(document.getElementById('privateChatModal')); | |
privateChatModal.show(); | |
// Reset form | |
document.getElementById('privateChatForm').reset(); | |
document.getElementById('userPreview').style.display = 'none'; | |
document.getElementById('startChatBtn').style.display = 'none'; | |
window.foundUser = null; | |
}, 300); | |
} | |
function startGroupChat() { | |
const modal = new bootstrap.Modal(document.getElementById('newChatModal')); | |
modal.hide(); | |
setTimeout(() => { | |
const groupChatModal = new bootstrap.Modal(document.getElementById('groupChatModal')); | |
groupChatModal.show(); | |
// Reset form | |
document.getElementById('groupChatForm').reset(); | |
document.getElementById('groupMembers').innerHTML = ` | |
<div class="input-group mb-2"> | |
<input type="text" class="form-control member-input" placeholder="Enter user ID"> | |
<button type="button" class="btn btn-outline-success" onclick="addMemberField()"> | |
<i class="fas fa-plus"></i> | |
</button> | |
</div> | |
`; | |
}, 300); | |
} | |
async function findUser() { | |
const userIdInput = document.getElementById('userIdInput'); | |
const uniqueId = userIdInput.value.trim().toUpperCase(); | |
if (!uniqueId) { | |
MainJS.showError('Please enter a user ID'); | |
return; | |
} | |
try { | |
const response = await MainJS.apiRequest('/api/find_user', { | |
method: 'POST', | |
body: JSON.stringify({ unique_id: uniqueId }) | |
}); | |
if (response.success) { | |
window.foundUser = response.user; | |
const userPreview = document.getElementById('userPreview'); | |
userPreview.innerHTML = ` | |
<div class="d-flex align-items-center"> | |
<div class="avatar bg-success me-3"> | |
${response.user.name[0].toUpperCase()} | |
</div> | |
<div> | |
<div class="fw-bold">${MainJS.escapeHtml(response.user.name)}</div> | |
<small class="text-muted">${response.user.unique_id}</small> | |
<div class="small ${response.user.online ? 'text-success' : 'text-muted'}"> | |
${response.user.online ? 'online' : 'offline'} | |
</div> | |
</div> | |
</div> | |
`; | |
userPreview.style.display = 'block'; | |
document.getElementById('startChatBtn').style.display = 'block'; | |
} else { | |
MainJS.showError(response.message); | |
document.getElementById('userPreview').style.display = 'none'; | |
document.getElementById('startChatBtn').style.display = 'none'; | |
window.foundUser = null; | |
} | |
} catch (error) { | |
console.error('Failed to find user:', error); | |
MainJS.showError('Failed to find user'); | |
} | |
} | |
async function handleStartPrivateChat(event) { | |
event.preventDefault(); | |
if (!window.foundUser) { | |
MainJS.showError('Please find a user first'); | |
return; | |
} | |
try { | |
const response = await MainJS.apiRequest('/api/start_conversation', { | |
method: 'POST', | |
body: JSON.stringify({ | |
type: 'private', | |
target_user_id: window.foundUser.user_id | |
}) | |
}); | |
if (response.success) { | |
// Close modal | |
const modal = bootstrap.Modal.getInstance(document.getElementById('privateChatModal')); | |
modal.hide(); | |
// Reload conversations | |
await loadConversations(); | |
// Select the new conversation | |
selectConversation(response.conversation_id); | |
MainJS.showSuccess('Private chat started'); | |
} else { | |
throw new Error(response.message); | |
} | |
} catch (error) { | |
console.error('Failed to start private chat:', error); | |
MainJS.showError('Failed to start private chat'); | |
} | |
} | |
function addMemberField() { | |
const groupMembers = document.getElementById('groupMembers'); | |
const memberInputs = groupMembers.querySelectorAll('.member-input'); | |
if (memberInputs.length >= 9) { | |
MainJS.showError('Maximum 9 members allowed'); | |
return; | |
} | |
const newField = document.createElement('div'); | |
newField.className = 'input-group mb-2'; | |
newField.innerHTML = ` | |
<input type="text" class="form-control member-input" placeholder="Enter user ID"> | |
<button type="button" class="btn btn-outline-danger" onclick="removeMemberField(this)"> | |
<i class="fas fa-minus"></i> | |
</button> | |
`; | |
groupMembers.appendChild(newField); | |
} | |
function removeMemberField(button) { | |
button.closest('.input-group').remove(); | |
} | |
async function handleCreateGroup(event) { | |
event.preventDefault(); | |
const groupNameInput = document.getElementById('groupNameInput'); | |
const groupName = groupNameInput.value.trim(); | |
if (!groupName) { | |
MainJS.showError('Please enter a group name'); | |
return; | |
} | |
const memberInputs = document.querySelectorAll('.member-input'); | |
const memberIds = Array.from(memberInputs) | |
.map(input => input.value.trim().toUpperCase()) | |
.filter(id => id.length > 0); | |
if (memberIds.length < 2) { | |
MainJS.showError('Please add at least 2 members'); | |
return; | |
} | |
if (memberIds.length > 9) { | |
MainJS.showError('Maximum 9 members allowed'); | |
return; | |
} | |
try { | |
// Find all users first to validate | |
const userPromises = memberIds.map(id => | |
MainJS.apiRequest('/api/find_user', { | |
method: 'POST', | |
body: JSON.stringify({ unique_id: id }) | |
}) | |
); | |
const userResults = await Promise.all(userPromises); | |
const validUsers = []; | |
for (let i = 0; i < userResults.length; i++) { | |
if (userResults[i].success) { | |
validUsers.push(userResults[i].user.user_id); | |
} else { | |
MainJS.showError(`User ${memberIds[i]} not found`); | |
return; | |
} | |
} | |
// Create group | |
const response = await MainJS.apiRequest('/api/start_conversation', { | |
method: 'POST', | |
body: JSON.stringify({ | |
type: 'group', | |
group_name: groupName, | |
participant_ids: validUsers | |
}) | |
}); | |
if (response.success) { | |
// Close modal | |
const modal = bootstrap.Modal.getInstance(document.getElementById('groupChatModal')); | |
modal.hide(); | |
// Reload conversations | |
await loadConversations(); | |
// Select the new conversation | |
selectConversation(response.conversation_id); | |
MainJS.showSuccess('Group created successfully'); | |
} else { | |
throw new Error(response.message); | |
} | |
} catch (error) { | |
console.error('Failed to create group:', error); | |
MainJS.showError('Failed to create group'); | |
} | |
} | |
// Helper functions | |
function getCurrentUserId() { | |
return window.currentUserId || null; | |
} | |
function getConversationType(conversationId) { | |
const conversation = conversations.find(c => c.id === conversationId); | |
return conversation ? conversation.type : 'private'; | |
} | |
// Cleanup on page unload | |
window.addEventListener('beforeunload', () => { | |
stopPolling(); | |
}); | |