Jaward commited on
Commit
35bf16f
·
verified ·
1 Parent(s): 176bc42

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +155 -225
app.py CHANGED
@@ -5,6 +5,8 @@ import gradio as gr
5
  import asyncio
6
  import logging
7
  import torch
 
 
8
  from serpapi import GoogleSearch
9
  from pydantic import BaseModel
10
  from autogen_agentchat.agents import AssistantAgent
@@ -20,6 +22,7 @@ import soundfile as sf
20
  import tempfile
21
  from pydub import AudioSegment
22
  from TTS.api import TTS
 
23
 
24
  # Set up logging
25
  logging.basicConfig(
@@ -330,6 +333,15 @@ def generate_markdown_slides(slides, title, speaker="Prof. AI Feynman", date="Ap
330
  logger.error(traceback.format_exc())
331
  return None
332
 
 
 
 
 
 
 
 
 
 
333
  # Async function to update audio preview
334
  async def update_audio_preview(audio_file):
335
  if audio_file:
@@ -337,6 +349,26 @@ async def update_audio_preview(audio_file):
337
  return audio_file
338
  return None
339
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  # Async function to generate lecture materials and audio
341
  async def on_generate(api_service, api_key, serpapi_key, title, lecture_content_description, lecture_type, speaker_audio, num_slides):
342
  model_client = get_model_client(api_service, api_key)
@@ -401,9 +433,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
401
  label = "Research: in progress..."
402
  yield (
403
  html_with_progress(label, progress),
404
- [],
405
- "",
406
- []
407
  )
408
  await asyncio.sleep(0.1)
409
 
@@ -449,9 +479,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
449
  label = "Slides: generating..."
450
  yield (
451
  html_with_progress(label, progress),
452
- [],
453
- "",
454
- []
455
  )
456
  await asyncio.sleep(0.1)
457
  elif source == "slide_agent" and message.target == "script_agent":
@@ -476,9 +504,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
476
  label = "Scripts: generating..."
477
  yield (
478
  html_with_progress(label, progress),
479
- [],
480
- "",
481
- []
482
  )
483
  await asyncio.sleep(0.1)
484
  elif source == "script_agent" and message.target == "feynman_agent":
@@ -492,9 +518,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
492
  label = "Review: in progress..."
493
  yield (
494
  html_with_progress(label, progress),
495
- [],
496
- "",
497
- []
498
  )
499
  await asyncio.sleep(0.1)
500
 
@@ -504,9 +528,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
504
  label = "Slides: generating..."
505
  yield (
506
  html_with_progress(label, progress),
507
- [],
508
- "",
509
- []
510
  )
511
  await asyncio.sleep(0.1)
512
 
@@ -539,9 +561,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
539
  label = "Scripts: generating..."
540
  yield (
541
  html_with_progress(label, progress),
542
- [],
543
- "",
544
- []
545
  )
546
  await asyncio.sleep(0.1)
547
  else:
@@ -575,9 +595,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
575
  label = "Scripts generated and saved. Reviewing..."
576
  yield (
577
  html_with_progress(label, progress),
578
- [],
579
- "",
580
- []
581
  )
582
  await asyncio.sleep(0.1)
583
  else:
@@ -597,11 +615,17 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
597
  logger.info("Feynman Agent completed lecture review: %s", message.content)
598
  progress = 90
599
  label = "Lecture materials ready. Generating audio..."
 
 
 
 
 
600
  yield (
601
  html_with_progress(label, progress),
602
- [],
603
- "",
604
- []
 
605
  )
606
  await asyncio.sleep(0.1)
607
 
@@ -617,9 +641,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
617
  logger.debug("Message from %s, type: %s, content: %s", source, type(msg), msg.to_text() if hasattr(msg, 'to_text') else str(msg))
618
  yield (
619
  error_html,
620
- [],
621
- "",
622
- []
623
  )
624
  return
625
 
@@ -632,9 +654,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
632
  <p style="margin-top: 20px;">Expected {total_slides} slides, but generated {len(slides)}. Please try again.</p>
633
  </div>
634
  """,
635
- [],
636
- "",
637
- []
638
  )
639
  return
640
 
@@ -647,9 +667,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
647
  <p style="margin-top: 20px;">Scripts must be a list of strings. Please try again.</p>
648
  </div>
649
  """,
650
- [],
651
- "",
652
- []
653
  )
654
  return
655
 
@@ -662,9 +680,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
662
  <p style="margin-top: 20px;">Generated {len(slides)} slides but {len(scripts)} scripts. Please try again.</p>
663
  </div>
664
  """,
665
- [],
666
- "",
667
- []
668
  )
669
  return
670
 
@@ -678,60 +694,68 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
678
  <p style="margin-top: 20px;">Please try again.</p>
679
  </div>
680
  """,
681
- [],
682
- "",
683
- []
684
  )
