from __future__ import annotations from .service import StudyService def _sources_md(result) -> str: return ("**Sources** (open the files below to verify at the exact slide/page)\n\n" + result.sources_md) \ if result.sources_md else "" def build_app(service: StudyService, title: str = "StudyHub"): import gradio as gr with gr.Blocks(title=title) as demo: gr.Markdown( f"# {title}\nBrowse by topic or ask freely. Answers are grounded in the course materials, " "cite the exact file + slide/page, and reply in your language — even when the source is in " "Italian. Short snippets go to a no-training model provider to generate answers." ) with gr.Tab("Topics"): gr.Markdown("Pick a module → a topic. Answers draw on **all** of that topic's sources " "(slides, notes, transcripts), each cited.") module_dd = gr.Dropdown( choices=[(f"{m['title']} · {m['lecturer']}".strip(" ·"), m["id"]) for m in service.modules()], label="Module", ) topic_dd = gr.Dropdown(choices=[], label="Topic") t_sources = gr.Markdown() sum_btn = gr.Button("Generate summary") t_summary = gr.Markdown() t_sum_files = gr.File(label="Summary sources — download the cited files", file_count="multiple") gr.Markdown("—") with gr.Row(): t_q = gr.Textbox(label="Ask about this topic (any language)", lines=1, scale=4) t_ask = gr.Button("Ask", variant="primary", scale=1) t_ans = gr.Markdown() t_ans_src = gr.Markdown() t_files = gr.File(label="Cited sources", file_count="multiple") def _topic_src_md(tid): srcs = service.topic_sources(tid) if tid else [] if not srcs: return "_No sources linked to this topic yet._" return "**Sources:** " + " · ".join(f"`{s['type']}` {s['file'].split('/')[-1]}" for s in srcs) def _on_module(mid): choices = [(t["title"], t["id"]) for t in service.topics(mid)] if mid else [] return gr.update(choices=choices, value=None), "" def _summarize(tid): if not tid: return "Pick a topic first.", [] r = service.topic_summary(tid) return (r.answer + (("\n\n" + _sources_md(r)) if r.sources_md else "")), service.cited_files(r) def _topic_ask(tid, q): if not tid: return "Pick a topic first.", "", [] r = service.topic_ask(tid, q or "") return r.answer, _sources_md(r), service.cited_files(r) module_dd.change(_on_module, inputs=module_dd, outputs=[topic_dd, t_sources]) topic_dd.change(_topic_src_md, inputs=topic_dd, outputs=t_sources) sum_btn.click(_summarize, inputs=topic_dd, outputs=[t_summary, t_sum_files]) t_ask.click(_topic_ask, inputs=[topic_dd, t_q], outputs=[t_ans, t_ans_src, t_files]) with gr.Tab("Ask"): q = gr.Textbox(label="Your question (Italian or English)", lines=2) ask = gr.Button("Ask", variant="primary") a_ans = gr.Markdown() a_src = gr.Markdown() a_files = gr.File(label="Cited sources — click to open/download", file_count="multiple") def _ask(query): r = service.ask(query or "") return r.answer, _sources_md(r), service.cited_files(r) ask.click(_ask, inputs=q, outputs=[a_ans, a_src, a_files]) with gr.Tab("Summaries"): doc = gr.Dropdown(choices=service.list_files(), label="Document") sbtn = gr.Button("Summarize", variant="primary") s_out = gr.Markdown() s_src = gr.Markdown() s_files = gr.File(label="Cited sources — click to open/download", file_count="multiple") def _sum(file): if not file: return "Pick a document first.", "", [] r = service.summarize(file) return r.answer, _sources_md(r), service.cited_files(r) sbtn.click(_sum, inputs=doc, outputs=[s_out, s_src, s_files]) with gr.Tab("Quiz"): topic = gr.Textbox(label="Topic") n = gr.Slider(1, 10, value=5, step=1, label="Number of questions") qbtn = gr.Button("Generate quiz", variant="primary") q_out = gr.Markdown() q_src = gr.Markdown() q_files = gr.File(label="Cited sources — click to open/download", file_count="multiple") def _quiz(t, num): r = service.quiz(t or "", int(num)) return r.answer, _sources_md(r), service.cited_files(r) qbtn.click(_quiz, inputs=[topic, n], outputs=[q_out, q_src, q_files]) with gr.Tab("Browse & Download"): mat = gr.Dropdown(choices=service.list_files(), label="Material") dbtn = gr.Button("Get file") dfile = gr.File(label="Download") def _dl(file): return service.download_path(file) if file else None dbtn.click(_dl, inputs=mat, outputs=dfile) return demo