Skip to content

Instantly share code, notes, and snippets.

@ivangolo
Last active July 22, 2022 21:35
Show Gist options
  • Save ivangolo/db2d38fd99b5d680bfd91ea8768fe6c9 to your computer and use it in GitHub Desktop.
Save ivangolo/db2d38fd99b5d680bfd91ea8768fe6c9 to your computer and use it in GitHub Desktop.
{% 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