685
  return
686
 
687
- # Collect .txt files for download
688
- txt_files = [f for f in os.listdir(OUTPUT_DIR) if f.endswith('.txt')]
689
- txt_files.sort() # Sort for consistent display
690
- txt_file_paths = [os.path.join(OUTPUT_DIR, f) for f in txt_files]
691
 
692
- # Initialize audio timeline placeholders
693
- audio_urls = [None] * len(scripts)
694
- audio_timeline = ""
695
- for i in range(len(scripts)):
696
- audio_timeline += f'<audio id="audio-{i+1}" controls style="display: inline-block; margin: 0 10px; width: 200px;"><source src="" type="audio/mpeg"></audio>'
697
 
698
- # Display lecture materials immediately
699
- slides_json = json.dumps({"slides": markdown_slides, "audioFiles": audio_urls})
700
- html_controls = f"""
701
  <div id="lecture-container" style="height: 700px; border: 1px solid #ddd; border-radius: 8px; display: flex; flex-direction: column; justify-content: space-between;">
 
 
 
702
  <div style="padding: 20px; text-align: center;">
703
  <div id="audio-timeline" style="display: flex; justify-content: center; margin-bottom: 10px;">
704
- {audio_timeline}
705
  </div>
706
  <div style="display: flex; justify-content: center; margin-bottom: 10px;">
707
  <button id="prev-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏮</button>
708
  <button id="play-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏯</button>
709
  <button id="next-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏭</button>
710
- <button id="fullscreen-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">🖥️</button>
711
  </div>
712
  </div>
713
  </div>
714
  <script>
715
- const lectureData = {slides_json};
716
  let currentSlide = 0;
717
  const totalSlides = lectureData.slides.length;
718
  let audioElements = [];
 
 
 
 
 
 
 
 
 
 
 
 
719
 
720
- // Populate audio elements
721
- for (let i = 0; i < totalSlides; i++) {{
722
- const audio = document.getElementById(`audio-${{i+1}}`);
723
- audioElements.push(audio);
 
 
 
724
  }}
725
 
726
- function updateSlideDisplay() {{
727
- window.updateSlideContent(lectureData.slides[currentSlide]);
728
- audioElements.forEach((audio, index) => {{
729
  if (audio && audio.pause) {{
730
  audio.pause();
731
  audio.currentTime = 0;
732
- if (index === currentSlide && audio.src) {{
733
- audio.play().catch(e => console.error('Audio play failed:', e));
734
- }}
735
  }}
736
  }});
737
  }}
@@ -739,25 +763,49 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
739
  function prevSlide() {{
740
  if (currentSlide > 0) {{
741
  currentSlide--;
742
- updateSlideDisplay();
 
 
 
 
743
  }}
744
  }}
745
 
746
  function nextSlide() {{
747
  if (currentSlide < totalSlides - 1) {{
748
  currentSlide++;
749
- updateSlideDisplay();
 
 
 
 
750
  }}
751
  }}
752
 
753
  function playAll() {{
 
 
 
 
 
 
 
 
 
 
 
 
754
  let index = currentSlide;
755
  function playNext() {{
756
- if (index >= totalSlides) return;
 
 
 
 
757
  currentSlide = index;
758
- updateSlideDisplay();
759
  const audio = audioElements[index];
760
- if (audio && audio.src) {{
761
  audio.play().then(() => {{
762
  audio.addEventListener('ended', () => {{
763
  index++;
@@ -780,7 +828,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
780
  const container = document.getElementById('lecture-container');
781
  if (!document.fullscreenElement) {{
782
  container.requestFullscreen().catch(err => {{
783
- console.error(`Error attempting to enable full-screen mode: ${{err.message}}`);
784
  }});
785
  }} else {{
786
  document.exitFullscreen();
@@ -793,27 +841,31 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
793
  document.getElementById('next-btn').addEventListener('click', nextSlide);
794
  document.getElementById('fullscreen-btn').addEventListener('click', toggleFullScreen);
795
 
796
- // Initialize first slide
797
- updateSlideDisplay();
798
  </script>
799
  """
 
