Skip to content

feat: migrate to Inkeep Sidebar Chat with custom trigger button #590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ui/BaseHead.astro
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ const defaultDescription =
<script
is:inline
type="module"
src="https://unpkg.com/@inkeep/widgets-embed@0.2.237/dist/embed.js"
src="https://cdn.jsdelivr.net/npm/@inkeep/cxkit-js@0.5/dist/embed.js"
defer></script>
<script
is:inline
Expand Down
138 changes: 132 additions & 6 deletions src/ui/DocsLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const pageHasDialects = Object.keys(dialects).includes(
<MDXStyles />
<Banner />
<Header />

<!-- Main app content -->
<div class="fixed-container">
<div class="fixed-elements">
<DocsSidebar
Expand All @@ -55,6 +57,7 @@ const pageHasDialects = Object.keys(dialects).includes(
</div>
</div>
</div>

<main class="documentation-container">
<div class="documentation-content">
{pageHasDialects && <div style="height: 52px" />}
Expand All @@ -63,6 +66,115 @@ const pageHasDialects = Object.keys(dialects).includes(
<!-- <Footer /> -->
</div>
</main>

<!-- Inkeep Sidebar Chat Target - Positioned as overlay -->
<div id="ikp-sidebar-chat-target" style="position: fixed; top: 0; right: 0; height: 100vh; z-index: 999;"></div>

<!-- Inkeep Sidebar Chat Initialization -->
<script is:inline>
(function () {
const config = {
baseSettings: {
apiKey: "4f4da4a5733032ef8ff23e3b7906954877fd0ee54d58d1e0",
},
aiChatSettings: {
exampleQuestions: ["How do I get started with Drizzle ORM?", "What databases are supported?", "How do I migrate my schema?"],
introMessage: "👋 Hi! I'm here to help you with Drizzle ORM. Ask me anything!",
},
position: "right",
minWidth: 300,
maxWidth: 600,
defaultWidth: 420,
defaultOpen: false,
};

let sidebarWidget = null;
let isSidebarOpen = false;

const initSidebarChat = () => {
// Check if already initialized
if (window.__ikpSidebarInited) return;

// Check if Inkeep is available
if (window.Inkeep && typeof window.Inkeep.SidebarChat === "function") {
try {
// Add onOpenChange callback to track sidebar state
const configWithCallback = {
...config,
onOpenChange: (isOpen) => {
isSidebarOpen = isOpen;
console.log("Sidebar state changed:", isOpen);
}
};

sidebarWidget = window.Inkeep.SidebarChat("#ikp-sidebar-chat-target", configWithCallback);
window.__ikpSidebarInited = true;

// Add click-outside-to-close functionality
setupClickOutsideToClose();

console.log("Inkeep Sidebar Chat initialized successfully");
} catch (error) {
console.error("Failed to initialize Inkeep Sidebar Chat:", error);
}
} else {
// Retry after a short delay if Inkeep is not available yet
setTimeout(initSidebarChat, 100);
}
};

const setupClickOutsideToClose = () => {
// Remove any existing click listener to prevent duplicates
document.removeEventListener('click', handleClickOutside);

// Add new click listener
document.addEventListener('click', handleClickOutside);
};

const handleClickOutside = (event) => {
// Only proceed if sidebar is open
if (!isSidebarOpen) return;

// Check if click was inside the sidebar or on the trigger button
const sidebarContainer = document.querySelector('#ikp-sidebar-chat-target');
const triggerButton = document.querySelector('[data-inkeep-sidebar-chat-trigger]');

if (!sidebarContainer || !triggerButton) return;

// If click was on trigger button or inside sidebar, don't close
if (triggerButton.contains(event.target) || sidebarContainer.contains(event.target)) {
return;
}

// Check if the click was on any child elements of the sidebar
const sidebarElements = sidebarContainer.querySelectorAll('*');
for (let element of sidebarElements) {
if (element.contains(event.target)) {
return;
}
}

// Click was outside - close the sidebar by clicking the trigger button
triggerButton.click();
};

// Initialize when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initSidebarChat, { once: true });
} else {
initSidebarChat();
}

// Also initialize on load and Astro page transitions
window.addEventListener("load", initSidebarChat, { once: true });
document.addEventListener("astro:page-load", initSidebarChat);
document.addEventListener("astro:after-swap", () => {
// Reset initialization flag for page transitions
window.__ikpSidebarInited = false;
initSidebarChat();
});
})();
</script>
<style is:global>
.fixed-container {
position: fixed;
Expand All @@ -72,6 +184,14 @@ const pageHasDialects = Object.keys(dialects).includes(
z-index: 99;
pointer-events: none;
}
/* Ensure the toggle button inside fixed container is clickable */
.fixed-container [data-inkeep-sidebar-chat-trigger] {
pointer-events: all;
}
/* Ensure the sidebar chat target is interactive */
.fixed-container #ikp-sidebar-chat-target {
pointer-events: all;
}

.fixed-elements {
max-width: 1440px;
Expand Down Expand Up @@ -207,7 +327,8 @@ const pageHasDialects = Object.keys(dialects).includes(
}
</script>
<script is:inline>
const changeNpmTab = () => {
if (!window.changeNpmTab) {
window.changeNpmTab = () => {
const packageManagers = ["npm", "yarn", "pnpm", "bun"];

let packageManagerIndex = packageManagers.indexOf(packageManager);
Expand Down Expand Up @@ -273,8 +394,10 @@ const pageHasDialects = Object.keys(dialects).includes(
});
});
};
}

const updateCollapsedSections = () => {
if (!window.updateCollapsedSections) {
window.updateCollapsedSections = () => {
document
.querySelectorAll(".nav-items-collapsable")
.forEach((section) => {
Expand All @@ -296,8 +419,10 @@ const pageHasDialects = Object.keys(dialects).includes(
JSON.stringify(expandedSections),
);
};
}

const scrollSidebar = () => {
if (!window.scrollSidebar) {
window.scrollSidebar = () => {
const expandedSections = localStorage.getItem("expandedSections")
? JSON.parse(localStorage.getItem("expandedSections"))
: [];
Expand Down Expand Up @@ -369,10 +494,11 @@ const pageHasDialects = Object.keys(dialects).includes(
}
}
};
}

scrollSidebar();
updateCollapsedSections();
changeNpmTab();
if (window.scrollSidebar) scrollSidebar();
if (window.updateCollapsedSections) updateCollapsedSections();
if (window.changeNpmTab) changeNpmTab();
</script>
<script>
class Rem1Element extends HTMLElement {
Expand Down
50 changes: 40 additions & 10 deletions src/ui/components/CenteredSearchWithAI.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<div class="nav_search-wrap">
<div class="search-wrap--centered" id="docsearch"></div>
<div id="custom-button" class="nav_ai-btn"></div>
<div id="custom-button" class="nav_ai-btn">
<button
data-inkeep-sidebar-chat-trigger=""
class="chat-trigger-btn"
>
<img src="/svg/drizzle.svg" alt="Drizzle" class="drizzle-logo" />
Ask AI
</button>
</div>
</div>
<script type="module" is:inline data-astro-rerun>
docsearch({
Expand Down Expand Up @@ -42,15 +50,6 @@
botAvatarSrcUrl: '/svg/drizzle.svg',
}
}

//@ts-ignore
const inkeepWidget = Inkeep().embed({
componentType: "ChatButton",
targetElement: document.getElementById("custom-button"),
properties,
});

window.inkeepWidget = inkeepWidget;
</script>
<style>
.nav_search-wrap {
Expand All @@ -72,6 +71,37 @@
border-radius: 4px;
}

.chat-trigger-btn {
width: 100%;
height: 100%;
background: inherit;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
font-size: 13px;
font-weight: 500;
color: #666;
transition: all 0.2s ease;
}

html[class~=dark] .chat-trigger-btn {
color: #ccc;
}

.chat-trigger-btn:hover {
opacity: 0.8;
}

.drizzle-logo {
width: 16px;
height: 16px;
flex-shrink: 0;
}

@media screen and (max-width: 768px) {
.nav_search-wrap {
display: none;
Expand Down
6 changes: 0 additions & 6 deletions src/ui/components/landing/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,6 @@ import DBImage from "@/assets/images/landing/dbLight.svg"
},
};

// Embed the widget using the `Inkeep.embed()` function.
const inkeepWidget = Inkeep().embed(config);

// Add event listener to open the Inkeep modal when the button is clicked
inkeepButton.addEventListener("click", handleOpen);
window.inkeepWidget = inkeepWidget;
</script>

<script src="./snake/index.ts"></script>
Expand Down