document.addEventListener('DOMContentLoaded', () => { const chatWindow = document.getElementById('chat-window'); const chatInput = document.getElementById('chat-input'); const sendBtn = document.getElementById('send-btn'); const modeItems = document.querySelectorAll('.mode-item'); const currentModeBadge = document.getElementById('current-mode-badge'); const newChatBtn = document.getElementById('new-chat-btn'); const chatHistoryList = document.getElementById('chat-history'); // Settings elements const creativityRange = document.getElementById('creativity-range'); const creativityVal = document.getElementById('creativity-val'); const limitsToggle = document.getElementById('limits-toggle'); const themeSwatches = document.querySelectorAll('.theme-swatch'); const saveSettingsBtn = document.getElementById('save-settings-btn'); let currentMode = 'regular'; let currentChatId = null; // --- Sidebar & Mode Switching --- modeItems.forEach(item => { item.addEventListener('click', () => { if (item.classList.contains('active')) return; modeItems.forEach(i => i.classList.remove('active')); item.classList.add('active'); currentMode = item.dataset.mode; currentModeBadge.textContent = item.querySelector('span').textContent; startNewChat(); }); }); function startNewChat() { currentChatId = null; chatWindow.innerHTML = `

New ${currentMode} Chat

How can I help you in this mode?

`; // Remove active class from history items document.querySelectorAll('.history-item').forEach(i => i.classList.remove('active')); } newChatBtn.addEventListener('click', startNewChat); // --- History Loading --- async function loadHistory() { try { const resp = await fetch('api/history.php?action=list'); const data = await resp.json(); if (data.success) { renderHistory(data.chats); } } catch (e) { console.error('Failed to load history:', e); } } function renderHistory(chats) { if (!chats || chats.length === 0) { chatHistoryList.innerHTML = '
No recent chats
'; return; } chatHistoryList.innerHTML = chats.map(chat => `
${escapeHtml(chat.title)}
`).join(''); // Add event listeners to history items chatHistoryList.querySelectorAll('.history-item').forEach(item => { item.addEventListener('click', (e) => { if (e.target.classList.contains('delete-chat')) { deleteChat(e.target.dataset.id); return; } loadChat(item.dataset.id); }); }); } function getModeIcon(mode) { switch(mode) { case 'coding': return 'code-slash'; case 'game': return 'controller'; case 'app': return 'window'; default: return 'chat-left-dots'; } } async function loadChat(chatId) { if (currentChatId == chatId) return; chatWindow.innerHTML = '
'; try { const resp = await fetch(`api/history.php?action=messages&chat_id=${chatId}`); const data = await resp.json(); if (data.success) { currentChatId = chatId; currentMode = data.mode; // Update Sidebar Mode UI modeItems.forEach(i => { if (i.dataset.mode === currentMode) i.classList.add('active'); else i.classList.remove('active'); }); // Update badge const activeModeItem = Array.from(modeItems).find(i => i.dataset.mode === currentMode); if (activeModeItem) { currentModeBadge.textContent = activeModeItem.querySelector('span').textContent; } // Render messages chatWindow.innerHTML = ''; data.messages.forEach(msg => { appendMessage(msg.role, msg.content, false); // false = don't animate existing // Special handling for game/app mode launch buttons if ((currentMode === 'game' || currentMode === 'app') && msg.role === 'assistant') { addLaunchButton(msg.content); } }); // Highlight active history item document.querySelectorAll('.history-item').forEach(i => { if (i.dataset.id == chatId) i.classList.add('active'); else i.classList.remove('active'); }); chatWindow.scrollTop = chatWindow.scrollHeight; } } catch (e) { console.error(e); chatWindow.innerHTML = '
Failed to load chat
'; } } async function deleteChat(chatId) { if (!confirm('Are you sure you want to delete this chat?')) return; try { const resp = await fetch(`api/history.php?action=delete&chat_id=${chatId}`); const data = await resp.json(); if (data.success) { if (currentChatId == chatId) startNewChat(); loadHistory(); } } catch (e) { console.error(e); } } // --- Chat Logic --- async function sendMessage() { const message = chatInput.value.trim(); if (!message) return; const isNewChat = !currentChatId; // Clear input and disable chatInput.value = ''; chatInput.style.height = 'auto'; toggleLoading(true); // Append User Message appendMessage('user', message); try { const response = await fetch('api/chat.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message, mode: currentMode, chat_id: currentChatId, creativity: creativityRange.value, limits_off: limitsToggle.checked ? 1 : 0 }) }); const data = await response.json(); if (data.success) { currentChatId = data.chat_id; appendMessage('assistant', data.message); // Special handling for game/app mode if (currentMode === 'game' || currentMode === 'app') { addLaunchButton(data.message); } // If it was a new chat, refresh history to show the title if (isNewChat) { loadHistory(); } } else { appendMessage('assistant', 'Error: ' + (data.error || 'Unknown error')); } } catch (error) { appendMessage('assistant', 'Error: ' + error.message); } finally { toggleLoading(false); } } function appendMessage(role, text, animate = true) { if (role === 'system') return; // Remove empty state if present const emptyState = chatWindow.querySelector('.my-auto'); if (emptyState) emptyState.remove(); const msgDiv = document.createElement('div'); msgDiv.className = `message message-${role} ${animate ? 'animate-fade-in' : ''}`; msgDiv.innerHTML = formatText(text); chatWindow.appendChild(msgDiv); chatWindow.scrollTop = chatWindow.scrollHeight; } function formatText(text) { let formatted = text; // Code blocks: ```[lang]\n[code]``` formatted = formatted.replace(/```(\w+)?\s*([\s\S]*?)```/g, (match, lang, code) => { const safeCode = code.trim().replace(/`/g, '\`'); return `
${lang || 'code'} Copy
${escapeHtml(code.trim())}
`; }); // Simple line breaks for non-code parts if (!formatted.includes('
'); } return formatted; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function addLaunchButton(content) { const match = content.match(/```(?:html|xml)?\s*([\s\S]*?)```/i); let codeToLaunch = match ? match[1] : content; const hasHtmlTags = / { if (!codeToLaunch.toLowerCase().includes('AI Generated App${codeToLaunch}`; } const blob = new Blob([codeToLaunch], { type: 'text/html' }); const url = URL.createObjectURL(blob); window.open(url, '_blank'); }; chatWindow.lastElementChild.appendChild(btn); } } function toggleLoading(isLoading) { sendBtn.disabled = isLoading; chatInput.disabled = isLoading; if (isLoading) { sendBtn.innerHTML = ''; } else { sendBtn.innerHTML = ''; chatInput.focus(); } } sendBtn.addEventListener('click', sendMessage); chatInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); chatInput.addEventListener('input', () => { chatInput.style.height = 'auto'; chatInput.style.height = (chatInput.scrollHeight) + 'px'; }); // --- Settings & Themes --- creativityRange.addEventListener('input', () => { creativityVal.textContent = creativityRange.value; }); themeSwatches.forEach(swatch => { swatch.addEventListener('click', () => { const theme = swatch.dataset.theme; document.documentElement.setAttribute('data-theme', theme); themeSwatches.forEach(s => s.classList.remove('active')); swatch.classList.add('active'); }); }); saveSettingsBtn.addEventListener('click', async () => { const theme = document.documentElement.getAttribute('data-theme'); const settings = { theme: theme, creativity: creativityRange.value, limits_off: limitsToggle.checked ? '1' : '0' }; saveSettingsBtn.disabled = true; saveSettingsBtn.innerHTML = ' Saving...'; try { const resp = await fetch('api/settings.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings) }); const data = await resp.json(); if (data.success) { const modal = bootstrap.Modal.getInstance(document.getElementById('settingsModal')); if (modal) modal.hide(); showToast('Settings saved successfully!'); } } catch (e) { console.error(e); showToast('Failed to save settings', 'danger'); } finally { saveSettingsBtn.disabled = false; saveSettingsBtn.textContent = 'Save changes'; } }); function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.className = `position-fixed bottom-0 start-50 translate-middle-x mb-4 bg-${type} text-white px-4 py-2 rounded-pill shadow-lg animate-fade-in`; toast.style.zIndex = '2050'; toast.innerHTML = ` ${message}`; document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; toast.style.transition = 'opacity 0.5s ease'; setTimeout(() => toast.remove(), 500); }, 3000); } // Set active theme swatch on load const currentTheme = document.documentElement.getAttribute('data-theme'); const activeSwatch = document.querySelector(`.theme-swatch[data-theme="${currentTheme}"]`); if (activeSwatch) activeSwatch.classList.add('active'); // Initial load loadHistory(); });