Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -72,11 +72,11 @@ def search_web(query: str, serpapi_key: str) -> str:
|
|
72 |
|
73 |
if "error" in results:
|
74 |
logger.error("SerpApi error: %s", results["error"])
|
75 |
-
return
|
76 |
|
77 |
if "organic_results" not in results or not results["organic_results"]:
|
78 |
logger.info("No search results found for query: %s", query)
|
79 |
-
return
|
80 |
|
81 |
formatted_results = []
|
82 |
for item in results["organic_results"][:5]:
|
@@ -87,11 +87,11 @@ def search_web(query: str, serpapi_key: str) -> str:
|
|
87 |
|
88 |
formatted_output = "\n".join(formatted_results)
|
89 |
logger.info("Successfully retrieved search results for query: %s", query)
|
90 |
-
return
|
91 |
|
92 |
except Exception as e:
|
93 |
logger.error("Unexpected error during search: %s", str(e))
|
94 |
-
return
|
95 |
|
96 |
# Define helper function for progress HTML
|
97 |
def html_with_progress(label, progress):
|
@@ -338,32 +338,7 @@ async def update_audio_preview(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,
|
342 |
-
if not serpapi_key:
|
343 |
-
yield (
|
344 |
-
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 |
-
[]
|
351 |
-
)
|
352 |
-
return
|
353 |
-
|
354 |
-
if tts is None:
|
355 |
-
yield (
|
356 |
-
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 |
-
[]
|
364 |
-
)
|
365 |
-
return
|
366 |
-
|
367 |
model_client = get_model_client(api_service, api_key)
|
368 |
|
369 |
total_slides = num_slides # Use exactly the number of slides from input
|
@@ -433,10 +408,9 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
433 |
initial_message = f"""
|
434 |
Lecture Title: {title}
|
435 |
Lecture Content Description: {lecture_content_description}
|
436 |
-
Additional Instructions: {instructions}
|
437 |
Audience: {lecture_type}
|
438 |
Number of Slides: {total_slides}
|
439 |
-
Please start by researching the topic.
|
440 |
"""
|
441 |
logger.info("Starting lecture generation for title: %s with %d slides", title, total_slides)
|
442 |
|
@@ -451,7 +425,11 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
451 |
|
452 |
try:
|
453 |
logger.info("Research Agent starting...")
|
454 |
-
|
|
|
|
|
|
|
|
|
455 |
logger.info("Swarm execution completed")
|
456 |
|
457 |
slide_retry_count = 0
|
@@ -574,13 +552,13 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
574 |
scripts = extracted_json
|
575 |
logger.info("Script Agent generated scripts for %d slides", len(scripts))
|
576 |
for i, script in enumerate(scripts):
|
577 |
-
script_file = os.path.join(OUTPUT_DIR, f"slide_{i+1}
|
578 |
try:
|
579 |
with open(script_file, "w", encoding="utf-8") as f:
|
580 |
f.write(script)
|
581 |
-
logger.info("Saved
|
582 |
except Exception as e:
|
583 |
-
logger.error("Error saving
|
584 |
progress = 75
|
585 |
label = "Scripts generated and saved. Reviewing..."
|
586 |
yield (
|
@@ -625,7 +603,7 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
625 |
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
|
629 |
</div>
|
630 |
""",
|
631 |
[]
|
@@ -708,15 +686,15 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
708 |
try:
|
709 |
with open(script_file, "w", encoding="utf-8") as f:
|
710 |
f.write(cleaned_script or "")
|
711 |
-
logger.info("Saved
|
712 |
except Exception as e:
|
713 |
-
logger.error("Error saving
|
714 |
|
715 |
if not cleaned_script:
|
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"
|
720 |
yield (
|
721 |
html_with_progress(label, progress),
|
722 |
[]
|
@@ -770,10 +748,10 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
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
|
775 |
else:
|
776 |
-
audio_timeline += f'<span id="audio-{i+1}" style="margin: 0
|
777 |
|
778 |
slides_info = json.dumps({"slides": markdown_slides, "audioFiles": audio_files})
|
779 |
|
@@ -782,8 +760,8 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
782 |
<div id="slide-content" style="flex: 1; overflow: auto; padding: 20px; text-align: center;">
|
783 |
<!-- Slides will be rendered here -->
|
784 |
</div>
|
785 |
-
<div style="padding: 20px;">
|
786 |
-
<div style="
|
787 |
{audio_timeline}
|
788 |
</div>
|
789 |
<div style="display: flex; justify-content: center; margin-bottom: 10px;">
|
@@ -799,16 +777,19 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
799 |
let currentSlide = 0;
|
800 |
const totalSlides = lectureData.slides.length;
|
801 |
let audioElements = [];
|
802 |
-
let isPlayingAll = false;
|
803 |
|
804 |
for (let i = 0; i < totalSlides; i++) {{
|
805 |
-
const audio = document.getElementById(`audio-${
|
806 |
audioElements.push(audio);
|
807 |
}}
|
808 |
|
809 |
function renderSlide() {{
|
810 |
const slideContent = document.getElementById('slide-content');
|
811 |
-
|
|
|
|
|
|
|
|
|
812 |
}}
|
813 |
|
814 |
function updateSlide() {{
|
@@ -836,51 +817,27 @@ Example: 'Received {total_slides} slides and {total_slides} scripts. Lecture is
|
|
836 |
}}
|
837 |
|
838 |
function playAll() {{
|
839 |
-
|
840 |
-
|
841 |
-
|
842 |
-
|
843 |
-
|
844 |
-
|
845 |
-
|
846 |
-
|
847 |
-
|
848 |
-
|
849 |
-
|
850 |
-
|
851 |
-
|
852 |
-
|
853 |
-
|
854 |
-
|
855 |
-
|
856 |
-
|
857 |
-
|
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++;
|
867 |
-
if (currentSlide < totalSlides) {{
|
868 |
-
updateSlide();
|
869 |
-
playCurrentSlide();
|
870 |
-
}} else {{
|
871 |
-
isPlayingAll = false;
|
872 |
-
document.getElementById('play-btn').innerText = '⏯';
|
873 |
-
}}
|
874 |
-
}}, {{ once: true }});
|
875 |
-
}}).catch(e => {{
|
876 |
-
console.error('Audio play failed:', e);
|
877 |
-
currentSlide++;
|
878 |
-
playCurrentSlide();
|
879 |
-
}});
|
880 |
-
}} else {{
|
881 |
-
currentSlide++;
|
882 |
-
playCurrentSlide();
|
883 |
}}
|
|
|
884 |
}}
|
885 |
|
886 |
// Attach event listeners
|
@@ -920,7 +877,6 @@ with gr.Blocks(title="Agent Feynman") as demo:
|
|
920 |
with gr.Group():
|
921 |
title = gr.Textbox(label="Lecture Title", placeholder="e.g. Introduction to AI")
|
922 |
lecture_content_description = gr.Textbox(label="Lecture Content Description", placeholder="e.g. Focus on recent advancements")
|
923 |
-
instructions = gr.Textbox(label="Additional Instructions", placeholder="e.g. Include examples")
|
924 |
lecture_type = gr.Dropdown(["Conference", "University", "High school"], label="Audience", value="University")
|
925 |
api_service = gr.Dropdown(
|
926 |
choices=[
|
@@ -933,7 +889,7 @@ with gr.Blocks(title="Agent Feynman") as demo:
|
|
933 |
value="Google-gemini-1.5-flash"
|
934 |
)
|
935 |
api_key = gr.Textbox(label="Model Provider API Key", type="password", placeholder="Not required for Ollama")
|
936 |
-
serpapi_key = gr.Textbox(label="SerpApi Key", type="password", placeholder="Enter your SerpApi key")
|
937 |
num_slides = gr.Slider(1, 20, step=1, label="Number of Slides", value=3)
|
938 |
speaker_audio = gr.Audio(label="Speaker sample audio (MP3 or WAV)", type="filepath", elem_id="speaker-audio")
|
939 |
generate_btn = gr.Button("Generate Lecture")
|
@@ -955,7 +911,7 @@ with gr.Blocks(title="Agent Feynman") as demo:
|
|
955 |
|
956 |
generate_btn.click(
|
957 |
fn=on_generate,
|
958 |
-
inputs=[api_service, api_key, serpapi_key, title, lecture_content_description,
|
959 |
outputs=[slide_display, file_output]
|
960 |
)
|
961 |
|
|
|
72 |
|
73 |
if "error" in results:
|
74 |
logger.error("SerpApi error: %s", results["error"])
|
75 |
+
return None
|
76 |
|
77 |
if "organic_results" not in results or not results["organic_results"]:
|
78 |
logger.info("No search results found for query: %s", query)
|
79 |
+
return None
|
80 |
|
81 |
formatted_results = []
|
82 |
for item in results["organic_results"][:5]:
|
|
|
87 |
|
88 |
formatted_output = "\n".join(formatted_results)
|
89 |
logger.info("Successfully retrieved search results for query: %s", query)
|
90 |
+
return formatted_output
|
91 |
|
92 |
except Exception as e:
|
93 |
logger.error("Unexpected error during search: %s", str(e))
|
94 |
+
return None
|
95 |
|
96 |
# Define helper function for progress HTML
|
97 |
def html_with_progress(label, progress):
|
|
|
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)
|
343 |
|
344 |
total_slides = num_slides # Use exactly the number of slides from input
|
|
|
408 |
initial_message = f"""
|
409 |
Lecture Title: {title}
|
410 |
Lecture Content Description: {lecture_content_description}
|
|
|
411 |
Audience: {lecture_type}
|
412 |
Number of Slides: {total_slides}
|
413 |
+
Please start by researching the topic, or proceed without research if search is unavailable.
|
414 |
"""
|
415 |
logger.info("Starting lecture generation for title: %s with %d slides", title, total_slides)
|
416 |
|
|
|
425 |
|
426 |
try:
|
427 |
logger.info("Research Agent starting...")
|
428 |
+
if serpapi_key:
|
429 |
+
task_result = await Console(swarm.run_stream(task=initial_message))
|
430 |
+
else:
|
431 |
+
logger.warning("No SerpApi key provided, bypassing research phase")
|
432 |
+
task_result = await Console(swarm.run_stream(task=f"{initial_message}\nNo search available, proceed with slide generation."))
|
433 |
logger.info("Swarm execution completed")
|
434 |
|
435 |
slide_retry_count = 0
|
|
|
552 |
scripts = extracted_json
|
553 |
logger.info("Script Agent generated scripts for %d slides", len(scripts))
|
554 |
for i, script in enumerate(scripts):
|
555 |
+
script_file = os.path.join(OUTPUT_DIR, f"slide_{i+1}_script.txt")
|
556 |
try:
|
557 |
with open(script_file, "w", encoding="utf-8") as f:
|
558 |
f.write(script)
|
559 |
+
logger.info("Saved script to %s", script_file)
|
560 |
except Exception as e:
|
561 |
+
logger.error("Error saving script to %s: %s", script_file, str(e))
|
562 |
progress = 75
|
563 |
label = "Scripts generated and saved. Reviewing..."
|
564 |
yield (
|
|
|
603 |
f"""
|
604 |
<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;">
|
605 |
<h2 style="color: #d9534f;">{error_message}</h2>
|
606 |
+
<p style="margin-top: 20px;">Please try again with a different model or adjust your inputs.</p>
|
607 |
</div>
|
608 |
""",
|
609 |
[]
|
|
|
686 |
try:
|
687 |
with open(script_file, "w", encoding="utf-8") as f:
|
688 |
f.write(cleaned_script or "")
|
689 |
+
logger.info("Saved script to %s: %s", script_file, cleaned_script)
|
690 |
except Exception as e:
|
691 |
+
logger.error("Error saving script to %s: %s", script_file, str(e))
|
692 |
|
693 |
if not cleaned_script:
|
694 |
logger.error("Skipping audio for slide %d due to empty or invalid script", i + 1)
|
695 |
audio_files.append(None)
|
696 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
697 |
+
label = f"Generated speech for slide {i + 1}/{len(scripts)}..."
|
698 |
yield (
|
699 |
html_with_progress(label, progress),
|
700 |
[]
|
|
|
748 |
# Generate audio timeline with playable audio elements
|
749 |
audio_timeline = ""
|
750 |
for i, audio_file in enumerate(audio_files):
|
751 |
+
if audio_file and os.path.exists(audio_file):
|
752 |
+
audio_timeline += f'<audio id="audio-{i+1}" controls src="file://{audio_file}" style="display: inline-block; margin: 0 10px; width: 200px;"></audio>'
|
753 |
else:
|
754 |
+
audio_timeline += f'<span id="audio-{i+1}" style="display: inline-block; margin: 0 10px;">slide_{i+1}.mp3 (not generated)</span>'
|
755 |
|
756 |
slides_info = json.dumps({"slides": markdown_slides, "audioFiles": audio_files})
|
757 |
|
|
|
760 |
<div id="slide-content" style="flex: 1; overflow: auto; padding: 20px; text-align: center;">
|
761 |
<!-- Slides will be rendered here -->
|
762 |
</div>
|
763 |
+
<div style="padding: 20px; text-align: center;">
|
764 |
+
<div style="display: flex; justify-content: center; margin-bottom: 10px;">
|
765 |
{audio_timeline}
|
766 |
</div>
|
767 |
<div style="display: flex; justify-content: center; margin-bottom: 10px;">
|
|
|
777 |
let currentSlide = 0;
|
778 |
const totalSlides = lectureData.slides.length;
|
779 |
let audioElements = [];
|
|
|
780 |
|
781 |
for (let i = 0; i < totalSlides; i++) {{
|
782 |
+
const audio = document.getElementById(`audio-${i+1}`);
|
783 |
audioElements.push(audio);
|
784 |
}}
|
785 |
|
786 |
function renderSlide() {{
|
787 |
const slideContent = document.getElementById('slide-content');
|
788 |
+
if (lectureData.slides[currentSlide]) {{
|
789 |
+
slideContent.innerHTML = lectureData.slides[currentSlide];
|
790 |
+
}} else {{
|
791 |
+
slideContent.innerHTML = '<h2>No slide content available</h2>';
|
792 |
+
}}
|
793 |
}}
|
794 |
|
795 |
function updateSlide() {{
|
|
|
817 |
}}
|
818 |
|
819 |
function playAll() {{
|
820 |
+
let index = 0;
|
821 |
+
function playNext() {{
|
822 |
+
if (index >= totalSlides) return;
|
823 |
+
const audio = audioElements[index];
|
824 |
+
if (audio && audio.play) {{
|
825 |
+
audio.play().then(() => {{
|
826 |
+
audio.addEventListener('ended', () => {{
|
827 |
+
index++;
|
828 |
+
playNext();
|
829 |
+
}}, {{ once: true }});
|
830 |
+
}}).catch(e => {{
|
831 |
+
console.error('Audio play failed:', e);
|
832 |
+
index++;
|
833 |
+
playNext();
|
834 |
+
}});
|
835 |
+
}} else {{
|
836 |
+
index++;
|
837 |
+
playNext();
|
838 |
+
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
839 |
}}
|
840 |
+
playNext();
|
841 |
}}
|
842 |
|
843 |
// Attach event listeners
|
|
|
877 |
with gr.Group():
|
878 |
title = gr.Textbox(label="Lecture Title", placeholder="e.g. Introduction to AI")
|
879 |
lecture_content_description = gr.Textbox(label="Lecture Content Description", placeholder="e.g. Focus on recent advancements")
|
|
|
880 |
lecture_type = gr.Dropdown(["Conference", "University", "High school"], label="Audience", value="University")
|
881 |
api_service = gr.Dropdown(
|
882 |
choices=[
|
|
|
889 |
value="Google-gemini-1.5-flash"
|
890 |
)
|
891 |
api_key = gr.Textbox(label="Model Provider API Key", type="password", placeholder="Not required for Ollama")
|
892 |
+
serpapi_key = gr.Textbox(label="SerpApi Key", type="password", placeholder="Enter your SerpApi key (optional)")
|
893 |
num_slides = gr.Slider(1, 20, step=1, label="Number of Slides", value=3)
|
894 |
speaker_audio = gr.Audio(label="Speaker sample audio (MP3 or WAV)", type="filepath", elem_id="speaker-audio")
|
895 |
generate_btn = gr.Button("Generate Lecture")
|
|
|
911 |
|
912 |
generate_btn.click(
|
913 |
fn=on_generate,
|
914 |
+
inputs=[api_service, api_key, serpapi_key, title, lecture_content_description, lecture_type, speaker_audio, num_slides],
|
915 |
outputs=[slide_display, file_output]
|
916 |
)
|
917 |
|