800
  yield (
801
- html_controls,
802
  txt_file_paths,
803
- markdown_slides[0],
804
- []
 
805
  )
806
 
807
- # Audio generation
808
- audio_files = []
809
  validated_speaker_wav = await validate_and_convert_speaker_audio(speaker_audio)
810
  if not validated_speaker_wav:
811
  logger.error("Invalid speaker audio after conversion, skipping TTS")
812
  yield (
813
- html_controls,
814
- txt_file_paths,
815
- markdown_slides[0],
816
- []
 
 
 
817
  )
818
  return
819
 
@@ -831,15 +883,15 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
831
 
832
  if not cleaned_script:
833
  logger.error("Skipping audio for slide %d due to empty or invalid script", i + 1)
834
- audio_files.append(None)
835
- audio_urls[i] = None
836
  progress = 90 + ((i + 1) / len(scripts)) * 10
837
  label = f"Generated audio for slide {i + 1}/{len(scripts)}..."
838
  yield (
839
- html_controls,
840
  txt_file_paths,
841
- markdown_slides[currentSlide if 'currentSlide' in locals() else 0],
842
- []
 
843
  )
844
  await asyncio.sleep(0.1)
845
  continue
@@ -859,122 +911,15 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
859
  raise RuntimeError("TTS generation failed")
860
 
861
  logger.info("Generated audio for slide %d: %s", i + 1, audio_file)
