Spaces:
Runtime error
Runtime error
/* | |
Author: Rodolfo Torres | |
Email: rodolfo.torres@outlook.com | |
LinkedIn: https://www.linkedin.com/in/rodolfo-torres-p | |
License: This code is licensed under GPL-3.0 | |
The code is licensed under the GPL-3.0 license, which is a widely used open-source license, ensuring that any derivative work is also open source. | |
It grants users the freedom to use, modify, and distribute the software, as well as any modifications or extensions made to it. | |
However, any modified versions of the software must also be licensed under GPL-3.0. | |
For more details, please refer to the full text of the GPL-3.0 license at https://www.gnu.org/licenses/gpl-3.0.html. | |
*/ | |
/*/ Not change any values of the variables below, | |
use the "json/config.json" file to make your settings. /*/ | |
let data_index = ""; | |
let prompts_name = ""; | |
let prompts_expert = ""; | |
let prompts_image = ""; | |
let prompts_background_color = ""; | |
let prompts_training = ""; | |
let chat_font_size = ""; | |
let API_URL = ""; | |
let source = ""; | |
let google_voice = ""; | |
let google_voice_lang_code = ""; | |
let microphone_speak_lang = ""; | |
let chat_minlength = 0; | |
let chat_maxlength = 0; | |
let lang_index = 0; | |
let scrollPosition = 0; | |
let use_text_stream = false; | |
let display_microphone_in_chat = false; | |
let display_avatar_in_chat = false; | |
let display_contacts_user_list = false; | |
let display_copy_text_button_in_chat = false; | |
let filter_badwords = true; | |
let display_audio_button_answers = true; | |
let chat_history = true; | |
let hasBadWord = false; | |
let chat = []; | |
let pmt = []; | |
let array_widgets = []; | |
let array_chat = []; | |
let lang = []; | |
let = badWords = [] | |
let array_messages = []; | |
let array_voices = []; | |
let filterBotWords = ["Robot:", "Bot:"]; | |
//---- end configs----// | |
//Modify the option below according to the prompt you want to use. | |
let user_prompt_lang = "en"; | |
let textModal; | |
let fileModal; | |
let uuid = ''; | |
let chatId; | |
let recognition; | |
if (window.location.protocol === 'file:') { | |
alert('This file is not runnable locally, an http server is required, please read the documentation.'); | |
} | |
//Loads the characters from the config.json file and appends them to the initial slider | |
loadData("json/config.json", ["json/prompts-" + user_prompt_lang + ".json", "json/lang.json", "json/badwords.json"]); | |
/** | |
* Function to load data from the given URL and an array of URLs using Promise.all and map functions. | |
* | |
* @param {string} url - The URL to fetch the data from. | |
* @param {Array} urls - An array of URLs to fetch additional data from. | |
* @returns {Promise} - A Promise that resolves with the fetched data and updates the necessary variables. | |
*/ | |
function loadData(url, urls) { | |
// Fetch data from the given url and an array of urls using Promise.all and map functions | |
return Promise.all([fetch(url).then(res => res.json()), ...urls.map(url => fetch(url).then(res => res.json()))]) | |
.then(([out, OutC, OutL, OutB, OutT]) => { | |
// Extract necessary data from the response | |
lang = OutL; | |
if (filter_badwords) { badWords = OutB.badwords.split(',') } | |
lang_index = lang.use_lang_index; | |
use_text_stream = out.use_text_stream; | |
display_avatar_in_chat = out.display_avatar_in_chat; | |
display_microphone_in_chat = out.display_microphone_in_chat; | |
microphone_speak_lang = out.microphone_speak_lang; | |
google_voice = out.google_voice; | |
google_voice_lang_code = out.google_voice_lang_code; | |
display_contacts_user_list = out.display_contacts_user_list; | |
display_copy_text_button_in_chat = out.display_copy_text_button_in_chat; | |
display_audio_button_answers = out.display_audio_button_answers; | |
filter_badwords = out.filter_badwords; | |
chat_history = out.chat_history; | |
chat_font_size = out.chat_font_size; | |
loadSpeechRecognition(); | |
copy_text_in_chat = display_copy_text_button_in_chat ? `<button class="copy-text" onclick="copyText(this)"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> <span class="label-copy-code">${lang["translate"][lang_index].copy_text1}</span></button>` : ''; | |
var s = document.createElement('style'); s.innerHTML = '.message-text{font-size:' + chat_font_size + ' !important;}'; document.head.appendChild(s); | |
if (!display_contacts_user_list) { | |
$(".toggle_employees_list").hide(); | |
$(".col-contacts-border").hide(); | |
} | |
if (display_microphone_in_chat) { | |
$("#microphone-button").show() | |
} | |
// Populate array_widgets with character data and create HTML elements for each character card | |
$("#load-character").html(""); | |
$(".ai-contacts-scroll").html(""); | |
for (var i = 0; i < OutC.length; i++) { | |
array_widgets.push({ | |
'id': OutC[i]['id'], | |
'name': OutC[i]['name'], | |
'widget_name': OutC[i]['widget_name'], | |
'image': OutC[i]['image'], | |
'welcome_message': OutC[i]['welcome_message'], | |
'display_welcome_message': OutC[i]['display_welcome_message'], | |
'training': OutC[i]['training'], | |
'description': OutC[i]['description'], | |
'chat_minlength': OutC[i]['chat_minlength'], | |
'chat_maxlength': OutC[i]['chat_maxlength'], | |
'max_num_chats_api': OutC[i]['max_num_chats_api'] | |
}) | |
$("#load-character").append(` | |
<div class="card-option start-chat" data-index="${i}"> | |
<div class="card-option-img"><img src="${array_widgets[i]['image']}" alt="${array_widgets[i]['widget_name']}" title="${array_widgets[i]['widget_name']}"></div> | |
<div class="card-option-title"><h5>${array_widgets[i]['widget_name']}</h5></div> | |
</div> | |
`) | |
} | |
// Get chat history and update the last_chat property for each character | |
if (chat_history) { | |
arr2 = JSON.parse(localStorage.getItem("oracle_chat_v1")); | |
array_widgets.forEach((item1) => { | |
const item2 = (arr2 && arr2.find((item2) => item2.id === item1.id)); | |
if (item2) { | |
item1.last_chat = item2.last_chat; | |
} | |
}); | |
} | |
translate(); | |
$("#loading").fadeOut(); | |
}).catch(err => { throw err }) | |
} | |
/** | |
* Function to retrieve the current date and time. | |
* | |
* @returns {string} - A string representing the current date and time in a localized format. | |
*/ | |
function currentDate() { | |
const timestamp = new Date(); | |
return timestamp.toLocaleString(); | |
} | |
// Define a placeholder for the image | |
const placeholder = "img/placeholder.svg"; | |
/** | |
* Event listener for the scroll event that checks if the image is in the visible area. | |
*/ | |
$(window).on("scroll", function () { | |
$("img[data-src]").each(function () { | |
if (isElementInViewport($(this))) { | |
$(this).attr("src", $(this).attr("data-src")); | |
$(this).removeAttr("data-src"); | |
} | |
}); | |
}); | |
/** | |
* Helper function to check if the element is in the visible area. | |
* | |
* @param {Object} el - The element to be checked. | |
* @returns {boolean} - A boolean indicating whether the element is in the visible area. | |
*/ | |
function isElementInViewport(el) { | |
const rect = el.get(0).getBoundingClientRect(); | |
return ( | |
rect.bottom >= 0 && | |
rect.right >= 0 && | |
rect.top <= $(window).height() && | |
rect.left <= $(window).width() | |
); | |
} | |
/** | |
* Main function of the chat API responsible for getting a response based on the provided prompt. | |
* | |
* @param {string} prompt - The prompt or message from the user. | |
* @returns {Promise<void>} - A Promise that resolves when the response is obtained and displayed in the chat. | |
*/ | |
async function getResponse(prompt) { | |
//Conversation history | |
array_chat.push({ "name": "User", "message": prompt, "isImg": false, "date": currentDate() }) | |
array_messages = []; | |
//Converting chat to API model | |
for (let i = 0; i < array_chat.length; i++) { | |
let message = { "role": "", "content": "" }; | |
if (array_chat[i].training === true) { | |
let system_message = { "role": "system", "content": array_chat[i].message }; | |
array_messages.push(system_message); | |
} else { | |
if (array_chat[i].name === "User") { | |
message.role = "user"; | |
} else { | |
message.role = "assistant"; | |
} | |
message.content = array_chat[i].message; | |
array_messages.push(message); | |
} | |
} | |
if (array_messages.length > max_num_chats_api) { | |
var slice_messages = max_num_chats_api - 2; | |
array_messages = array_messages.slice(0, 2).concat(array_messages.slice(-slice_messages)); | |
} | |
try { | |
let question = array_messages[array_messages.length - 1].content; | |
let curr_settings = getSettings(); | |
allow_bool = false; | |
if(curr_settings['answersToggle']){ | |
allow_bool = true; | |
} | |
// Data to send to the server | |
var questionData = { | |
question: question, | |
allow_bool: allow_bool, | |
}; | |
const fullPrompt = "That is a responses' example maded in English to test capacities of that chat"; | |
const randomID = generateUniqueID(); | |
$("#overflow-chat").append(` | |
<div class="conversation-thread thread-ai"> | |
${avatar_in_chat} | |
<div class="message-container"> | |
<div class="message-info"> | |
${copy_text_in_chat} | |
${audio_in_chat} | |
<div class="user-name"><h5>${prompts_name}</h5></div> | |
<div class="message-text"> | |
<div class="chat-response ${randomID}"><span class='get-stream'></span><span class='cursor'></span></div> | |
</div> | |
<div class="date-chat"><img src="img/icon-clock.svg"> ${currentDate()}</div> | |
</div> | |
</div> | |
</div> | |
`); | |
// Make a POST request to the /answer_question endpoint | |
$.ajax({ | |
type: "POST", | |
url: `/answer_question/${uuid}`, | |
data: JSON.stringify(questionData), | |
contentType: "application/json", | |
success: function (data) { | |
// The response is in data.answer | |
var response = data.answer; | |
$(".cursor").remove(); | |
str = $(`.${randomID}`).html(); | |
str = escapeHtml(str); | |
$(`.${randomID}`).html(str); | |
$(`.chat_${randomID} .chat-audio`).fadeIn('slow'); | |
enableChat(); | |
scrollChatBottom(); | |
//if(!use_text_stream){ | |
$(`.${randomID}`).append(response); | |
scrollChatBottom(); | |
//} | |
array_chat.push({ "name": prompts_name, "message": response, "date": currentDate() }); | |
checkClearChatDisplay(); | |
saveChatHistory(); | |
//enableChat(); | |
} | |
}); | |
$(`.chat_${randomID} .chat-audio`).hide(); | |
scrollChatBottom(); | |
} catch (e) { | |
console.error(`Error creating SSE: ${e}`); | |
} | |
} | |
/** | |
* Function to generate a unique ID with an optional prefix. | |
* | |
* @param {string} prefix - The optional prefix for the generated ID. Default is 'id_'. | |
* @returns {string} - A string representing the unique ID with the specified prefix and timestamp. | |
*/ | |
function generateUniqueID(prefix = 'id_') { | |
const timestamp = Date.now(); | |
return `${prefix}${timestamp}`; | |
} | |
/** | |
* Function to stream the chat content based on the received source and randomID. | |
* | |
* @param {EventSource} source - The source of the event stream. | |
* @param {string} randomID - A string representing the unique ID for the chat. | |
* @returns {boolean} - A boolean indicating whether the streaming is successful or not. | |
*/ | |
function streamChat(source, randomID) { | |
let fullPrompt = ""; | |
let partPrompt = ""; | |
source.addEventListener('message', function (e) { | |
//console.log(e.data); | |
let data = e.data; | |
let tokens = {}; | |
if (typeof data === 'string') { | |
if (data.startsWith('[ERROR]')) { | |
let message = data.substr('[ERROR]'.length).trim(); | |
toastr.error(message); | |
enableChat(); | |
return; | |
} else if (data === '[DONE]') { | |
$(".cursor").remove(); | |
str = $(`.${randomID}`).html(); | |
str = escapeHtml(str); | |
$(`.${randomID}`).html(str); | |
$(`.chat_${randomID} .chat-audio`).fadeIn('slow'); | |
enableChat(); | |
scrollChatBottom(); | |
if (!use_text_stream) { | |
$(`.${randomID}`).append(fullPrompt); | |
scrollChatBottom(); | |
} | |
array_chat.push({ "name": prompts_name, "message": fullPrompt, "date": currentDate() }); | |
checkClearChatDisplay(); | |
saveChatHistory(); | |
return false; | |
} else { | |
try { | |
tokens = JSON.parse(data); | |
} catch (err) { | |
if (typeof data === "string") { | |
toastr.error("❌ " + data) | |
enableChat(); | |
return false; | |
} | |
} | |
} | |
} | |
if (!tokens || !tokens.choices || tokens.choices.length === 0) { | |
toastr.error("❌ " + tokens.message) | |
enableChat(); | |
$(`.chat_${randomID}`).remove(); | |
return; | |
} | |
var choice = tokens.choices[0]; | |
partPrompt = ""; | |
if (choice.content || choice.text) { | |
fullPrompt += choice.content || choice.text; | |
partPrompt = choice.content || choice.text; | |
} | |
console.log('partPrompt:', partPrompt); | |
if (use_text_stream) { | |
$(`.${randomID} .get-stream`).append(partPrompt); | |
if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { | |
scrollChatBottom(); | |
} | |
} | |
}); | |
} | |
/** | |
* Function to save the chat history into the local storage. | |
*/ | |
function saveChatHistory() { | |
/* | |
if (array_widgets[data_index]) { | |
array_widgets[data_index].last_chat = array_chat; | |
} | |
if(chat_history){ | |
localStorage.setItem("text_talk_v1", JSON.stringify(array_widgets)); | |
} | |
console.log("Saving...") | |
*/ | |
} | |
/** | |
* Function that appends the AI response in the chat in HTML. | |
* | |
* @param {string} response - The response message from the AI. | |
*/ | |
function responseChat(response) { | |
for (var i = 0; i < filterBotWords.length; i++) { | |
if (response.indexOf(filterBotWords[i]) !== -1) { | |
response = response.replace(filterBotWords[i], ""); | |
} | |
} | |
array_chat.push({ "name": prompts_name, "message": response, "date": currentDate() }) | |
response = escapeHtml(response) | |
avatar_in_chat = ""; | |
if (display_avatar_in_chat === true) { | |
avatar_in_chat = `<div class="user-image"><img src="img/robot-avatar.png" alt="${prompts_name}" title="${prompts_name}"></div>`; | |
} | |
audio_in_chat = ""; | |
if (display_audio_button_answers === true) { | |
audio_in_chat = `<div class='chat-audio'><img data-play="false" src='img/btn_tts_play.svg'></div>`; | |
} | |
$("#overflow-chat").append(` | |
<div class="conversation-thread thread-ai"> | |
${avatar_in_chat} | |
<div class="message-container"> | |
<div class="message-info"> | |
${copy_text_in_chat} | |
${audio_in_chat} | |
<div class="user-name"><h5>${prompts_name}</h5></div> | |
<div class="message-text"> | |
<div class="chat-response">${response}</div> | |
<div class="date-chat"><img src="img/icon-clock.svg"> ${currentDate()}</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
`); | |
scrollChatBottom(); | |
enableChat(); | |
saveChatHistory(); | |
checkClearChatDisplay(); | |
} | |
/** | |
* Function to append an image to the chat. | |
* | |
* @param {string} chat - The chat message. | |
*/ | |
function appendChatImg(chat) { | |
const imageID = Date.now(); | |
IAimagePrompt = chat.replace("/img ", ""); | |
$("#overflow-chat").append(` | |
<div class="conversation-thread thread-ai"> | |
<div class="message-container"> | |
<div class="message-info"> | |
<div class="user-name"><h5>${prompts_name}</h5></div> | |
<div class="message-text"> | |
<div class="chat-response no-white-space"> | |
<p>${lang["translate"][lang_index].creating_ia_image} <strong class='ia-image-prompt-label'>${IAimagePrompt}</strong> | |
<div class="wrapper-image-ia image_ia_${imageID}"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="40" height="40"> | |
<circle cx="50" cy="50" r="40" stroke="#c5c5c5" stroke-width="8" fill="none" /> | |
<circle cx="50" cy="50" r="40" stroke="#249ef7" stroke-width="8" fill="none" stroke-dasharray="250" stroke-dashoffset="0"> | |
<animate attributeName="stroke-dashoffset" dur="2s" repeatCount="indefinite" from="0" to="250" /> | |
</circle> | |
</svg> | |
</div> | |
<p class='expire-img-message'>${lang["translate"][lang_index].expire_img_message}</p> | |
</div> | |
</div> | |
</div> | |
<div class='date-chat'><img src='img/icon-clock.svg'> ${currentDate()}</div> | |
</div> | |
</div> | |
`); | |
scrollChatBottom(); | |
$("#chat").val(""); | |
} | |
/** | |
* Function that sends the user's chat message to the chat in HTML and to the API. | |
* | |
*/ | |
function sendUserChat() { | |
let chat = $("#chat").val(); | |
if (filter_badwords) { | |
// Create regex to check if word is forbidden | |
const regex = new RegExp(`\\b(${badWords.join('|')})(?=\\s|$)`, 'gi'); | |
// Check if message contains a bad word | |
const hasBadWord = regex.test(chat); | |
// Replace bad words with asterisks | |
if (hasBadWord) { | |
const sanitizedMessage = chat.replace(regex, match => '*'.repeat(match.length)); | |
$("#chat").val(sanitizedMessage); | |
toastr.error(`${lang["translate"][lang_index].badword_feedback}`); | |
return false; | |
} | |
} | |
//checks if the user has entered the minimum amount of characters | |
if (chat.length < chat_minlength) { | |
toastr.error(`${lang["translate"][lang_index].error_chat_minlength} ${chat_minlength} ${lang["translate"][lang_index].error_chat_minlength_part2}`); | |
return false; | |
} | |
chat = escapeHtml(chat) | |
avatar_in_chat = display_avatar_in_chat ? `<div class="user-image"><img onerror="this.src='img/no-image.svg'" src="img/robot-avatar.png" alt="${prompts_name}" title="${prompts_name}"></div>` : ''; | |
audio_in_chat = display_audio_button_answers ? `<div class='chat-audio'><img data-play="false" src='img/btn_tts_play.svg'></div>` : ''; | |
$("#overflow-chat").append(` | |
<div class="conversation-thread thread-user"> | |
<div class="message-container"> | |
<div class="message-info"> | |
${copy_text_in_chat} | |
${audio_in_chat} | |
<div class="user-name"><h5>${lang["translate"][lang_index].you}</h5></div> | |
<div class="message-text"><div class="chat-response">${chat}</div></div> | |
<div class="date-chat"><img src="img/icon-clock.svg"> ${currentDate()}</div> | |
</div> | |
</div> | |
</div> | |
`); | |
scrollChatBottom(); | |
hljs.highlightAll(); | |
if (chat.includes("/img")) { | |
appendChatImg(chat); | |
} else { | |
getResponse(chat); | |
} | |
$("#chat").val(""); | |
disableChat(); | |
} | |
/** | |
* Send a message in the chat by pressing the Enter key. | |
* | |
* @param {object} e - The event object. | |
* @returns {boolean} - Returns false to prevent the default behavior of the Enter key. | |
*/ | |
$("#chat").keypress(function (e) { | |
if (e.which === 13 && !e.shiftKey) { | |
sendUserChat(); | |
return false; | |
} | |
}); | |
/** | |
* Event listener for the click event on the chat send button. | |
* Calls the 'sendUserChat' function when the button is clicked. | |
*/ | |
$(".btn-send-chat").on("click", function () { | |
sendUserChat(); | |
}) | |
/** | |
* Translates text elements in the HTML using the translation object. | |
*/ | |
function translate() { | |
translationObj = lang.translate[lang_index]; | |
// Loop through all the keys in the translationObj object | |
for (let key in translationObj) { | |
// Get the value of the current key | |
let value = translationObj[key]; | |
// Find all elements in the HTML that contain the block between {{ and }} | |
let elements = document.body.querySelectorAll('*:not(script):not(style)'); | |
elements.forEach(function (element) { | |
for (let i = 0; i < element.childNodes.length; i++) { | |
let node = element.childNodes[i]; | |
if (node.nodeType === Node.TEXT_NODE) { | |
let text = node.nodeValue; | |
let regex = new RegExp(`{{\\s*${key}\\s*}}`, 'g'); | |
if (regex.test(text)) { | |
// Use the innerHTML property to interpret HTML tags | |
node.parentElement.innerHTML = text.replace(regex, value); | |
} | |
} else if (node.nodeType === Node.ELEMENT_NODE) { | |
// For elements with HTML attributes, replace the key's value in the attribute | |
let attributes = node.attributes; | |
for (let j = 0; j < attributes.length; j++) { | |
let attribute = attributes[j]; | |
if (attribute.value.includes(`{{${key}}}`)) { | |
let newValue = attribute.value.replace(`{{${key}}}`, value); | |
node.setAttribute(attribute.name, newValue); | |
} | |
} | |
} | |
} | |
}); | |
} | |
} | |
/** | |
* Closes the chat interface and shows the chat options. | |
* Restores the previous scroll position and adjusts the UI accordingly. | |
*/ | |
function closeChat() { | |
hideChat(); | |
enableChat(); | |
$(window).scrollTop(scrollPosition); | |
$(".cards-options, .select-option-title, #chat-background").show(); | |
$(".message-area-bottom, .ai-chat-top").hide(); | |
$("#body-frame").removeClass("body-frame-chat"); | |
$(".chat-frame").removeClass("chat-frame-talk"); | |
$(".hide-section").removeClass("hideOnMobile"); | |
$("body").removeClass("custom-body"); | |
$("#overflow-chat").hide(); | |
return false; | |
} | |
/** | |
* Stops the ongoing chat conversation. | |
* Closes the chat source and enables the chat. | |
*/ | |
function stopChat() { | |
if (source) { | |
enableChat(); | |
source.close(); | |
$(".cursor").remove(); | |
} | |
} | |
/** | |
* Attaches an event listener to the cancel chat button. | |
* Calls the stopChat function on click event. | |
*/ | |
$(".btn-cancel-chat").on("click", function () { | |
stopChat(); | |
}) | |
/** | |
* Listens for the Escape key event. | |
* Calls the closeChat function when the Escape key is pressed. | |
*/ | |
document.addEventListener("keydown", function (event) { | |
if (event.key === "Escape") { | |
closeChat(); | |
} | |
}); | |
/** | |
* Hides the chat element. | |
* Calls the hideFeedback and cancelSpeechSynthesis functions. | |
* Shows the hide-section and hides the chat-background. | |
* Hides the overflow-chat if the user agent matches the specified mobile devices. | |
*/ | |
function hideChat() { | |
hideFeedback(); | |
cancelSpeechSynthesis(); | |
$(".hide-section").show(); | |
$("#chat-background").hide(); | |
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { | |
$("#overflow-chat").hide(); | |
} | |
} | |
/** | |
* Adds an event to the send button to submit the provided text. | |
* Makes a POST call to the /store_text endpoint to store the text. | |
* Handles errors and displays Toastr messages as necessary. | |
*/ | |
$('#sendButton').click(function (evt) { | |
evt.preventDefault(); | |
var textData = { | |
text: $('#textArea').val(), // The text to be sent | |
}; | |
// Set Toastr position to top | |
toastr.options.positionClass = 'toast-top-center'; | |
// Check if the text variable is empty | |
if (textData.text.trim() === '') { | |
toastr.error("Error: Text cannot be empty."); | |
return; | |
} | |
// Disable the button and add a spinner | |
var sendButton = $('#sendButton'); | |
sendButton.prop('disabled', true); | |
sendButton.html('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Sending...'); | |
// Make a POST call to the /store_text endpoint | |
$.ajax({ | |
type: "POST", | |
url: `/store_text/${uuid}`, | |
data: JSON.stringify(textData), | |
contentType: "application/json", | |
success: function (data) { | |
// Enable the button again | |
sendButton.prop('disabled', false); | |
sendButton.html('Send'); | |
$('#textArea').val(''); | |
// Close the modal after sending the text | |
textModal.hide(); | |
displayChat(chatId); | |
}, | |
error: function (xhr, status, error) { | |
// Check if there is a backend error code | |
if (xhr.status === 400 || xhr.status === 500) { | |
toastr.error(`Error: ${xhr.status} - ${error}`); | |
} else { | |
toastr.error("Error: Connection refused. Please try again later."); | |
} | |
// Enable the button again | |
sendButton.prop('disabled', false); | |
sendButton.html('Send'); | |
} | |
}); | |
}); | |
/** | |
* Adds an event to the send button to upload the file. | |
* Makes a POST call to the /upload_file endpoint to upload the file. | |
* Handles errors and displays Toastr messages as necessary. | |
*/ | |
$('#sendButton2').click(function (evt) { | |
evt.preventDefault(); | |
var formData = new FormData($('#file-form')[0]); | |
var sendButton = $('#sendButton2'); | |
// Set Toastr position to top | |
toastr.options.positionClass = 'toast-top-center'; | |
var fileInput = $('#fileInput')[0]; | |
var fileSize = fileInput.files[0].size; // Size in bytes | |
var maxSize = 1*1024*1024; // 1MB in bytes | |
// Validate the file size | |
if (fileSize > maxSize) { | |
toastr.error('Error: File size exceeds 1MB limit.'); | |
return; | |
} | |
// Disable the button and add a spinner | |
sendButton.prop('disabled', true); | |
sendButton.html('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Uploading...'); | |
$.ajax({ | |
url: `/upload_file/${uuid}`, | |
type: 'POST', | |
data: formData, | |
async: false, | |
cache: false, | |
contentType: false, | |
processData: false, | |
success: function (data) { | |
$('#fileInput').val(''); | |
// Enable the button again | |
sendButton.prop('disabled', false); | |
sendButton.html('Send'); | |
// Close the modal after sending the text | |
textModal.hide(); | |
displayChat(chatId); | |
}, | |
error: function (xhr, status, error) { | |
// Show error message with Toastr | |
toastr.error('Error uploading the file'); | |
// Enable the button again | |
sendButton.prop('disabled', false); | |
sendButton.html('Send'); | |
} | |
}); | |
}); | |
/** | |
* Adds an event to the send button to send the URL. | |
* Makes a POST call to the /store_text endpoint to send the URL. | |
* Handles errors and displays Toastr messages as necessary. | |
*/ | |
$('#sendButton3').click(function () { | |
var textData = { | |
html_url: $('#url').val(), | |
}; | |
// Set Toastr position to top | |
toastr.options.positionClass = 'toast-top-center'; | |
var sendButton = $('#sendButton3'); | |
// Check if the text variable is empty | |
if (textData.html_url.trim() === '') { | |
toastr.error("Error: URL cannot be empty."); | |
return; | |
} | |
// Validate the URL | |
var urlRegex = new RegExp('^(https?:\\/\\/)?'+ | |
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ | |
'((\\d{1,3}\\.){3}\\d{1,3}))'+ | |
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ | |
'(\\?[;&a-z\\d%_.~+=-]*)?'+ | |
'(\\#[-a-z\\d_]*)?$','i'); | |
if (!urlRegex.test(textData.html_url)) { | |
toastr.error("Error: Invalid URL."); | |
return; | |
} | |
// Disable the button and add a spinner | |
sendButton.prop('disabled', true); | |
sendButton.html('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Sending...'); | |
// Make a POST call to the /store_text endpoint | |
$.ajax({ | |
type: "POST", | |
url: `/store_text/${uuid}`, | |
data: JSON.stringify(textData), | |
contentType: "application/json", | |
success: function (data) { | |
$('#url').val(''); | |
// Enable the button again | |
sendButton.prop('disabled', false); | |
sendButton.html('Send'); | |
// Close the modal after sending the text | |
textModal.hide(); | |
displayChat(chatId); | |
}, | |
error: function (xhr, status, error) { | |
if (xhr.status === 0) { | |
toastr.error('Error: Connection refused. Please try again later.'); | |
} else if (xhr.status === 400 || xhr.status === 500) { | |
toastr.error(`Error: ${xhr.status} - ${error}`); | |
} | |
// Enable the button again | |
sendButton.prop('disabled', false); | |
sendButton.html('Send'); | |
} | |
}); | |
}); | |
/** | |
* Attaches a click event to the elements with the "start-chat" class. | |
* Displays different modals based on the data-index attribute of the clicked element. | |
*/ | |
$(document).delegate(".start-chat", "click", function () { | |
chatId = $(this).attr("data-index"); | |
if (chatId == 0) { | |
textModal = new bootstrap.Modal(document.getElementById('modalText'), { | |
keyboard: false | |
}); | |
textModal.show(); | |
} else if (chatId == 1) { | |
textModal = new bootstrap.Modal(document.getElementById('modalFile'), { | |
keyboard: false | |
}); | |
textModal.show(); | |
} else if (chatId == 2) { | |
textModal = new bootstrap.Modal(document.getElementById('modalUrl'), { | |
keyboard: false | |
}); | |
textModal.show(); | |
} | |
}) | |
/** | |
* Displays the chat based on the provided index. | |
* Sets up the necessary variables and elements for the chat display. | |
* @param {number} index - The index of the chat to be displayed. | |
*/ | |
function displayChat(index) { | |
data_index = index; | |
cancelSpeechSynthesis(); | |
$(".hide-section").addClass("hideOnMobile"); | |
$(".chat-frame").addClass("chat-frame-talk"); | |
stopChat(); | |
scrollPosition = $(this).scrollTop(); | |
array_messages = []; | |
$("#overflow-chat").html(""); | |
$("#overflow-chat").show(); | |
$("#body-frame").addClass("body-frame-chat"); | |
array_chat = []; | |
prompts_name = array_widgets[data_index]['name']; | |
prompts_widget_name = array_widgets[data_index]['widget_name']; | |
prompts_expert = array_widgets[data_index]['expert']; | |
prompts_image = array_widgets[data_index]['image']; | |
prompts_background_color = array_widgets[data_index]['background_thumb_color']; | |
prompts_training = array_widgets[data_index]['training']; | |
displayWelcomeMessage = array_widgets[data_index]['display_welcome_message']; | |
welcome_message = array_widgets[data_index]['welcome_message']; | |
chat_minlength = array_widgets[data_index]['chat_minlength']; | |
chat_maxlength = array_widgets[data_index]['chat_maxlength']; | |
max_num_chats_api = array_widgets[data_index]['max_num_chats_api']; | |
lastChatLength = (array_widgets[data_index] && array_widgets[data_index]['last_chat']) ? array_widgets[data_index]['last_chat'].length : []; | |
$(".ai-chat-top-job").html(prompts_widget_name) | |
$(".cards-options, .select-option-title").hide(); | |
$("#chat").val(""); | |
// Set the maxlength attribute of the chat element to the value of chat_maxlength | |
$("#chat").attr("maxlength", chat_maxlength); | |
$(".message-area-bottom, .ai-chat-top").show(); | |
if (lastChatLength > 2) { | |
loadChat(); | |
} else { | |
const chat = { "name": prompts_name, "message": prompts_training, "training": true, "date": currentDate() }; | |
array_chat.push(chat); | |
if (displayWelcomeMessage) { | |
responseChat(array_widgets[data_index]['welcome_message']); | |
} | |
} | |
setTimeout(function () { | |
enableChat(); | |
}, 100); | |
$("body").addClass("custom-body"); | |
translate(); | |
} | |
/** | |
* Escapes special characters in a string with their corresponding HTML codes. | |
* @param {string} str - The input string to be escaped. | |
* @returns {string} - The string with escaped characters. | |
*/ | |
const escapeHtml = (str) => { | |
// Check if the string contains <code> or <pre> tags | |
if (/<code>|<\/code>|<pre>|<\/pre>/g.test(str)) { | |
// Returns the string without replacing the characters inside the tags | |
return str; | |
} | |
// Replaces special characters with their respective HTML codes | |
str = str.replace(/[&<>"'`{}()\[\]]/g, (match) => { | |
switch (match) { | |
case '<': return '<'; | |
case '>': return '>'; | |
case '{': return '{'; | |
case '}': return '}'; | |
case '(': return '('; | |
case ')': return ')'; | |
case '[': return '['; | |
case ']': return ']'; | |
default: return match; | |
} | |
}); | |
// Remove the stream <span class="get-stream"> | |
str = str.replace(/<span\s+class="get-stream">/g, ""); | |
// Remove the closing tag </span> | |
str = str.replace(/<\/span>/g, ""); | |
// Replaces the ```code``` snippet with <pre><code>code</code></pre> | |
str = str.replace(/```(\w+)?([\s\S]*?)```/g, '<pre><code>$2</code><button class="copy-code" onclick="copyCode(this)"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> <span class="label-copy-code">' + lang["translate"][lang_index].copy_code1 + '</span></button></pre>').replace(/(\d+\.\s)/g, "<strong>$1</strong>").replace(/(^[A-Za-z\s]+:)/gm, "<strong>$1</strong>"); | |
return str; | |
}; | |
/** | |
* Copies the text content to the clipboard. | |
* @param {HTMLElement} button - The button element that triggers the copy action. | |
*/ | |
function copyText(button) { | |
const div = button.parentElement; | |
const code = div.querySelector('.chat-response'); | |
const range = document.createRange(); | |
range.selectNode(code); | |
window.getSelection().removeAllRanges(); | |
window.getSelection().addRange(range); | |
document.execCommand("copy"); | |
window.getSelection().removeAllRanges(); | |
button.innerHTML = lang["translate"][lang_index].copy_text2; | |
} | |
/** | |
* Copies the content of the <pre> tag to the clipboard. | |
* @param {HTMLElement} button - The button element that triggers the copy action. | |
*/ | |
function copyCode(button) { | |
const pre = button.parentElement; | |
const code = pre.querySelector('code'); | |
const range = document.createRange(); | |
range.selectNode(code); | |
window.getSelection().removeAllRanges(); | |
window.getSelection().addRange(range); | |
document.execCommand("copy"); | |
window.getSelection().removeAllRanges(); | |
button.innerHTML = lang["translate"][lang_index].copy_code2; | |
} | |
/** | |
* Clears the chat history for the specified target. Displays a confirmation dialog before clearing. | |
* @param {string} target - The target for clearing the chat history. Can be "all" to clear all characters' chat history or "current" to clear the current character's chat history. | |
*/ | |
function clearChat(target) { | |
// Display confirmation dialog using SweetAlert2 library | |
Swal.fire({ | |
title: lang["translate"][lang_index].confirmation_delete_chat1, | |
text: lang["translate"][lang_index].confirmation_delete_chat2, | |
icon: 'warning', | |
showCancelButton: true, | |
confirmButtonColor: '#3085d6', | |
cancelButtonColor: '#d33', | |
confirmButtonText: lang["translate"][lang_index].confirmation_delete_chat3, | |
cancelButtonText: lang["translate"][lang_index].confirmation_delete_chat4 | |
}).then((result) => { | |
// If user confirms deletion | |
if (result.isConfirmed) { | |
// If target is "all", clear chat history for all characters | |
if (target == "all") { | |
for (var i = 0; i < array_widgets.length; i++) { | |
array_widgets[i]['last_chat'] = []; | |
} | |
// Display success message using SweetAlert2 | |
Swal.fire( | |
lang["translate"][lang_index].confirmation_delete_chat5, | |
lang["translate"][lang_index].confirmation_delete_chat_all, | |
'success' | |
) | |
} else { | |
// Otherwise, clear chat history for current character only | |
array_widgets[data_index]['last_chat'] = []; | |
// Display success message using SweetAlert2 | |
Swal.fire( | |
lang["translate"][lang_index].confirmation_delete_chat5, | |
lang["translate"][lang_index].confirmation_delete_current_chat, | |
'success' | |
) | |
} | |
// Clear chat display | |
$("#overflow-chat").html(""); | |
// Reset chat history and add initial message | |
array_chat = []; | |
array_chat.push({ | |
"name": prompts_name, | |
"message": prompts_training, | |
"training": true, | |
"isImg": false, | |
"date": currentDate() | |
}) | |
// Save updated character data to local storage | |
localStorage.setItem("text_talk_v1", JSON.stringify(array_widgets)); | |
// If enabled, display welcome message for current character | |
if (displayWelcomeMessage) { | |
responseChat(array_widgets[data_index]['welcome_message']); | |
} | |
} | |
}) | |
} | |
/** | |
* Loads the chat history for the current character from the local storage. | |
*/ | |
function loadChat() { | |
if (chat_history) { | |
checkClearChatDisplay(); | |
for (var i = 0; i < array_widgets[data_index]['last_chat'].length; i++) { | |
const currentChat = array_widgets[data_index]['last_chat'][i]; | |
if (currentChat.name === "User") { | |
if (currentChat.isImg === true) { | |
const imageID = Date.now(); | |
const imgURL = Array.isArray(currentChat.imgURL) ? currentChat.imgURL.map(url => url).join('') : ''; | |
const imgHtml = Array.isArray(currentChat.imgURL) ? currentChat.imgURL.map(url => `<div class="image-ia"><img onerror="this.src='img/no-image.svg'" src="${url}"></div>`).join('') : ''; | |
const chatHtml = ` | |
<div class="conversation-thread thread-ai"> | |
<div class="message-container"> | |
<div class="message-info"> | |
<div class="user-name"><h5>${prompts_name}</h5></div> | |
<div class="message-text"> | |
<div class="chat-response no-white-space"> | |
<p>${lang["translate"][lang_index].creating_ia_image} <strong class='ia-image-prompt-label'>${currentChat.message}</strong> | |
<div class="wrapper-image-ia image_ia_${imageID}"> | |
${imgHtml} | |
</div> | |
<p class='expire-img-message'>${lang["translate"][lang_index].expire_img_message}</p> | |
</div> | |
</div> | |
</div> | |
<div class='date-chat'><img src='img/icon-clock.svg'> ${currentChat.date || ''}</div> | |
</div> | |
</div> | |
`; | |
$("#overflow-chat").append(chatHtml); | |
array_chat.push({ "name": "User", "message": currentChat.message, "isImg": true, imgURL: currentChat.imgURL, "date": currentDate() }); | |
} else { | |
const chatResponse = escapeHtml(currentChat.message) | |
const chatHtml = ` | |
<div class="conversation-thread thread-user"> | |
<div class="message-container"> | |
<div class="message-info"> | |
${copy_text_in_chat} | |
${audio_in_chat} | |
<div class="user-name"><h5>${lang["translate"][lang_index].you}</h5></div> | |
<div class="message-text"> | |
<div class="chat-response">${chatResponse}</div> | |
</div> | |
<div class='date-chat'><img src='img/icon-clock.svg'> ${currentChat.date || ''}</div> | |
</div> | |
</div> | |
</div> | |
`; | |
$("#overflow-chat").append(chatHtml); | |
array_chat.push({ "name": "User", "message": currentChat.message, "isImg": false, "date": currentDate() }); | |
} | |
} else { | |
avatar_in_chat = display_avatar_in_chat ? `<div class="user-image"><img onerror="this.src='img/no-image.svg'" src="img/robot-avatar.png" alt="${prompts_name}" title="${prompts_name}"></div>` : ''; | |
audio_in_chat = display_audio_button_answers ? `<div class='chat-audio'><img data-play="false" src='img/btn_tts_play.svg'></div>` : ''; | |
if (!currentChat.training) { | |
const chatResponse = escapeHtml(currentChat.message) | |
const chatHtml = ` | |
<div class="conversation-thread thread-ai"> | |
${avatar_in_chat} | |
<div class="message-container"> | |
<div class="message-info"> | |
${copy_text_in_chat} | |
${audio_in_chat} | |
<div class="user-name"><h5>${currentChat.name}</h5></div> | |
<div class="message-text"> | |
<div class="chat-response">${chatResponse}</div> | |
</div> | |
<div class='date-chat'><img src='img/icon-clock.svg'> ${currentChat.date || ''}</div> | |
</div> | |
</div> | |
</div> | |
`; | |
$("#overflow-chat").append(chatHtml); | |
} | |
array_chat.push({ "name": prompts_name, "message": currentChat.message, "training": currentChat.training, "date": currentDate() }); | |
} | |
} | |
hljs.highlightAll(); | |
setTimeout(function () { | |
scrollChatBottom(); | |
}, 10); | |
} else { | |
if (displayWelcomeMessage) { | |
responseChat(welcome_message); | |
} | |
} | |
} | |
/** | |
* Checks the display for the "Clear Chat" option based on the chat history for the current character. | |
*/ | |
function checkClearChatDisplay() { | |
if (array_widgets[data_index] && array_widgets[data_index].last_chat && array_widgets[data_index].last_chat.length > 1) { | |
if (chat_history) { | |
$("#clear-chat").show(); | |
} | |
} else { | |
$("#clear-chat").hide(); | |
} | |
// Check if there is chat history for any character | |
const hasLastChat = array_widgets.some((result) => { | |
return result.last_chat && result.last_chat.length > 2; | |
}); | |
// Display or hide the "Clear All Chats" option based on the presence of chat history | |
if (hasLastChat) { | |
$("#clear-all-chats").show(); | |
} else { | |
$("#clear-all-chats").hide(); | |
} | |
} | |
/** | |
* Hides the error messages shown on the screen. | |
*/ | |
function hideFeedback() { | |
toastr.remove() | |
} | |
/** | |
* Forces the chat to scroll to the bottom of the conversation. | |
*/ | |
function scrollChatBottom() { | |
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { | |
let body = document.getElementsByTagName("html")[0]; | |
body.scrollTop = body.scrollHeight; | |
} else { | |
let objDiv = document.getElementById("overflow-chat"); | |
objDiv.scrollTop = objDiv.scrollHeight; | |
} | |
hljs.highlightAll(); | |
setTimeout(function () { | |
if (window.innerWidth < 768) { | |
window.scrollTo(0, document.documentElement.scrollHeight); | |
} | |
}, 100); | |
} | |
/** | |
* Enables the chat input by setting the appropriate attributes and focusing on the chat input box. | |
*/ | |
function enableChat() { | |
$(".character-typing").css('visibility', 'hidden') | |
$(".btn-send-chat,#chat").attr("disabled", false); | |
$(".btn-send-chat").show(); | |
$(".btn-cancel-chat").hide(); | |
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); | |
if (!isMobile) { | |
setTimeout(function () { | |
$('#chat').focus(); | |
}, 500); | |
} | |
} | |
/** | |
* Disables the chat input by setting the appropriate attributes and adjusting the visibility of certain elements. | |
*/ | |
function disableChat() { | |
$(".character-typing").css('visibility', 'visible') | |
$(".character-typing").css('display', 'flex'); | |
$(".character-typing span").html(prompts_name); | |
$(".btn-send-chat,#chat").attr("disabled", true); | |
$(".btn-send-chat").hide(); | |
$(".btn-cancel-chat").show(); | |
} | |
/** | |
* Creates a text file based on the data provided. | |
* @param {Array} data - An array containing chat data. | |
* @returns {Blob} A Blob object representing the text file. | |
*/ | |
function createTextFile(data) { | |
let text = ""; | |
// Iterate over the array_chat array and add each message to the text variable | |
data.shift(); | |
data.forEach(chat => { | |
text += `${chat.name}: ${chat.message}\r\n`; | |
}); | |
text = text.replace("User:", lang["translate"][lang_index].you + ":"); | |
// Create a Blob object with the text | |
const blob = new Blob([text], { type: "text/plain" }); | |
// Return the Blob object | |
return blob; | |
} | |
/** | |
* Generates and downloads a PDF document based on the chat messages. | |
*/ | |
function downloadPdf() { | |
var docDefinition = { | |
content: [ | |
{ text: lang["translate"][lang_index].header_title_pdf, style: 'header' }, | |
"\n" | |
], | |
styles: { | |
header: { | |
fontSize: 22, | |
bold: true | |
}, | |
name: { | |
fontSize: 14, | |
color: '#0072c6', | |
bold: true | |
}, | |
message: { | |
fontSize: 12, | |
color: '#2c2c2c', | |
bold: false, | |
lineHeight: 1.2, | |
marginTop: 4 | |
}, | |
date: { | |
marginTop: 5, | |
fontSize: 10, | |
color: '#787878' | |
}, | |
defaultStyle: { | |
font: 'Roboto' | |
} | |
} | |
}; | |
// Adds each array element to the docDefinition | |
for (var i = 1; i < array_chat.length; i++) { | |
var message = array_chat[i]; | |
var name = { text: message.name + ': ', style: 'name' }; | |
var messageText = { text: message.message.replace(/[\u{1F300}-\u{1F6FF}\u{1F900}-\u{1F9FF}]/gu, ''), style: 'message' }; | |
docDefinition.content.push(name); | |
docDefinition.content.push(messageText); | |
docDefinition.content.push({ text: message.date, style: 'date' }); | |
docDefinition.content.push("\n"); | |
} | |
// Create a pdfMake instance | |
var pdfMakeInstance = pdfMake.createPdf(docDefinition); | |
// Download pdf | |
pdfMakeInstance.download('chat.pdf'); | |
} | |
/** | |
* Downloads a file with the provided Blob and filename. | |
* @param {Blob} blob - The Blob object to be downloaded. | |
* @param {string} fileName - The name of the file to be downloaded. | |
*/ | |
function downloadFile(blob, fileName) { | |
// Create a URL object with the Blob | |
const url = URL.createObjectURL(blob); | |
// Create a download link and add it to the document | |
const link = document.createElement("a"); | |
link.href = url; | |
link.download = fileName; | |
document.body.appendChild(link); | |
// Simulate a click on the link to trigger the download | |
link.click(); | |
// Remove the link from the document | |
document.body.removeChild(link); | |
} | |
/** | |
* Handles the download button click event. | |
*/ | |
function handleDownload() { | |
const blob = createTextFile(array_chat); | |
downloadFile(blob, "chat.txt"); | |
} | |
/** | |
* Handles the chat audio functionality. | |
*/ | |
$(document).on("click", ".chat-audio", function () { | |
var $this = $(this); | |
var $img = $this.find("img"); | |
var $chatResponse = $this.siblings(".message-text").find(".chat-response") | |
var play = $img.attr("data-play") == "true"; | |
if (play) { | |
cancelSpeechSynthesis(); | |
} | |
$img.attr({ | |
"src": "img/btn_tts_" + (play ? "play" : "stop") + ".svg", | |
"data-play": play ? "false" : "true" | |
}); | |
if (!play) { | |
cancelSpeechSynthesis(); | |
// Remove the text copy button before synthesizing speech | |
var chatResponseText = $chatResponse.html().replace(/<button\b[^>]*\bclass="[^"]*\bcopy-code\b[^"]*"[^>]*>.*?<\/button>/ig, ""); | |
// Checks if the feature is supported before calling the function | |
if ('speechSynthesis' in window) { | |
doSpeechSynthesis(chatResponseText, $chatResponse); | |
} | |
} | |
}); | |
/** | |
* Cleans the string for speech synthesis by removing unwanted characters and tags. | |
* @param {string} str - The string to be cleaned. | |
* @returns {string} - The cleaned string. | |
*/ | |
function cleanStringToSynthesis(str) { | |
str = str.trim() | |
.replace(/<[^>]*>/g, "") | |
.replace(/[\u{1F600}-\u{1F64F}|\u{1F300}-\u{1F5FF}|\u{1F680}-\u{1F6FF}|\u{2600}-\u{26FF}|\u{2700}-\u{27BF}|\u{1F900}-\u{1F9FF}|\u{1F1E0}-\u{1F1FF}|\u{1F200}-\u{1F2FF}|\u{1F700}-\u{1F77F}|\u{1F780}-\u{1F7FF}|\u{1F800}-\u{1F8FF}|\u{1F900}-\u{1F9FF}|\u{1FA00}-\u{1FA6F}|\u{1FA70}-\u{1FAFF}]/gu, '') | |
.replace(/<div\s+class="date-chat".*?<\/div>/g, '') | |
.replace(/\n/g, ''); | |
return str; | |
} | |
/** | |
* Cancels the ongoing speech synthesis. | |
*/ | |
function cancelSpeechSynthesis() { | |
if (window.speechSynthesis) { | |
window.speechSynthesis.cancel(); | |
} | |
} | |
/** | |
* Performs text-to-speech synthesis for long text. | |
* @param {string} longText - The long text to be synthesized. | |
* @param {jQuery} chatResponse - The jQuery element representing the chat response. | |
*/ | |
function doSpeechSynthesis(longText, chatResponse) { | |
$("span.chat-response-highlight").each(function () { | |
$(this).replaceWith($(this).text()); | |
}); | |
longText = cleanStringToSynthesis(longText); | |
// The maximum number of characters in each part | |
const maxLength = 100; | |
// Find the indices of punctuation marks in the longText string | |
const punctuationIndices = [...longText.matchAll(/[,.?!]/g)].map(match => match.index); | |
// Divide the text into smaller parts at the punctuation marks | |
const textParts = []; | |
let startIndex = 0; | |
for (let i = 0; i < punctuationIndices.length; i++) { | |
if (punctuationIndices[i] - startIndex < maxLength) { | |
continue; | |
} | |
textParts.push(longText.substring(startIndex, punctuationIndices[i] + 1)); | |
startIndex = punctuationIndices[i] + 1; | |
} | |
if (startIndex < longText.length) { | |
textParts.push(longText.substring(startIndex)); | |
} | |
const utterances = textParts.map(textPart => { | |
const settings = getSettings(); | |
google_voice_lang_code = settings.voiceOfPlayback.split('***')[0]; | |
google_voice = settings.voiceOfPlayback.split('***')[1]; | |
const utterance = new SpeechSynthesisUtterance(textPart); | |
utterance.lang = google_voice_lang_code; | |
utterance.voice = speechSynthesis.getVoices().find(voice => voice.name === google_voice); | |
if (!utterance.voice) { | |
const backupVoice = array_voices.find(voice => voice.lang === utterance.lang); | |
if (backupVoice) { | |
utterance.voice = speechSynthesis.getVoices().find(voice => voice.name === backupVoice.name); | |
} | |
} | |
return utterance; | |
}); | |
// Define the end of speech event | |
utterances[utterances.length - 1].addEventListener("end", () => { | |
$(".chat-audio img").attr("src", "img/btn_tts_play.svg"); | |
$(".chat-audio img").attr("data-play", "false"); | |
}); | |
let firstChat = false; | |
// Read each piece of text sequentially | |
function speakTextParts(index = 0) { | |
if (index < utterances.length) { | |
const textToHighlight = textParts[index]; | |
const highlightIndex = longText.indexOf(textToHighlight); | |
// Highlight the text | |
chatResponse.html(chatResponse.html().replace(textToHighlight, `<span class="chat-response-highlight">${textToHighlight}</span>`)); | |
// Speak the text | |
speechSynthesis.speak(utterances[index]); | |
utterances[index].addEventListener("end", () => { | |
// Remove the highlight | |
chatResponse.html(chatResponse.html().replace(`<span class="chat-response-highlight">${textToHighlight}</span>`, textToHighlight)); | |
speakTextParts(index + 1); | |
}); | |
// Remove the highlight if speech synthesis is interrupted | |
speechSynthesis.addEventListener('pause', () => { | |
chatResponse.html(chatResponse.html().replace(`<span class="chat-response-highlight">${textToHighlight}</span>`, textToHighlight)); | |
}, { once: true }); | |
} | |
} | |
// Begin speak | |
speakTextParts(); | |
} | |
/** | |
* Callback function triggered when the available voices change. | |
* Retrieves the available text-to-speech voices. | |
*/ | |
window.speechSynthesis.onvoiceschanged = function () { | |
getTextToSpeechVoices(); | |
}; | |
/** | |
* Displays the available voices in the console. | |
*/ | |
function displayVoices() { | |
console.table(array_voices) | |
} | |
/** | |
* Retrieves the available text-to-speech voices. | |
*/ | |
function getTextToSpeechVoices() { | |
window.speechSynthesis.getVoices().forEach(function (voice) { | |
const voiceObj = { | |
name: voice.name, | |
lang: voice.lang | |
}; | |
array_voices.push(voiceObj); | |
}); | |
} | |
/** | |
* Event listener to display the item's description when the default modal is shown. | |
* @param {Event} event - The event object. | |
*/ | |
const myModalEl = document.getElementById('modalDefault') | |
myModalEl.addEventListener('show.bs.modal', event => { | |
$("#modalDefault .modal-body").html(array_widgets[data_index].description); | |
}) | |
/** | |
* Event listener to load the settings when the configuration modal is shown. | |
* Loads the settings upon page load. | |
*/ | |
const myModalConfig = document.getElementById('modalConfig') | |
myModalConfig.addEventListener('show.bs.modal', event => { | |
loadSettings(); // Cargar los ajustes al cargar la página | |
}) | |
/** | |
* Key for the localStorage storage item. | |
*/ | |
const localStorageKey = "col-contacts-border-display"; | |
// Get the current display state of the div from localStorage, if it exists | |
let displayState = localStorage.getItem(localStorageKey); | |
if (displayState) { | |
$(".col-contacts-border").css("display", displayState); | |
} else { | |
// If the display state of the div is not stored in localStorage, set the default state to "none" | |
$(".col-contacts-border").css("display", "none"); | |
} | |
/** | |
* Add the click event to toggle the display state of the div. | |
*/ | |
$(".toggle_employees_list").on("click", function () { | |
$(".col-contacts-border").toggle(); | |
// Get the new display state of the div | |
displayState = $(".col-contacts-border").css("display"); | |
// Store the new display state of the div in localStorage | |
localStorage.setItem(localStorageKey, displayState); | |
}); | |
/** | |
* Toastr options for displaying notifications. | |
*/ | |
toastr.options = { | |
"closeButton": true, | |
"debug": false, | |
"newestOnTop": false, | |
"progressBar": true, | |
"positionClass": "toast-bottom-full-width", | |
"preventDuplicates": true, | |
"onclick": null, | |
"showDuration": "300", | |
"hideDuration": "1000", | |
"timeOut": "5000", | |
"extendedTimeOut": "2000", | |
"showEasing": "swing", | |
"hideEasing": "linear", | |
"showMethod": "fadeIn", | |
"hideMethod": "fadeOut" | |
} | |
// Select the chat textarea element | |
const textarea = document.querySelector('#chat'); | |
// Select the microphone button element | |
const microphoneButton = document.querySelector('#microphone-button'); | |
// Initialize a variable to keep track of whether the system is transcribing speech or not | |
let isTranscribing = false; // Initially not transcribing | |
/** | |
* Loads the speech recognition functionality if supported by the browser. | |
* Initiates the speech recognition functionality and handles the start and end events, as well as the result event. | |
*/ | |
function loadSpeechRecognition() { | |
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) { | |
recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)(); | |
const settings = getSettings(); | |
microphone_speak_lang = settings.microphoneLanguage; | |
recognition.lang = microphone_speak_lang; | |
recognition.continuous = true; | |
recognition.addEventListener('start', () => { | |
console.log('microphone activated'); | |
$(".btn-send-chat").attr("disabled", true); | |
$("#microphone-button").attr("src", "img/mic-stop.svg") | |
}); | |
recognition.addEventListener('result', (event) => { | |
const transcript = event.results[0][0].transcript; | |
textarea.value += transcript + '\n'; | |
}); | |
recognition.addEventListener('end', () => { | |
console.log('microphone off'); | |
$(".btn-send-chat").attr("disabled", false); | |
$("#microphone-button").attr("src", "img/mic-start.svg") | |
isTranscribing = false; // Define transcription as finished | |
}); | |
microphoneButton.addEventListener('click', () => { | |
if (!isTranscribing) { | |
// Start transcription if not already transcribing | |
recognition.start(); | |
isTranscribing = true; | |
} else { | |
/// Stop transcription if already transcribing | |
recognition.stop(); | |
isTranscribing = false; | |
} | |
}); | |
} else { | |
toastr.error('Web Speech Recognition API not supported by browser'); | |
$("#microphone-button").hide() | |
} | |
} | |
/** | |
* Generates a unique identifier (UUID) using the current timestamp and a random number. | |
* @returns {string} A string representing the generated UUID. | |
*/ | |
function generateUUID() { | |
let d = new Date().getTime(); | |
if (typeof performance !== 'undefined' && typeof performance.now === 'function') { | |
d += performance.now(); //use high-precision timer if available | |
} | |
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | |
const r = (d + Math.random() * 16) % 16 | 0; | |
d = Math.floor(d / 16); | |
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); | |
}); | |
} | |
/** | |
* Loads the data from localStorage into the form if available. | |
*/ | |
function loadSettings() { | |
const settings = getSettings(); | |
/// Loading default values | |
$('#voiceOfPlayback').val(settings.voiceOfPlayback); | |
$('#microphoneLanguage').val(settings.microphoneLanguage); | |
$('#answersToggle').prop('checked', settings.answersToggle); | |
} | |
/** | |
* Retrieves the user settings from localStorage or creates and saves default settings if not found. | |
* @returns {object} - The user settings. | |
*/ | |
function getSettings() { | |
let settings = ''; | |
const textTalkSettings = localStorage.getItem('text-talk-settings'); | |
if (textTalkSettings) { | |
settings = JSON.parse(textTalkSettings); | |
} else { | |
settings = createAndSaveSettings(); // Calls the function to create and save settings if not found in localStorage | |
} | |
if(uuid == ''){ | |
uuid = settings.id; | |
} | |
return settings; | |
} | |
/** | |
* Creates and saves the settings in the localStorage. | |
* @returns {object} - The created settings. | |
*/ | |
function createAndSaveSettings() { | |
const settings = { | |
id: generateUUID(), | |
voiceOfPlayback: `${google_voice_lang_code}***${google_voice}`, | |
microphoneLanguage: microphone_speak_lang, | |
answersToggle: true | |
}; | |
localStorage.setItem('text-talk-settings', JSON.stringify(settings)); | |
return settings; | |
} | |
// Check if the voice synthesis is supported by the browser | |
if ('speechSynthesis' in window) { | |
// Wait for the voices to be loaded before listing them | |
window.speechSynthesis.onvoiceschanged = function () { | |
// Get all available voices | |
const voices = speechSynthesis.getVoices(); | |
// Filter voices that have 'en' as a prefix to identify English voices | |
const englishVoices = voices.filter(voice => voice.lang.startsWith('en')); | |
// Get the select element by its id | |
const dropdown = document.getElementById('voiceOfPlayback'); | |
// Remove previous options from the dropdown | |
dropdown.innerHTML = ''; | |
// Populate the dropdown with available English voices | |
englishVoices.forEach(function (voice) { | |
const option = document.createElement('option'); | |
option.value = `${voice.lang}***${voice.name}`; | |
option.text = voice.name; | |
dropdown.appendChild(option); | |
}); | |
}; | |
} else { | |
console.error('Voice synthesis is not supported by this browser.'); | |
} | |
// Load microphone recognition languages | |
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) { | |
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)(); | |
// Get supported languages for voice recognition | |
const supportedLanguages = { 'en-US': 'Google US English', 'en-GB': 'Google UK English' }; | |
// Get the select element by its id | |
const dropdown = document.getElementById('microphoneLanguage'); | |
// Remove previous options from the dropdown | |
dropdown.innerHTML = ''; | |
// Populate the dropdown with available languages for voice recognition | |
for (const langCode in supportedLanguages) { | |
if (Object.hasOwnProperty.call(supportedLanguages, langCode)) { | |
const langName = supportedLanguages[langCode]; | |
const option = document.createElement('option'); | |
option.value = langCode; | |
option.text = langName; | |
dropdown.appendChild(option); | |
} | |
} | |
} else { | |
console.error('Voice recognition is not supported by this browser.'); | |
} | |
$(document).ready(function () { | |
// Event handler for saving settings when submitting the form | |
$('#modal-settings-submit').click(function (event) { | |
event.preventDefault(); // Prevent the form from being submitted | |
let settings = getSettings(); | |
settings = { | |
id: settings.id, | |
voiceOfPlayback: $('#voiceOfPlayback').val(), | |
microphoneLanguage: $('#microphoneLanguage').val(), | |
answersToggle: $('#answersToggle').is(':checked') | |
}; | |
recognition.lang = settings.microphoneLanguage; | |
localStorage.setItem('text-talk-settings', JSON.stringify(settings)); | |
$('#modalConfig').modal('hide'); | |
}); | |
// Handle character count | |
$('#textArea').on('input', function () { | |
var maxLength = 4000; | |
var currentLength = $(this).val().length; | |
var remaining = maxLength - currentLength; | |
$('#charCount').text(remaining); | |
}); | |
}); | |