import asyncio import json from fasthtml.common import * from shad4fast import * from vespa.application import Vespa from backend.colpali import load_model, get_result_from_query from backend.vespa_app import get_vespa_app from frontend.app import Home, Search, SearchResult, SearchBox from frontend.layout import Layout highlight_js_theme_link = Link(id="highlight-theme", rel="stylesheet", href="") highlight_js_theme = Script(src="/static/js/highlightjs-theme.js") highlight_js = HighlightJS( langs=["python", "javascript", "java", "json", "xml"], dark="github-dark", light="github", ) app, rt = fast_app( htmlkw={"cls": "h-full"}, pico=False, hdrs=( ShadHead(tw_cdn=False, theme_handle=True), highlight_js, highlight_js_theme_link, highlight_js_theme, ), ) vespa_app: Vespa = get_vespa_app() class ModelManager: _instance = None model = None processor = None @staticmethod def get_instance(): if ModelManager._instance is None: ModelManager._instance = ModelManager() ModelManager._instance.initialize_model_and_processor() return ModelManager._instance def initialize_model_and_processor(self): if self.model is None or self.processor is None: # Ensure no reinitialization self.model, self.processor = load_model() if self.model is None or self.processor is None: print("Failed to initialize model or processor at startup") else: print("Model and processor loaded at startup") @rt("/static/{filepath:path}") def serve_static(filepath: str): return FileResponse(f"./static/{filepath}") @rt("/") def get(): return Layout(Home()) @rt("/search") def get(request): # Extract the 'query' parameter from the URL using query_params query_value = request.query_params.get("query", "").strip() # Always render the SearchBox first if not query_value: # Show SearchBox and a message for missing query return Layout( Div( SearchBox(query_value=query_value), Div( P( "No query provided. Please enter a query.", cls="text-center text-muted-foreground", ), cls="p-10", ), cls="grid", ) ) # Show the loading message if a query is provided return Layout(Search(request)) # Show SearchBox and Loading message initially @rt("/fetch_results") def get(request, query: str, nn: bool = True): # Check if the request came from HTMX; if not, redirect to /search if "hx-request" not in request.headers: return RedirectResponse("/search") # Extract the 'query' parameter from the URL # Fetch model and processor manager = ModelManager.get_instance() model = manager.model processor = manager.processor # Fetch real search results from Vespa result = asyncio.run( get_result_from_query( vespa_app, processor=processor, model=model, query=query, nn=nn, gen_sim_map=True, ) ) # Extract search results from the result payload search_results = ( result["root"]["children"] if "root" in result and "children" in result["root"] else [] ) # Directly return the search results without the full page layout return SearchResult(search_results) @rt("/app") def get(): return Layout(Div(P(f"Connected to Vespa at {vespa_app.url}"), cls="p-4")) @rt("/run_query") def get(query: str, nn: bool = False): # dummy-function to avoid running the query every time # result = get_result_dummy(query, nn) # If we want to run real, uncomment the following lines model, processor = get_model_and_processor() result = asyncio.run( get_result_from_query( vespa_app, processor=processor, model=model, query=query, nn=nn ) ) # model, processor = get_model_and_processor() # result = asyncio.run( # get_result_from_query(vespa_app, processor=processor, model=model, query=query, nn=nn) # ) return Layout(Div(H1("Result"), Pre(Code(json.dumps(result, indent=2))), cls="p-4")) if __name__ == "__main__": # ModelManager.get_instance() # Initialize once at startup serve()