862
- audio_files.append(audio_file)
863
- audio_urls[i] = f"/gradio_api/file={audio_file}"
864
- # Update the audio element's src
865
- audio_timeline = ""
866
- for j, url in enumerate(audio_urls):
867
- if url:
868
- audio_timeline += f'<audio id="audio-{j+1}" controls src="{url}" style="display: inline-block; margin: 0 10px; width: 200px;"></audio>'
869
- else:
870
- audio_timeline += f'<audio id="audio-{j+1}" controls style="display: inline-block; margin: 0 10px; width: 200px;"><source src="" type="audio/mpeg"></audio>'
871
- html_controls = f"""
872
- <div id="lecture-container" style="height: 700px; border: 1px solid #ddd; border-radius: 8px; display: flex; flex-direction: column; justify-content: space-between;">
873
- <div style="padding: 20px; text-align: center;">
874
- <div id="audio-timeline" style="display: flex; justify-content: center; margin-bottom: 10px;">
875
- {audio_timeline}
876
- </div>
877
- <div style="display: flex; justify-content: center; margin-bottom: 10px;">
878
- <button id="prev-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏮</button>
879
- <button id="play-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏯</button>
880
- <button id="next-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏭</button>
881
- <button id="fullscreen-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">🖥️</button>
882
- </div>
883
- </div>
884
- </div>
885
- <script>
886
- const lectureData = {json.dumps({"slides": markdown_slides, "audioFiles": audio_urls})};
887
- let currentSlide = {currentSlide if 'currentSlide' in locals() else 0};
888
- const totalSlides = lectureData.slides.length;
889
- let audioElements = [];
890
-
891
- // Populate audio elements
892
- for (let i = 0; i < totalSlides; i++) {{
893
- const audio = document.getElementById(`audio-${{i+1}}`);
894
- audioElements.push(audio);
895
- }}
896
-
897
- function updateSlideDisplay() {{
898
- window.updateSlideContent(lectureData.slides[currentSlide]);
899
- audioElements.forEach((audio, index) => {{
900
- if (audio && audio.pause) {{
901
- audio.pause();
902
- audio.currentTime = 0;
903
- if (index === currentSlide && audio.src) {{
904
- audio.play().catch(e => console.error('Audio play failed:', e));
905
- }}
906
- }}
907
- }});
908
- }}
909
-
910
- function prevSlide() {{
911
- if (currentSlide > 0) {{
912
- currentSlide--;
913
- updateSlideDisplay();
914
- }}
915
- }}
916
-
917
- function nextSlide() {{
918
- if (currentSlide < totalSlides - 1) {{
919
- currentSlide++;
920
- updateSlideDisplay();
921
- }}
922
- }}
923
-
924
- function playAll() {{
925
- let index = currentSlide;
926
- function playNext() {{
927
- if (index >= totalSlides) return;
928
- currentSlide = index;
929
- updateSlideDisplay();
930
- const audio = audioElements[index];
931
- if (audio && audio.src) {{
932
- audio.play().then(() => {{
933
- audio.addEventListener('ended', () => {{
934
- index++;
935
- playNext();
936
- }}, {{ once: true }});
937
- }}).catch(e => {{
938
- console.error('Audio play failed:', e);
939
- index++;
940
- playNext();
941
- }});
942
- }} else {{
943
- index++;
944
- playNext();
945
- }}
946
- }}
947
- playNext();
948
- }}
949
-
950
- function toggleFullScreen() {{
951
- const container = document.getElementById('lecture-container');
952
- if (!document.fullscreenElement) {{
953
- container.requestFullscreen().catch(err => {{
954
- console.error(`Error attempting to enable full-screen mode: ${{err.message}}`);
955
- }});
956
- }} else {{
957
- document.exitFullscreen();
958
- }}
959
- }}
960
-
961
- // Attach event listeners
962
- document.getElementById('prev-btn').addEventListener('click', prevSlide);
963
- document.getElementById('play-btn').addEventListener('click', playAll);
964
- document.getElementById('next-btn').addEventListener('click', nextSlide);
965
- document.getElementById('fullscreen-btn').addEventListener('click', toggleFullScreen);
966
-
967
- // Initialize first slide
968
- updateSlideDisplay();
969
- </script>
970
- """
971
  progress = 90 + ((i + 1) / len(scripts)) * 10
972
  label = f"Generated audio for slide {i + 1}/{len(scripts)}..."
973
  yield (
974
- html_controls,
975
  txt_file_paths,
976
- markdown_slides[currentSlide if 'currentSlide' in locals() else 0],
977
- []
 
978
  )
979
  await asyncio.sleep(0.1)
980
  break
@@ -982,15 +927,15 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
982
  logger.error("Error generating audio for slide %d (attempt %d): %s\n%s", i + 1, attempt, str(e), traceback.format_exc())
983
  if attempt == max_audio_retries:
984
  logger.error("Max retries reached for slide %d, skipping", i + 1)
985
- audio_files.append(None)
986
- audio_urls[i] = None
987
  progress = 90 + ((i + 1) / len(scripts)) * 10
988
  label = f"Generated audio for slide {i + 1}/{len(scripts)}..."
989
  yield (
990
- html_controls,
991
  txt_file_paths,
992
- markdown_slides[currentSlide if 'currentSlide' in locals() else 0],
993
- []
 
994
  )
995
  await asyncio.sleep(0.1)
996
  break
@@ -1007,9 +952,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
1007
  <p style="margin-top: 20px;">Please try again or adjust your inputs.</p>
1008
  </div>
1009
  """,
1010
- [],
1011
- "",
1012
- []
1013
  )
1014
  return
1015
 
@@ -1044,9 +987,11 @@ with gr.Blocks(title="Agent Feynman") as demo:
1044
  <p style="margin-top: 10px; font-size: 16px;">Please Generate lecture content via the form on the left first before lecture begins</p>
1045
  </div>
1046
  """
