chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
// 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);
|
||||
Reference in New Issue
Block a user