// application/static/js/components/uiManager.js import Initialize from "./initialize.js"; import Chat from "./chat.js"; import RenderSymbols from "./renderSymbols.js"; class UIManager{ constructor(){ this.initializer = new Initialize(this); this.chat = new Chat(this); this.renderSymbols = new RenderSymbols(); this.menu = document.getElementById('menu'); this.hamburger = document.getElementById('hamburger'); this.messagesDiv = document.getElementById('messages'); this.container = document.getElementById('container') this.prevChatsCont = document.getElementById('prevChatsCont'); this.textBox = document.getElementById('textBox'); this.sendBtn = document.getElementById('sendBtn'); this.newChat = document.getElementById('newChat'); this.models = document.getElementById('models'); this.initialized = false; this.webSearchBtn = document.getElementById('webSearch'); this.webSearch = false; this.aiDiv; this.userDiv; this.aiP; this.userP; this.imageMode = false; this.dialog = null; // Add the image input element (hidden) this.imageInput = document.createElement('input'); this.imageInput.type = 'file'; this.imageInput.id = 'imageInput'; this.imageInput.accept = 'image/*'; this.imageInput.style.display = 'none'; document.body.appendChild(this.imageInput); // Append to body // Add the paperclip icon (label for the input) this.imageUploadLabel = document.createElement('label'); this.imageUploadLabel.htmlFor = 'imageInput'; // Connect label to input this.imageUploadLabel.className = 'image-upload-label'; this.imageUploadLabel.innerHTML = ''; // FontAwesome icon this.textBox.parentNode.insertBefore(this.imageUploadLabel, this.textBox); // Event listener for the paperclip icon (image upload) this.imageUploadLabel.addEventListener('click', () => { this.imageInput.click(); // Trigger the file input }); this.imageInput.addEventListener('change', () => { this.handleImageUpload(); }); console.log("UIManager constructor called"); // Log constructor } async run(){ console.log("UIManager run called"); // Log run await this.initializer.initialize(); this.handleTextBoxHeight(); this.events(); } events(){ this.sendBtn.addEventListener('click',()=>{ this.send(); }) window.addEventListener('keydown',(e)=>{ if (e.key === '/' && document.activeElement === this.textBox) { e.preventDefault(); this.showDialog(); } if(e.key=='Enter' && !this.sendBtn.disabled){ this.send(); } }) this.newChat.addEventListener('click', async ()=>{ await this.initializer.initialize(); }) this.webSearchBtn.addEventListener('click', ()=>{ if(this.webSearch){ this.webSearchBtn.style.color = 'white'; } else{ this.webSearchBtn.style.color = 'rgba(30,30,250,0.8)'; } this.webSearch = !this.webSearch; }) document.getElementById('closeAlert').onclick = ()=>{ document.getElementById('alert').style.display = 'none' } } showDialog() { if (this.dialog) { this.dialog.remove(); this.dialog = null; return; } this.dialog = document.createElement('div'); this.dialog.style.position = 'absolute'; this.dialog.style.top = `${this.textBox.offsetTop - 100}px`; this.dialog.style.left = `${this.textBox.offsetLeft}px`; this.dialog.style.backgroundColor = 'white'; this.dialog.style.border = '1px solid black'; this.dialog.style.padding = '10px'; this.dialog.style.zIndex = '1000'; this.dialog.innerHTML = ` `; document.body.appendChild(this.dialog); document.getElementById('dialogImage').addEventListener('click', () => { this.imageMode = true; this.textBox.value = "/image "; this.dialog.remove(); this.dialog = null; }); document.getElementById('dialogSearch').addEventListener('click', () => { this.webSearch = true; this.webSearchBtn.style.color = 'rgba(30,30,250,0.8)'; this.textBox.value = "/search "; this.dialog.remove(); this.dialog = null; }); } async send(){ let promptText = this.textBox.value; let imageBase64 = null; if (this.imageMode) { const file = await this.getImageFile(); if (file) { imageBase64 = await this.getBase64(file); } promptText = promptText.replace(/^\/image\s*/, ''); this.imageMode = false; // Reset image mode after processing } this.appendUserMsg(promptText, imageBase64); // Pass image data this.appendAiMsg(); await this.chat.chat(); } async getImageFile() { return new Promise(resolve => { const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; input.onchange = e => { const file = e.target.files[0]; resolve(file); }; input.click(); }); } async getBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); } async handleImageUpload() { const file = this.imageInput.files[0]; if (!file) return; this.imageMode = true; // Set image mode const reader = new FileReader(); reader.onload = (e) => { // Display image preview if (this.userDiv) { const imgPreview = document.createElement('img'); imgPreview.src = e.target.result; imgPreview.className = 'uploaded-image-preview'; this.userDiv.appendChild(imgPreview); // Add to current userDiv } // Generate and display caption this.generateAndDisplayCaption(e.target.result); } reader.readAsDataURL(file); this.imageInput.value = ''; // Clear the input } async generateAndDisplayCaption(imageData) { try { const caption = await this.initializer.image_captioning.generate_caption(imageData); if (this.userDiv) { // Check if userDiv exists. const captionElement = document.createElement('div'); captionElement.className = 'image-caption'; captionElement.textContent = `Caption: ${caption}`; this.userDiv.appendChild(captionElement); } } catch (error) { console.error("Error generating caption:", error); } } appendUserMsg(msg=false, imageBase64 = null){ this.userDiv = document.createElement('div'); this.userDiv.className = 'user'; this.userP = document.createElement('p'); if(msg){ this.userP.innerText = msg; } else{ this.userP.innerText = this.textBox.value; } if (imageBase64) { const img = document.createElement('img'); img.src = imageBase64; img.style.maxWidth = '100%'; img.style.height = 'auto'; this.userDiv.appendChild(img); } this.userDiv.appendChild(this.userP); this.messagesDiv.appendChild(this.userDiv); } appendAiMsg(msg=false){ this.aiDiv = document.createElement('div'); this.aiDiv.className = 'ai'; this.aiP = document.createElement('p'); if(msg){ this.aiP.innerText=msg; } this.aiDiv.appendChild(this.aiP); this.messagesDiv.appendChild(this.aiDiv); } handleTextBoxHeight() { this.textBox.oninput = () => { this.textBox.style.height = 'auto'; if (this.textBox.scrollHeight <= 150) { if(this.textBox.scrollHeight>60){ this.textBox.style.height = `${this.textBox.scrollHeight}px`; } } else { this.textBox.style.height = '150px'; } }; } addChat(){ const prevChat = document.createElement('div'); prevChat.innerText = this.initializer.convTitle; prevChat.className = 'prevChat'; prevChat.id = this.initializer.convId; this.prevChatsCont.prepend(prevChat); prevChat.style.backgroundColor = 'rgb(53, 53, 53)'; prevChat.addEventListener('click', ()=>{ this.initializer.reInitialize(prevChat.id); }) } addReadAloudButton(parentDiv) { const btn = document.createElement('button'); btn.className = 'read-aloud-btn'; btn.innerHTML = ''; // FontAwesome icon btn.addEventListener('click', () => { this.speakText(parentDiv.querySelector('p').innerText); }); parentDiv.appendChild(btn); } async speakText(text) { try { const response = await fetch(`/tts?text=${encodeURIComponent(text)}`); if (!response.ok) { console.error("TTS request failed:", response.status, response.statusText); return; } const audioBlob = await response.blob(); const audioUrl = URL.createObjectURL(audioBlob); const audio = new Audio(audioUrl); audio.play(); } catch (error) { console.error("Error during TTS:", error); } } displayMemory(memoryText) { let memoryDisplay = document.querySelector('.memory-display'); if (!memoryDisplay) { memoryDisplay = document.createElement('div'); memoryDisplay.className = 'memory-display'; this.messagesDiv.parentNode.insertBefore(memoryDisplay, this.messagesDiv); //show above messages } memoryDisplay.textContent = `Memory: ${memoryText}`; } showReasoning() { const reasoningDisplay = document.createElement('div'); reasoningDisplay.className = 'reasoning-display'; reasoningDisplay.textContent = 'Reasoning...'; reasoningDisplay.dataset.startTime = Date.now(); // Store start time this.messagesDiv.appendChild(reasoningDisplay); // Add to messages return reasoningDisplay; } updateReasoningTime(reasoningDisplay) { const endTime = Date.now(); const startTime = parseInt(reasoningDisplay.dataset.startTime, 10); const duration = (endTime - startTime) / 1000; // in seconds reasoningDisplay.textContent = `Reasoning... (${duration.toFixed(2)}s)`; } } export default UIManager