1047
- slide_display = gr.Markdown(label="Lecture Slides", value="Waiting for lecture content...")
1048
- controls_display = gr.HTML(label="Controls", value=default_slide_html)
1049
  file_output = gr.File(label="Download Generated Files")
 
 
 
1050
 
1051
  speaker_audio.change(
1052
  fn=update_audio_preview,
@@ -1054,26 +999,11 @@ with gr.Blocks(title="Agent Feynman") as demo:
1054
  outputs=speaker_audio
1055
  )
1056
 
1057
- # JavaScript to update slide content dynamically
1058
- demo.load(
1059
- fn=None,
1060
- inputs=None,
1061
- outputs=None,
1062
- js="""
1063
- () => {
1064
- window.updateSlideContent = (content) => {
1065
- document.querySelector('#slide-display textarea').value = content;
1066
- document.querySelector('#slide-display').dispatchEvent(new Event('input'));
1067
- };
1068
- }
1069
- """
1070
- )
1071
-
1072
  generate_btn.click(
1073
  fn=on_generate,
1074
  inputs=[api_service, api_key, serpapi_key, title, lecture_content_description, lecture_type, speaker_audio, num_slides],
1075
- outputs=[controls_display, file_output, slide_display, gr.State()]
1076
  )
1077
 
1078
  if __name__ == "__main__":
1079
- demo.launch(allowed_paths=[OUTPUT_DIR])
 
5
  import asyncio
6
  import logging
7
  import torch
8
+ import zipfile
9
+ import io
10
  from serpapi import GoogleSearch
11
  from pydantic import BaseModel
12
  from autogen_agentchat.agents import AssistantAgent
 
22
  import tempfile
23
  from pydub import AudioSegment
24
  from TTS.api import TTS
25
+ import markdown
26
 
27
  # Set up logging
