// App State const state = { currentChatId: null, chats: [], isGenerating: false, theme: localStorage.getItem('theme') || 'system', sidebarOpen: false }; // DOM Elements const elements = { chatContainer: document.getElementById('chat-container'), messagesWrapper: document.getElementById('messages-wrapper'), messageInput: document.getElementById('message-input'), sendBtn: document.getElementById('send-btn'), welcomeScreen: document.getElementById('welcome-screen'), sidebar: document.getElementById('sidebar'), toggleSidebar: document.getElementById('toggle-sidebar'), themeToggle: document.getElementById('theme-toggle'), newChatBtn: document.getElementById('new-chat-btn'), suggestionBtns: document.querySelectorAll('.suggestion-btn'), attachBtn: document.getElementById('attach-btn') }; // Initialize document.addEventListener('DOMContentLoaded', () => { initializeTheme(); loadChats(); setupEventListeners(); setupTextareaAutoResize(); }); // Theme Management function initializeTheme() { if (state.theme === 'dark' || (state.theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) { document.documentElement.classList.add('dark'); document.getElementById('hljs-theme').href = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css'; } else { document.documentElement.classList.remove('dark'); document.getElementById('hljs-theme').href = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css'; } } function toggleTheme() { if (document.documentElement.classList.contains('dark')) { document.documentElement.classList.remove('dark'); state.theme = 'light'; document.getElementById('hljs-theme').href = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css'; } else { document.documentElement.classList.add('dark'); state.theme = 'dark'; document.getElementById('hljs-theme').href = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css'; } localStorage.setItem('theme', state.theme); feather.replace(); } // Chat Management function loadChats() { const saved = localStorage.getItem('chats'); if (saved) { state.chats = JSON.parse(saved); if (state.chats.length > 0 && !state.currentChatId) { loadChat(state.chats[0].id); } } if (!state.currentChatId) { createNewChat(); } updateSidebar(); } function saveChats() { localStorage.setItem('chats', JSON.stringify(state.chats)); updateSidebar(); } function createNewChat() { const id = Date.now().toString(); const chat = { id, title: 'New Chat', messages: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; state.chats.unshift(chat); state.currentChatId = id; saveChats(); renderMessages(); elements.messageInput.focus(); } function loadChat(id) { state.currentChatId = id; renderMessages(); updateSidebar(); // On mobile, close sidebar if (window.innerWidth < 1024) { elements.sidebar.shadowRoot.querySelector('aside').classList.add('-translate-x-full'); } } function deleteChat(id, event) { event.stopPropagation(); state.chats = state.chats.filter(c => c.id !== id); if (state.currentChatId === id) { if (state.chats.length > 0) { loadChat(state.chats[0].id); } else { createNewChat(); } } saveChats(); } function updateChatTitle(chatId, firstMessage) { const chat = state.chats.find(c => c.id === chatId); if (chat && chat.title === 'New Chat') { chat.title = firstMessage.slice(0, 30) + (firstMessage.length > 30 ? '...' : ''); saveChats(); } } // Message Handling async function sendMessage(content = null) { const text = content || elements.messageInput.value.trim(); if (!text || state.isGenerating) return; elements.messageInput.value = ''; elements.messageInput.style.height = 'auto'; elements.welcomeScreen?.classList.add('hidden'); const userMessage = { id: Date.now().toString(), role: 'user', content: text, timestamp: new Date().toISOString() }; const chat = state.chats.find(c => c.id === state.currentChatId); if (!chat) return; chat.messages.push(userMessage); chat.updatedAt = new Date().toISOString(); updateChatTitle(state.currentChatId, text); saveChats(); renderMessages(); scrollToBottom(); // Show typing indicator showTypingIndicator(); try { state.isGenerating = true; updateSendButton(); // Build conversation context from previous messages // Limit to last 10 messages to avoid token limits const recentMessages = chat.messages.slice(-10); const contextPrompt = buildContextPrompt(recentMessages); // Use Pollinations AI with context // The approach: include context in the system/instruction part of the prompt const response = await fetch('https://text.pollinations.ai/', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ messages: [ { role: "system", content: "You are a helpful AI assistant. You have access to the conversation history and should maintain context throughout the conversation. Be concise but thorough in your responses." }, ...recentMessages.slice(0, -1).map(m => ({ role: m.role, content: m.content })), { role: "user", content: text } ], model: "openai-large", seed: Math.floor(Math.random() * 1000), jsonMode: false }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.text(); hideTypingIndicator(); const assistantMessage = { id: (Date.now() + 1).toString(), role: 'assistant', content: data, timestamp: new Date().toISOString() }; chat.messages.push(assistantMessage); chat.updatedAt = new Date().toISOString(); saveChats(); renderMessages(); scrollToBottom(); } catch (error) { console.error('Error:', error); hideTypingIndicator(); showError('Failed to get response. Please try again. Error: ' + error.message); } finally { state.isGenerating = false; updateSendButton(); } } function buildContextPrompt(messages) { // This function formats the conversation history for context // It's used as a fallback if the API doesn't support message arrays let context = "Previous conversation context:\n\n"; for (let i = 0; i < messages.length - 1; i++) { const msg = messages[i]; const role = msg.role === 'user' ? 'User' : 'Assistant'; context += `${role}: ${msg.content}\n\n`; } const lastMessage = messages[messages.length - 1]; context += `User: ${lastMessage.content}\n\nAssistant:`; return context; } // Rendering function renderMessages() { const chat = state.chats.find(c => c.id === state.currentChatId); if (!chat) return; if (chat.messages.length === 0) { elements.welcomeScreen?.classList.remove('hidden'); elements.messagesWrapper.innerHTML = ''; elements.messagesWrapper.appendChild(elements.welcomeScreen); return; } elements.welcomeScreen?.classList.add('hidden'); // Clear current messages (except welcome screen) Array.from(elements.messagesWrapper.children).forEach(child => { if (child.id !== 'welcome-screen') { child.remove(); } }); chat.messages.forEach((msg, index) => { const msgEl = document.createElement('chat-message'); msgEl.setAttribute('content', msg.content); msgEl.setAttribute('role', msg.role); msgEl.setAttribute('timestamp', msg.timestamp); msgEl.classList.add('message-enter'); msgEl.style.animationDelay = `${index * 0.1}s`; elements.messagesWrapper.appendChild(msgEl); }); scrollToBottom(); } function showTypingIndicator() { const indicator = document.createElement('typing-indicator'); indicator.id = 'typing-indicator'; elements.messagesWrapper.appendChild(indicator); scrollToBottom(); } function hideTypingIndicator() { const indicator = document.getElementById('typing-indicator'); indicator?.remove(); } function showError(message) { const errorEl = document.createElement('div'); errorEl.className = 'p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl text-red-600 dark:text-red-400 text-center message-enter'; errorEl.textContent = message; elements.messagesWrapper.appendChild(errorEl); scrollToBottom(); } function scrollToBottom() { elements.chatContainer.scrollTo({ top: elements.chatContainer.scrollHeight, behavior: 'smooth' }); } function updateSendButton() { elements.sendBtn.disabled = state.isGenerating; elements.sendBtn.innerHTML = state.isGenerating ? '
' : ''; feather.replace(); } function updateSidebar() { if (elements.sidebar) { elements.sidebar.setAttribute('chats', JSON.stringify(state.chats)); elements.sidebar.setAttribute('active-chat', state.currentChatId); } } // Event Listeners function setupEventListeners() { // Send button elements.sendBtn.addEventListener('click', () => sendMessage()); // Enter key (Shift+Enter for new line) elements.messageInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // Suggestion buttons elements.suggestionBtns.forEach(btn => { btn.addEventListener('click', () => { const text = btn.querySelector('.text-sm').textContent; elements.messageInput.value = text; sendMessage(); }); }); // Theme toggle elements.themeToggle.addEventListener('click', toggleTheme); // New chat elements.newChatBtn.addEventListener('click', createNewChat); // Sidebar toggle (mobile) elements.toggleSidebar?.addEventListener('click', () => { if (elements.sidebar) { elements.sidebar.toggle(); } }); // Attach button (placeholder) elements.attachBtn.addEventListener('click', () => { alert('File upload coming soon!'); }); // Handle sidebar events elements.sidebar?.addEventListener('chat-selected', (e) => { loadChat(e.detail.chatId); }); elements.sidebar?.addEventListener('chat-deleted', (e) => { deleteChat(e.detail.chatId, e.detail.event); }); elements.sidebar?.addEventListener('new-chat', () => { createNewChat(); }); } // Textarea Auto-resize function setupTextareaAutoResize() { const textarea = elements.messageInput; textarea.addEventListener('input', () => { textarea.style.height = 'auto'; textarea.style.height = Math.min(textarea.scrollHeight, 128) + 'px'; }); } // Expose to window for components window.appState = state; window.loadChat = loadChat; window.deleteChat = deleteChat; window.createNewChat = createNewChat;