import gradio as gr from src.frontend.callbacks import ( ui_bootstrap, on_switch_notebook, on_create_notebook, on_rename_notebook, on_delete_notebook, on_ingest_files, on_ingest_url, on_chat, on_report, on_quiz, on_podcast, on_download, ) from src.backend.auth import require_login CUSTOM_CSS = """ .gradio-container { max-width: 1320px !important; margin: 0 auto !important; padding-top: 18px !important; } .hero { border: 1px solid rgba(255,255,255,.08); border-radius: 16px; padding: 16px 18px; background: linear-gradient(145deg, rgba(50,87,255,.16), rgba(145,92,255,.12)); backdrop-filter: blur(6px); } .hero h1 { margin: 0; font-size: 1.5rem; letter-spacing: .2px; } .hero p { margin: 6px 0 0; opacity: .88; } .panel { border: 1px solid rgba(255,255,255,.08); border-radius: 14px; background: linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02)); padding: 10px; } .chat-panel { border: 1px solid rgba(255,255,255,.08); border-radius: 14px; padding: 10px; background: linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02)); } .chat-panel .message-wrap { border-radius: 12px; } .chat-input textarea { min-height: 92px !important; } .primary-btn button { border-radius: 10px !important; } """ def build_app(): theme = gr.themes.Soft( primary_hue="blue", secondary_hue="indigo", neutral_hue="slate", spacing_size="md", radius_size="lg", ) with gr.Blocks(title="NotebookLM Clone", theme=theme, css=CUSTOM_CSS) as demo: gr.Markdown( """

📓 NotebookLM Clone

Organize notebooks, ingest sources, and chat with RAG-backed citations.

""" ) login = gr.LoginButton() login.activate() username_state = gr.State("") def on_load(request: gr.Request): username = require_login(request) dd, chat, arts = ui_bootstrap(username) return username, dd, chat, arts with gr.Row(equal_height=True): with gr.Column(scale=1, min_width=360, elem_classes=["panel"]): user_box = gr.Textbox(label="User", interactive=False) with gr.Accordion("Notebook", open=True): notebook_dd = gr.Dropdown( label="Notebooks", choices=[], interactive=True, ) nb_new = gr.Textbox(label="Create notebook", placeholder="Name") btn_create = gr.Button("Create", elem_classes=["primary-btn"]) nb_rename = gr.Textbox(label="Rename notebook", placeholder="New name") btn_rename = gr.Button("Rename") btn_delete = gr.Button("Delete current", variant="stop") with gr.Accordion("Ingest", open=True): file_up = gr.File(label="Upload PDF / PPTX / TXT", file_count="multiple") btn_ingest_files = gr.Button("Ingest Files", elem_classes=["primary-btn"]) ingest_status = gr.Textbox(label="File ingest status", interactive=False) url_in = gr.Textbox(label="URL", placeholder="https://...") btn_ingest_url = gr.Button("Ingest URL") url_status = gr.Textbox(label="URL ingest status", interactive=False) with gr.Accordion("Artifacts", open=False): topic = gr.Textbox(label="Topic / prompt") extra = gr.Textbox(label="Extra prompt (optional)") with gr.Row(): btn_report = gr.Button("Generate Report") btn_quiz = gr.Button("Generate Quiz") btn_podcast = gr.Button("Generate Podcast") artifact_status = gr.Textbox(label="Artifact status", interactive=False) artifacts_list = gr.Dropdown(label="Artifacts", choices=[], interactive=True) download_btn = gr.Button("Download selected") download_file = gr.File(label="Download", interactive=False) podcast_audio = gr.Audio(label="Podcast Audio", interactive=False) with gr.Column(scale=2, min_width=560, elem_classes=["chat-panel"]): chatbot = gr.Chatbot( height=520, label="Chat (RAG + citations)", bubble_full_width=False, ) with gr.Row(): msg = gr.Textbox( label="Message", placeholder="Ask about your uploaded sources...", elem_classes=["chat-input"], scale=5, ) send = gr.Button( "Send", variant="primary", scale=1, elem_classes=["primary-btn"], ) demo.load( on_load, inputs=None, outputs=[username_state, notebook_dd, chatbot, artifacts_list], queue=False, api_name=False, ) username_state.change( lambda u: u, inputs=username_state, outputs=user_box, queue=False, api_name=False, ) notebook_dd.change( on_switch_notebook, inputs=[username_state, notebook_dd], outputs=[chatbot, artifacts_list], queue=False, api_name=False, ) btn_create.click( on_create_notebook, inputs=[username_state, nb_new], outputs=[notebook_dd, chatbot, artifacts_list], queue=False, api_name=False, ) btn_rename.click( on_rename_notebook, inputs=[username_state, notebook_dd, nb_rename], outputs=[notebook_dd], queue=False, api_name=False, ) btn_delete.click( on_delete_notebook, inputs=[username_state, notebook_dd], outputs=[notebook_dd, chatbot, artifacts_list], queue=False, api_name=False, ) btn_ingest_files.click( on_ingest_files, inputs=[username_state, notebook_dd, file_up], outputs=[ingest_status], queue=True, api_name=False, ) btn_ingest_url.click( on_ingest_url, inputs=[username_state, notebook_dd, url_in], outputs=[url_status], queue=True, api_name=False, ) send.click( on_chat, inputs=[username_state, notebook_dd, chatbot, msg], outputs=[chatbot, msg], queue=True, api_name=False, ) btn_report.click( on_report, inputs=[username_state, notebook_dd, topic, extra], outputs=[artifact_status, artifacts_list, download_file], queue=True, api_name=False, ) btn_quiz.click( on_quiz, inputs=[username_state, notebook_dd, topic, extra], outputs=[artifact_status, artifacts_list, download_file], queue=True, api_name=False, ) btn_podcast.click( on_podcast, inputs=[username_state, notebook_dd, topic, extra], outputs=[artifact_status, artifacts_list, download_file, podcast_audio], queue=True, api_name=False, ) download_btn.click( on_download, inputs=[username_state, notebook_dd, artifacts_list], outputs=[download_file], queue=False, api_name=False, ) return demo