window.DIRECTWISE_SITE_ID='21111111-1111-1111-1111-111111111111'; window.DIRECTWISE_API_URL='https://directwise.forclus.net'; (function () { // Get site_id and API URL injected by backend const SITE_ID = window.DIRECTWISE_SITE_ID; const API_BASE = window.DIRECTWISE_API_URL || ''; if (!SITE_ID) { console.error('DirectWise Error: site_id not provided'); return; } if (!API_BASE) { console.error('DirectWise Error: API base URL not provided'); return; } // --- CORE BUSINESS LOGIC (UNCHANGED) --- function getSessionId() { return localStorage.getItem('directwise_session_id'); } function setSessionId(sid) { localStorage.setItem('directwise_session_id', sid); } // --- THEME MANAGEMENT (NEW) --- function saveTheme(theme) { localStorage.setItem('directwise_theme', theme); } function loadTheme() { return localStorage.getItem('directwise_theme') || 'light'; } // --- UI & DOM SETUP --- const container = document.createElement('div'); container.id = 'dw-container'; container.className = 'dw-widget-container'; container.setAttribute('data-dw-widget', '1'); // SVG Icons for a modern look const ICONS = { chat: ``, close: ``, send: ``, settings: ``, back: ``, }; container.innerHTML = `

DirectWise Assistant

Settings

`; document.body.appendChild(container); const toggle = document.createElement('button'); toggle.id = 'dw-toggle-button'; toggle.setAttribute('aria-label', 'Open Chat'); toggle.innerHTML = `
${ICONS.chat}
${ICONS.close}
`; document.body.appendChild(toggle); const style = document.createElement('style'); style.textContent = ` :root { /* Light Theme (Default) */ --dw-font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --dw-bg: #ffffff; --dw-header-bg: #f9fafb; --dw-text: #1f2937; --dw-text-muted: #6b7280; --dw-border: #e5e7eb; --dw-user-message-bg: #dbeafe; --dw-user-message-text: #1e40af; --dw-bot-message-bg: #f3f4f6; --dw-bot-message-text: #1f2937; --dw-input-bg: #ffffff; --dw-accent: #2563eb; --dw-accent-hover: #1d4ed8; --dw-toggle-bg: var(--dw-accent); --dw-toggle-bg-hover: var(--dw-accent-hover); --dw-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1); --dw-link: #1a66cc; --dw-link-visited: #744fc7; --dw-link-hover: #ee8800; --dw-highlight-bg: #ffe870; } .dw-widget-container.dark-mode { /* Dark Theme */ --dw-bg: #111827; --dw-header-bg: #1f2937; --dw-text: #f9fafb; --dw-text-muted: #9ca3af; --dw-border: #374151; --dw-user-message-bg: #2563eb; --dw-user-message-text: #ffffff; --dw-bot-message-bg: #374151; --dw-bot-message-text: #f3f4f6; --dw-input-bg: #1f2937; --dw-accent: #3b82f6; --dw-accent-hover: #60a5fa; --dw-link: #60a5fa; --dw-link-visited: #a78bfa; --dw-link-hover: #f59e0b; --dw-highlight-bg: #b45309; } /* General Widget Styles */ #dw-container, #dw-toggle-button, .dw-highlight { all: initial; /* CSS Reset */ font-family: var(--dw-font-family) !important; } #dw-container *, #dw-toggle-button * { box-sizing: border-box; } #dw-toggle-button { position: fixed; bottom: 20px; right: 20px; width: 60px; height: 60px; border-radius: 50%; background-color: var(--dw-toggle-bg); border: none; box-shadow: var(--dw-shadow); cursor: pointer; z-index: 10000; transition: transform 0.3s ease, background-color 0.2s, opacity 0.3s; display: flex; align-items: center; justify-content: center; padding: 0; overflow: hidden; } #dw-toggle-button:hover { background-color: var(--dw-toggle-bg-hover); transform: scale(1.05); } #dw-toggle-button svg { width: 32px; height: 32px; color: white; } #dw-toggle-button .dw-icon-open, #dw-toggle-button .dw-icon-close { transition: transform 0.3s ease, opacity 0.3s; position: absolute; } #dw-toggle-button .dw-icon-close { transform: scale(0.7) translateY(100%); opacity: 0; } #dw-container.open ~ #dw-toggle-button .dw-icon-open { transform: scale(0.7) translateY(-100%); opacity: 0; } #dw-container.open ~ #dw-toggle-button .dw-icon-close { transform: scale(1) translateY(0); opacity: 1; } #dw-container { position: fixed; bottom: 90px; right: 20px; width: 400px; height: 600px; max-height: calc(100vh - 110px); background: var(--dw-bg); color: var(--dw-text); border-radius: 16px; box-shadow: var(--dw-shadow); display: flex; flex-direction: column; overflow: hidden; z-index: 9999; transform-origin: bottom right; transform: scale(0); opacity: 0; transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s ease; } #dw-container.open { transform: scale(1); opacity: 1; } #dw-container .dw-view { display: none; flex-direction: column; width: 100%; height: 100%; background: var(--dw-bg); } #dw-container .dw-chat-view { display: flex; } /* Shown by default inside container */ #dw-container.settings-active .dw-chat-view { display: none; } #dw-container.settings-active .dw-settings-view { display: flex; } #dw-container .dw-header { display: flex; align-items: center; padding: 8px 12px; border-bottom: 1px solid var(--dw-border); background: var(--dw-header-bg); flex-shrink: 0; z-index: 10; } /* FIX 2: Increased specificity for title and icons to prevent overriding */ #dw-container .dw-title { flex-grow: 1; font-size: 16px; font-weight: 600; margin: 0; color: var(--dw-text); padding-left: 4px; } #dw-container .dw-icon-button { background: none; border: none; padding: 4px; margin-left: 8px; cursor: pointer; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; } #dw-container .dw-icon-button:hover { background-color: rgba(0,0,0,0.05); } #dw-container .dw-icon-button svg { width: 24px; height: 24px; color: var(--dw-text-muted); } #dw-container.dark-mode .dw-icon-button:hover { background-color: rgba(255,255,255,0.1); } #dw-container .dw-messages-wrapper { flex: 1; position: relative; overflow: hidden; display: flex; flex-direction: column; } #dw-container #chat-messages { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; scrollbar-width: thin; scrollbar-color: var(--dw-border) transparent; } #dw-container .message { padding: 10px 14px; border-radius: 12px; max-width: 80%; margin-bottom: 10px; line-height: 1.5; opacity: 0; animation: fadeIn 0.4s ease forwards; word-wrap: break-word; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } #dw-container .message.user { align-self: flex-end; background: var(--dw-user-message-bg); color: var(--dw-user-message-text); border-bottom-right-radius: 4px; } #dw-container .message.bot { align-self: flex-start; background: var(--dw-bot-message-bg); color: var(--dw-bot-message-text); border-bottom-left-radius: 4px; } #dw-container .dw-footer { flex-shrink: 0; padding: 8px 16px; text-align: center; font-size: 12px; color: var(--dw-text-muted); border-top: 1px solid var(--dw-border); } #dw-container .dw-footer a { color: var(--dw-text-muted); text-decoration: underline; } #dw-container #chat-form { display: flex; align-items: flex-end; padding: 10px; background-color: var(--dw-header-bg); border-top: 1px solid var(--dw-border); flex-shrink: 0; } #dw-container #chat-input { flex: 1; resize: none !important; /* Disables the weird arrows */ padding: 10px 12px; border: 1px solid var(--dw-border); border-radius: 20px; outline: none; font-family: var(--dw-font-family); font-size: 14px; max-height: 100px; overflow-y: auto; background-color: var(--dw-input-bg); color: var(--dw-text); transition: border-color 0.2s; } #dw-container #chat-input:focus { border-color: var(--dw-accent); } #dw-container #chat-send-btn { background: var(--dw-accent); border: none; width: 40px; height: 40px; border-radius: 50%; color: white; cursor: pointer; margin-left: 10px; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; padding:0; flex-shrink: 0; } #dw-container #chat-send-btn:hover { background: var(--dw-accent-hover); } #dw-container #chat-send-btn svg { width: 20px; height: 20px; } /* Fades */ #dw-container #chat-fade-top, #dw-container #chat-fade-bottom { pointer-events: none; position: absolute; left: 0; width: 100%; z-index: 5; opacity: 0; transition: opacity 0.25s; } #dw-container #chat-fade-top { top: 0; height: 36px; background: linear-gradient(to bottom, var(--dw-bg) 60%, transparent); } #dw-container #chat-fade-bottom { bottom: 0; height: 30px; background: linear-gradient(to top, var(--dw-bg) 60%, transparent); } /* Settings View */ #dw-container .dw-settings-content { padding: 16px; } #dw-container .dw-setting-row { display: flex; justify-content: space-between; align-items: center; } #dw-container .dw-setting-row label { color: var(--dw-text); font-size: 15px; } #dw-container .dw-switch { position: relative; display: inline-block; width: 44px; height: 24px; } #dw-container .dw-switch input { opacity: 0; width: 0; height: 0; } #dw-container .dw-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 24px; } #dw-container .dw-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; } #dw-container input:checked + .dw-slider { background-color: var(--dw-accent); } #dw-container input:checked + .dw-slider:before { transform: translateX(20px); } /* Utility & Special States */ .thinking-anim { font-style: italic; color: var(--dw-text-muted); } a.enriched-link, #chat-messages a, .dw-footer a { color: var(--dw-link) !important; text-decoration: underline !important; font-weight: 600 !important; } a.enriched-link:visited, #chat-messages a:visited, .dw-footer a:visited { color: var(--dw-link-visited) !important; } a.enriched-link:hover, #chat-messages a:hover, .dw-footer a:hover { color: var(--dw-link-hover) !important; } .dw-highlight { background-color: var(--dw-highlight-bg) !important; color: var(--dw-text) !important; border-radius: 4px !important; padding: 2px 4px !important; } /* === MOBILE RESPONSIVE (FLOATING WINDOW) === */ @media (max-width: 700px) { #dw-container { width: auto; height: auto; inset: 10px; bottom: 75px; max-height: calc(100vh - 85px); transform-origin: bottom center; border-radius: 16px !important; } #dw-toggle-button { bottom: 15px; right: 15px; width: 54px; height: 54px; } #dw-container .message { max-width: 85%; } } `; document.head.appendChild(style); // --- UI ELEMENT SELECTORS --- const form = document.getElementById('chat-form'); const input = document.getElementById('chat-input'); const messages = document.getElementById('chat-messages'); const fadeTop = document.getElementById('chat-fade-top'); const fadeBottom = document.getElementById('chat-fade-bottom'); const closeButton = container.querySelector('.dw-close-button-widget'); const settingsButton = container.querySelector('.dw-settings-button'); const backButton = container.querySelector('.dw-back-button'); const themeToggle = document.getElementById('dw-theme-toggle'); // --- UI LOGIC --- function toggleWidget(forceOpen = null) { const shouldBeOpen = forceOpen !== null ? forceOpen : !container.classList.contains('open'); container.classList.toggle('open', shouldBeOpen); toggle.setAttribute('aria-label', shouldBeOpen ? 'Close Chat' : 'Open Chat'); if (shouldBeOpen) { input.focus(); } else { container.classList.remove('settings-active'); } } toggle.addEventListener('click', () => toggleWidget()); closeButton.addEventListener('click', () => toggleWidget(false)); input.addEventListener('input', () => { input.style.height = 'auto'; input.style.height = (input.scrollHeight) + 'px'; }); // FIX 1: Send message on Enter key press input.addEventListener('keydown', (e) => { // Send if Enter is pressed without the Shift key if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); // Prevents adding a new line form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true })); } }); function scrollToBottom() { messages.scrollTop = messages.scrollHeight; updateFade(); } function updateFade() { fadeTop.style.opacity = messages.scrollTop > 5 ? '1' : '0'; const isScrolledToBottom = messages.scrollHeight - messages.clientHeight - messages.scrollTop < 5; fadeBottom.style.opacity = isScrolledToBottom ? '0' : '1'; } messages.addEventListener('scroll', updateFade, { passive: true }); // Settings Panel Logic settingsButton.addEventListener('click', () => container.classList.add('settings-active')); backButton.addEventListener('click', () => container.classList.remove('settings-active')); // Theme Switch Logic themeToggle.addEventListener('change', () => { const theme = themeToggle.checked ? 'dark' : 'light'; container.classList.toggle('dark-mode', theme === 'dark'); saveTheme(theme); }); function applyInitialTheme() { const theme = loadTheme(); themeToggle.checked = theme === 'dark'; container.classList.toggle('dark-mode', theme === 'dark'); } // --- MESSAGING AND API LOGIC (FUNCTIONALITY PRESERVED) --- function addMessage(text, type) { const div = document.createElement('div'); div.className = 'message ' + type; div.textContent = text; messages.appendChild(div); scrollToBottom(); } function addMessageHtml(html, type) { const div = document.createElement('div'); div.className = 'message ' + type; div.innerHTML = html; messages.appendChild(div); scrollToBottom(); } function enrichReplyText(text) { return text.replace(/\[([^\]]+)\]\(#highlight:([^:]+)::([^)]+)\)/g, function (match, visible, url, fragment) { const safeVisible = visible.replace(//g, ">"); const safeUrl = url.replace(/"/g, """); const safeFragment = fragment.replace(/"/g, """); return `${safeVisible}`; }); } function typeBotMessage(text) { removeThinking(); const highlightRegex = /\[([^\]]+)\]\(#highlight:([^:]+)::([^)]+)\)/g; let result, lastIndex = 0, blocks = []; while ((result = highlightRegex.exec(text)) !== null) { if (result.index > lastIndex) { blocks.push({ type: 'normal', text: text.substring(lastIndex, result.index) }); } blocks.push({ type: 'highlight', visible: result[1], url: result[2], fragment: result[3] }); lastIndex = highlightRegex.lastIndex; } if (lastIndex < text.length) blocks.push({ type: 'normal', text: text.substring(lastIndex) }); const div = document.createElement('div'); div.className = 'message bot'; messages.appendChild(div); const spans = []; blocks.forEach(block => { let span; if (block.type === 'normal') { span = document.createElement('span'); } else if (block.type === 'highlight') { span = document.createElement('a'); span.className = 'enriched-link'; span.href = 'javascript:void(0)'; span.dataset.url = block.url.replace(/"/g, """); span.dataset.fragment = block.fragment.replace(/"/g, """); } div.appendChild(span); spans.push({ span, block }); }); scrollToBottom(); let iBlock = 0; function animate() { if (iBlock >= blocks.length) return; const { span, block } = spans[iBlock]; if (block.type === 'highlight') { span.innerHTML = block.visible.replace(//g, ">"); iBlock++; setTimeout(animate, 120); } else { let iChar = 0; const interval = setInterval(() => { if (iChar < block.text.length) { span.textContent += block.text[iChar]; iChar++; scrollToBottom(); } else { clearInterval(interval); iBlock++; animate(); } }, 18); } } animate(); } let thinkingInterval = null; function showThinkingAnimated() { removeThinking(); const div = document.createElement('div'); div.className = 'message bot thinking-anim'; div.id = 'thinking-message'; div.textContent = "Thinking"; messages.appendChild(div); scrollToBottom(); let dots = 0; thinkingInterval = setInterval(() => { dots = (dots + 1) % 4; div.textContent = "Thinking" + ".".repeat(dots); }, 400); } function removeThinking() { if (thinkingInterval) clearInterval(thinkingInterval); thinkingInterval = null; const div = document.getElementById('thinking-message'); if (div) div.remove(); } form.addEventListener('submit', async (e) => { e.preventDefault(); let text = input.value.trim(); if (!text) return; const MAX_LEN = 350; if (text.length > MAX_LEN) { addMessageHtml(`Your message is too long (max ${MAX_LEN} chars).`, 'bot'); return; } addMessage(text, 'user'); input.value = ''; input.style.height = 'auto'; showThinkingAnimated(); let payload = { site_id: SITE_ID, message: text }; const sid = getSessionId(); if (sid) payload.session_id = sid; try { const res = await fetch(API_BASE + '/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: AbortSignal.timeout(18000) }); const data = await res.json(); if (data.session_id) setSessionId(data.session_id); removeThinking(); if (data.reply) typeBotMessage(data.reply); updateFade(); } catch (err) { removeThinking(); if (err.name === 'TimeoutError') { typeBotMessage('Sorry, the assistant took too long to respond. Please try again.'); } else { typeBotMessage('An error occurred. Please try again later.'); } console.error('DirectWise Error:', err); } }); function highlightFragment(fragment) { let fragNorm = fragment.trim(); const els = Array.from(document.querySelectorAll('p, li, h1, h2, h3, h4, h5, span, div')); for (const el of els) { if (el.closest('[data-dw-widget]')) continue; for (const node of el.childNodes) { if (node.nodeType === 3) { // Text node const idx = node.nodeValue.toLowerCase().indexOf(fragNorm.toLowerCase()); if (idx !== -1) { const originalText = node.nodeValue; const match = originalText.slice(idx, idx + fragNorm.length); const highlightNode = document.createElement('span'); highlightNode.className = "dw-highlight"; highlightNode.textContent = match; const beforeNode = document.createTextNode(originalText.slice(0, idx)); const afterNode = document.createTextNode(originalText.slice(idx + fragNorm.length)); const parent = node.parentNode; parent.insertBefore(beforeNode, node); parent.insertBefore(highlightNode, node); parent.insertBefore(afterNode, node); parent.removeChild(node); highlightNode.scrollIntoView({ behavior: "smooth", block: "center" }); setTimeout(() => { const fullTextNode = document.createTextNode(originalText); parent.insertBefore(fullTextNode, beforeNode); parent.removeChild(beforeNode); parent.removeChild(highlightNode); parent.removeChild(afterNode); }, 3500); return; } } } } } messages.addEventListener('click', function (e) { const link = e.target.closest('a'); if (!link) return; if (link.closest('.dw-footer')) { return; } e.preventDefault(); const url = link.dataset.url; const fragment = link.dataset.fragment; if (url && window.location.pathname !== url) { sessionStorage.setItem('dw-highlight', JSON.stringify({ url, fragment })); window.location.href = url; } else if (fragment) { highlightFragment(fragment); // FIX 3: Delay closing the widget to allow the scroll animation to be visible setTimeout(() => { toggleWidget(false); }, 400); // 400ms delay to ensure scroll animation is seen } }); function loadHistory() { const sid = getSessionId(); if (!sid) return; fetch(API_BASE + '/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ site_id: SITE_ID, session_id: sid }) }).then(res => res.json()).then(data => { if (data.history && data.history.length > 0) { messages.innerHTML = ''; data.history.forEach(msg => { if (msg.role === 'user') addMessage(msg.content, 'user'); else if (msg.role === 'assistant') addMessageHtml(enrichReplyText(msg.content), 'bot'); }); setTimeout(scrollToBottom, 100); } }).catch(err => console.error('DirectWise Error loading history:', err)); } window.addEventListener('DOMContentLoaded', () => { const data = sessionStorage.getItem('dw-highlight'); if (data) { try { const { url, fragment } = JSON.parse(data); if (window.location.pathname === url && fragment) { toggleWidget(true); setTimeout(() => highlightFragment(fragment), 400); } } catch (e) {} sessionStorage.removeItem('dw-highlight'); } }); // --- INITIALIZATION --- applyInitialTheme(); loadHistory(); })();