28
  logging.basicConfig(
 
333
  logger.error(traceback.format_exc())
334
  return None
335
 
336
+ # Function to convert Markdown to HTML
337
+ def markdown_to_html(md_text):
338
+ try:
339
+ html = markdown.markdown(md_text)
340
+ return html
341
+ except Exception as e:
342
+ logger.error(f"Failed to convert Markdown to HTML: {str(e)}")
343
+ return "<p>Error rendering slide content</p>"
344
+
345
  # Async function to update audio preview
346
  async def update_audio_preview(audio_file):
347
  if audio_file:
 
349
  return audio_file
350
  return None
351
 
352
+ # Function to create a zip file of all .txt files
353
+ def create_zip_of_txt_files():
354
+ txt_files = [f for f in os.listdir(OUTPUT_DIR) if f.endswith('.txt')]
355
+ if not txt_files:
356
+ return None
357
+
358
+ zip_buffer = io.BytesIO()
359
+ with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
360
+ for txt_file in txt_files:
361
+ file_path = os.path.join(OUTPUT_DIR, txt_file)
362
+ zip_file.write(file_path, txt_file)
363
+
364
+ zip_buffer.seek(0)
365
+ zip_path = os.path.join(OUTPUT_DIR, "lecture_files.zip")
366
+ with open(zip_path, "wb") as f:
367
+ f.write(zip_buffer.getvalue())
368
+
369
+ logger.info("Created zip file: %s", zip_path)
370
+ return zip_path
371
+
372
  # Async function to generate lecture materials and audio
373
  async def on_generate(api_service, api_key, serpapi_key, title, lecture_content_description, lecture_type, speaker_audio, num_slides):
374
  model_client = get_model_client(api_service, api_key)
 
433
  label = "Research: in progress..."
434
  yield (
435
  html_with_progress(label, progress),
436
+ [], 0, [], None
 
 
437
  )
438
  await asyncio.sleep(0.1)
439
 
 
479
  label = "Slides: generating..."
480
  yield (
481
  html_with_progress(label, progress),
482
+ [], 0, [], None
 
 
483
  )
484
  await asyncio.sleep(0.1)
485
  elif source == "slide_agent" and message.target == "script_agent":
 
504
  label = "Scripts: generating..."
505
  yield (
506
  html_with_progress(label, progress),
507
+ [], 0, [], None
 
 
508
  )
509
  await asyncio.sleep(0.1)
510
  elif source == "script_agent" and message.target == "feynman_agent":
 
518
  label = "Review: in progress..."
519
  yield (
520
  html_with_progress(label, progress),
521
+ [], 0, [], None
 
 
522
  )
523
  await asyncio.sleep(0.1)
524
 
 
528
  label = "Slides: generating..."
529
  yield (
530
  html_with_progress(label, progress),
531
+ [], 0, [], None
 
 
532
  )
533
  await asyncio.sleep(0.1)
534
 
 
561
  label = "Scripts: generating..."
562
  yield (
563
  html_with_progress(label, progress),
564
+ [], 0, [], None
 
 
565
  )
566
  await asyncio.sleep(0.1)
567
  else:
 
595
  label = "Scripts generated and saved. Reviewing..."
596
  yield (
597
  html_with_progress(label, progress),
598
+ [], 0, [], None
 
 
599
  )
600
  await asyncio.sleep(0.1)
601
  else:
 
615
  logger.info("Feynman Agent completed lecture review: %s", message.content)
616
  progress = 90
617
  label = "Lecture materials ready. Generating audio..."
618
+ # Collect .txt files for download
619
+ txt_files = [f for f in os.listdir(OUTPUT_DIR) if f.endswith('.txt')]
620
+ txt_files.sort() # Sort for consistent display
621
+ txt_file_paths = [os.path.join(OUTPUT_DIR, f) for f in txt_files]
622
+ zip_file_path = create_zip_of_txt_files()
623
  yield (
624
  html_with_progress(label, progress),
625
+ txt_file_paths,
626
+ 0,
627
+ [None] * total_slides,
628
+ zip_file_path
629
  )
630
  await asyncio.sleep(0.1)
631
 
 
641
  logger.debug("Message from %s, type: %s, content: %s", source, type(msg), msg.to_text() if hasattr(msg, 'to_text') else str(msg))
642
  yield (
643
  error_html,
644
+ [], 0, [], None
 
 
645
  )
646
  return
647
 
 
654
  <p style="margin-top: 20px;">Expected {total_slides} slides, but generated {len(slides)}. Please try again.</p>
655
  </div>
656
  """,
657
+ [], 0, [], None
 
 
658
  )
659
  return
660
 
 
667
  <p style="margin-top: 20px;">Scripts must be a list of strings. Please try again.</p>
668
  </div>
669
  """,
670
+ [], 0, [], None
 
 
671
  )
672
  return
673
 
 
680
  <p style="margin-top: 20px;">Generated {len(slides)} slides but {len(scripts)} scripts. Please try again.</p>
681
  </div>
682
  """,
683
+ [], 0, [], None
 
 
684
  )
685
  return
686
 
 
694
  <p style="margin-top: 20px;">Please try again.</p>
695
  </div>
696
  """,
697
+ [], 0, [], None
 
 
698
  )
699
  return
700
 
701
+ # Convert Markdown slides to HTML for rendering
702
+ html_slides = [markdown_to_html(md) for md in markdown_slides]
 
 
703
 
704
+ # Initialize audio files list with None
705
+ audio_files = [None] * len(scripts)
 
 
 
706
 
