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 = `

EasyFarms Assistant

`; 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 += `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(); });