tAnboyyy commited on
Commit
6709c8b
·
1 Parent(s): 04ab5b4

Enhance UI components and improve notebook management functionality

Browse files
Files changed (1) hide show
  1. app.py +194 -150
app.py CHANGED
@@ -69,7 +69,32 @@ CUSTOM_CSS = """
69
  #auth-text { white-space: nowrap; margin: 8px 0 16px 0; font-size: 0.95rem; opacity: 0.9; }
70
  .gr-button { padding: 14px 28px !important; font-size: 0.9rem !important; border-radius: 12px !important; white-space: nowrap !important; width: auto !important; }
71
  .gr-button[aria-label*="Logout"] { min-width: auto !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; }
72
- .header-bar .gr-button { padding-left: 40px !important; padding-right: 40px !important; min-width: 220px !important; font-size: 0.8rem !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  .dark .header-bar { border-bottom: 1px solid #334155; }
74
 
75
  .hero-section { margin-bottom: 16px; }
@@ -85,8 +110,14 @@ CUSTOM_CSS = """
85
  .hero-sub { font-size: 1rem; color: #64748b; margin: 0; line-height: 1.5; }
86
 
87
  .section-card { padding: 24px; border-radius: 16px; background: #f8fafc; margin: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
88
- .notebook-card { padding: 14px 20px; border-radius: 12px; background: #fff; margin: 8px; border: 1px solid #e2e8f0; display: flex; align-items: center; gap: 12px; transition: background 0.15s ease; }
89
  .notebook-card:hover { background: #f8fafc; }
 
 
 
 
 
 
90
 
91
  .section-title { font-size: 1.125rem; font-weight: 600; color: #1e293b; margin: 0 0 16px 0; }
92
  .section-row { display: flex !important; align-items: center !important; gap: 16px !important; margin-bottom: 12px; }
@@ -102,6 +133,11 @@ CUSTOM_CSS = """
102
  .section-title { color: #f1f5f9 !important; }
103
  .notebook-card { background: #334155 !important; border-color: #475569; }
104
  .notebook-card:hover { background: #475569 !important; }
 
 
 
 
 
105
  .status { color: #94a3b8 !important; background: #334155 !important; }
106
  }
107
  .dark .hero-title { color: #f1f5f9 !important; }
@@ -206,7 +242,31 @@ def _initial_load(profile: gr.OAuthProfile | None = None):
206
  status = f"Signed in as {user_id}" if user_id else "Sign in with Hugging Face to manage notebooks."
207
  auth_update = f"You are logged in as {getattr(profile, 'name', None) or user_id} ({_user_id(profile)})" if user_id else ""
208
  auth_row_visible = bool(user_id)
209
- return state, selected, status, auth_update, gr.update(visible=auth_row_visible), gr.update(visible=bool(user_id)), gr.update(visible=not bool(user_id))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
 
212
  REPORT_SCOPE_LABELS = {
@@ -617,7 +677,7 @@ with gr.Blocks(
617
  ) as demo:
618
  with gr.Row(elem_classes=["header-bar"]):
619
  gr.Markdown("### 📓 NotebookLM Clone")
620
- login_btn = gr.LoginButton(value="🤗 Login with Hugging Face", size="lg", elem_id=["lgn-btn"])
621
 
622
  with gr.Row(visible=False) as auth_info_row:
623
  auth_text = gr.Markdown("", elem_id="auth-text")
@@ -625,7 +685,7 @@ with gr.Blocks(
625
  gr.HTML("""
626
  <div class="container hero-section">
627
  <h1 class="hero-title">📓 NotebookLM Clone</h1>
628
- <p class="hero-sub">Chat with your documents. Generate reports, quizzes, and podcasts with citations.</p>
629
  </div>
630
  """)
631
 
@@ -635,48 +695,65 @@ with gr.Blocks(
635
  with gr.Column(visible=False) as app_content:
636
  nb_state = gr.State([])
637
  selected_notebook_id = gr.State(None)
638
-
639
- with gr.Group(elem_classes=["create-strip"]):
640
- with gr.Row(elem_classes=["create-row"]):
641
- gr.Markdown("Create new notebook", elem_classes=["create-label"])
642
- create_txt = gr.Textbox(
643
- placeholder="Enter new notebook name",
644
- show_label=False,
645
- container=False,
646
- value="",
647
- )
648
- create_btn = gr.Button("Create", variant="primary", size="sm")
649
-
650
- with gr.Group(elem_classes=["section-card"]):
651
- gr.HTML("<br>")
652
- gr.Markdown("**Your Notebooks**", elem_classes=["section-title"])
653
- notebook_status = gr.Markdown("Sign in with Hugging Face to manage notebooks.", elem_classes=["status"])
654
- gr.HTML("<br>")
655
-
656
- @gr.render(inputs=[nb_state])
657
- def render_notebooks(state):
 
 
658
  if not state:
659
  gr.Markdown("No notebooks yet. Create one to get started.")
660
  else:
661
  for i, (nb_id, name) in enumerate(state):
662
  idx = i
663
- with gr.Row(elem_classes=["notebook-card"]):
664
- name_txt = gr.Textbox(value=name, show_label=False, scale=4, min_width=240, key=f"nb-name-{nb_id}")
665
- select_btn = gr.Button("Select", variant="primary", scale=1, min_width=80, size="sm", elem_id="select-btn")
666
- rename_btn = gr.Button("Rename", variant="secondary", scale=1, min_width=80, size="sm", elem_id="rename-btn")
667
- delete_btn = gr.Button("Delete", variant="secondary", scale=1, min_width=80, size="sm", elem_id="delete-btn")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
 
669
- def on_select(nb_id=nb_id, name_txt=name_txt):
670
- return nb_id, name_txt
671
 
672
- def on_select_status(name_txt=name_txt):
673
- return f"Selected notebook updated. Use this for chat/ingestion.\n**Notebook: {name_txt}**"
674
 
675
  select_btn.click(
676
  on_select,
677
  inputs=None,
678
- outputs=[selected_notebook_id, name_txt],
679
- ).then(on_select_status, inputs=[name_txt], outputs=[notebook_status]).then(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd])
 
680
 
681
  rename_btn.click(
682
  _safe_rename,
@@ -690,11 +767,14 @@ with gr.Blocks(
690
  inputs=[gr.State(idx), nb_state, selected_notebook_id],
691
  outputs=[nb_state, selected_notebook_id, notebook_status],
692
  api_name=False,
693
- ).then(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd])
694
-
695
- with gr.Group(elem_classes=["section-card"]):
696
- gr.Markdown("**Sources**", elem_classes=["section-title"])
697
- gr.Markdown("*Upload PDFs, ingest URLs, or add text to your selected notebook*")
 
 
 
698
  with gr.Row(elem_classes=["section-row"]):
699
  pdf_upload_btn = gr.UploadButton(
700
  "Upload PDFs",
@@ -703,6 +783,7 @@ with gr.Blocks(
703
  type="filepath",
704
  variant="secondary",
705
  )
 
706
  with gr.Row(elem_classes=["section-row"]):
707
  uploaded_pdf_dd = gr.Dropdown(
708
  label="Uploaded PDFs",
@@ -711,7 +792,8 @@ with gr.Blocks(
711
  scale=3,
712
  allow_custom_value=False,
713
  )
714
- remove_pdf_btn = gr.Button("Remove selected PDF", variant="stop", scale=1, elem_id="delete-btn")
 
715
  with gr.Row(elem_classes=["section-row"]):
716
  url_txt = gr.Textbox(
717
  label="Ingest web URL",
@@ -719,23 +801,17 @@ with gr.Blocks(
719
  value="",
720
  scale=3,
721
  )
722
- ingest_url_btn = gr.Button("Ingest URL", variant="primary", scale=1, elem_id="ingest-url-btn")
723
- remove_url_btn = gr.Button("Delete URL", variant="stop", scale=1, elem_id="delete-btn")
724
-
725
- status = gr.Markdown("Sign in with Hugging Face to upload context material.", elem_classes=["status"])
726
-
727
- gr.HTML("<br>")
728
- gr.HTML("<br>")
729
- gr.HTML("<br>")
730
-
731
- gr.Markdown("**Add Text**", elem_classes=["section-title"])
732
- gr.Markdown("*Select a notebook above, then paste or type your text*")
733
- with gr.Row():
734
  txt_title = gr.Textbox(
735
  label="Title",
736
  placeholder="Give this text a name (e.g. 'Lecture Notes Week 1')",
737
  scale=1,
738
  )
 
739
  txt_input = gr.Textbox(
740
  label="Text Content",
741
  placeholder="Paste or type your text here...",
@@ -743,12 +819,25 @@ with gr.Blocks(
743
  )
744
  submit_btn = gr.Button("Save & Process", variant="primary")
745
  upload_status = gr.Markdown("", elem_classes=["status"])
746
- sources_display = gr.Markdown("")
747
 
748
- gr.HTML("<br>")
749
- with gr.Group(elem_classes=["section-card"]):
750
- gr.Markdown("**Reports**", elem_classes=["section-title"])
751
- gr.Markdown("*Generate a concise report based on your uploaded PDFs, ingested URLs, or added text.*")
 
 
 
 
 
 
 
 
 
 
 
 
 
752
  with gr.Row(elem_classes=["section-row"]):
753
  report_scope_dd = gr.Dropdown(
754
  label="Report scope",
@@ -760,27 +849,50 @@ with gr.Blocks(
760
  report_status = gr.Markdown("Select a scope and click generate.", elem_classes=["status"])
761
  report_output = gr.Markdown("", elem_id="report-output")
762
 
763
- with gr.Group(elem_classes=["section-card"]):
764
- gr.Markdown("**Chat**", elem_classes=["section-title"])
765
- gr.Markdown("*Ask questions about your notebook sources. Answers are grounded in retrieved chunks with citations.*")
766
- chat_history_state = gr.State([])
767
- chatbot = gr.Chatbot(label="Chat history", height=400)
768
- chat_input = gr.Textbox(
769
- label="Message",
770
- placeholder="Ask a question about your sources...",
771
- show_label=False,
772
- lines=2,
 
 
 
 
773
  )
774
- chat_submit_btn = gr.Button("Send", variant="primary")
775
- chat_status = gr.Markdown("", elem_classes=["status"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
776
 
777
  demo.load(
778
  _initial_load,
779
  inputs=None,
780
- outputs=[nb_state, selected_notebook_id, status, auth_text, auth_info_row, app_content, login_container],
781
  api_name=False,
782
  )
783
  demo.load(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd], api_name=False)
 
 
784
 
785
  def _on_notebook_select_for_chat(notebook_id):
786
  hist, _ = _load_chat_history(notebook_id)
@@ -792,112 +904,51 @@ with gr.Blocks(
792
  outputs=[chat_history_state, chatbot],
793
  api_name=False,
794
  )
 
 
 
 
795
 
796
  create_btn.click(
797
  _safe_create,
798
  inputs=[create_txt, nb_state, selected_notebook_id],
799
- outputs=[create_txt, nb_state, selected_notebook_id, status],
800
  api_name=False,
801
  ).then(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd])
802
 
803
  pdf_upload_btn.upload(
804
  _safe_upload_pdfs,
805
  inputs=[pdf_upload_btn, selected_notebook_id],
806
- outputs=[status],
807
  api_name=False,
808
  ).then(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd])
809
 
810
  ingest_url_btn.click(
811
  _safe_ingest_url,
812
  inputs=[url_txt, selected_notebook_id],
813
- outputs=[url_txt, status],
814
  api_name=False,
815
  )
816
 
817
  remove_url_btn.click(
818
  _safe_remove_url,
819
  inputs=[url_txt, selected_notebook_id],
820
- outputs=[url_txt, status],
821
  api_name=False
822
  )
823
 
824
  remove_pdf_btn.click(
825
  _safe_remove_pdf,
826
  inputs=[uploaded_pdf_dd, selected_notebook_id],
827
- outputs=[status],
828
  api_name=False,
829
  ).then(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd])
830
 
831
-
832
- # Text Input Section
833
- gr.Markdown("---")
834
- gr.Markdown("## Add Text")
835
- gr.Markdown("Select a notebook above, then paste or type your text.")
836
-
837
- with gr.Row():
838
- txt_title = gr.Textbox(
839
- label="Title",
840
- placeholder="Give this text a name (e.g. 'Lecture Notes Week 1')",
841
- scale=1,
842
- )
843
-
844
- txt_input = gr.Textbox(
845
- label="Text Content",
846
- placeholder="Paste or type your text here...",
847
- lines=10,
848
- )
849
-
850
- submit_btn = gr.Button("Save & Process", variant="primary")
851
-
852
- upload_status = gr.Markdown("", elem_classes=["status"])
853
-
854
- # Podcast Section
855
- gr.Markdown("---")
856
- gr.Markdown("## Podcast")
857
- gr.Markdown("Generate a podcast script for the selected notebook using all ingested content.")
858
- with gr.Row():
859
- podcast_btn = gr.Button("Generate Podcast", variant="primary")
860
- podcast_audio_btn = gr.Button("Generate Podcast Audio", variant="secondary")
861
- podcast_status = gr.Markdown("", elem_classes=["status"])
862
- podcast_script = gr.Markdown("")
863
- podcast_audio = gr.Audio(label="Podcast Audio", type="filepath")
864
-
865
- # Quiz Section
866
- gr.Markdown("---")
867
- gr.Markdown("## Generate Quiz")
868
- gr.Markdown("Select a source type then generate a quiz.")
869
-
870
- quiz_source_type = gr.Radio(
871
- choices=["Text", "PDF", "URL", "All"],
872
- value="All",
873
- label="Source type",
874
- )
875
- quiz_pdf_dd = gr.Dropdown(
876
- label="Select PDF",
877
- choices=[],
878
- value=None,
879
- visible=False,
880
- )
881
- generate_quiz_btn = gr.Button("Generate Quiz", variant="primary")
882
- quiz_status = gr.Markdown("")
883
- quiz_state = gr.State([])
884
-
885
- quiz_components = []
886
- for i in range(5):
887
- with gr.Group(visible=False) as q_group:
888
- q_text = gr.Markdown("")
889
- q_radio = gr.Radio(choices=[], label="Your answer", visible=False)
890
- q_textbox = gr.Textbox(label="Your answer", visible=False)
891
- quiz_components.append({"group": q_group, "text": q_text, "radio": q_radio, "textbox": q_textbox})
892
-
893
- submit_quiz_btn = gr.Button("Submit Answers", variant="secondary", visible=False)
894
- quiz_results = gr.Markdown("")
895
-
896
  submit_btn.click(
897
  _do_upload,
898
  inputs=[txt_input, txt_title, selected_notebook_id],
899
  outputs=[upload_status],
900
- )
901
 
902
  report_btn.click(
903
  _generate_report,
@@ -906,13 +957,6 @@ with gr.Blocks(
906
  api_name=False,
907
  )
908
 
909
- selected_notebook_id.change(
910
- _load_sources,
911
- inputs=[selected_notebook_id],
912
- outputs=[podcast_status, podcast_script],
913
- api_name=False,
914
- )
915
-
916
  podcast_btn.click(
917
  _safe_generate_podcast,
918
  inputs=[selected_notebook_id],
 
69
  #auth-text { white-space: nowrap; margin: 8px 0 16px 0; font-size: 0.95rem; opacity: 0.9; }
70
  .gr-button { padding: 14px 28px !important; font-size: 0.9rem !important; border-radius: 12px !important; white-space: nowrap !important; width: auto !important; }
71
  .gr-button[aria-label*="Logout"] { min-width: auto !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; }
72
+ .header-bar .gr-button { padding-left: 28px !important; padding-right: 28px !important; min-width: 220px !important; font-size: 0.9rem !important; }
73
+ #login-btn,
74
+ #login-btn.gr-button,
75
+ #login-btn button,
76
+ #login-btn .gr-button {
77
+ display: inline-flex !important;
78
+ flex-direction: row !important;
79
+ align-items: center !important;
80
+ justify-content: center !important;
81
+ gap: 8px !important;
82
+ width: auto !important;
83
+ max-width: 100% !important;
84
+ min-width: 220px !important;
85
+ overflow: hidden !important;
86
+ }
87
+ #login-btn p,
88
+ #login-btn span,
89
+ #login-btn .md,
90
+ #login-btn .md p {
91
+ margin: 0 !important;
92
+ font-size: 0.95rem !important;
93
+ line-height: 1.2 !important;
94
+ white-space: nowrap !important;
95
+ overflow: hidden !important;
96
+ text-overflow: ellipsis !important;
97
+ }
98
  .dark .header-bar { border-bottom: 1px solid #334155; }
99
 
100
  .hero-section { margin-bottom: 16px; }
 
110
  .hero-sub { font-size: 1rem; color: #64748b; margin: 0; line-height: 1.5; }
111
 
112
  .section-card { padding: 24px; border-radius: 16px; background: #f8fafc; margin: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
113
+ .notebook-card { padding: 14px 20px; border-radius: 12px; background: #fff; margin: 8px 0 !important; border: 1px solid #e2e8f0; display: flex; align-items: center; gap: 12px; transition: background 0.15s ease; width: 100% !important; box-sizing: border-box !important; overflow: hidden; }
114
  .notebook-card:hover { background: #f8fafc; }
115
+ .notebook-selected { border: 2px solid #3b82f6 !important; box-shadow: none !important; }
116
+
117
+ .manager-card { border-top: 4px solid #3b82f6; }
118
+ .sources-card { border-top: 4px solid #14b8a6; }
119
+ .chat-card { border-top: 4px solid #8b5cf6; }
120
+ .artifacts-card { border-top: 4px solid #f97316; }
121
 
122
  .section-title { font-size: 1.125rem; font-weight: 600; color: #1e293b; margin: 0 0 16px 0; }
123
  .section-row { display: flex !important; align-items: center !important; gap: 16px !important; margin-bottom: 12px; }
 
133
  .section-title { color: #f1f5f9 !important; }
134
  .notebook-card { background: #334155 !important; border-color: #475569; }
135
  .notebook-card:hover { background: #475569 !important; }
136
+ .notebook-selected { border: 2px solid #60a5fa !important; box-shadow: none !important; }
137
+ .manager-card { border-top-color: #60a5fa; }
138
+ .sources-card { border-top-color: #2dd4bf; }
139
+ .chat-card { border-top-color: #a78bfa; }
140
+ .artifacts-card { border-top-color: #fb923c; }
141
  .status { color: #94a3b8 !important; background: #334155 !important; }
142
  }
143
  .dark .hero-title { color: #f1f5f9 !important; }
 
242
  status = f"Signed in as {user_id}" if user_id else "Sign in with Hugging Face to manage notebooks."
243
  auth_update = f"You are logged in as {getattr(profile, 'name', None) or user_id} ({_user_id(profile)})" if user_id else ""
244
  auth_row_visible = bool(user_id)
245
+ source_status = "" if user_id else "Sign in with Hugging Face to upload context material."
246
+ notebook_status_update = gr.update(
247
+ value="Sign in with Hugging Face to manage notebooks." if not user_id else "",
248
+ visible=not bool(user_id),
249
+ )
250
+ return (
251
+ state,
252
+ selected,
253
+ notebook_status_update,
254
+ auth_update,
255
+ gr.update(visible=auth_row_visible),
256
+ gr.update(visible=bool(user_id)),
257
+ gr.update(visible=not bool(user_id)),
258
+ source_status,
259
+ )
260
+
261
+
262
+ def _selected_notebook_text(selected_id, state) -> str:
263
+ if not selected_id:
264
+ return "**Selected notebook:** None"
265
+ name_map = {str(notebook_id): name for notebook_id, name in (state or [])}
266
+ name = name_map.get(str(selected_id))
267
+ if name:
268
+ return f"**Selected notebook:** {name}"
269
+ return "**Selected notebook:** Unknown"
270
 
271
 
272
  REPORT_SCOPE_LABELS = {
 
677
  ) as demo:
678
  with gr.Row(elem_classes=["header-bar"]):
679
  gr.Markdown("### 📓 NotebookLM Clone")
680
+ login_btn = gr.LoginButton(value="Login with Hugging Face", size="lg", elem_id="login-btn")
681
 
682
  with gr.Row(visible=False) as auth_info_row:
683
  auth_text = gr.Markdown("", elem_id="auth-text")
 
685
  gr.HTML("""
686
  <div class="container hero-section">
687
  <h1 class="hero-title">📓 NotebookLM Clone</h1>
688
+ <p class="hero-sub">Chat with your documents. Generate reports, quizzes, and podcasts.</p>
689
  </div>
690
  """)
691
 
 
695
  with gr.Column(visible=False) as app_content:
696
  nb_state = gr.State([])
697
  selected_notebook_id = gr.State(None)
698
+ chat_history_state = gr.State([])
699
+ quiz_state = gr.State([])
700
+
701
+ with gr.Group(elem_classes=["section-card", "manager-card"]):
702
+ gr.Markdown("**Notebook Manager**", elem_classes=["section-title"])
703
+ selected_notebook_md = gr.Markdown("**Selected notebook:** None", elem_classes=["status"])
704
+
705
+ with gr.Group(elem_classes=["create-strip"]):
706
+ with gr.Row(elem_classes=["create-row"]):
707
+ gr.Markdown("Create new notebook", elem_classes=["create-label"])
708
+ create_txt = gr.Textbox(
709
+ placeholder="Enter new notebook name",
710
+ show_label=False,
711
+ container=False,
712
+ value="",
713
+ )
714
+ create_btn = gr.Button("Create", variant="primary", size="sm")
715
+
716
+ notebook_status = gr.Markdown("", elem_classes=["status"], visible=False)
717
+
718
+ @gr.render(inputs=[nb_state, selected_notebook_id])
719
+ def render_notebooks(state, selected_id):
720
  if not state:
721
  gr.Markdown("No notebooks yet. Create one to get started.")
722
  else:
723
  for i, (nb_id, name) in enumerate(state):
724
  idx = i
725
+ is_selected = str(nb_id) == str(selected_id)
726
+ row_class = ["notebook-card", "notebook-selected"] if is_selected else ["notebook-card"]
727
+ with gr.Row(elem_classes=row_class):
728
+ name_txt = gr.Textbox(
729
+ value=name,
730
+ show_label=False,
731
+ scale=4,
732
+ min_width=240,
733
+ key=f"nb-name-{nb_id}",
734
+ )
735
+ select_btn = gr.Button(
736
+ "Selected" if is_selected else "Select",
737
+ variant="primary" if is_selected else "secondary",
738
+ scale=1,
739
+ min_width=90,
740
+ size="sm",
741
+ )
742
+ rename_btn = gr.Button("Rename", variant="secondary", scale=1, min_width=80, size="sm")
743
+ delete_btn = gr.Button("Delete", variant="stop", scale=1, min_width=80, size="sm")
744
 
745
+ def on_select(nb_id=nb_id):
746
+ return nb_id
747
 
748
+ def on_select_status(name=name):
749
+ return f"Selected notebook: {name}"
750
 
751
  select_btn.click(
752
  on_select,
753
  inputs=None,
754
+ outputs=[selected_notebook_id],
755
+ api_name=False,
756
+ ).then(on_select_status, inputs=None, outputs=[notebook_status], api_name=False)
757
 
758
  rename_btn.click(
759
  _safe_rename,
 
767
  inputs=[gr.State(idx), nb_state, selected_notebook_id],
768
  outputs=[nb_state, selected_notebook_id, notebook_status],
769
  api_name=False,
770
+ )
771
+
772
+ with gr.Group(elem_classes=["section-card", "sources-card"]):
773
+ gr.Markdown("**Upload Sources**", elem_classes=["section-title"])
774
+ gr.Markdown("*Add PDF, URL, and text content into the selected notebook.*")
775
+
776
+ source_status = gr.Markdown("", elem_classes=["status"])
777
+
778
  with gr.Row(elem_classes=["section-row"]):
779
  pdf_upload_btn = gr.UploadButton(
780
  "Upload PDFs",
 
783
  type="filepath",
784
  variant="secondary",
785
  )
786
+
787
  with gr.Row(elem_classes=["section-row"]):
788
  uploaded_pdf_dd = gr.Dropdown(
789
  label="Uploaded PDFs",
 
792
  scale=3,
793
  allow_custom_value=False,
794
  )
795
+ remove_pdf_btn = gr.Button("Remove selected PDF", variant="stop", scale=1)
796
+
797
  with gr.Row(elem_classes=["section-row"]):
798
  url_txt = gr.Textbox(
799
  label="Ingest web URL",
 
801
  value="",
802
  scale=3,
803
  )
804
+ ingest_url_btn = gr.Button("Ingest URL", variant="primary", scale=1)
805
+ remove_url_btn = gr.Button("Delete URL", variant="stop", scale=1)
806
+
807
+ gr.Markdown("**Text Source**", elem_classes=["section-title"])
808
+ with gr.Row(elem_classes=["section-row"]):
 
 
 
 
 
 
 
809
  txt_title = gr.Textbox(
810
  label="Title",
811
  placeholder="Give this text a name (e.g. 'Lecture Notes Week 1')",
812
  scale=1,
813
  )
814
+
815
  txt_input = gr.Textbox(
816
  label="Text Content",
817
  placeholder="Paste or type your text here...",
 
819
  )
820
  submit_btn = gr.Button("Save & Process", variant="primary")
821
  upload_status = gr.Markdown("", elem_classes=["status"])
822
+ sources_display = gr.Markdown("No sources yet.")
823
 
824
+ with gr.Group(elem_classes=["section-card", "chat-card"]):
825
+ gr.Markdown("**Chat**", elem_classes=["section-title"])
826
+ gr.Markdown("*Ask questions about your notebook sources. Answers are grounded in retrieved chunks with citations.*")
827
+ chatbot = gr.Chatbot(label="Chat history", height=400)
828
+ chat_input = gr.Textbox(
829
+ label="Message",
830
+ placeholder="Ask a question about your sources...",
831
+ show_label=False,
832
+ lines=2,
833
+ )
834
+ chat_submit_btn = gr.Button("Send", variant="primary")
835
+ chat_status = gr.Markdown("", elem_classes=["status"])
836
+
837
+ with gr.Group(elem_classes=["section-card", "artifacts-card"]):
838
+ gr.Markdown("**Artifacts**", elem_classes=["section-title"])
839
+
840
+ gr.Markdown("**Report**")
841
  with gr.Row(elem_classes=["section-row"]):
842
  report_scope_dd = gr.Dropdown(
843
  label="Report scope",
 
849
  report_status = gr.Markdown("Select a scope and click generate.", elem_classes=["status"])
850
  report_output = gr.Markdown("", elem_id="report-output")
851
 
852
+ gr.Markdown("**Podcast**")
853
+ with gr.Row(elem_classes=["section-row"]):
854
+ podcast_btn = gr.Button("Generate Podcast", variant="primary")
855
+ podcast_audio_btn = gr.Button("Generate Podcast Audio", variant="secondary")
856
+ podcast_status = gr.Markdown("", elem_classes=["status"])
857
+ podcast_script = gr.Markdown("")
858
+ podcast_audio = gr.Audio(label="Podcast Audio", type="filepath")
859
+
860
+ gr.Markdown("**Quiz**")
861
+ gr.Markdown("Select a source type then generate a quiz.")
862
+ quiz_source_type = gr.Radio(
863
+ choices=["Text", "PDF", "URL", "All"],
864
+ value="All",
865
+ label="Source type",
866
  )
867
+ quiz_pdf_dd = gr.Dropdown(
868
+ label="Select PDF",
869
+ choices=[],
870
+ value=None,
871
+ visible=False,
872
+ )
873
+ generate_quiz_btn = gr.Button("Generate Quiz", variant="primary")
874
+ quiz_status = gr.Markdown("")
875
+
876
+ quiz_components = []
877
+ for i in range(5):
878
+ with gr.Group(visible=False) as q_group:
879
+ q_text = gr.Markdown("")
880
+ q_radio = gr.Radio(choices=[], label="Your answer", visible=False)
881
+ q_textbox = gr.Textbox(label="Your answer", visible=False)
882
+ quiz_components.append({"group": q_group, "text": q_text, "radio": q_radio, "textbox": q_textbox})
883
+
884
+ submit_quiz_btn = gr.Button("Submit Answers", variant="secondary", visible=False)
885
+ quiz_results = gr.Markdown("")
886
 
887
  demo.load(
888
  _initial_load,
889
  inputs=None,
890
+ outputs=[nb_state, selected_notebook_id, notebook_status, auth_text, auth_info_row, app_content, login_container, source_status],
891
  api_name=False,
892
  )
893
  demo.load(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd], api_name=False)
894
+ demo.load(_load_sources, inputs=[selected_notebook_id], outputs=[sources_display], api_name=False)
895
+ demo.load(_selected_notebook_text, inputs=[selected_notebook_id, nb_state], outputs=[selected_notebook_md], api_name=False)
896
 
897
  def _on_notebook_select_for_chat(notebook_id):
898
  hist, _ = _load_chat_history(notebook_id)
 
904
  outputs=[chat_history_state, chatbot],
905
  api_name=False,
906
  )
907
+ selected_notebook_id.change(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd], api_name=False)
908
+ selected_notebook_id.change(_load_sources, inputs=[selected_notebook_id], outputs=[sources_display], api_name=False)
909
+ selected_notebook_id.change(_selected_notebook_text, inputs=[selected_notebook_id, nb_state], outputs=[selected_notebook_md], api_name=False)
910
+ nb_state.change(_selected_notebook_text, inputs=[selected_notebook_id, nb_state], outputs=[selected_notebook_md], api_name=False)
911
 
912
  create_btn.click(
913
  _safe_create,
914
  inputs=[create_txt, nb_state, selected_notebook_id],
915
+ outputs=[create_txt, nb_state, selected_notebook_id, notebook_status],
916
  api_name=False,
917
  ).then(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd])
918
 
919
  pdf_upload_btn.upload(
920
  _safe_upload_pdfs,
921
  inputs=[pdf_upload_btn, selected_notebook_id],
922
+ outputs=[source_status],
923
  api_name=False,
924
  ).then(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd])
925
 
926
  ingest_url_btn.click(
927
  _safe_ingest_url,
928
  inputs=[url_txt, selected_notebook_id],
929
+ outputs=[url_txt, source_status],
930
  api_name=False,
931
  )
932
 
933
  remove_url_btn.click(
934
  _safe_remove_url,
935
  inputs=[url_txt, selected_notebook_id],
936
+ outputs=[url_txt, source_status],
937
  api_name=False
938
  )
939
 
940
  remove_pdf_btn.click(
941
  _safe_remove_pdf,
942
  inputs=[uploaded_pdf_dd, selected_notebook_id],
943
+ outputs=[source_status],
944
  api_name=False,
945
  ).then(_list_uploaded_pdfs, inputs=[selected_notebook_id], outputs=[uploaded_pdf_dd])
946
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
947
  submit_btn.click(
948
  _do_upload,
949
  inputs=[txt_input, txt_title, selected_notebook_id],
950
  outputs=[upload_status],
951
+ ).then(_load_sources, inputs=[selected_notebook_id], outputs=[sources_display])
952
 
953
  report_btn.click(
954
  _generate_report,
 
957
  api_name=False,
958
  )
959
 
 
 
 
 
 
 
 
960
  podcast_btn.click(
961
  _safe_generate_podcast,
962
  inputs=[selected_notebook_id],