707
+ # Yield the lecture materials immediately after slides and scripts are ready
708
+ slides_info = json.dumps({"slides": html_slides, "audioFiles": audio_files})
709
+ html_output = f"""
710
  <div id="lecture-container" style="height: 700px; border: 1px solid #ddd; border-radius: 8px; display: flex; flex-direction: column; justify-content: space-between;">
711
+ <div id="slide-content" style="flex: 1; overflow: auto; padding: 20px; text-align: center; background-color: #fff; color: #333;">
712
+ {html_slides[0] if html_slides else "<p>No slide content available</p>"}
713
+ </div>
714
  <div style="padding: 20px; text-align: center;">
715
  <div id="audio-timeline" style="display: flex; justify-content: center; margin-bottom: 10px;">
716
+ <!-- Audio components will be rendered here by Gradio -->
717
  </div>
718
  <div style="display: flex; justify-content: center; margin-bottom: 10px;">
719
  <button id="prev-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏮</button>
720
  <button id="play-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏯</button>
721
  <button id="next-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏭</button>
722
+ <button id="fullscreen-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">☐</button>
723
  </div>
724
  </div>
725
  </div>
726
  <script>
727
+ const lectureData = {slides_info};
728
  let currentSlide = 0;
729
  const totalSlides = lectureData.slides.length;
730
  let audioElements = [];
731
+ let isPlaying = false;
732
+
733
+ // Function to populate audio elements (will be updated by Gradio)
734
+ function updateAudioElements() {{
735
+ audioElements = [];
736
+ for (let i = 0; i < totalSlides; i++) {{
737
+ const audio = document.getElementById(`audio-${{i+1}}`);
738
+ if (audio) {{
739
+ audioElements.push(audio);
740
+ }}
741
+ }}
742
+ }}
743
 
744
+ function renderSlide() {{
745
+ const slideContent = document.getElementById('slide-content');
746
+ if (lectureData.slides[currentSlide]) {{
747
+ slideContent.innerHTML = lectureData.slides[currentSlide];
748
+ }} else {{
749
+ slideContent.innerHTML = '<p>No slide content available</p>';
750
+ }}
751
  }}
752
 
753
+ function updateSlide() {{
754
+ renderSlide();
755
+ audioElements.forEach(audio => {{
756
  if (audio && audio.pause) {{
757
  audio.pause();
758
  audio.currentTime = 0;
 
 
 
759
  }}
760
  }});
761
  }}
 
763
  function prevSlide() {{
764
  if (currentSlide > 0) {{
765
  currentSlide--;
766
+ updateSlide();
767
+ const audio = audioElements[currentSlide];
768
+ if (audio && audio.play && isPlaying) {{
769
+ audio.play().catch(e => console.error('Audio play failed:', e));
770
+ }}
771
  }}
772
  }}
773
 
774
  function nextSlide() {{
775
  if (currentSlide < totalSlides - 1) {{
776
  currentSlide++;
777
+ updateSlide();
778
+ const audio = audioElements[currentSlide];
779
+ if (audio && audio.play && isPlaying) {{
780
+ audio.play().catch(e => console.error('Audio play failed:', e));
781
+ }}
782
  }}
783
  }}
784
 
785
  function playAll() {{
786
+ isPlaying = !isPlaying;
787
+ const playBtn = document.getElementById('play-btn');
788
+ playBtn.textContent = isPlaying ? '⏸' : '⏯';
789
+ if (!isPlaying) {{
790
+ audioElements.forEach(audio => {{
791
+ if (audio && audio.pause) {{
792
+ audio.pause();
793
+ audio.currentTime = 0;
794
+ }}
795
+ }});
796
+ return;
797
+ }}
798
  let index = currentSlide;
799
  function playNext() {{
800
+ if (index >= totalSlides || !isPlaying) {{
801
+ isPlaying = false;
802
+ playBtn.textContent = '⏯';
803
+ return;
804
+ }}
805
  currentSlide = index;
806
+ updateSlide();
807
  const audio = audioElements[index];
808
+ if (audio && audio.play) {{
809
  audio.play().then(() => {{
810
  audio.addEventListener('ended', () => {{
811
  index++;
 
828
  const container = document.getElementById('lecture-container');
829
  if (!document.fullscreenElement) {{
830
  container.requestFullscreen().catch(err => {{
831
+ console.error('Error attempting to enable full-screen mode:', err);
832
  }});
833
  }} else {{
834
  document.exitFullscreen();
 
841
  document.getElementById('next-btn').addEventListener('click', nextSlide);
842
  document.getElementById('fullscreen-btn').addEventListener('click', toggleFullScreen);
843
 
844
+ // Initialize
845
+ updateAudioElements();
846
  </script>
847
  """
