Spaces:
Sleeping
Sleeping
// 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 = '<i class="fas fa-paperclip"></i>'; // 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 = ` | |
<button id="dialogImage">Image</button> | |
<button id="dialogSearch">Search</button> | |
`; | |
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 = '<i class="fas fa-volume-up"></i>'; // 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 |