Spaces:
Running
Running
added support for lecture styles or preferences
Browse files
app.py
CHANGED
@@ -94,7 +94,7 @@ def search_web(query: str, serpapi_key: str) -> str:
|
|
94 |
logger.error("Unexpected error during search: %s", str(e))
|
95 |
return None
|
96 |
|
97 |
-
# Custom
|
98 |
def render_md_to_html(md_content: str) -> str:
|
99 |
try:
|
100 |
html_content = markdown.markdown(md_content, extensions=['extra', 'fenced_code', 'tables'])
|
@@ -103,7 +103,7 @@ def render_md_to_html(md_content: str) -> str:
|
|
103 |
logger.error("Failed to render Markdown to HTML: %s", str(e))
|
104 |
return "<div>Error rendering content</div>"
|
105 |
|
106 |
-
#
|
107 |
def create_slides(slides: list[dict], title: str, output_dir: str = OUTPUT_DIR) -> list[str]:
|
108 |
try:
|
109 |
html_files = []
|
@@ -145,7 +145,7 @@ def create_slides(slides: list[dict], title: str, output_dir: str = OUTPUT_DIR)
|
|
145 |
logger.error("Failed to create HTML slides: %s", str(e))
|
146 |
return []
|
147 |
|
148 |
-
#
|
149 |
def html_with_progress(label, progress):
|
150 |
return f"""
|
151 |
<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;">
|
@@ -201,7 +201,7 @@ def clean_script_text(script):
|
|
201 |
logger.info("Cleaned script: %s", script)
|
202 |
return script
|
203 |
|
204 |
-
# Helper
|
205 |
async def validate_and_convert_speaker_audio(speaker_audio):
|
206 |
if not speaker_audio or not os.path.exists(speaker_audio):
|
207 |
logger.warning("Speaker audio file does not exist: %s. Using default voice.", speaker_audio)
|
@@ -388,7 +388,7 @@ def get_gradio_file_url(local_path):
|
|
388 |
return f"/gradio_api/file={relative_path}"
|
389 |
|
390 |
# Async generate lecture materials and audio
|
391 |
-
async def on_generate(api_service, api_key, serpapi_key, title, lecture_content_description, lecture_type, speaker_audio, num_slides):
|
392 |
model_client = get_model_client(api_service, api_key)
|
393 |
|
394 |
if os.path.exists(OUTPUT_DIR):
|
@@ -427,7 +427,12 @@ You are a Slide Agent. Using the research from the conversation history and the
|
|
427 |
|
428 |
- The Introduction slide (first slide) should have the title "{title}" and content containing only the lecture title, speaker name (Prof. AI Feynman), and date {date}, centered, in plain text.
|
429 |
- The Closing slide (last slide) should have the title "Closing" and content containing only "The End\nThank you", centered, in plain text.
|
430 |
-
- The remaining {content_slides} slides should be content slides based on the lecture description
|
|
|
|
|
|
|
|
|
|
|
431 |
|
432 |
Output ONLY a JSON array wrapped in ```json ... ``` in a TextMessage, where each slide is an object with 'title' and 'content' keys. After generating the JSON, use the create_slides tool to produce HTML slides, then use the handoff_to_script_agent tool to pass the task to the Script Agent. Do not include any explanatory text or other messages.
|
433 |
|
@@ -448,11 +453,15 @@ Example output for 1 content slide (total 3 slides):
|
|
448 |
model_client=model_client,
|
449 |
handoffs=["feynman_agent"],
|
450 |
system_message=f"""
|
451 |
-
You are a Script Agent
|
|
|
|
|
|
|
|
|
|
|
|
|
452 |
|
453 |
-
|
454 |
-
- For the Closing slide, the script should be a brief farewell and thank you message.
|
455 |
-
- For the content slides, summarize the slide content academically.
|
456 |
|
457 |
Example for 3 slides (1 content slide):
|
458 |
```json
|
@@ -470,8 +479,8 @@ Example for 3 slides (1 content slide):
|
|
470 |
model_client=model_client,
|
471 |
handoffs=[],
|
472 |
system_message=f"""
|
473 |
-
You are Agent Feynman. Review the slides and scripts from the conversation history to ensure coherence, completeness, and that exactly {total_slides} slides and {total_slides} scripts are received, including the Introduction and Closing slides. Verify that HTML slide files exist in the outputs directory. Output a confirmation message summarizing the number of slides, scripts, and HTML files status. If slides, scripts, or HTML files are missing, invalid, or do not match the expected count ({total_slides}), report the issue clearly. Use 'TERMINATE' to signal completion.
|
474 |
-
Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files. Lecture is coherent. TERMINATE'
|
475 |
""")
|
476 |
|
477 |
swarm = Swarm(
|
@@ -480,7 +489,7 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
480 |
)
|
481 |
|
482 |
progress = 0
|
483 |
-
label = "
|
484 |
yield (
|
485 |
html_with_progress(label, progress),
|
486 |
[]
|
@@ -491,10 +500,11 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
491 |
Lecture Title: {title}
|
492 |
Lecture Content Description: {lecture_content_description}
|
493 |
Audience: {lecture_type}
|
|
|
494 |
Number of Content Slides: {content_slides}
|
495 |
Please start by researching the topic, or proceed without research if search is unavailable.
|
496 |
"""
|
497 |
-
logger.info("Starting lecture generation for title: %s with %d content slides (total %d slides)", title, content_slides, total_slides)
|
498 |
|
499 |
slides = None
|
500 |
scripts = None
|
@@ -763,14 +773,15 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
763 |
f.write(cleaned_script or "")
|
764 |
logger.info("Saved script to %s: %s", script_file, cleaned_script)
|
765 |
except Exception as e:
|
766 |
-
logger.error("Error saving script to %s: %s",
|
|
|
767 |
|
768 |
if not cleaned_script:
|
769 |
logger.error("Skipping audio for slide %d due to empty or invalid script", i + 1)
|
770 |
audio_files.append(None)
|
771 |
audio_urls[i] = None
|
772 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
773 |
-
label = f"Generating speech for slide {i + 1}/{len(scripts)}..."
|
774 |
yield (
|
775 |
html_with_progress(label, progress),
|
776 |
file_paths
|
@@ -1249,6 +1260,11 @@ with gr.Blocks(
|
|
1249 |
title = gr.Textbox(label="Lecture Title", placeholder="e.g. Introduction to AI")
|
1250 |
lecture_content_description = gr.Textbox(label="Lecture Content Description", placeholder="e.g. Focus on recent advancements")
|
1251 |
lecture_type = gr.Dropdown(["Conference", "University", "High school"], label="Audience", value="University")
|
|
|
|
|
|
|
|
|
|
|
1252 |
api_service = gr.Dropdown(
|
1253 |
choices=[
|
1254 |
"Azure AI Foundry",
|
@@ -1283,7 +1299,7 @@ with gr.Blocks(
|
|
1283 |
|
1284 |
generate_btn.click(
|
1285 |
fn=on_generate,
|
1286 |
-
inputs=[api_service, api_key, serpapi_key, title, lecture_content_description, lecture_type, speaker_audio, num_slides],
|
1287 |
outputs=[slide_display, file_output]
|
1288 |
)
|
1289 |
|
|
|
94 |
logger.error("Unexpected error during search: %s", str(e))
|
95 |
return None
|
96 |
|
97 |
+
# Custom renderer for slides - Markdown to HTML
|
98 |
def render_md_to_html(md_content: str) -> str:
|
99 |
try:
|
100 |
html_content = markdown.markdown(md_content, extensions=['extra', 'fenced_code', 'tables'])
|
|
|
103 |
logger.error("Failed to render Markdown to HTML: %s", str(e))
|
104 |
return "<div>Error rendering content</div>"
|
105 |
|
106 |
+
# Slide tool for generating HTML slides used by slide_agent
|
107 |
def create_slides(slides: list[dict], title: str, output_dir: str = OUTPUT_DIR) -> list[str]:
|
108 |
try:
|
109 |
html_files = []
|
|
|
145 |
logger.error("Failed to create HTML slides: %s", str(e))
|
146 |
return []
|
147 |
|
148 |
+
# Dynamic progress bar
|
149 |
def html_with_progress(label, progress):
|
150 |
return f"""
|
151 |
<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;">
|
|
|
201 |
logger.info("Cleaned script: %s", script)
|
202 |
return script
|
203 |
|
204 |
+
# Helper to validate and convert speaker audio
|
205 |
async def validate_and_convert_speaker_audio(speaker_audio):
|
206 |
if not speaker_audio or not os.path.exists(speaker_audio):
|
207 |
logger.warning("Speaker audio file does not exist: %s. Using default voice.", speaker_audio)
|
|
|
388 |
return f"/gradio_api/file={relative_path}"
|
389 |
|
390 |
# Async generate lecture materials and audio
|
391 |
+
async def on_generate(api_service, api_key, serpapi_key, title, lecture_content_description, lecture_type, lecture_style, speaker_audio, num_slides):
|
392 |
model_client = get_model_client(api_service, api_key)
|
393 |
|
394 |
if os.path.exists(OUTPUT_DIR):
|
|
|
427 |
|
428 |
- The Introduction slide (first slide) should have the title "{title}" and content containing only the lecture title, speaker name (Prof. AI Feynman), and date {date}, centered, in plain text.
|
429 |
- The Closing slide (last slide) should have the title "Closing" and content containing only "The End\nThank you", centered, in plain text.
|
430 |
+
- The remaining {content_slides} slides should be content slides based on the lecture description, audience type, and lecture style ({lecture_style}), with meaningful titles and content in valid Markdown format. Adapt the content to the lecture style to suit diverse learners:
|
431 |
+
- Feynman: Explains complex ideas with simplicity, clarity, and enthusiasm, emulating Richard Feynman's teaching style.
|
432 |
+
- Socratic: Poses thought-provoking questions to guide learners to insights without requiring direct interaction.
|
433 |
+
- Humorous: Infuses wit and light-hearted anecdotes to make content engaging and memorable.
|
434 |
+
- Inspirational - Motivating: Uses motivational language and visionary ideas to spark enthusiasm and curiosity.
|
435 |
+
- Reflective: Encourages introspection with a calm, contemplative tone to deepen understanding.
|
436 |
|
437 |
Output ONLY a JSON array wrapped in ```json ... ``` in a TextMessage, where each slide is an object with 'title' and 'content' keys. After generating the JSON, use the create_slides tool to produce HTML slides, then use the handoff_to_script_agent tool to pass the task to the Script Agent. Do not include any explanatory text or other messages.
|
438 |
|
|
|
453 |
model_client=model_client,
|
454 |
handoffs=["feynman_agent"],
|
455 |
system_message=f"""
|
456 |
+
You are a Script Agent modeled after Richard Feynman. Access the JSON array of {total_slides} slides from the conversation history, which includes an Introduction slide, {content_slides} content slides, and a Closing slide. Generate a narration script (1-2 sentences) for each of the {total_slides} slides, summarizing its content in a clear, academically inclined tone, with humor as Professor Feynman would deliver it. Ensure the lecture is engaging, covers the fundamental requirements of the topic, and aligns with the lecture style ({lecture_style}) to suit diverse learners:
|
457 |
+
- Feynman: Explains complex ideas with simplicity, clarity, and enthusiasm, emulating Richard Feynman's teaching style.
|
458 |
+
- Socratic: Poses thought-provoking questions to guide learners to insights without requiring direct interaction.
|
459 |
+
- Narrative: Use storytelling or analogies to explain concepts.
|
460 |
+
- Analytical: Focus on data, equations, or logical breakdowns.
|
461 |
+
- Humorous: Infuses wit and light-hearted anecdotes to make content engaging and memorable.
|
462 |
+
- Reflective: Encourages introspection with a calm, contemplative tone to deepen understanding.
|
463 |
|
464 |
+
Output ONLY a JSON array wrapped in ```json ... ``` with exactly {total_slides} strings, one script per slide, in the same order. Ensure the JSON is valid and complete. After outputting, use the handoff_to_feynman_agent tool. If scripts cannot be generated, retry once.
|
|
|
|
|
465 |
|
466 |
Example for 3 slides (1 content slide):
|
467 |
```json
|
|
|
479 |
model_client=model_client,
|
480 |
handoffs=[],
|
481 |
system_message=f"""
|
482 |
+
You are Agent Feynman. Review the slides and scripts from the conversation history to ensure coherence, completeness, and that exactly {total_slides} slides and {total_slides} scripts are received, including the Introduction and Closing slides. Verify that HTML slide files exist in the outputs directory and align with the lecture style ({lecture_style}). Output a confirmation message summarizing the number of slides, scripts, and HTML files status. If slides, scripts, or HTML files are missing, invalid, or do not match the expected count ({total_slides}), report the issue clearly. Use 'TERMINATE' to signal completion.
|
483 |
+
Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files. Lecture is coherent and aligns with {lecture_style} style. TERMINATE'
|
484 |
""")
|
485 |
|
486 |
swarm = Swarm(
|
|
|
489 |
)
|
490 |
|
491 |
progress = 0
|
492 |
+
label = "Researching lecture topic..."
|
493 |
yield (
|
494 |
html_with_progress(label, progress),
|
495 |
[]
|
|
|
500 |
Lecture Title: {title}
|
501 |
Lecture Content Description: {lecture_content_description}
|
502 |
Audience: {lecture_type}
|
503 |
+
Lecture Style: {lecture_style}
|
504 |
Number of Content Slides: {content_slides}
|
505 |
Please start by researching the topic, or proceed without research if search is unavailable.
|
506 |
"""
|
507 |
+
logger.info("Starting lecture generation for title: %s with %d content slides (total %d slides), style: %s", title, content_slides, total_slides, lecture_style)
|
508 |
|
509 |
slides = None
|
510 |
scripts = None
|
|
|
773 |
f.write(cleaned_script or "")
|
774 |
logger.info("Saved script to %s: %s", script_file, cleaned_script)
|
775 |
except Exception as e:
|
776 |
+
logger.error("Error saving script to %s: %s",
|
777 |
+
script_file, str(e))
|
778 |
|
779 |
if not cleaned_script:
|
780 |
logger.error("Skipping audio for slide %d due to empty or invalid script", i + 1)
|
781 |
audio_files.append(None)
|
782 |
audio_urls[i] = None
|
783 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
784 |
+
label = f"Generating lecture speech for slide {i + 1}/{len(scripts)}..."
|
785 |
yield (
|
786 |
html_with_progress(label, progress),
|
787 |
file_paths
|
|
|
1260 |
title = gr.Textbox(label="Lecture Title", placeholder="e.g. Introduction to AI")
|
1261 |
lecture_content_description = gr.Textbox(label="Lecture Content Description", placeholder="e.g. Focus on recent advancements")
|
1262 |
lecture_type = gr.Dropdown(["Conference", "University", "High school"], label="Audience", value="University")
|
1263 |
+
lecture_style = gr.Dropdown(
|
1264 |
+
["Feynman - Simplifies complex ideas with enthusiasm", "Socratic - Guides insights with probing questions", "Inspirational - Sparks enthusiasm with visionary ideas", "Reflective - Promotes introspection with a calm tone", "Humorous - Uses wit and anecdotes for engaging content"],
|
1265 |
+
label="Lecture Style",
|
1266 |
+
value="Feynman - Simplifies complex ideas with enthusiasm"
|
1267 |
+
)
|
1268 |
api_service = gr.Dropdown(
|
1269 |
choices=[
|
1270 |
"Azure AI Foundry",
|
|
|
1299 |
|
1300 |
generate_btn.click(
|
1301 |
fn=on_generate,
|
1302 |
+
inputs=[api_service, api_key, serpapi_key, title, lecture_content_description, lecture_type, lecture_style, speaker_audio, num_slides],
|
1303 |
outputs=[slide_display, file_output]
|
1304 |
)
|
1305 |
|