Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -340,22 +340,28 @@ async def update_audio_preview(audio_file):
|
|
| 340 |
# Async function to generate lecture materials and audio
|
| 341 |
async def on_generate(api_service, api_key, serpapi_key, title, topic, instructions, lecture_type, speaker_audio, num_slides):
|
| 342 |
if not serpapi_key:
|
| 343 |
-
yield
|
| 344 |
-
|
| 345 |
-
<
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
| 349 |
return
|
| 350 |
|
| 351 |
if tts is None:
|
| 352 |
-
yield
|
| 353 |
-
|
| 354 |
-
<
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
|
|
|
|
|
|
|
|
|
| 359 |
return
|
| 360 |
|
| 361 |
model_client = get_model_client(api_service, api_key)
|
|
@@ -418,7 +424,10 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 418 |
|
| 419 |
progress = 0
|
| 420 |
label = "Research: in progress..."
|
| 421 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 422 |
await asyncio.sleep(0.1)
|
| 423 |
|
| 424 |
initial_message = f"""
|
|
@@ -458,7 +467,10 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 458 |
if source == "research_agent" and message.target == "slide_agent":
|
| 459 |
progress = 25
|
| 460 |
label = "Slides: generating..."
|
| 461 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 462 |
await asyncio.sleep(0.1)
|
| 463 |
elif source == "slide_agent" and message.target == "script_agent":
|
| 464 |
if slides is None:
|
|
@@ -480,7 +492,10 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 480 |
continue
|
| 481 |
progress = 50
|
| 482 |
label = "Scripts: generating..."
|
| 483 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 484 |
await asyncio.sleep(0.1)
|
| 485 |
elif source == "script_agent" and message.target == "feynman_agent":
|
| 486 |
if scripts is None:
|
|
@@ -491,14 +506,20 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 491 |
logger.info("Extracted scripts JSON from HandoffMessage context: %s", scripts)
|
| 492 |
progress = 75
|
| 493 |
label = "Review: in progress..."
|
| 494 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 495 |
await asyncio.sleep(0.1)
|
| 496 |
|
| 497 |
elif source == "research_agent" and isinstance(message, TextMessage) and "handoff_to_slide_agent" in message.content:
|
| 498 |
logger.info("Research Agent completed research")
|
| 499 |
progress = 25
|
| 500 |
label = "Slides: generating..."
|
| 501 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 502 |
await asyncio.sleep(0.1)
|
| 503 |
|
| 504 |
elif source == "slide_agent" and isinstance(message, (TextMessage, StructuredMessage)):
|
|
@@ -528,7 +549,10 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 528 |
logger.error("Error saving slide content to %s: %s", content_file, str(e))
|
| 529 |
progress = 50
|
| 530 |
label = "Scripts: generating..."
|
| 531 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 532 |
await asyncio.sleep(0.1)
|
| 533 |
else:
|
| 534 |
logger.warning("No JSON extracted from slide_agent message")
|
|
@@ -559,7 +583,10 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 559 |
logger.error("Error saving raw script to %s: %s", script_file, str(e))
|
| 560 |
progress = 75
|
| 561 |
label = "Scripts generated and saved. Reviewing..."
|
| 562 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 563 |
await asyncio.sleep(0.1)
|
| 564 |
else:
|
| 565 |
logger.warning("No JSON extracted from script_agent message")
|
|
@@ -578,7 +605,10 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 578 |
logger.info("Feynman Agent completed lecture review: %s", message.content)
|
| 579 |
progress = 90
|
| 580 |
label = "Lecture materials ready. Generating audio..."
|
| 581 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 582 |
await asyncio.sleep(0.1)
|
| 583 |
|
| 584 |
logger.info("Slides state: %s", "Generated" if slides else "None")
|
|
@@ -591,65 +621,83 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 591 |
for msg in task_result.messages:
|
| 592 |
source = getattr(msg, 'source', getattr(msg, 'sender', None))
|
| 593 |
logger.debug("Message from %s, type: %s, content: %s", source, type(msg), msg.to_text() if hasattr(msg, 'to_text') else str(msg))
|
| 594 |
-
yield
|
| 595 |
-
|
| 596 |
-
<
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
|
|
|
|
|
|
|
|
|
| 600 |
return
|
| 601 |
|
| 602 |
if len(slides) != total_slides:
|
| 603 |
logger.error("Expected %d slides, but received %d", total_slides, len(slides))
|
| 604 |
-
yield
|
| 605 |
-
|
| 606 |
-
<
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
|
|
|
|
|
|
|
|
|
| 610 |
return
|
| 611 |
|
| 612 |
if not isinstance(scripts, list) or not all(isinstance(s, str) for s in scripts):
|
| 613 |
logger.error("Scripts are not a list of strings: %s", scripts)
|
| 614 |
-
yield
|
| 615 |
-
|
| 616 |
-
<
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
|
|
|
|
|
|
|
|
|
| 620 |
return
|
| 621 |
|
| 622 |
if len(scripts) != total_slides:
|
| 623 |
logger.error("Mismatch between number of slides (%d) and scripts (%d)", len(slides), len(scripts))
|
| 624 |
-
yield
|
| 625 |
-
|
| 626 |
-
<
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
|
|
|
|
|
|
|
|
|
| 630 |
return
|
| 631 |
|
| 632 |
markdown_slides = generate_markdown_slides(slides, title)
|
| 633 |
if not markdown_slides:
|
| 634 |
logger.error("Failed to generate Markdown slides")
|
| 635 |
-
yield
|
| 636 |
-
|
| 637 |
-
<
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
|
|
|
|
|
|
|
|
|
| 641 |
return
|
| 642 |
|
| 643 |
audio_files = []
|
| 644 |
validated_speaker_wav = await validate_and_convert_speaker_audio(speaker_audio)
|
| 645 |
if not validated_speaker_wav:
|
| 646 |
logger.error("Invalid speaker audio after conversion, skipping TTS")
|
| 647 |
-
yield
|
| 648 |
-
|
| 649 |
-
<
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
|
|
|
|
|
|
|
|
|
| 653 |
return
|
| 654 |
|
| 655 |
for i, script in enumerate(scripts):
|
|
@@ -668,8 +716,11 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 668 |
logger.error("Skipping audio for slide %d due to empty or invalid script", i + 1)
|
| 669 |
audio_files.append(None)
|
| 670 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
| 671 |
-
label = f"
|
| 672 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 673 |
await asyncio.sleep(0.1)
|
| 674 |
continue
|
| 675 |
|
|
@@ -691,7 +742,10 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 691 |
audio_files.append(audio_file)
|
| 692 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
| 693 |
label = f"Generated audio for slide {i + 1}/{len(scripts)}..."
|
| 694 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 695 |
await asyncio.sleep(0.1)
|
| 696 |
break
|
| 697 |
except Exception as e:
|
|
@@ -701,25 +755,25 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 701 |
audio_files.append(None)
|
| 702 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
| 703 |
label = f"Generated audio for slide {i + 1}/{len(scripts)}..."
|
| 704 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 705 |
await asyncio.sleep(0.1)
|
| 706 |
break
|
| 707 |
|
| 708 |
# Collect .txt files for download
|
| 709 |
txt_files = [f for f in os.listdir(OUTPUT_DIR) if f.endswith('.txt')]
|
| 710 |
txt_files.sort() # Sort for consistent display
|
| 711 |
-
|
| 712 |
-
for txt_file in txt_files:
|
| 713 |
-
file_path = os.path.join(OUTPUT_DIR, txt_file)
|
| 714 |
-
txt_links += f'<a href="file/{file_path}" download>{txt_file}</a> '
|
| 715 |
|
| 716 |
-
# Generate audio timeline
|
| 717 |
audio_timeline = ""
|
| 718 |
for i, audio_file in enumerate(audio_files):
|
| 719 |
if audio_file:
|
| 720 |
-
audio_timeline += f'<
|
| 721 |
else:
|
| 722 |
-
audio_timeline += f'<span id="audio-{i+1}">slide_{i+1}.mp3</span>
|
| 723 |
|
| 724 |
slides_info = json.dumps({"slides": markdown_slides, "audioFiles": audio_files})
|
| 725 |
|
|
@@ -733,16 +787,12 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 733 |
{audio_timeline}
|
| 734 |
</div>
|
| 735 |
<div style="display: flex; justify-content: center; margin-bottom: 10px;">
|
| 736 |
-
<button
|
| 737 |
-
<button
|
| 738 |
-
<button
|
| 739 |
<button style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">☐</button>
|
| 740 |
</div>
|
| 741 |
</div>
|
| 742 |
-
<div style="padding: 10px; text-align: center;">
|
| 743 |
-
<h4>Download Generated Files:</h4>
|
| 744 |
-
{txt_links}
|
| 745 |
-
</div>
|
| 746 |
</div>
|
| 747 |
<script>
|
| 748 |
const lectureData = {slides_info};
|
|
@@ -752,12 +802,8 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 752 |
let isPlayingAll = false;
|
| 753 |
|
| 754 |
for (let i = 0; i < totalSlides; i++) {{
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
audioElements.push(audio);
|
| 758 |
-
}} else {{
|
| 759 |
-
audioElements.push(null);
|
| 760 |
-
}}
|
| 761 |
}}
|
| 762 |
|
| 763 |
function renderSlide() {{
|
|
@@ -768,7 +814,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 768 |
function updateSlide() {{
|
| 769 |
renderSlide();
|
| 770 |
audioElements.forEach(audio => {{
|
| 771 |
-
if (audio) {{
|
| 772 |
audio.pause();
|
| 773 |
audio.currentTime = 0;
|
| 774 |
}}
|
|
@@ -792,13 +838,15 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 792 |
function playAll() {{
|
| 793 |
if (isPlayingAll) {{
|
| 794 |
audioElements.forEach(audio => {{
|
| 795 |
-
if (audio) audio.pause();
|
| 796 |
}});
|
| 797 |
isPlayingAll = false;
|
|
|
|
| 798 |
return;
|
| 799 |
}}
|
| 800 |
|
| 801 |
isPlayingAll = true;
|
|
|
|
| 802 |
currentSlide = 0;
|
| 803 |
updateSlide();
|
| 804 |
playCurrentSlide();
|
|
@@ -807,11 +855,12 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 807 |
function playCurrentSlide() {{
|
| 808 |
if (!isPlayingAll || currentSlide >= totalSlides) {{
|
| 809 |
isPlayingAll = false;
|
|
|
|
| 810 |
return;
|
| 811 |
}}
|
| 812 |
|
| 813 |
const audio = audioElements[currentSlide];
|
| 814 |
-
if (audio) {{
|
| 815 |
audio.play().then(() => {{
|
| 816 |
audio.addEventListener('ended', () => {{
|
| 817 |
currentSlide++;
|
|
@@ -820,6 +869,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 820 |
playCurrentSlide();
|
| 821 |
}} else {{
|
| 822 |
isPlayingAll = false;
|
|
|
|
| 823 |
}}
|
| 824 |
}}, {{ once: true }});
|
| 825 |
}}).catch(e => {{
|
|
@@ -833,22 +883,33 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
| 833 |
}}
|
| 834 |
}}
|
| 835 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 836 |
// Initialize first slide
|
| 837 |
renderSlide();
|
| 838 |
</script>
|
| 839 |
"""
|
| 840 |
logger.info("Lecture generation completed successfully")
|
| 841 |
-
yield
|
|
|
|
|
|
|
|
|
|
| 842 |
|
| 843 |
except Exception as e:
|
| 844 |
logger.error("Error during lecture generation: %s\n%s", str(e), traceback.format_exc())
|
| 845 |
-
yield
|
| 846 |
-
|
| 847 |
-
<
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
|
|
|
|
|
|
|
|
|
| 852 |
return
|
| 853 |
|
| 854 |
# Gradio interface
|
|
@@ -884,6 +945,7 @@ with gr.Blocks(title="Agent Feynman") as demo:
|
|
| 884 |
</div>
|
| 885 |
"""
|
| 886 |
slide_display = gr.HTML(label="Lecture Slides", value=default_slide_html)
|
|
|
|
| 887 |
|
| 888 |
speaker_audio.change(
|
| 889 |
fn=update_audio_preview,
|
|
@@ -894,7 +956,7 @@ with gr.Blocks(title="Agent Feynman") as demo:
|
|
| 894 |
generate_btn.click(
|
| 895 |
fn=on_generate,
|
| 896 |
inputs=[api_service, api_key, serpapi_key, title, topic, instructions, lecture_type, speaker_audio, num_slides],
|
| 897 |
-
outputs=[slide_display]
|
| 898 |
)
|
| 899 |
|
| 900 |
if __name__ == "__main__":
|
|
|
|
| 340 |
# Async function to generate lecture materials and audio
|
| 341 |
async def on_generate(api_service, api_key, serpapi_key, title, topic, instructions, lecture_type, speaker_audio, num_slides):
|
| 342 |
if not serpapi_key:
|
| 343 |
+
yield {
|
| 344 |
+
"html": f"""
|
| 345 |
+
<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;">
|
| 346 |
+
<h2 style="color: #d9534f;">SerpApi key required</h2>
|
| 347 |
+
<p style="margin-top: 20px;">Please provide a valid SerpApi key and try again.</p>
|
| 348 |
+
</div>
|
| 349 |
+
""",
|
| 350 |
+
"files": []
|
| 351 |
+
}
|
| 352 |
return
|
| 353 |
|
| 354 |
if tts is None:
|
| 355 |
+
yield {
|
| 356 |
+
"html": f"""
|
| 357 |
+
<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;">
|
| 358 |
+
<h2 style="color: #d9534f;">TTS model initialization failed</h2>
|
| 359 |
+
<p style="margin-top: 20px;">The TTS model failed to initialize at startup.</p>
|
| 360 |
+
<p>Please ensure the Coqui TTS model is properly installed and try restarting the application.</p>
|
| 361 |
+
</div>
|
| 362 |
+
""",
|
| 363 |
+
"files": []
|
| 364 |
+
}
|
| 365 |
return
|
| 366 |
|
| 367 |
model_client = get_model_client(api_service, api_key)
|
|
|
|
| 424 |
|
| 425 |
progress = 0
|
| 426 |
label = "Research: in progress..."
|
| 427 |
+
yield {
|
| 428 |
+
"html": html_with_progress(label, progress),
|
| 429 |
+
"files": []
|
| 430 |
+
}
|
| 431 |
await asyncio.sleep(0.1)
|
| 432 |
|
| 433 |
initial_message = f"""
|
|
|
|
| 467 |
if source == "research_agent" and message.target == "slide_agent":
|
| 468 |
progress = 25
|
| 469 |
label = "Slides: generating..."
|
| 470 |
+
yield {
|
| 471 |
+
"html": html_with_progress(label, progress),
|
| 472 |
+
"files": []
|
| 473 |
+
}
|
| 474 |
await asyncio.sleep(0.1)
|
| 475 |
elif source == "slide_agent" and message.target == "script_agent":
|
| 476 |
if slides is None:
|
|
|
|
| 492 |
continue
|
| 493 |
progress = 50
|
| 494 |
label = "Scripts: generating..."
|
| 495 |
+
yield {
|
| 496 |
+
"html": html_with_progress(label, progress),
|
| 497 |
+
"files": []
|
| 498 |
+
}
|
| 499 |
await asyncio.sleep(0.1)
|
| 500 |
elif source == "script_agent" and message.target == "feynman_agent":
|
| 501 |
if scripts is None:
|
|
|
|
| 506 |
logger.info("Extracted scripts JSON from HandoffMessage context: %s", scripts)
|
| 507 |
progress = 75
|
| 508 |
label = "Review: in progress..."
|
| 509 |
+
yield {
|
| 510 |
+
"html": html_with_progress(label, progress),
|
| 511 |
+
"files": []
|
| 512 |
+
}
|
| 513 |
await asyncio.sleep(0.1)
|
| 514 |
|
| 515 |
elif source == "research_agent" and isinstance(message, TextMessage) and "handoff_to_slide_agent" in message.content:
|
| 516 |
logger.info("Research Agent completed research")
|
| 517 |
progress = 25
|
| 518 |
label = "Slides: generating..."
|
| 519 |
+
yield {
|
| 520 |
+
"html": html_with_progress(label, progress),
|
| 521 |
+
"files": []
|
| 522 |
+
}
|
| 523 |
await asyncio.sleep(0.1)
|
| 524 |
|
| 525 |
elif source == "slide_agent" and isinstance(message, (TextMessage, StructuredMessage)):
|
|
|
|
| 549 |
logger.error("Error saving slide content to %s: %s", content_file, str(e))
|
| 550 |
progress = 50
|
| 551 |
label = "Scripts: generating..."
|
| 552 |
+
yield {
|
| 553 |
+
"html": html_with_progress(label, progress),
|
| 554 |
+
"files": []
|
| 555 |
+
}
|
| 556 |
await asyncio.sleep(0.1)
|
| 557 |
else:
|
| 558 |
logger.warning("No JSON extracted from slide_agent message")
|
|
|
|
| 583 |
logger.error("Error saving raw script to %s: %s", script_file, str(e))
|
| 584 |
progress = 75
|
| 585 |
label = "Scripts generated and saved. Reviewing..."
|
| 586 |
+
yield {
|
| 587 |
+
"html": html_with_progress(label, progress),
|
| 588 |
+
"files": []
|
| 589 |
+
}
|
| 590 |
await asyncio.sleep(0.1)
|
| 591 |
else:
|
| 592 |
logger.warning("No JSON extracted from script_agent message")
|
|
|
|
| 605 |
logger.info("Feynman Agent completed lecture review: %s", message.content)
|
| 606 |
progress = 90
|
| 607 |
label = "Lecture materials ready. Generating audio..."
|
| 608 |
+
yield {
|
| 609 |
+
"html": html_with_progress(label, progress),
|
| 610 |
+
"files": []
|
| 611 |
+
}
|
| 612 |
await asyncio.sleep(0.1)
|
| 613 |
|
| 614 |
logger.info("Slides state: %s", "Generated" if slides else "None")
|
|
|
|
| 621 |
for msg in task_result.messages:
|
| 622 |
source = getattr(msg, 'source', getattr(msg, 'sender', None))
|
| 623 |
logger.debug("Message from %s, type: %s, content: %s", source, type(msg), msg.to_text() if hasattr(msg, 'to_text') else str(msg))
|
| 624 |
+
yield {
|
| 625 |
+
"html": f"""
|
| 626 |
+
<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;">
|
| 627 |
+
<h2 style="color: #d9534f;">{error_message}</h2>
|
| 628 |
+
<p style="margin-top: 20px;">Please try again with a different model (e.g., Anthropic-claude-3-sonnet-20240229) or simplify the topic/instructions.</p>
|
| 629 |
+
</div>
|
| 630 |
+
""",
|
| 631 |
+
"files": []
|
| 632 |
+
}
|
| 633 |
return
|
| 634 |
|
| 635 |
if len(slides) != total_slides:
|
| 636 |
logger.error("Expected %d slides, but received %d", total_slides, len(slides))
|
| 637 |
+
yield {
|
| 638 |
+
"html": f"""
|
| 639 |
+
<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;">
|
| 640 |
+
<h2 style="color: #d9534f;">Incorrect number of slides</h2>
|
| 641 |
+
<p style="margin-top: 20px;">Expected {total_slides} slides, but generated {len(slides)}. Please try again.</p>
|
| 642 |
+
</div>
|
| 643 |
+
""",
|
| 644 |
+
"files": []
|
| 645 |
+
}
|
| 646 |
return
|
| 647 |
|
| 648 |
if not isinstance(scripts, list) or not all(isinstance(s, str) for s in scripts):
|
| 649 |
logger.error("Scripts are not a list of strings: %s", scripts)
|
| 650 |
+
yield {
|
| 651 |
+
"html": f"""
|
| 652 |
+
<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;">
|
| 653 |
+
<h2 style="color: #d9534f;">Invalid script format</h2>
|
| 654 |
+
<p style="margin-top: 20px;">Scripts must be a list of strings. Please try again.</p>
|
| 655 |
+
</div>
|
| 656 |
+
""",
|
| 657 |
+
"files": []
|
| 658 |
+
}
|
| 659 |
return
|
| 660 |
|
| 661 |
if len(scripts) != total_slides:
|
| 662 |
logger.error("Mismatch between number of slides (%d) and scripts (%d)", len(slides), len(scripts))
|
| 663 |
+
yield {
|
| 664 |
+
"html": f"""
|
| 665 |
+
<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;">
|
| 666 |
+
<h2 style="color: #d9534f;">Mismatch in slides and scripts</h2>
|
| 667 |
+
<p style="margin-top: 20px;">Generated {len(slides)} slides but {len(scripts)} scripts. Please try again.</p>
|
| 668 |
+
</div>
|
| 669 |
+
""",
|
| 670 |
+
"files": []
|
| 671 |
+
}
|
| 672 |
return
|
| 673 |
|
| 674 |
markdown_slides = generate_markdown_slides(slides, title)
|
| 675 |
if not markdown_slides:
|
| 676 |
logger.error("Failed to generate Markdown slides")
|
| 677 |
+
yield {
|
| 678 |
+
"html": f"""
|
| 679 |
+
<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;">
|
| 680 |
+
<h2 style="color: #d9534f;">Failed to generate slides</h2>
|
| 681 |
+
<p style="margin-top: 20px;">Please try again.</p>
|
| 682 |
+
</div>
|
| 683 |
+
""",
|
| 684 |
+
"files": []
|
| 685 |
+
}
|
| 686 |
return
|
| 687 |
|
| 688 |
audio_files = []
|
| 689 |
validated_speaker_wav = await validate_and_convert_speaker_audio(speaker_audio)
|
| 690 |
if not validated_speaker_wav:
|
| 691 |
logger.error("Invalid speaker audio after conversion, skipping TTS")
|
| 692 |
+
yield {
|
| 693 |
+
"html": f"""
|
| 694 |
+
<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;">
|
| 695 |
+
<h2 style="color: #d9534f;">Invalid speaker audio</h2>
|
| 696 |
+
<p style="margin-top: 20px;">Please upload a valid MP3 or WAV audio file and try again.</p>
|
| 697 |
+
</div>
|
| 698 |
+
""",
|
| 699 |
+
"files": []
|
| 700 |
+
}
|
| 701 |
return
|
| 702 |
|
| 703 |
for i, script in enumerate(scripts):
|
|
|
|
| 716 |
logger.error("Skipping audio for slide %d due to empty or invalid script", i + 1)
|
| 717 |
audio_files.append(None)
|
| 718 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
| 719 |
+
label = f"Generating speech for slide {i + 1}/{len(scripts)}..."
|
| 720 |
+
yield {
|
| 721 |
+
"html": html_with_progress(label, progress),
|
| 722 |
+
"files": []
|
| 723 |
+
}
|
| 724 |
await asyncio.sleep(0.1)
|
| 725 |
continue
|
| 726 |
|
|
|
|
| 742 |
audio_files.append(audio_file)
|
| 743 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
| 744 |
label = f"Generated audio for slide {i + 1}/{len(scripts)}..."
|
| 745 |
+
yield {
|
| 746 |
+
"html": html_with_progress(label, progress),
|
| 747 |
+
"files": []
|
| 748 |
+
}
|
| 749 |
await asyncio.sleep(0.1)
|
| 750 |
break
|
| 751 |
except Exception as e:
|
|
|
|
| 755 |
audio_files.append(None)
|
| 756 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
| 757 |
label = f"Generated audio for slide {i + 1}/{len(scripts)}..."
|
| 758 |
+
yield {
|
| 759 |
+
"html": html_with_progress(label, progress),
|
| 760 |
+
"files": []
|
| 761 |
+
}
|
| 762 |
await asyncio.sleep(0.1)
|
| 763 |
break
|
| 764 |
|
| 765 |
# Collect .txt files for download
|
| 766 |
txt_files = [f for f in os.listdir(OUTPUT_DIR) if f.endswith('.txt')]
|
| 767 |
txt_files.sort() # Sort for consistent display
|
| 768 |
+
txt_file_paths = [os.path.join(OUTPUT_DIR, f) for f in txt_files]
|
|
|
|
|
|
|
|
|
|
| 769 |
|
| 770 |
+
# Generate audio timeline with playable audio elements
|
| 771 |
audio_timeline = ""
|
| 772 |
for i, audio_file in enumerate(audio_files):
|
| 773 |
if audio_file:
|
| 774 |
+
audio_timeline += f'<audio id="audio-{i+1}" controls src="{audio_file}" style="margin: 0 5px;"></audio>'
|
| 775 |
else:
|
| 776 |
+
audio_timeline += f'<span id="audio-{i+1}" style="margin: 0 5px;">slide_{i+1}.mp3 (not generated)</span>'
|
| 777 |
|
| 778 |
slides_info = json.dumps({"slides": markdown_slides, "audioFiles": audio_files})
|
| 779 |
|
|
|
|
| 787 |
{audio_timeline}
|
| 788 |
</div>
|
| 789 |
<div style="display: flex; justify-content: center; margin-bottom: 10px;">
|
| 790 |
+
<button id="prev-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏮</button>
|
| 791 |
+
<button id="play-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏯</button>
|
| 792 |
+
<button id="next-btn" style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">⏭</button>
|
| 793 |
<button style="border-radius: 50%; width: 40px; height: 40px; margin: 0 5px; font-size: 1.2em; cursor: pointer;">☐</button>
|
| 794 |
</div>
|
| 795 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 796 |
</div>
|
| 797 |
<script>
|
| 798 |
const lectureData = {slides_info};
|
|
|
|
| 802 |
let isPlayingAll = false;
|
| 803 |
|
| 804 |
for (let i = 0; i < totalSlides; i++) {{
|
| 805 |
+
const audio = document.getElementById(`audio-${{i+1}}`);
|
| 806 |
+
audioElements.push(audio);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 807 |
}}
|
| 808 |
|
| 809 |
function renderSlide() {{
|
|
|
|
| 814 |
function updateSlide() {{
|
| 815 |
renderSlide();
|
| 816 |
audioElements.forEach(audio => {{
|
| 817 |
+
if (audio && audio.pause) {{
|
| 818 |
audio.pause();
|
| 819 |
audio.currentTime = 0;
|
| 820 |
}}
|
|
|
|
| 838 |
function playAll() {{
|
| 839 |
if (isPlayingAll) {{
|
| 840 |
audioElements.forEach(audio => {{
|
| 841 |
+
if (audio && audio.pause) audio.pause();
|
| 842 |
}});
|
| 843 |
isPlayingAll = false;
|
| 844 |
+
document.getElementById('play-btn').innerText = '⏯';
|
| 845 |
return;
|
| 846 |
}}
|
| 847 |
|
| 848 |
isPlayingAll = true;
|
| 849 |
+
document.getElementById('play-btn').innerText = '⏸';
|
| 850 |
currentSlide = 0;
|
| 851 |
updateSlide();
|
| 852 |
playCurrentSlide();
|
|
|
|
| 855 |
function playCurrentSlide() {{
|
| 856 |
if (!isPlayingAll || currentSlide >= totalSlides) {{
|
| 857 |
isPlayingAll = false;
|
| 858 |
+
document.getElementById('play-btn').innerText = '⏯';
|
| 859 |
return;
|
| 860 |
}}
|
| 861 |
|
| 862 |
const audio = audioElements[currentSlide];
|
| 863 |
+
if (audio && audio.play) {{
|
| 864 |
audio.play().then(() => {{
|
| 865 |
audio.addEventListener('ended', () => {{
|
| 866 |
currentSlide++;
|
|
|
|
| 869 |
playCurrentSlide();
|
| 870 |
}} else {{
|
| 871 |
isPlayingAll = false;
|
| 872 |
+
document.getElementById('play-btn').innerText = '⏯';
|
| 873 |
}}
|
| 874 |
}}, {{ once: true }});
|
| 875 |
}}).catch(e => {{
|
|
|
|
| 883 |
}}
|
| 884 |
}}
|
| 885 |
|
| 886 |
+
// Attach event listeners
|
| 887 |
+
document.getElementById('prev-btn').addEventListener('click', prevSlide);
|
| 888 |
+
document.getElementById('play-btn').addEventListener('click', playAll);
|
| 889 |
+
document.getElementById('next-btn').addEventListener('click', nextSlide);
|
| 890 |
+
|
| 891 |
// Initialize first slide
|
| 892 |
renderSlide();
|
| 893 |
</script>
|
| 894 |
"""
|
| 895 |
logger.info("Lecture generation completed successfully")
|
| 896 |
+
yield {
|
| 897 |
+
"html": html_output,
|
| 898 |
+
"files": txt_file_paths
|
| 899 |
+
}
|
| 900 |
|
| 901 |
except Exception as e:
|
| 902 |
logger.error("Error during lecture generation: %s\n%s", str(e), traceback.format_exc())
|
| 903 |
+
yield {
|
| 904 |
+
"html": f"""
|
| 905 |
+
<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;">
|
| 906 |
+
<h2 style="color: #d9534f;">Error during lecture generation</h2>
|
| 907 |
+
<p style="margin-top: 10px; font-size: 16px;">{str(e)}</p>
|
| 908 |
+
<p style="margin-top: 20px;">Please try again or adjust your inputs.</p>
|
| 909 |
+
</div>
|
| 910 |
+
""",
|
| 911 |
+
"files": []
|
| 912 |
+
}
|
| 913 |
return
|
| 914 |
|
| 915 |
# Gradio interface
|
|
|
|
| 945 |
</div>
|
| 946 |
"""
|
| 947 |
slide_display = gr.HTML(label="Lecture Slides", value=default_slide_html)
|
| 948 |
+
file_output = gr.File(label="Download Generated Files")
|
| 949 |
|
| 950 |
speaker_audio.change(
|
| 951 |
fn=update_audio_preview,
|
|
|
|
| 956 |
generate_btn.click(
|
| 957 |
fn=on_generate,
|
| 958 |
inputs=[api_service, api_key, serpapi_key, title, topic, instructions, lecture_type, speaker_audio, num_slides],
|
| 959 |
+
outputs=[slide_display, file_output]
|
| 960 |
)
|
| 961 |
|
| 962 |
if __name__ == "__main__":
|