Last active
July 22, 2022 21:35
-
-
Save ivangolo/db2d38fd99b5d680bfd91ea8768fe6c9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "dashboard/base.html" %} | |
{% load static %} | |
{% load render_bundle from webpack_loader %} | |
{% block css %} | |
{% render_bundle 'home' 'css' %} | |
<link href="https://releases.transloadit.com/uppy/v2.7.0/uppy.min.css" rel="stylesheet"> | |
<style> | |
.content-header { | |
display: none; | |
} | |
.content { | |
min-height: inherit; | |
position: relative; | |
min-width: inherit; | |
padding: 0 !important; | |
} | |
.main-footer { | |
background-color: #343a40; | |
border-color: #4b545c; | |
color: white; | |
} | |
#chat-wrapper { | |
min-height: inherit; | |
position: relative; | |
min-width: inherit; | |
} | |
#form { | |
padding: 0.25rem; | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
right: 0; | |
display: flex; | |
height: 74px; | |
box-sizing: border-box; | |
background-color: white; | |
border: 1px solid gray; | |
} | |
#message-input { | |
flex-grow: 1; | |
padding: 0 1rem; | |
border-radius: 2rem; | |
margin: 0.25rem; | |
background-color: white; | |
border: none; | |
font-family: sans-serif; | |
font-size: 0.9em; | |
} | |
#message-input:focus { | |
outline: none; | |
} | |
.button { | |
background-color: rgb(255, 255, 255); | |
border: 1px solid rgb(44, 93, 203); | |
color: rgb(44, 93, 203); | |
padding: 0 1rem; | |
margin: 0.25rem; | |
border-radius: 3px; | |
outline: none; | |
font: inherit; | |
cursor: pointer; | |
font-family: sans-serif; | |
} | |
.button { | |
background-color: rgb(255, 255, 255); | |
border: 1px solid rgb(44, 93, 203); | |
color: rgb(44, 93, 203); | |
padding: 0 1rem; | |
margin: 0.25rem; | |
border-radius: 3px; | |
outline: none; | |
font: inherit; | |
cursor: pointer; | |
font-family: sans-serif; | |
} | |
.button.button-upload { | |
display: block; | |
width: 220px; | |
height: 30px; | |
} | |
.button:hover { | |
color: rgb(255, 255, 255); | |
background: rgb(44, 93, 203); | |
} | |
#messages { | |
display: flex; | |
flex-direction: column; | |
padding: 10px 10px 60px 10px; | |
position: absolute; | |
top: 0; | |
right: 0; | |
left: 0; | |
bottom: 75px; | |
overflow: auto; | |
font-family: sans-serif; | |
font-size: 0.9em; | |
} | |
.message { | |
padding: 1rem 1.5rem; | |
border-radius: 10px; | |
word-wrap: break-word; | |
max-width: calc(100% - 40px - 32px); | |
margin-bottom: 10px; | |
} | |
.message_received { | |
background: rgb(236, 239, 247); | |
color: black; | |
align-self: flex-start; | |
padding: 20px 30px; | |
border-radius: 0px 12px 12px; | |
} | |
.message_sent { | |
color: white; | |
background: rgb(44, 93, 203);; | |
border-bottom-right-radius: 0; | |
align-self: flex-end; | |
} | |
.quick-replies { | |
align-self: flex-start; | |
box-sizing: border-box; | |
margin-bottom: 20px; | |
} | |
.quick-replies .button { | |
display: block; | |
margin-bottom: 10px; | |
padding: 10px 20px; | |
} | |
.message_received figure figcaption { | |
padding: 20px; | |
font-size: 1.2em; | |
} | |
</style> | |
{% endblock css %} | |
{% block head_title %} | |
Asistente | |
{% endblock head_title %} | |
{% block content %} | |
<div id="chat-wrapper" | |
data-chatbot-url="https://intodental.rsolver.com/api/asistente/" | |
data-initial-payload="/saludo" | |
data-upload-url="https://intodental.rsolver.com/api/asistente/" | |
data-api-user-email="asistente@intodental.cl" | |
data-api-user-password="FNZb2rd7Yu577nf4" | |
data-api-token-obtain-pair="https://intodental.rsolver.com/api/token/" | |
data-api-token-refresh="https://intodental.rsolver.com/api/token/refresh/" | |
data-api-dialogues="https://intodental.rsolver.com/api/dialogues/" | |
> | |
<div id="messages"></div> | |
<form id="form"> | |
<input id="message-input" autocomplete="off" autofocus placeholder="Escriba un mensaje"/> | |
<button class="button">Enviar</button> | |
</form> | |
</div> | |
{% endblock %} | |
{% block js %} | |
{% render_bundle 'home' 'js' %} | |
<script src="https://releases.transloadit.com/uppy/v2.7.0/uppy.min.js"></script> | |
<script src="https://releases.transloadit.com/uppy/locales/v2.0.7/es_ES.min.js"></script> | |
<script defer> | |
const chatWrapper = document.getElementById("chat-wrapper"); | |
const messages = document.getElementById('messages'); | |
const form = document.getElementById('form'); | |
const messageInput = document.getElementById('message-input'); | |
// Connect to RASA server | |
const initialPayload = chatWrapper.dataset.initialPayload || ""; | |
// Image API | |
const chatbotUrl = chatWrapper.dataset.chatbotUrl; | |
const uploadUrl = chatWrapper.dataset.uploadUrl; | |
const apiUserEmail = chatWrapper.dataset.apiUserEmail; | |
const apiUserPassword = chatWrapper.dataset.apiUserPassword; | |
const apiTokenObtainPairUrl = chatWrapper.dataset.apiTokenObtainPair; | |
const apiTokenRefreshUrl = chatWrapper.dataset.apiTokenRefresh; | |
const apiDialoguesUrl = chatWrapper.dataset.apiDialogues; | |
// Get access and refresh tokens pair using user email and password | |
const obtainTokens = (url, email, password) => { | |
console.log("Obtaining access and refresh tokens") | |
const formData = new FormData(); | |
formData.append("email", email); | |
formData.append("password", password); | |
return fetch(url, { | |
method: 'post', | |
body: formData | |
}).then(res => res.json()) | |
.catch(error => { | |
console.error('An error occurred while obtaining tokens:', error) | |
}) | |
.then(response => { | |
window.localStorage.setItem('accessToken', response['access']); | |
}); | |
} | |
// Refresh access token using stored refresh token | |
const refreshAccessToken = () => { | |
console.log("Getting a new access token using refresh token") | |
return fetch(apiTokenRefreshUrl, { | |
method: 'post', | |
body: new FormData() | |
}).then(res => res.json()) | |
.catch(error => { | |
console.error('An error occurred while refreshing the access token', error) | |
}) | |
.then(response => { | |
window.localStorage.setItem('accessToken', response['access']); | |
}); | |
} | |
const scrollToBottom = () => { | |
messages.scrollTop = messages.scrollHeight; | |
} | |
const appendMessage = (msg, type) => { | |
const item = document.createElement('div'); | |
item.innerHTML = msg; | |
item.classList.add("message"); | |
item.classList.add(`message_${type}`); | |
messages.appendChild(item); | |
scrollToBottom(); | |
} | |
const appendImage = (src, type, caption) => { | |
const item = document.createElement('div'); | |
item.classList.add("message"); | |
item.classList.add(`message_${type}`); | |
const figure = document.createElement("figure"); | |
figure.style.maxWidth = `${Math.round(window.screen.availWidth * 0.3)}px`; | |
const img = document.createElement('img'); | |
img.src = src; | |
img.onload = scrollToBottom; | |
img.width = Math.round(window.screen.availWidth * 0.3); | |
figure.appendChild(img); | |
if (caption) { | |
const figcaption = document.createElement("figcaption"); | |
figcaption.innerHTML = caption; | |
figure.appendChild(figcaption); | |
} | |
item.appendChild(figure); | |
messages.appendChild(item); | |
} | |
// Append response buttons | |
const appendQuickReplies = (quickReplies) => { | |
const quickRepliesNode = document.createElement('div'); | |
quickRepliesNode.classList.add("quick-replies"); | |
quickReplies.forEach(quickReply => { | |
const quickReplyDiv = document.createElement('button'); | |
quickReplyDiv.innerHTML = quickReply.title; | |
quickReplyDiv.classList.add("button"); | |
quickReplyDiv.addEventListener("click", () => { | |
messages.removeChild(quickRepliesNode); | |
appendMessage(quickReply.title, "sent"); | |
sendMessage(quickReply.payload); | |
}) | |
quickRepliesNode.appendChild(quickReplyDiv); | |
}) | |
messages.appendChild(quickRepliesNode); | |
scrollToBottom(); | |
} | |
const initUppy = (target) => { | |
const uppy = new Uppy.Core({ | |
locale: Uppy.locales.es_ES, | |
restrictions: { | |
maxFileSize: 1000000, | |
maxNumberOfFiles: 1, | |
minNumberOfFiles: 1, | |
allowedFileTypes: ['image/*'], | |
}, | |
}) | |
const dialogue = getCookie("dialogue"); | |
if (dialogue) { | |
uppy.setMeta({dialogue: dialogue}); | |
} | |
uppy.use(Uppy.Dashboard, { | |
inline: true, | |
target: target, | |
note: 'Solo 1 imagen, de hasta un 1 Mb de tamaño', | |
width: Math.round(window.screen.availWidth * 0.5), | |
height: Math.round(window.screen.availHeight * 0.5), | |
}) | |
.use(Uppy.XHRUpload, { | |
endpoint: uploadUrl, | |
headers: { | |
'Authorization': `Bearer ${window.localStorage.getItem('accessToken')}` | |
} | |
}) | |
uppy.on('upload-success', (file, response) => { | |
const httpBody = response.body // extracted response data | |
const imageUrl = httpBody.image_data.result.image_uri, | |
caption = httpBody.image_data.result.caption; | |
appendImage(imageUrl, "received", caption); | |
uppy.close(); | |
target.remove(); | |
if (httpBody.utterances) { | |
for(let utterance of httpBody.utterances) { | |
appendUtterance(utterance); | |
} | |
} | |
}) | |
uppy.on('upload-error', (file, error, response) => { | |
if (response.status === 401) { | |
console.log("Unauthorized access to Image api"); | |
console.log("Pausing uploads"); | |
uppy.pauseAll(); | |
refreshAccessToken().then(() => { | |
console.log("Resuming uploads"); | |
uppy.getPlugin('XHRUpload').setOptions({ | |
headers: { | |
'Authorization': `Bearer ${window.localStorage.getItem('accessToken')}` | |
} | |
}) | |
uppy.retryAll(); | |
uppy.resumeAll(); | |
}); | |
} | |
}); | |
return uppy; | |
} | |
const appendUploadElement = (type) => { | |
const item = document.createElement("div"); | |
item.classList.add("message"); | |
item.classList.add(`message_${type}`); | |
messages.appendChild(item); | |
initUppy(item); | |
scrollToBottom(); | |
} | |
const appendUtterance = (utterance) => { | |
if (utterance.text) { | |
appendMessage(utterance.text, "received"); | |
} | |
if (utterance.attachment) { | |
appendImage(utterance.attachment.payload.src, "received"); | |
} | |
if (utterance.buttons) { | |
appendQuickReplies(utterance.buttons); | |
} | |
if (utterance.action_name && utterance.action_name == "upload-rx") { | |
appendUploadElement("received"); | |
} | |
} | |
const getCookie = (name) => { | |
var cookieArr = document.cookie.split(";"); | |
for(var i = 0; i < cookieArr.length; i++) { | |
var cookiePair = cookieArr[i].split("="); | |
if(name == cookiePair[0].trim()) { | |
return decodeURIComponent(cookiePair[1]); | |
} | |
} | |
return null; | |
} | |
const setCookie = (cName, cValue, expDay) => { | |
const expires = expDay.toUTCString(); | |
document.cookie = `${cName}=${cValue};expires=${expires};path=/;SameSite=None;Secure`; | |
} | |
const sendMessage = (message, isInitialPaylod = false) => { | |
const formData = new FormData(); | |
formData.append("message", message); | |
const dialogue = getCookie("dialogue"); | |
if (dialogue) { | |
formData.append("dialogue", dialogue); | |
} | |
return fetch(chatbotUrl, { | |
method: 'post', | |
body: formData, | |
headers: { | |
'Authorization': `Bearer ${window.localStorage.getItem('accessToken')}` | |
} | |
}).then(response => { | |
if (response.status === 401) { | |
refreshAccessToken().then(() => { | |
sendMessage(message, isInitialPaylod); | |
}) | |
} | |
if (response.status >= 500 && response.status <= 599) { | |
throw new Error(response.status); | |
} | |
return response; | |
}) | |
.then(response => response.json()) | |
.then(response => { | |
if (!response.utterances) { | |
return; | |
} | |
let date = new Date(); | |
date.setTime(date.getTime() + (1 * 30 * 60 * 1000)); | |
setCookie("dialogue", response.dialogue, date); | |
for(let utterance of response.utterances) { | |
appendUtterance(utterance); | |
} | |
}) | |
.catch(error => { | |
appendMessage("Servicio temporalmente no disponible. Intente de nuevo más tarde.", "received"); | |
}); | |
} | |
// Form submit event handler | |
form.addEventListener('submit', (e) => { | |
e.preventDefault(); | |
const message = messageInput.value.trim(); | |
if (!message) { | |
return; | |
} | |
messageInput.value = ''; | |
appendMessage(message, "sent"); | |
sendMessage(message); | |
}); | |
const appendDialogue = (messages) => { | |
messages.forEach((message, index) => { | |
console.log(message); | |
if (message.from === "bot") { | |
appendMessage(message.text, "received"); | |
// Mostrar botones solo cuando no ha sido respondido por el usuario | |
if (message.buttons && !message.replied) { | |
appendQuickReplies(message.buttons); | |
} | |
if (message.action_name && message.action_name == "upload-rx") { | |
appendUploadElement("received"); | |
} | |
} else { | |
appendMessage(message.text, "sent"); | |
} | |
}); | |
} | |
const getDialogue = async () => { | |
const formData = new FormData() | |
const dialogue = getCookie("dialogue"); | |
if (dialogue) { | |
formData.append("dialogue", dialogue); | |
} | |
const response = await fetch(apiDialoguesUrl, { | |
method: 'post', | |
body: formData, | |
headers: { | |
'Authorization': `Bearer ${window.localStorage.getItem('accessToken')}` | |
} | |
}).then(res => res.json()); | |
if (!response.dialogue || response.dialogue.finished) { | |
sendMessage(initialPayload, true); | |
} else { | |
console.log(`Diálogo ${response.dialogue.id} en curso`); | |
appendDialogue(response.dialogue.messages) | |
} | |
} | |
// Get tokens at page's load and get dialogue | |
obtainTokens(apiTokenObtainPairUrl, apiUserEmail, apiUserPassword).then(() => { | |
getDialogue(); | |
}); | |
</script> | |
{% endblock js %} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment