191 lines
6.3 KiB
JavaScript
191 lines
6.3 KiB
JavaScript
// Create chat-widget div
|
|
var chatWidgetDiv = document.createElement('div');
|
|
chatWidgetDiv.className = 'chat-widget';
|
|
chatWidgetDiv.innerHTML = `
|
|
<button id="openChatBtn">Ask AI</button>
|
|
`;
|
|
document.body.appendChild(chatWidgetDiv);
|
|
|
|
// Create chat-popup div
|
|
const date = new Date();
|
|
var chatPopupDiv = document.createElement('div');
|
|
chatPopupDiv.className = 'chat-popup';
|
|
chatPopupDiv.id = 'chatPopup';
|
|
chatPopupDiv.innerHTML = `
|
|
<div class="chatHeader">
|
|
<div class="header-wrapper">
|
|
<h3 class="assistant-title">Ray Docs AI - Ask a question</h3>
|
|
</div>
|
|
<button id="closeChatBtn" class="btn">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div id="chatContainer" class="chatContentContainer">
|
|
<div id="result">
|
|
Please note that the results of this bot are automated and may be incorrect or contain inappropriate information.
|
|
<div id="anchor"></div>
|
|
</div>
|
|
<div class="input-group">
|
|
<textarea id="searchBar" class="input" rows="3" placeholder="Do not include any personal or confidential information."></textarea>
|
|
<button id="searchBtn" class="btn btn-primary">Ask AI</button>
|
|
</div>
|
|
</div>
|
|
<div class="chatFooter text-right p-2">
|
|
© Copyright ${date.getFullYear()}, The Ray Team.
|
|
</div>
|
|
`;
|
|
chatPopupDiv.messages = [];
|
|
document.body.appendChild(chatPopupDiv);
|
|
|
|
const anchorDiv = document.getElementById('anchor');
|
|
const chatContainerDiv = document.getElementById('chatContainer');
|
|
|
|
const blurDiv = document.createElement('div');
|
|
blurDiv.id = 'blurDiv';
|
|
blurDiv.classList.add('blurDiv-hidden');
|
|
document.body.appendChild(blurDiv);
|
|
|
|
// blur background when chat popup is open
|
|
document.getElementById('openChatBtn').addEventListener('click', function () {
|
|
document.querySelector('.search-button__wrapper').classList.remove('show');
|
|
document.getElementById('chatPopup').style.display = 'block';
|
|
blurDiv.classList.remove('blurDiv-hidden');
|
|
});
|
|
|
|
// un-blur background when chat popup is closed
|
|
document.getElementById('closeChatBtn').addEventListener('click', function () {
|
|
document.getElementById('chatPopup').style.display = 'none';
|
|
blurDiv.classList.add('blurDiv-hidden');
|
|
});
|
|
|
|
// set code highlighting options
|
|
marked.setOptions({
|
|
renderer: new marked.Renderer(),
|
|
highlight: function (code) {
|
|
return hljs.highlight('python', code).value;
|
|
},
|
|
});
|
|
|
|
function highlightCode() {
|
|
document.querySelectorAll('pre code').forEach((block) => {
|
|
hljs.highlightBlock(block);
|
|
});
|
|
}
|
|
|
|
function renderCopyButtons(resultDiv) {
|
|
let preElements = resultDiv.querySelectorAll('pre');
|
|
preElements.forEach((preElement, index) => {
|
|
preElement.style.position = 'relative';
|
|
|
|
let uniqueId = `button-id-${index}`;
|
|
preElement.id = uniqueId;
|
|
|
|
// Set the proper attributes to the button to make is a sphinx copy button
|
|
let copyButton = document.createElement('button');
|
|
copyButton.className = 'copybtn o-tooltip--left';
|
|
copyButton.setAttribute('data-tooltip', 'Copy');
|
|
copyButton.setAttribute('data-clipboard-target', `#${uniqueId}`);
|
|
copyButton.style.position = 'absolute';
|
|
copyButton.style.top = '10px';
|
|
copyButton.style.right = '10px';
|
|
copyButton.style.opacity = 'inherit';
|
|
|
|
let imgElement = document.createElement('img');
|
|
imgElement.src = window.data.copyIconSrc;
|
|
imgElement.alt = 'Copy to clipboard';
|
|
|
|
copyButton.appendChild(imgElement);
|
|
preElement.appendChild(copyButton);
|
|
});
|
|
}
|
|
|
|
const searchBar = document.getElementById('searchBar');
|
|
const searchBtn = document.getElementById('searchBtn');
|
|
|
|
function rayAssistant(event) {
|
|
const resultDiv = document.getElementById('result');
|
|
|
|
const searchTerm = searchBar.value;
|
|
// handle empty input
|
|
if (!searchTerm) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
event.type === 'click' ||
|
|
(event.type === 'keydown' && event.key === 'Enter')
|
|
) {
|
|
// for improved UX, we want to equate a carriage return as hitting the send button and prevent default behavior of entering a new line
|
|
if (event.type === 'keydown' && event.key === 'Enter') {
|
|
event.preventDefault();
|
|
}
|
|
// clear search bar value and change placeholder to prompt user to ask follow up question
|
|
searchBar.value = '';
|
|
searchBar.placeholder = 'Ask follow up question here';
|
|
|
|
// Add query to the list of messages
|
|
chatPopupDiv.messages.push({role: 'user', content: searchTerm});
|
|
|
|
let msgBlock = document.createElement('div');
|
|
|
|
resultDiv.insertBefore(msgBlock, anchorDiv);
|
|
|
|
let divider = document.createElement('hr');
|
|
msgBlock.appendChild(divider);
|
|
|
|
let msgHeader = document.createElement('div');
|
|
msgHeader.style.fontWeight = 'bold';
|
|
// capitalize first letter of question header
|
|
msgHeader.innerHTML =
|
|
searchTerm.charAt(0).toUpperCase() + searchTerm.slice(1);
|
|
msgBlock.appendChild(msgHeader);
|
|
|
|
async function readStream() {
|
|
try {
|
|
const response = await fetch(
|
|
'https://ray-assistant-public-bxauk.cld-kvedzwag2qa8i5bj.s.anyscaleuserdata.com/chat',
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
},
|
|
body: JSON.stringify({messages: chatPopupDiv.messages}),
|
|
}
|
|
);
|
|
const reader = response.body.getReader();
|
|
let decoder = new TextDecoder('utf-8');
|
|
let msgContent = document.createElement('div');
|
|
msgBlock.appendChild(msgContent);
|
|
msgContent.innerHTML = '';
|
|
|
|
let collectChunks = '';
|
|
|
|
while (true) {
|
|
const {done, value} = await reader.read();
|
|
if (done) {
|
|
renderCopyButtons(resultDiv);
|
|
break;
|
|
}
|
|
|
|
const chunk = decoder.decode(value, {stream: true});
|
|
collectChunks += chunk;
|
|
let html = marked.parse(collectChunks);
|
|
html = DOMPurify.sanitize(html);
|
|
msgContent.innerHTML = html;
|
|
highlightCode();
|
|
}
|
|
chatPopupDiv.messages.push({role: 'assistant', content: collectChunks});
|
|
// we want to autoscroll to the bottom of the chat container after the answer is streamed in
|
|
chatContainerDiv.scrollTo(0, chatContainerDiv.scrollHeight);
|
|
} catch (error) {
|
|
console.error('Fetch API failed:', error);
|
|
}
|
|
}
|
|
readStream().then((res) => console.log(res));
|
|
}
|
|
}
|
|
|
|
searchBtn.addEventListener('click', rayAssistant);
|
|
searchBar.addEventListener('keydown', rayAssistant);
|