848
+ logger.info("Yielding lecture materials before audio generation")
849
  yield (
850
+ html_output,
851
  txt_file_paths,
852
+ 0,
853
+ audio_files,
854
+ zip_file_path
855
  )
856
 
857
+ # Now generate audio files progressively
 
858
  validated_speaker_wav = await validate_and_convert_speaker_audio(speaker_audio)
859
  if not validated_speaker_wav:
860
  logger.error("Invalid speaker audio after conversion, skipping TTS")
861
  yield (
862
+ f"""
863
+ <div style="display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%; min-height: 700px; padding: 20px; text-align: center; border: 1px solid #ddd; border-radius: 8px;">
864
+ <h2 style="color: #d9534f;">Invalid speaker audio</h2>
865
+ <p style="margin-top: 20px;">Please upload a valid MP3 or WAV audio file and try again.</p>
866
+ </div>
867
+ """,
868
+ [], 0, [], None
869
  )
870
  return
871
 
 
883
 
884
  if not cleaned_script:
885
  logger.error("Skipping audio for slide %d due to empty or invalid script", i + 1)
886
+ audio_files[i] = None
 
887
  progress = 90 + ((i + 1) / len(scripts)) * 10
888
  label = f"Generated audio for slide {i + 1}/{len(scripts)}..."
889
  yield (
890
+ html_output,
891
  txt_file_paths,
892
+ 0,
893
+ audio_files,
894
+ zip_file_path
895
  )
896
  await asyncio.sleep(0.1)
897
  continue
 
911
  raise RuntimeError("TTS generation failed")
912
 
913
  logger.info("Generated audio for slide %d: %s", i + 1, audio_file)
914
+ audio_files[i] = audio_file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
915
  progress = 90 + ((i + 1) / len(scripts)) * 10
916
  label = f"Generated audio for slide {i + 1}/{len(scripts)}..."
917
  yield (
918
+ html_output,
919
  txt_file_paths,
920
+ 0,
921
+ audio_files,
922
+ zip_file_path
923
  )
924
  await asyncio.sleep(0.1)
925
  break
 
927
  logger.error("Error generating audio for slide %d (attempt %d): %s\n%s", i + 1, attempt, str(e), traceback.format_exc())
928
  if attempt == max_audio_retries:
929
  logger.error("Max retries reached for slide %d, skipping", i + 1)
930
+ audio_files[i] = None
 
931
  progress = 90 + ((i + 1) / len(scripts)) * 10
932
  label = f"Generated audio for slide {i + 1}/{len(scripts)}..."
933
  yield (
934
+ html_output,
935
  txt_file_paths,
936
+ 0,
937
+ audio_files,
938
+ zip_file_path
939
  )
940
  await asyncio.sleep(0.1)
941
  break
 
952
  <p style="margin-top: 20px;">Please try again or adjust your inputs.</p>
953
  </div>
954
  """,
955
+ [], 0, [], None
 
 
956
  )
957
  return
958
 
 
987
  <p style="margin-top: 10px; font-size: 16px;">Please Generate lecture content via the form on the left first before lecture begins</p>
988
  </div>
989
  """
990
+ slide_display = gr.HTML(label="Lecture Slides", value=default_slide_html)
 
991
  file_output = gr.File(label="Download Generated Files")
992
+ audio_outputs = gr.Audio(label="Slide Audio", visible=False)
993
+ slide_index = gr.State(value=0)
994
+ zip_output = gr.File(label="Download All Files as ZIP")
995
 
996
  speaker_audio.change(
997
  fn=update_audio_preview,
 
999
  outputs=speaker_audio
1000
  )
1001
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1002
  generate_btn.click(
1003
  fn=on_generate,
1004
  inputs=[api_service, api_key, serpapi_key, title, lecture_content_description, lecture_type, speaker_audio, num_slides],
1005
+ outputs=[slide_display, file_output, slide_index, audio_outputs, zip_output]
1006
  )
1007
 
1008
  if __name__ == "__main__":
1009
+ demo.launch(allowed_paths=[OUTPUT_DIR], max_file_size="5mb")