Spaces:
Running
Running
| document.addEventListener('DOMContentLoaded', () => { | |
| // --- DOM Elements --- | |
| const chatArea = document.getElementById('chat-area'); | |
| const closeSidebarBtn = document.getElementById('close-sidebar-btn'); | |
| const messageInput = document.getElementById('message-input'); | |
| const sendBtn = document.getElementById('send-btn'); | |
| const newChatBtn = document.getElementById('new-chat-btn'); | |
| const chatMessages = document.getElementById('chat-messages'); | |
| const chatHistoryList = document.getElementById('chat-history-list'); | |
| const menuToggle = document.getElementById('menu-toggle'); | |
| const appContainer = document.getElementById('app-container'); | |
| const chatTitle = document.getElementById('chat-title'); | |
| const imageUploadBtn = document.getElementById('image-upload-btn'); | |
| const imageUploadInput = document.getElementById('image-upload-input'); | |
| const imagePreviewContainer = document.getElementById('image-preview-container'); | |
| const imagePreview = document.getElementById('image-preview'); | |
| const removeImageBtn = document.getElementById('remove-image-btn'); | |
| // --- State --- | |
| let currentSessionId = null; | |
| let conversationsCache = {}; | |
| let selectedImageFile = null; | |
| let imgbbApiKey = ''; | |
| // --- Initialization --- | |
| const init = async () => { | |
| if (!messageInput || !sendBtn || !imageUploadBtn || !imagePreviewContainer) { | |
| console.error("Critical UI elements not found. Check HTML IDs."); | |
| return; | |
| } | |
| await fetchConfig(); | |
| loadCache(); | |
| await renderChatHistoryFromAPI(); | |
| // Event Listeners | |
| sendBtn.addEventListener('click', sendMessage); | |
| messageInput.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); | |
| imageUploadBtn.addEventListener('click', () => imageUploadInput.click()); | |
| imageUploadInput.addEventListener('change', handleImageSelect); | |
| removeImageBtn.addEventListener('click', removeSelectedImage); | |
| // Sidebar Listeners | |
| newChatBtn.addEventListener('click', () => { startNewChat(); closeSidebar(); }); | |
| menuToggle.addEventListener('click', (event) => { | |
| event.stopPropagation(); | |
| appContainer.classList.toggle('sidebar-visible'); | |
| }); | |
| closeSidebarBtn.addEventListener('click', (event) => { | |
| event.stopPropagation(); | |
| closeSidebar(); | |
| }); | |
| chatArea.addEventListener('click', () => { | |
| if (appContainer.classList.contains('sidebar-visible')) { | |
| closeSidebar(); | |
| } | |
| }); | |
| }; | |
| const fetchConfig = async () => { | |
| try { | |
| const response = await fetch('/config'); | |
| const config = await response.json(); | |
| imgbbApiKey = config.imgbb_api_key; | |
| if (!imgbbApiKey) { console.error("ImgBB API Key is missing."); } | |
| } catch (error) { console.error('Failed to fetch config:', error); } | |
| }; | |
| // --- Core Chat Functions --- | |
| const sendMessage = async () => { | |
| const messageText = messageInput.value.trim(); | |
| if (!messageText && !selectedImageFile) return; | |
| displayMessage({ role: 'user', content: messageText, imageUrl: selectedImageFile }); | |
| const loadingIndicator = displayMessage({ role: 'assistant', content: 'Thinking...', isLoading: true }); | |
| try { | |
| let permanentImageUrl = null; | |
| if (selectedImageFile) { permanentImageUrl = await uploadImageToImgBB(selectedImageFile); } | |
| const formData = new FormData(); | |
| formData.append('message', messageText); | |
| formData.append('session_id', currentSessionId); | |
| if (permanentImageUrl) { formData.append('image_url', permanentImageUrl); } | |
| const response = await fetch('/chat', { method: 'POST', body: formData }); | |
| if (!response.ok) throw new Error('Network response was not ok.'); | |
| const data = await response.json(); | |
| chatMessages.removeChild(loadingIndicator); | |
| displayMessage({ role: 'assistant', content: data.response }); | |
| updateCache(data.session_id, { content: messageText, imageUrl: permanentImageUrl }, { content: data.response }); | |
| } catch (error) { | |
| console.error('Error sending message:', error); | |
| loadingIndicator.innerHTML = marked.parse("Sorry, something went wrong."); | |
| loadingIndicator.classList.remove('loading'); | |
| } finally { | |
| messageInput.value = ''; messageInput.style.height = 'auto'; removeSelectedImage(); | |
| } | |
| }; | |
| const uploadImageToImgBB = async (imageFile) => { | |
| if (!imgbbApiKey) throw new Error("ImgBB API Key not configured."); | |
| const formData = new FormData(); | |
| formData.append('image', imageFile); | |
| formData.append('key', imgbbApiKey); | |
| const response = await fetch('https://api.imgbb.com/1/upload', { method: 'POST', body: formData }); | |
| const result = await response.json(); | |
| if (result.success) { return result.data.url; } | |
| else { throw new Error(result.error.message || 'Image upload failed.'); } | |
| }; | |
| // --- Caching and State Management --- | |
| const startNewChat = () => { | |
| currentSessionId = null; | |
| chatMessages.innerHTML = `<div class="welcome-message"><h1>EasyFarms Assistant</h1></div>`; | |
| chatTitle.textContent = "New Chat"; | |
| updateActiveChatItem(); | |
| }; | |
| const switchChat = async (sessionId) => { | |
| currentSessionId = sessionId; | |
| chatMessages.innerHTML = ''; | |
| if (conversationsCache[sessionId] && conversationsCache[sessionId].messages) { | |
| conversationsCache[sessionId].messages.forEach(displayMessage); | |
| } else { | |
| const loading = displayMessage({ role: 'assistant', content: 'Loading chat history...', isLoading: true }); | |
| try { | |
| const response = await fetch(`/history/messages/${sessionId}`); | |
| const messages = await response.json(); | |
| if (!conversationsCache[sessionId]) conversationsCache[sessionId] = {}; | |
| conversationsCache[sessionId].messages = messages; | |
| saveCache(); | |
| chatMessages.removeChild(loading); | |
| messages.forEach(displayMessage); | |
| } catch (error) { | |
| loading.innerHTML = marked.parse("Failed to load chat history."); | |
| } | |
| } | |
| chatTitle.textContent = conversationsCache[sessionId]?.title || "Chat"; | |
| updateActiveChatItem(); | |
| closeSidebar(); | |
| }; | |
| const updateCache = (sessionId, userTurn, assistantTurn) => { | |
| const isNewChat = !currentSessionId; | |
| currentSessionId = sessionId; | |
| if (isNewChat) { | |
| const title = (userTurn.content || "Image Query").substring(0, 30) + '...'; | |
| conversationsCache[sessionId] = { title, messages: [] }; | |
| const item = document.createElement('div'); | |
| item.className = 'chat-history-item'; | |
| item.textContent = title; | |
| item.dataset.sessionId = sessionId; | |
| item.addEventListener('click', () => switchChat(sessionId)); | |
| chatHistoryList.prepend(item); | |
| } | |
| const userMessage = { role: 'user', content: userTurn.content }; | |
| if (userTurn.imageUrl) userMessage.imageUrl = userTurn.imageUrl; | |
| const assistantMessage = { role: 'assistant', content: assistantTurn.content }; | |
| conversationsCache[sessionId].messages.push(userMessage, assistantMessage); | |
| saveCache(); | |
| updateActiveChatItem(); | |
| }; | |
| const displayMessage = (message) => { | |
| const { role, content, imageUrl, isLoading } = message; | |
| const sender = role || message.sender; | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.classList.add('message', `${sender}-message`); | |
| let htmlContent = ''; | |
| const imageSrc = (typeof imageUrl === 'object' && imageUrl instanceof File) ? URL.createObjectURL(imageUrl) : imageUrl; | |
| if (imageSrc) { htmlContent += `<img src="${imageSrc}" alt="User upload" class="user-upload">`; } | |
| if (content) { htmlContent += marked.parse(content); } | |
| messageDiv.innerHTML = htmlContent || (isLoading ? '...' : ''); | |
| if (isLoading) messageDiv.classList.add('loading'); | |
| chatMessages.appendChild(messageDiv); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| return messageDiv; | |
| }; | |
| // --- Image Preview Handling --- | |
| const handleImageSelect = (event) => { | |
| const file = event.target.files[0]; | |
| if (file) { | |
| selectedImageFile = file; | |
| imagePreview.src = URL.createObjectURL(file); | |
| imagePreviewContainer.style.display = 'block'; | |
| } | |
| }; | |
| const removeSelectedImage = () => { | |
| selectedImageFile = null; | |
| imageUploadInput.value = ''; | |
| imagePreviewContainer.style.display = 'none'; | |
| imagePreview.src = '#'; | |
| }; | |
| // --- LocalStorage Cache & Sidebar Rendering --- | |
| const saveCache = () => localStorage.setItem('easyfarms_cache', JSON.stringify(conversationsCache)); | |
| const loadCache = () => { | |
| const saved = localStorage.getItem('easyfarms_cache'); | |
| if (saved) conversationsCache = JSON.parse(saved); | |
| }; | |
| const renderChatHistoryFromAPI = async () => { | |
| try { | |
| const response = await fetch('/history/sessions'); | |
| const sessions = await response.json(); | |
| chatHistoryList.innerHTML = ''; | |
| sessions.reverse().forEach(session => { | |
| if (!conversationsCache[session.session_id]) conversationsCache[session.session_id] = {}; | |
| conversationsCache[session.session_id].title = session.title; | |
| const item = document.createElement('div'); | |
| item.className = 'chat-history-item'; | |
| item.textContent = session.title; | |
| item.dataset.sessionId = session.session_id; | |
| item.addEventListener('click', () => switchChat(session.session_id)); | |
| chatHistoryList.appendChild(item); | |
| }); | |
| saveCache(); | |
| updateActiveChatItem(); | |
| } catch (error) { console.error("Failed to render chat history from API:", error); } | |
| }; | |
| const updateActiveChatItem = () => { | |
| document.querySelectorAll('.chat-history-item').forEach(item => { | |
| item.classList.toggle('active', item.dataset.sessionId === currentSessionId); | |
| }); | |
| }; | |
| const closeSidebar = () => appContainer.classList.remove('sidebar-visible'); | |
| // --- Start the Application --- | |
| init(); | |
| }); |