lollms-webui/static/js/chat.js
2023-04-24 00:19:15 +02:00

408 lines
19 KiB
JavaScript

// Dirty fix for disabling speech synth for firefox browsers :()
const userAgent = navigator.userAgent;
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
console.log('Text copied to clipboard');
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
function addMessage(sender, message, id, rank = 0, can_edit = false) {
const chatWindow = document.getElementById('chat-window');
const chatForm = document.getElementById('chat-form');
const userInput = document.getElementById('user-input');
console.log(id)
const messageElement = document.createElement('div');
messageElement.classList.add('bg-secondary', 'drop-shadow-sm', 'p-4', 'mx-6', 'my-4', 'flex', 'flex-col', 'space-x-2', 'rounded-lg', 'shadow-lg', 'bg-gray-300', 'text-black', 'dark:text-gray-200', 'dark:bg-gray-800', 'hover:bg-gray-400', 'dark:hover:bg-gray-700', 'transition-colors', 'duration-300');
messageElement.id = id
//messageElement.classList.add(sender);
messageElement.setAttribute('id', id);
const senderElement = document.createElement('div');
senderElement.classList.add('font-normal', 'underline', 'text-sm');
senderElement.innerHTML = sender;
const messageTextElement = document.createElement('div');
messageTextElement.classList.add('font-medium', 'text-md', 'whitespace-pre-wrap');
messageTextElement.innerHTML = message;
// Create a hidden div element needed to buffer responses before commiting them to the visible message
const hiddenElement = document.createElement('div');
hiddenElement.style.display = 'none';
hiddenElement.innerHTML = '';
messageElement.appendChild(senderElement);
messageElement.appendChild(messageTextElement);
if (can_edit) {
// Create buttons container
const buttonsContainer = document.createElement('div');
// Add the 'flex' class to the div
buttonsContainer.classList.add('flex');
// Add the 'justify-end' class to the div
buttonsContainer.classList.add('justify-end');
// Set the width and height of the container to 100%
buttonsContainer.style.width = '100%';
buttonsContainer.style.height = '100%';
const clipboardButton = document.createElement('button');
clipboardButton.classList.add('bg-gray-500', 'hover:bg-gray-700', 'text-white', 'font-bold', 'py-0', 'px-0', "w-10", "h-10");
clipboardButton.style.float = 'right'; // set the float property to right
clipboardButton.style.display = 'inline-block'
clipboardButton.innerHTML = '';
const clipboardImg = document.createElement('img');
clipboardImg.src = "/static/images/copy.png";
clipboardImg.classList.add('py-1', 'px-1', 'rounded', 'w-10', 'h-10');
clipboardButton.title = "clipboard message";
clipboardButton.appendChild(clipboardImg)
clipboardButton.addEventListener('click', () => {
copyToClipboard(messageTextElement.innerText)
})
const resendButton = document.createElement('button');
resendButton.classList.add('bg-gray-500', 'hover:bg-gray-700', 'text-white', 'font-bold', 'py-0', 'px-0', "w-10", "h-10");
resendButton.style.float = 'right'; // set the float property to right
resendButton.style.display = 'inline-block'
resendButton.innerHTML = '';
const resendImg = document.createElement('img');
resendImg.src = "/static/images/refresh.png";
resendImg.classList.add('py-1', 'px-1', 'rounded', 'w-10', 'h-10');
resendButton.title = "Resend message";
resendButton.appendChild(resendImg)
resendButton.addEventListener('click', () => {
// get user input and clear input field
message = userInput.value;
userInput.value = '';
// add user message to chat window
const sendbtn = document.querySelector("#submit-input")
const waitAnimation = document.querySelector("#wait-animation")
const stopGeneration = document.querySelector("#stop-generation")
sendbtn.style.display = "none";
waitAnimation.style.display = "block";
stopGeneration.style.display = "block";
// local stuff
let messageTextElement_ = undefined
let hiddenElement_ = undefined
elements = addMessage("", "", 0, 0, can_edit = true);
fetch("/run_to", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: messageElement.id,
message: message
})
})
.then(function (response) {
const stream = new ReadableStream({
start(controller) {
const reader = response.body.getReader();
function push() {
reader.read().then(function (result) {
if (result.done) {
console.log(result)
sendbtn.style.display = "block";
waitAnimation.style.display = "none";
stopGeneration.style.display = "none";
controller.close();
return;
}
controller.enqueue(result.value);
push();
})
}
push();
}
});
const textDecoder = new TextDecoder();
const readableStreamDefaultReader = stream.getReader();
let entry_counter = 0
function readStream() {
readableStreamDefaultReader.read().then(function (result) {
if (result.done) {
console.log(result)
return;
}
text = textDecoder.decode(result.value);
// The server will first send a json containing information about the message just sent
if (entry_counter == 0) {
// We parse it and
infos = JSON.parse(text)
elements.setID(infos.response_id)
elements.setSender(infos.bot)
messageTextElement_ = elements['messageTextElement'];
hiddenElement_ = elements['hiddenElement'];
entry_counter++;
}
else {
entry_counter++;
prefix = "FINAL:";
if(text.startsWith(prefix)){
console.log("Final text found")
text = text.substring(prefix.length);
hiddenElement.innerHTML = text
messageTextElement.innerHTML = text
}
else{
// For the other enrtries, these are just the text of the chatbot
for (const char of text) {
txt = hiddenElement_.innerHTML;
if (char != '\f') {
txt += char
hiddenElement_.innerHTML = txt
messageTextElement_.innerHTML = txt
}
// scroll to bottom of chat window
chatWindow.scrollTop = chatWindow.scrollHeight;
}
}
}
readStream();
});
}
readStream();
});
});
const editButton = document.createElement('button');
editButton.classList.add('bg-gray-500', 'hover:bg-gray-700', 'text-white', 'font-bold', 'py-0', 'px-0', "w-10", "h-10", "rounded-l");
editButton.style.float = 'right'; // set the float property to right
editButton.style.display = 'inline-block'
editButton.innerHTML = '';
const editImg = document.createElement('img');
editImg.src = "/static/images/edit_discussion.png";
editImg.classList.add('py-1', 'px-1', 'rounded', 'w-10', 'h-10');
editButton.title = "Edit message";
editButton.appendChild(editImg)
editButton.addEventListener('click', () => {
const inputBlock = document.createElement('div')
inputBlock.classList.add('flex','flex-col');
const inputField = document.createElement('textarea');
inputField.type = 'text';
inputField.classList.add('font-medium', 'resize-y','h-24', 'text-md', 'border', 'border-gray-300', 'p-1');
inputField.value = messageTextElement.innerText;
//buttonsContainer.style.display = "none"
const saveButton = document.createElement('button');
saveButton.classList.add('bg-gray-500', 'hover:bg-gray-700', 'text-white', 'font-bold', 'py-2', 'px-4', 'my-2', 'ml-2');
saveButton.innerHTML = 'Save';
inputBlock.appendChild(inputField)
inputBlock.appendChild(saveButton)
saveButton.addEventListener('click', () => {
const newText = inputField.value;
messageTextElement.innerText = newText;
// make request to update message
const url = `/update_message?id=${id}&message=${newText}`;
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
else {
console.log("Updated")
}
})
.catch(error => {
console.error('There was a problem updating the message:', error);
});
//buttonsContainer.style.display = 'block'
messageElement.replaceChild(messageTextElement, inputBlock);
//messageElement.removeChild(inputField);
messageElement.removeChild(saveButton);
});
messageElement.replaceChild(inputBlock, messageTextElement);
inputField.focus();
});
const deleteButton = document.createElement('button');
deleteButton.classList.add('bg-gray-500', 'hover:bg-gray-700', 'text-white', 'font-bold', 'py-0', 'px-0', "w-10", "h-10");
deleteButton.style.float = 'right'; // set the float property to right
deleteButton.style.display = 'inline-block'
deleteButton.innerHTML = '';
const deleteImg = document.createElement('img');
deleteImg.src = "/static/images/delete_discussion.png";
deleteImg.classList.add('py-2', 'px-2', 'rounded', 'w-15', 'h-15');
deleteButton.title = "Delete message";
deleteButton.appendChild(deleteImg)
deleteButton.addEventListener('click', () => {
const url = `/delete_message?id=${id}`;
fetch(url)
.then(response => response.json())
.then(data => {
console.log(data.new_rank)
messageElement.style.display = "none"
})
.catch(error => {
console.error('There was a problem updating the message:', error);
});
});
const rank_up = document.createElement('button');
rank_up.classList.add('bg-gray-500', 'hover:bg-gray-700', 'text-white', 'font-bold', 'py-0', 'px-0', "w-10", "h-10");
rank_up.title = "Upvote";
rank_up.style.float = 'right'; // set the float property to right
rank_up.style.display = 'inline-block'
rank_up.innerHTML = '';
const thumbUpImg = document.createElement('img');
thumbUpImg.src = "/static/images/thumb_up.png";
thumbUpImg.classList.add('py-2', 'px-2', 'rounded', 'w-15', 'h-15');
rank_up.appendChild(thumbUpImg)
const thumbUpBadge = document.createElement('span');
thumbUpBadge.innerText = "";
thumbUpBadge.classList.add('inline-flex', 'items-center', 'justify-center', 'h-4', 'w-4', 'rounded-full', 'bg-red-500', 'text-white', 'text-xs', 'top-0', 'right-0');
rank_up.appendChild(thumbUpBadge)
rank_up.addEventListener('click', () => {
const url = `/message_rank_up?id=${id}`;
fetch(url)
.then(response => response.json())
.then(data => {
console.log(data.new_rank)
if (data.new_rank > 0) {
thumbUpBadge.innerText = `${data.new_rank}`
thumbDownBadge.innerText = ``
thumbUpBadge.display = `block`
thumbDownBadge.display = 'none'
}
else if (data.new_rank < 0) {
thumbUpBadge.innerText = ``
thumbDownBadge.innerText = `${data.new_rank}`
thumbUpBadge.display = `none`
thumbDownBadge.display = 'block'
}
else {
thumbUpBadge.innerText = ``
thumbDownBadge.innerText = ``
thumbUpBadge.display = `none`
thumbDownBadge.display = 'none'
}
})
.catch(error => {
console.error('There was a problem updating the message:', error);
});
});
const rank_down = document.createElement('button');
rank_down.classList.add('bg-gray-500', 'hover:bg-gray-700', 'text-white', 'font-bold', 'py-0', 'px-0', "w-10", "h-10");
rank_down.title = "Downvote";
rank_down.style.float = 'right'; // set the float property to right
rank_down.style.display = 'inline-block'
rank_down.innerHTML = '';
const thumbDownImg = document.createElement('img');
thumbDownImg.src = "/static/images/thumb_down.png";
thumbDownImg.classList.add('py-2', 'px-2', 'rounded', 'w-15', 'h-15');
rank_down.appendChild(thumbDownImg)
const thumbDownBadge = document.createElement('span');
thumbDownBadge.innerText = "";
thumbDownBadge.classList.add('inline-flex', 'items-center', 'justify-center', 'h-4', 'w-4', 'rounded-full', 'bg-red-500', 'text-white', 'text-xs', 'top-0', 'right-0');
rank_down.appendChild(thumbDownBadge)
rank_down.addEventListener('click', () => {
const url = `/message_rank_down?id=${id}`;
fetch(url)
.then(response => response.json())
.then(data => {
console.log(data)
if (data.new_rank > 0) {
thumbUpBadge.innerText = `${data.new_rank}`
thumbDownBadge.innerText = ``
thumbUpBadge.display = `block`
thumbDownBadge.display = 'none'
}
else if (data.new_rank < 0) {
thumbUpBadge.innerText = ``
thumbDownBadge.innerText = `${data.new_rank}`
thumbUpBadge.display = `none`
thumbDownBadge.display = 'block'
}
else {
thumbUpBadge.innerText = ``
thumbDownBadge.innerText = ``
thumbUpBadge.display = `none`
thumbDownBadge.display = 'none'
}
})
.catch(error => {
console.error('There was a problem updating the message:', error);
});
});
buttonsContainer.appendChild(editButton);
buttonsContainer.appendChild(clipboardButton);
buttonsContainer.appendChild(resendButton);
buttonsContainer.appendChild(deleteButton);
buttonsContainer.appendChild(rank_up);
buttonsContainer.appendChild(rank_down);
messageElement.appendChild(buttonsContainer);
// Dirty fix for disabling speech synth for firefox browsers :()
if (!userAgent.match(/firefox|fxios/i)) {
attachAudio_modules(messageTextElement, buttonsContainer);
}
if (rank > 0) {
thumbUpBadge.innerText = `${rank}`
thumbDownBadge.innerText = ``
thumbUpBadge.display = `block`
thumbDownBadge.display = 'none'
}
else if (rank < 0) {
thumbUpBadge.innerText = ``
thumbDownBadge.innerText = `${rank}`
thumbUpBadge.display = `none`
thumbDownBadge.display = 'block'
}
else {
thumbUpBadge.innerText = ``
thumbDownBadge.innerText = ``
thumbUpBadge.display = `none`
thumbDownBadge.display = 'none'
}
chatWindow.buttonsContainer = buttonsContainer
}
else{
chatWindow.buttonsContainer = undefined
}
chatWindow.appendChild(messageElement);
chatWindow.appendChild(hiddenElement);
// scroll to bottom of chat window
chatWindow.scrollTop = chatWindow.scrollHeight;
messageElement.messageTextElement = messageTextElement
messageElement.hiddenElement = hiddenElement
messageElement.senderElement = senderElement
messageElement.messageTextElement = messageTextElement
messageElement.id = id
messageElement.setID=(id)=>{
messageElement.id = id
}
messageElement.setSender=(sender)=>{
messageElement.senderElement.innerHTML = sender
}
messageElement.setMessage=(message)=>{
messageElement.messageTextElement.innerHTML = message
}
// Return all needed stuff
return messageElement
}