Spaces:
Sleeping
Sleeping
| const launcher = document.getElementById('askross-launcher'); | |
| const openChat = document.getElementById('open-chat'); | |
| const closeChat = document.getElementById('close-chat'); | |
| const chatbotContainer = document.getElementById('chatbot-container'); | |
| const chatForm = document.getElementById('chat-form'); | |
| const userInput = document.getElementById('user-input'); | |
| const chatMessages = document.getElementById('chat-messages'); | |
| const typingIndicator = document.getElementById('typing-indicator'); | |
| const scrollBottom = document.getElementById('scroll-bottom'); | |
| let thread_id = localStorage.getItem('askross_thread_id'); | |
| if (!thread_id) { | |
| thread_id = crypto.randomUUID(); | |
| localStorage.setItem('askross_thread_id', thread_id); | |
| } | |
| const pageContext = { | |
| page_id: window.location.pathname === '/' ? 'home' : 'page', | |
| page_title: document.title || 'AskRoss', | |
| }; | |
| let isFirstMessage = true; | |
| const homepageQuestions = [ | |
| "How can I get a mortgage if the bank said no?", | |
| "What mortgage services do you offer?", | |
| "Can I refinance my home?", | |
| "How do I improve my credit score?" | |
| ]; | |
| // ✅ Welcome + Suggestions (clean spacing) | |
| function showSuggestions() { | |
| const wrapper = document.createElement('div'); | |
| wrapper.className = 'suggestions-wrapper'; | |
| const welcome = document.createElement('div'); | |
| welcome.className = 'welcome-message'; | |
| welcome.innerHTML = `<div class="bubble"><strong>Hi, I’m AskRoss 👋</strong><br>Simple mortgage advice — even if the bank said no.</div>`; | |
| const divider = document.createElement('div'); | |
| divider.className = 'suggestion-divider'; | |
| divider.textContent = 'Popular questions'; | |
| wrapper.appendChild(welcome); | |
| wrapper.appendChild(divider); | |
| homepageQuestions.forEach(q => { | |
| const btn = document.createElement('button'); | |
| btn.className = 'suggestion-btn'; | |
| btn.textContent = q; | |
| btn.onclick = () => sendMessage(q); | |
| wrapper.appendChild(btn); | |
| }); | |
| chatMessages.appendChild(wrapper); | |
| } | |
| // OPEN | |
| openChat.addEventListener('click', () => { | |
| chatbotContainer.classList.add('open'); | |
| launcher.style.display = 'none'; | |
| if (chatMessages.children.length === 0) { | |
| showSuggestions(); | |
| } | |
| }); | |
| // CLOSE | |
| closeChat.addEventListener('click', () => { | |
| chatbotContainer.classList.remove('open'); | |
| launcher.style.display = 'flex'; | |
| }); | |
| // Initialize closed by default | |
| chatbotContainer.classList.remove('open'); | |
| launcher.style.display = 'flex'; | |
| // SEND | |
| async function sendMessage(message) { | |
| if (!message) return; | |
| if (isFirstMessage) { | |
| chatMessages.innerHTML = ''; | |
| isFirstMessage = false; | |
| } | |
| appendMessage('user', message); | |
| typingIndicator.classList.remove('hidden'); | |
| scrollToBottom(); | |
| try { | |
| const response = await fetch('/chat', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ | |
| message, | |
| thread_id, | |
| page_id: pageContext.page_id, | |
| page_title: pageContext.page_title | |
| }) | |
| }); | |
| const data = await response.json(); | |
| typingIndicator.classList.add('hidden'); | |
| if (data.message) { | |
| appendMessage('bot', data.message); | |
| } else { | |
| appendMessage('bot', "Sorry, something went wrong"); | |
| } | |
| updateScrollButton(); | |
| scrollToBotMessage(); | |
| } catch { | |
| typingIndicator.classList.add('hidden'); | |
| appendMessage('bot', "Network error. Please try again."); | |
| updateScrollButton(); | |
| scrollToBottom(); | |
| } | |
| updateScrollButton(); | |
| } | |
| function updateScrollButton() { | |
| const nearBottom = chatMessages.scrollHeight - chatMessages.scrollTop - chatMessages.clientHeight < 80; | |
| scrollBottom.classList.toggle('hidden', nearBottom); | |
| } | |
| chatMessages.addEventListener('scroll', updateScrollButton); | |
| scrollBottom.addEventListener('click', () => { | |
| scrollToBottom(); | |
| updateScrollButton(); | |
| }); | |
| chatForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const message = userInput.value.trim(); | |
| if (!message) return; | |
| userInput.value = ''; | |
| sendMessage(message); | |
| }); | |
| // Markdown | |
| function escapeHtml(text) { | |
| return text | |
| .replace(/&/g,'&') | |
| .replace(/</g,'<') | |
| .replace(/>/g,'>'); | |
| } | |
| function normalizeMarkdownLinks(md) { | |
| let normalized = md; | |
| normalized = normalized.replace(/<a\s+href="(https?:\/\/[^\s"<>]+)"[^>]*>([^<]+)<\/a>/gi, '[$2]($1)'); | |
| normalized = normalized.replace(/(https?:\/\/[^\s"<>]+)(?:"\s*target="[^"]*"\s*rel="[^"]*"\s*>|\s*target="[^"]*"\s*rel="[^"]*"\s*>|>)([^\n<>]+)/gi, '[$2]($1)'); | |
| return normalized; | |
| } | |
| function markdownToHtml(md) { | |
| const normalized = normalizeMarkdownLinks(md); | |
| const html = marked.parse(normalized); | |
| return DOMPurify.sanitize(html, {ADD_ATTR: ['target', 'rel']}); | |
| } | |
| // ✅ Messages (NO avatars) | |
| function appendMessage(sender, text) { | |
| const wrapper = document.createElement('div'); | |
| wrapper.className = `message ${sender}`; | |
| const bubble = document.createElement('div'); | |
| bubble.className = 'bubble'; | |
| if (sender === 'bot') { | |
| bubble.innerHTML = markdownToHtml(text); | |
| } else { | |
| bubble.textContent = text; | |
| } | |
| wrapper.appendChild(bubble); | |
| chatMessages.appendChild(wrapper); | |
| } | |
| function scrollToBottom() { | |
| chatMessages.scrollTo({ | |
| top: chatMessages.scrollHeight, | |
| behavior: 'smooth' | |
| }); | |
| } | |
| function scrollToBotMessage() { | |
| const lastBot = chatMessages.querySelector('.message.bot:last-child'); | |
| if (lastBot) { | |
| lastBot.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| } else { | |
| scrollToBottom(); | |
| } | |
| } | |