Spaces:
Running
Running
The sidebar doesn’t extend and the chat function doesn’t work. Make sure that it can handle conversations with context and not just single messages
a8c3c60 verified | // 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 | |
| ? '<div class="typing-dot w-2 h-2 bg-white rounded-full inline-block mx-0.5"></div><div class="typing-dot w-2 h-2 bg-white rounded-full inline-block mx-0.5"></div><div class="typing-dot w-2 h-2 bg-white rounded-full inline-block mx-0.5"></div>' | |
| : '<i data-feather="send" class="w-5 h-5"></i>'; | |
| 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; |