from fasthtml.components import Body, Div, Header, Img, Nav, Title
from fasthtml.xtend import A, Script
from lucide_fasthtml import Lucide
from shad4fast import Button, Separator
layout_script = Script(
"""
document.addEventListener("DOMContentLoaded", function () {
const main = document.querySelector('main');
const aside = document.querySelector('aside');
const body = document.body;
if (main && aside && main.nextElementSibling === aside) {
// If we have both main and aside, adjust the layout for larger screens
body.classList.remove('grid-cols-1'); // Remove single-column layout
body.classList.add('md:grid-cols-[minmax(0,_45fr)_minmax(0,_15fr)]'); // Two-column layout on larger screens
} else if (main) {
// If only main, keep it full width
body.classList.add('grid-cols-1');
}
});
"""
)
overlay_scrollbars = Script(
"""
(function () {
const { OverlayScrollbars } = OverlayScrollbarsGlobal;
function getPreferredTheme() {
return localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
? 'dark'
: 'light';
}
function applyOverlayScrollbars(element, scrollbarTheme) {
// Destroy existing OverlayScrollbars instance if it exists
const instance = OverlayScrollbars(element);
if (instance) {
instance.destroy();
}
// Reinitialize OverlayScrollbars with the new theme
OverlayScrollbars(element, {
scrollbars: {
theme: scrollbarTheme,
visibility: 'auto',
autoHide: 'leave',
autoHideDelay: 800
}
});
}
function updateScrollbarTheme() {
const isDarkMode = getPreferredTheme() === 'dark';
const scrollbarTheme = isDarkMode ? 'os-theme-light' : 'os-theme-dark'; // Light theme in dark mode, dark theme in light mode
const mainElement = document.querySelector('main');
const chatMessagesElement = document.querySelector('#chat-messages'); // Select the chat message container by ID
if (mainElement) {
applyOverlayScrollbars(mainElement, scrollbarTheme);
}
if (chatMessagesElement) {
applyOverlayScrollbars(chatMessagesElement, scrollbarTheme);
}
}
// Apply the correct theme immediately when the page loads
updateScrollbarTheme();
// Observe changes in the 'dark' class on the element
const observer = new MutationObserver(updateScrollbarTheme);
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
})();
"""
)
def Logo():
return Div(
Img(
src="https://assets.vespa.ai/logos/vespa-logo-black.svg",
alt="Vespa Logo",
cls="h-full dark:hidden",
),
Img(
src="https://assets.vespa.ai/logos/vespa-logo-white.svg",
alt="Vespa Logo Dark Mode",
cls="h-full hidden dark:block",
),
cls="h-[27px]",
)
def ThemeToggle(variant="ghost", cls=None, **kwargs):
return Button(
Lucide("sun", cls="dark:flex hidden"),
Lucide("moon", cls="dark:hidden"),
variant=variant,
size="icon",
cls=f"theme-toggle {cls}",
**kwargs,
)
def Links():
return Nav(
A(
Button(Lucide(icon="github"), size="icon", variant="ghost"),
href="https://github.com/vespa-engine/vespa",
target="_blank",
),
A(
Button(Lucide(icon="slack"), size="icon", variant="ghost"),
href="https://slack.vespa.ai",
target="_blank",
),
Separator(orientation="vertical"),
ThemeToggle(),
cls="flex items-center space-x-3",
)
def Layout(*c, **kwargs):
return (
Title("Visual Retrieval ColPali"),
Body(
Header(
A(Logo(), href="/"),
Links(),
cls="min-h-[55px] h-[55px] w-full flex items-center justify-between px-4",
),
*c,
**kwargs,
cls="grid grid-rows-[55px_1fr] min-h-0",
),
layout_script,
overlay_scrollbars,
)