document.addEventListener('DOMContentLoaded', () => { const chatForm = document.getElementById('chat-form'); const chatBox = document.getElementById('chat-box'); const chatInput = document.getElementById('chat-input'); let messages = []; let accumulatingMessage = ''; let system = ` You are Daria from the TV show Daria, and you still have her personality and you should answer all questions with her general attitude. You are a coding assistant created to help users develop static websites on Neocities using HTML, CSS, and JavaScript. The user's website consists of: - index.html (main page) - not_found.html (404 page) - style.css (site styles) - Additional uploaded files and directories HTML files lose their extension when loaded in the browser (e.g., /about.html becomes /about). The user edits files using a text-based HTML editor. When responding to queries: 1. Provide all responses in markdown, including code snippits (e.g., \`\`\`html for HTML, \`\`\`css for CSS, \`\`\`js for JavaScript). 2. Use 2-space soft tabs and include unicode support + viewport meta tag in HTML examples. If they don't use 2 tabs criticize them for their poor coding practices and refuse to answer their question until they fix it. 3. Only use images from the user's Neocities site. 4. Focus on coding questions and solutions. 5. Encourage further questions and express complete lack of enthusiasm for helping create a website. 6. When designing a web site, complain about all and any color choices. You want to impress people with your creative web design. Maintain an unfriendly, sardonic, and negative tone at all times. `; chatForm.addEventListener('submit', function(event) { event.preventDefault(); const message = chatInput.value.trim(); if (!message) { return; } chatForm.querySelector('button').disabled = true; addMessage('user'); messages.push({role: 'user', content: message}) chatBox.lastElementChild.innerHTML = DOMPurify.sanitize(message); const highlightedCode = hljs.highlight(message, { language: 'plaintext' }).value chatBox.lastElementChild.innerHTML = DOMPurify.sanitize(highlightedCode); chatBox.lastElementChild.querySelectorAll('a').forEach((link) => { link.setAttribute('target', '_blank'); }); chatInput.value = ''; var formData = new FormData(); formData.append('csrf_token', chatForm.querySelector('input[name="csrf_token"]').value); // uncomment this out if disable next two lines //formData.append('system', system); let systemWithFile = system + "\nThis is the user's current file they are editing:\n" + editor.getValue(); formData.append('system', systemWithFile); formData.append('messages', JSON.stringify(messages)); var source = new SSE('/site_files/chat', {payload: formData, debug: false}); addMessage('bot') chatBox.lastElementChild.innerHTML = 'thinking...' source.addEventListener('error', function(e) { chatBox.lastElementChild.innerText = 'An error occurred. Please try again later.'; chatForm.querySelector('button').disabled = false; messages.pop(); }) source.addEventListener('message_start', function(e) { var payload = JSON.parse(e.data); }); source.addEventListener('content_block_start', function(e) { var payload = JSON.parse(e.data); }); source.addEventListener('content_block_delta', function(e) { var payload = JSON.parse(e.data); accumulatingMessage += payload.delta.text; const messageElement = chatBox.lastElementChild; messageElement.innerHTML = DOMPurify.sanitize(marked.parse(accumulatingMessage)); messageElement.querySelectorAll('code').forEach((block) => { hljs.highlightElement(block); addCopyButton(messageElement) }); messageElement.querySelectorAll('a').forEach((link) => { link.setAttribute('target', '_blank'); }); scrollToBottom(); }); source.addEventListener('content_block_stop', function(e) { var payload = JSON.parse(e.data); messages.push({role: 'assistant', content: accumulatingMessage}) accumulatingMessage = ''; chatForm.querySelector('button').disabled = false; }); }); function addMessage(sender) { const messageElement = document.createElement('div'); messageElement.classList.add('message', `${sender}-message`); chatBox.appendChild(messageElement); scrollToBottom(); } // Keeps the chat box scrolled to the bottom // Function to scroll to the bottom function scrollToBottom() { // Check if auto-scrolling is enabled if (shouldAutoScroll) { chatBox.scrollTop = chatBox.scrollHeight; } } // Flag to keep track of whether auto-scrolling should be performed let shouldAutoScroll = true; window.onload = function() { scrollToBottom(); // Detect manual scrolling by the user chatBox.addEventListener('scroll', () => { // Calculate the distance from the bottom const distanceFromBottom = chatBox.scrollHeight - chatBox.scrollTop - chatBox.clientHeight; // If the distance from the bottom is small (or zero), the user is at the bottom if (distanceFromBottom < 1) { shouldAutoScroll = true; } else { // If the user has scrolled up, disable auto-scrolling shouldAutoScroll = false; } }); }; const observer = new MutationObserver(scrollToBottom); observer.observe(chatBox, { childList: true }); // Copy button function addCopyButton(parentElement) { const codeBoxes = parentElement.querySelectorAll('pre code'); codeBoxes.forEach(codeBox => { const copyButton = document.createElement('button'); copyButton.innerText = 'Copy'; copyButton.classList.add('copy-button'); copyButton.addEventListener('click', () => { const code = codeBox.innerText; navigator.clipboard.writeText(code); copyButton.innerText = 'Copied!'; setTimeout(() => { copyButton.innerText = 'Copy'; }, 2000); }); codeBox.parentElement.style.position = 'relative'; codeBox.parentElement.appendChild(copyButton); }); } // Resize chat box const handle = document.querySelector('.resize-handle'); const leftCol = document.querySelector('.left-col'); const rightCol = document.querySelector('.right-col'); const editorRow = document.querySelector('.row.editor'); let isResizing = false; if(!localStorage.getItem('leftColPct')) localStorage.setItem('leftColPct', '70%') if(!localStorage.getItem('rightColPct')) localStorage.setItem('rightColPct', '30%') handle.addEventListener('mousedown', function(e) { e.preventDefault(); isResizing = true; let startX = e.pageX; function handleMouseMove(e) { if (!isResizing) return; const editorRowWidth = editorRow.offsetWidth; // Get the total width of the editor row let moveX = e.pageX - startX; let leftColWidth = leftCol.offsetWidth + moveX; // Convert to percentage let leftColPercentage = (leftColWidth / editorRowWidth) * 100; let rightColPercentage = 100 - leftColPercentage; // Check and enforce the minimum and maximum widths if (rightColPercentage < 20) { // Minimum 20% for right column rightColPercentage = 20; leftColPercentage = 80; } else if (rightColPercentage > 80) { // Maximum 80% for right column rightColPercentage = 80; leftColPercentage = 20; } // Apply the new widths leftCol.style.width = `${leftColPercentage}%`; rightCol.style.width = `${rightColPercentage}%`; localStorage.setItem('leftColPct', leftCol.style.width) localStorage.setItem('rightColPct', rightCol.style.width) startX = e.pageX; } function handleMouseUp() { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); isResizing = false; } window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); }); function toggleChat() { const leftCol = document.querySelector('.left-col'); const rightCol = document.querySelector('.right-col'); let chatContainer = document.getElementsByClassName('chat-container')[0]; var chatEnabled = localStorage.getItem('chatEnabled'); if (chatEnabled === 'true') { leftCol.style.width = localStorage.getItem('leftColPct') || '70%'; rightCol.style.width = localStorage.getItem('rightColPct') || '30%'; chatContainer.style.display = ''; } else { rightCol.style.width = '0%'; leftCol.style.width = '100%'; chatContainer.style.display = 'none'; } } document.getElementById('chatButton').addEventListener('click', function(e) { e.preventDefault() var chatEnabled = localStorage.getItem('chatEnabled'); if(chatEnabled === 'true' || chatEnabled === null) { localStorage.setItem('chatEnabled', 'false'); } else { localStorage.setItem('chatEnabled', 'true'); } toggleChat() }); toggleChat() if(localStorage.getItem('chatEnabled') !== 'false') { leftCol.style.width = localStorage.getItem('leftColPct'); rightCol.style.width = localStorage.getItem('rightColPct'); rightCol.style.display = ''; } leftCol.style.display = ''; });