From 6cc62486a2433f12d4dc81b9edc5b81ef31fcb69 Mon Sep 17 00:00:00 2001 From: Jackson Yu Date: Wed, 3 Jun 2026 15:10:47 -0400 Subject: [PATCH 1/3] feat: update docs assistant to a single unified bot --- assistant.js | 169 +++++++++++++++++++-------------------------------- styles.css | 10 +-- 2 files changed, 69 insertions(+), 110 deletions(-) diff --git a/assistant.js b/assistant.js index b843607b..9bf9ac33 100644 --- a/assistant.js +++ b/assistant.js @@ -58,19 +58,8 @@ botContainer.id = 'docs-bot' botContainer.classList.add('bot-iframe-container') - // The default docs bot covers everything under botpress.com/docs. - // The ADK section gets its own bot (agent-0) with knowledge scoped to - // ADK pages + the ADK skill references. The iframe URL is swapped on - // route changes — see checkPathChange below. - const DEFAULT_BOT_URL = 'https://botpress.github.io/docs-bot/' - const ADK_BOT_URL = 'https://botpress.github.io/docs-bot/adk-bot-frontend/' - - function isAdkRoute() { - // Match /docs/adk/ on the live site (pathname includes /docs/ prefix). - // The bare /adk and /adk/ teaser routes keep using the default docs bot. - return /\/adk\/.+/.test(window.location.pathname) - } - + // A single docs assistant (marg) covers everything under botpress.com/docs. + const MARG_BOT_URL = 'https://botpress.github.io/docs-bot/marg-frontend/' const iframe = document.createElement('iframe') iframe.title = 'Botpress' @@ -80,78 +69,49 @@ iframe.style.top = '0' iframe.style.left = '0' iframe.allow = 'clipboard-write' - iframe.src = DEFAULT_BOT_URL - - const adkIframe = document.createElement('iframe') - adkIframe.title = 'Botpress ADK' - adkIframe.style.width = '100%' - adkIframe.style.height = '100%' - adkIframe.style.position = 'absolute' - adkIframe.style.top = '0' - adkIframe.style.left = '0' - adkIframe.allow = 'clipboard-write' - adkIframe.src = ADK_BOT_URL + iframe.src = MARG_BOT_URL // "ready" means the React app inside the iframe has mounted and rendered its // first frame. We detect this via the `requestTheme` postMessage the frontend - // sends from a useEffect on mount — that fires after the first paint, not just - // after the HTML document loads. The `load` event fires too early (HTML loaded - // but React not yet mounted), so using it directly causes a blank-white flash. - let defaultReady = false - let adkReady = false - - function markReady(isAdk) { - if (isAdk) adkReady = true - else defaultReady = true + // sends from a useEffect on mount — the `load` event fires too early (HTML + // loaded but React not yet mounted), so using it directly causes a flash. + let ready = false + + function markReady() { + ready = true showActiveIframe() } - // Fallback: if the iframe never sends requestTheme (e.g. default docs bot - // doesn't have the hook), mark ready 600ms after the HTML load event so the - // panel isn't stuck hidden forever. + // Fallback: if the iframe never sends requestTheme, mark ready 600ms after + // the HTML load event so the panel isn't stuck hidden forever. iframe.addEventListener('load', () => { - setTimeout(() => { if (!defaultReady) markReady(false) }, 600) - }) - adkIframe.addEventListener('load', () => { - setTimeout(() => { if (!adkReady) markReady(true) }, 600) + setTimeout(() => { if (!ready) markReady() }, 600) }) function showActiveIframe() { - const adk = isAdkRoute() - const target = adk ? adkIframe : iframe - const other = adk ? iframe : adkIframe - const ready = adk ? adkReady : defaultReady - if (ready) { - // Bring the active bot to the front (z-index swap, no repaint = no flash). - target.style.zIndex = '2' - target.style.pointerEvents = 'auto' - other.style.zIndex = '0' - other.style.pointerEvents = 'none' - } - // If target isn't ready yet, leave both layers as-is — 'other' keeps its - // current z-index so the panel stays populated. markReady() fires again - // once the target's React app has mounted. + iframe.style.zIndex = '2' + iframe.style.pointerEvents = 'auto' + } } + // Kept as a function so downstream message senders need no changes. function getActiveIframe() { - return isAdkRoute() ? adkIframe : iframe + return iframe } - // Default starts above ADK so non-ADK pages show the correct bot before - // either React app sends its first readiness signal (requestTheme). iframe.style.zIndex = '1' iframe.style.pointerEvents = 'none' - adkIframe.style.zIndex = '0' - adkIframe.style.pointerEvents = 'none' botContainer.style.position = 'relative' botContainer.appendChild(iframe) - botContainer.appendChild(adkIframe) showActiveIframe() panel.appendChild(mobileDismiss) resizeHandle.appendChild(toggleCloseButton) + // The iframe header now provides the collapse control, so hide this edge + // toggle to avoid two "hide" buttons. The drag-to-resize handle remains. + toggleCloseButton.style.display = 'none' panel.appendChild(botContainer) panel.appendChild(resizeHandle) @@ -248,6 +208,27 @@ updateOverlay() } + // Maximize/restore the panel width (driven by the iframe's expand button). + // Remembers the prior inline width so "restore" returns to the CSS default + // or a drag-resized width. Persisted so it survives in-docs navigation. + let widePrevWidth = '' + let isWide = false + function setWide(wide) { + if (wide === isWide) return + if (wide) { + widePrevWidth = panel.style.width + const target = Math.round(Math.min(window.innerWidth * 0.6, 820)) + panel.style.width = target + 'px' + } else { + panel.style.width = widePrevWidth + } + isWide = wide + try { sessionStorage.setItem('bot-panel-wide', wide ? '1' : '0') } catch (e) {} + } + function toggleWidth() { + setWide(!isWide) + } + let isResizing = false let startX = 0 let startWidth = 0 @@ -258,13 +239,12 @@ if (e.target.closest('#bot-toggle-close')) { return } + // Drag-to-resize is intentionally disabled: the panel uses fixed widths + // (default + the expand button). We still track press/move here only so a + // plain click on the edge can toggle the panel, while a drag is ignored. isResizing = true hasMoved = false startX = e.clientX - startWidth = parseInt(window.getComputedStyle(panel).width, 10) - panel.classList.add('resizing') - document.body.style.cursor = 'col-resize' - document.body.style.userSelect = 'none' e.preventDefault() e.stopPropagation() }) @@ -276,13 +256,7 @@ if (moveDistance > clickThreshold) { hasMoved = true } - - if (hasMoved) { - const diff = startX - e.clientX - const maxWidth = window.innerWidth * 1 - const newWidth = Math.max(368, Math.min(maxWidth, startWidth + diff)) - panel.style.width = newWidth + 'px' - } + // No width mutation — resizing the panel by dragging is disabled. e.preventDefault() e.stopPropagation() @@ -291,14 +265,6 @@ document.addEventListener('mouseup', (e) => { if (isResizing) { isResizing = false - panel.classList.remove('resizing') - document.body.style.cursor = '' - document.body.style.userSelect = '' - - if (!hasMoved) { - togglePanel() - } - hasMoved = false e.preventDefault() e.stopPropagation() @@ -413,6 +379,10 @@ closePanel() } + if (event.data.type === 'toggleWidth') { + toggleWidth() + } + if (event.data.type === 'requestCurrentPage') { sendPanelOpenedMessage() } @@ -452,11 +422,7 @@ const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light' // The iframe's React app just mounted — mark it as visually ready so we // can show it without a blank-white flash. - if (event.source === adkIframe.contentWindow) { - if (!adkReady) markReady(true) - } else if (event.source === iframe.contentWindow) { - if (!defaultReady) markReady(false) - } + if (event.source === iframe.contentWindow && !ready) markReady() // Respond with the current theme to whichever iframe asked. if (event.source && typeof event.source.postMessage === 'function') { try { event.source.postMessage({ type: 'themeChanged', theme }, '*') } catch (_e) {} @@ -464,13 +430,10 @@ } }) - // Watch for docs theme toggles and forward to both iframes so they stay in - // sync regardless of which one is currently visible. + // Watch for docs theme toggles and forward to the iframe so it stays in sync. const themeObserver = new MutationObserver(() => { const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light' - ;[iframe, adkIframe].forEach(f => { - if (f && f.contentWindow) f.contentWindow.postMessage({ type: 'themeChanged', theme }, '*') - }) + if (iframe.contentWindow) iframe.contentWindow.postMessage({ type: 'themeChanged', theme }, '*') }) themeObserver.observe(document.documentElement, { attributes: true, @@ -541,23 +504,19 @@ if (window.location.pathname !== lastPath) { lastPath = window.location.pathname - showActiveIframe() - - const isExpanded = panel.classList.contains('bot-panel-expanded') - if (isExpanded) { - const activeIframe = isAdkRoute() ? adkIframe : iframe - if (activeIframe && activeIframe.contentWindow) { - activeIframe.contentWindow.postMessage( - { - type: 'pageChanged', - data: { - path: window.location.pathname, - title: document.title.replace(' - Botpress', ''), - }, + // Always tell the bot the page changed so it can refresh its page + // context, even when the panel is collapsed (it routes on the page URL). + if (iframe.contentWindow) { + iframe.contentWindow.postMessage( + { + type: 'pageChanged', + data: { + path: window.location.pathname, + title: document.title.replace(' - Botpress', ''), }, - '*' - ) - } + }, + '*' + ) } } } diff --git a/styles.css b/styles.css index 7a2b4992..a4e0dc01 100644 --- a/styles.css +++ b/styles.css @@ -206,8 +206,8 @@ img { } .bot-resize-handle:hover { - cursor: col-resize; - pointer-events: auto; + cursor: default; + pointer-events: none; } .bot-panel-collapsed #bot-toggle-close { @@ -240,7 +240,7 @@ img { display: flex; flex-direction: row; transform: translateX(100%); - resize: horizontal; + resize: none; overflow: visible; transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1); } @@ -260,10 +260,10 @@ img { bottom: 0; width: 40px; height: 100%; - cursor: col-resize; + cursor: default; background-color: transparent; z-index: 30; - pointer-events: auto; + pointer-events: none; display: flex; align-items: center; justify-content: center; From 6b5a832eee66396f853ee72579d586b792698382 Mon Sep 17 00:00:00 2001 From: Jackson Yu Date: Thu, 4 Jun 2026 12:34:59 -0400 Subject: [PATCH 2/3] fix: resolve iframe in bottom-bar + menu IIFEs so Ask-a-question reaches the bot --- assistant.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/assistant.js b/assistant.js index 9bf9ac33..ca62dba1 100644 --- a/assistant.js +++ b/assistant.js @@ -539,6 +539,14 @@ } function initInputBubble() { + // The iframe is created in a separate IIFE, so resolve it from the DOM here + // (this closure can't see initBotPanel's getActiveIframe). Without this the + // bottom "Ask a question" bar threw a ReferenceError and silently did nothing. + function getActiveIframe() { + const container = document.getElementById('docs-bot') + return container ? container.querySelector('iframe') : null + } + const inputBubble = document.createElement('div') inputBubble.id = 'ask-ai-input-bubble' inputBubble.classList.add('ask-ai-input-bubble') @@ -793,6 +801,13 @@ function initAskAIOverride() { const overriddenButtons = new WeakSet() + // Resolve the iframe from the DOM (defined in a separate IIFE) so this + // closure can message it. + function getActiveIframe() { + const container = document.getElementById('docs-bot') + return container ? container.querySelector('iframe') : null + } + function findAndOverrideButton() { const button = document.getElementById('page-context-menu-button') From c44a202f8eddb275c467f0cdaf17e04a2b774411 Mon Sep 17 00:00:00 2001 From: Jackson Yu Date: Fri, 5 Jun 2026 10:57:43 -0400 Subject: [PATCH 3/3] refactor: remove unused resize-handle and edge close-button code/styles --- assistant.js | 63 ----------------------------------------- styles.css | 79 ---------------------------------------------------- 2 files changed, 142 deletions(-) diff --git a/assistant.js b/assistant.js index ca62dba1..e3563357 100644 --- a/assistant.js +++ b/assistant.js @@ -29,18 +29,6 @@ ` - const toggleCloseButton = document.createElement('button') - toggleCloseButton.id = 'bot-toggle-close' - toggleCloseButton.classList.add('bot-toggle-close') - toggleCloseButton.setAttribute('aria-label', 'Close bot') - toggleCloseButton.innerHTML = ` - - ` - - const resizeHandle = document.createElement('div') - resizeHandle.classList.add('bot-resize-handle') - resizeHandle.setAttribute('aria-label', 'Resize panel') - const mobileDismiss = document.createElement('div') mobileDismiss.classList.add('bot-mobile-dismiss') mobileDismiss.setAttribute('aria-label', 'Swipe down to close') @@ -108,12 +96,7 @@ showActiveIframe() panel.appendChild(mobileDismiss) - resizeHandle.appendChild(toggleCloseButton) - // The iframe header now provides the collapse control, so hide this edge - // toggle to avoid two "hide" buttons. The drag-to-resize handle remains. - toggleCloseButton.style.display = 'none' panel.appendChild(botContainer) - panel.appendChild(resizeHandle) document.body.appendChild(overlay) document.body.appendChild(panel) @@ -229,48 +212,6 @@ setWide(!isWide) } - let isResizing = false - let startX = 0 - let startWidth = 0 - let hasMoved = false - const clickThreshold = 5 - - resizeHandle.addEventListener('mousedown', (e) => { - if (e.target.closest('#bot-toggle-close')) { - return - } - // Drag-to-resize is intentionally disabled: the panel uses fixed widths - // (default + the expand button). We still track press/move here only so a - // plain click on the edge can toggle the panel, while a drag is ignored. - isResizing = true - hasMoved = false - startX = e.clientX - e.preventDefault() - e.stopPropagation() - }) - - document.addEventListener('mousemove', (e) => { - if (!isResizing) return - - const moveDistance = Math.abs(e.clientX - startX) - if (moveDistance > clickThreshold) { - hasMoved = true - } - // No width mutation — resizing the panel by dragging is disabled. - - e.preventDefault() - e.stopPropagation() - }) - - document.addEventListener('mouseup', (e) => { - if (isResizing) { - isResizing = false - hasMoved = false - e.preventDefault() - e.stopPropagation() - } - }) - let touchStartY = 0 let touchCurrentY = 0 let touchStartTime = 0 @@ -329,10 +270,6 @@ panel.addEventListener('touchcancel', handleTouchEnd, { passive: false }) toggleButton.addEventListener('click', togglePanel) - toggleCloseButton.addEventListener('click', (e) => { - e.stopPropagation() - closePanel() - }) mobileDismiss.addEventListener('click', closePanel) overlay.addEventListener('click', (e) => { if (isMobile() && panel.classList.contains('bot-panel-expanded')) { diff --git a/styles.css b/styles.css index a4e0dc01..f74d6cbc 100644 --- a/styles.css +++ b/styles.css @@ -182,52 +182,6 @@ img { height: 16px; } -#bot-toggle-close { - width: 35px; - height: 35px; - flex-shrink: 0; - color: light-dark(#1a1a1a, #fcfcfc); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - z-index: 32; - pointer-events: none; - background-color: light-dark(#fcfcfc, #1a1a1a); - border: 1px solid light-dark(rgb(226, 222, 230), rgb(255 255 255/0.07)); - border-radius: 0.75em; - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - opacity: 1; - transition: opacity 0.2s ease-in-out; -} - -.bot-resize-handle:hover { - cursor: default; - pointer-events: none; -} - -.bot-panel-collapsed #bot-toggle-close { - opacity: 0; - pointer-events: none; -} - -.bot-panel-expanded .bot-resize-handle:hover #bot-toggle-close { - pointer-events: auto; -} - -#bot-toggle-close:hover { - background-color: light-dark(#f0f0f0, #2a2a2a); -} - -#bot-toggle-close svg { - width: 16px; - height: 16px; -} - .bot-panel { position: fixed; top: 3.55rem; @@ -253,22 +207,6 @@ img { transform: translateX(100%); } -.bot-resize-handle { - position: absolute; - left: -20px; - top: 0; - bottom: 0; - width: 40px; - height: 100%; - cursor: default; - background-color: transparent; - z-index: 30; - pointer-events: none; - display: flex; - align-items: center; - justify-content: center; -} - .bot-iframe-container { flex: 1; height: 100%; @@ -279,14 +217,6 @@ img { border-left: 1px solid light-dark(rgb(226, 222, 230), rgb(255 255 255/0.07)); } -.bot-panel.resizing { - transition: none; -} - -.bot-panel.resizing .bot-iframe-container { - pointer-events: none; -} - .bot-iframe { width: 100%; height: 100%; @@ -328,15 +258,6 @@ img { display: none !important; } - .bot-resize-handle { - display: none !important; - pointer-events: none !important; - } - - #bot-toggle-close { - display: none !important; - } - /* Show mobile dismiss arrow */ .bot-mobile-dismiss { display: flex;