from urllib.parse import quote_plus
from fasthtml.components import Div, H1, P, Img, H2, Form, Span
from fasthtml.xtend import Script, A
from lucide_fasthtml import Lucide
from shad4fast import Button, Input, Badge
def check_input_script():
return Script(
"""
window.onload = function() {
const input = document.getElementById('search-input');
const button = document.querySelector('[data-button="search-button"]');
function checkInputValue() { button.disabled = input.value.trim() === ""; }
input.addEventListener('input', checkInputValue);
checkInputValue();
};
"""
)
def SearchBox(with_border=False, query_value=""):
grid_cls = "grid gap-2 items-center p-3 bg-muted/80 dark:bg-muted/40 w-full"
if with_border:
grid_cls = "grid gap-2 p-3 rounded-md border border-input bg-muted/80 dark:bg-muted/40 w-full ring-offset-background focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:border-input"
return Form(
Div(
Lucide(icon="search", cls="absolute left-2 top-2 text-muted-foreground"),
Input(
placeholder="Enter your search query...",
name="query",
value=query_value,
id="search-input",
cls="text-base pl-10 border-transparent ring-offset-transparent ring-0 focus-visible:ring-transparent",
style="font-size: 1rem",
autofocus=True,
),
cls="relative",
),
Div(
Span("controls", cls="text-muted-foreground"),
Button(
Lucide(icon="arrow-right", size="21"),
size="sm",
type="submit",
data_button="search-button",
disabled=True,
),
cls="flex justify-between",
),
check_input_script(),
action=f"/search?query={quote_plus(query_value)}", # This takes the user to /search with the loading message
method="GET",
hx_get=f"/fetch_results?query={quote_plus(query_value)}", # This fetches the results asynchronously
hx_trigger="load", # Trigger this after the page loads
hx_target="#search-results", # Update the search results div dynamically
hx_swap="outerHTML", # Replace the search results div entirely
hx_indicator="#loading-indicator", # Show the loading indicator while fetching results
cls=grid_cls,
)
def SampleQueries():
sample_queries = [
"What is the future of energy storage?",
"What is sustainable energy?",
"How to reduce carbon emissions?",
]
query_badges = []
for query in sample_queries:
query_badges.append(
A(
Badge(
Div(
Lucide(
icon="text-search", size="18", cls="text-muted-foreground"
),
Span(query, cls="text-base font-normal"),
cls="flex gap-2 items-center",
),
variant="outline",
cls="text-base font-normal text-muted-foreground",
),
href=f"/search?query={quote_plus(query)}",
cls="no-underline",
)
)
return Div(*query_badges, cls="grid gap-2 justify-items-center")
def Hero():
return Div(
H1(
"Vespa.Ai + ColPali",
cls="text-5xl md:text-7xl font-bold tracking-wide md:tracking-wider bg-clip-text text-transparent bg-gradient-to-r from-black to-gray-700 dark:from-white dark:to-gray-300 animate-fade-in",
),
P(
"Efficient Document Retrieval with Vision Language Models",
cls="text-lg md:text-2xl text-muted-foreground md:tracking-wide",
),
cls="grid gap-5 text-center",
)
def Home():
return Div(
Div(
Hero(),
SearchBox(with_border=True),
SampleQueries(),
cls="grid gap-8 -mt-[34vh]",
),
cls="grid w-full h-full max-w-screen-md items-center gap-4 mx-auto",
)
def Search(request, search_results=[]):
query_value = request.query_params.get("query", "").strip()
return Div(
Div(
SearchBox(
query_value=query_value
), # Pass the query value to pre-fill the SearchBox
Div(
LoadingMessage(), # Show the loading message initially
id="search-results", # This will be replaced by the search results
),
cls="grid",
),
cls="grid",
)
def LoadingMessage():
return Div(
P("Loading... Please wait.", cls="text-base text-center"),
cls="p-10 text-center text-muted-foreground",
id="loading-indicator",
)
def SearchResult(results=[]):
if not results:
return Div(
P(
"No results found for your query.",
cls="text-muted-foreground text-base text-center",
),
cls="grid p-10",
)
# Otherwise, display the search results
result_items = []
for result in results:
fields = result["fields"] # Extract the 'fields' part of each result
base64_image = (
f"data:image/jpeg;base64,{fields['image']}" # Format base64 image
)
# Print the fields that start with 'sim_map'
for key, value in fields.items():
if key.startswith("sim_map"):
print(f"{key}")
result_items.append(
Div(
Div(
Img(src=base64_image, alt=fields["title"], cls="max-w-full h-auto"),
cls="bg-background px-3 py-5",
),
Div(
Div(
H2(fields["title"], cls="text-xl font-semibold"),
P(
fields["text"], cls="text-muted-foreground"
), # Use the URL as the description
cls="text-sm grid gap-y-4",
),
cls="bg-background px-3 py-5",
),
cls="grid grid-cols-subgrid col-span-2",
)
)
return Div(
*result_items,
cls="grid grid-cols-2 gap-px bg-border",
id="search-results", # This will be the target for HTMX updates
)