"""Ask pipeline: question -> MaxSim retrieval over the store -> top-K page images -> MiniCPM answer grounded in those pages.""" from __future__ import annotations from core.pdf import render_page from core.store import Store from models.colembed import ColEmbed from models.minicpm import MiniCPM class AskPipeline: def __init__(self, embedder: ColEmbed, store: Store, llm: MiniCPM): self.embedder = embedder self.store = store self.llm = llm def run(self, question: str, doc_ids: list[str] | None, top_k: int): """Return (answer markdown, gallery items [(image, caption)]).""" question = (question or "").strip() if not question: raise ValueError("Please enter a question.") docs = self.store.list_docs() if not docs: raise ValueError("No manuals indexed yet — add one in the Library tab.") hits = self.embedder.search(question, self.store, doc_ids or None, int(top_k)) names = {d["doc_id"]: d["name"] for d in docs} pages = [ (f"{names[doc_id]} — p.{page}", render_page(self.store.pdf_path(doc_id), page), score) for doc_id, page, score in hits ] answer = self.llm.answer(question, [(label, img) for label, img, _ in pages]) gallery = [(img, f"{label} (score {score:.1f})") for label, img, score in pages] return answer, gallery