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 |
|