Spaces:
Running
Running
visualized agents progress on tasks
Browse files
app.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
# Lectūra Research Demo: A Multi-Agent Tool for Self-taught Mastery.
|
2 |
-
# Author: Jaward Sesay
|
3 |
# © Lectūra Labs 2025. All rights reserved.
|
4 |
import os
|
5 |
import json
|
@@ -172,31 +172,35 @@ def create_slides(slides: list[dict], title: str, instructor_name: str, output_d
|
|
172 |
|
173 |
# Track stage outputs
|
174 |
stage_outputs = {}
|
|
|
175 |
|
176 |
-
def add_stage_output(stage_name, output):
|
177 |
if stage_name not in stage_outputs:
|
178 |
stage_outputs[stage_name] = []
|
179 |
stage_outputs[stage_name].append(output)
|
|
|
180 |
|
181 |
# Dynamic progress bar with accordion
|
182 |
def html_with_progress(label, progress):
|
183 |
accordion_html = ""
|
184 |
if stage_outputs:
|
185 |
accordion_html = """
|
186 |
-
<div style="width: 70%; margin-top: 20px; border: 1px solid #ddd; border-radius:
|
187 |
"""
|
188 |
for stage_name, outputs in stage_outputs.items():
|
|
|
189 |
accordion_html += f"""
|
190 |
<div style="border-bottom: 1px solid #ddd;">
|
191 |
-
<div style="padding: 10px; background-color: #f8f9fa; cursor: pointer;" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">
|
192 |
-
<h4 style="margin: 0; color: #555;">{stage_name}</h4>
|
|
|
193 |
</div>
|
194 |
-
<div style="padding: 10px; display: none; background-color: white;">
|
195 |
"""
|
196 |
for output in outputs:
|
197 |
accordion_html += f"""
|
198 |
-
<div style="margin-bottom: 10px; padding: 10px; background-color: #
|
199 |
-
<pre style="margin: 0; white-space: pre-wrap; word-wrap: break-word;">{output}</pre>
|
200 |
</div>
|
201 |
"""
|
202 |
accordion_html += """
|
@@ -454,8 +458,9 @@ async def on_generate(api_service, api_key, serpapi_key, title, lecture_content_
|
|
454 |
print(f"Received serpapi_key: '{serpapi_key}' (type: {type(serpapi_key)}, length: {len(serpapi_key) if serpapi_key else 0})")
|
455 |
|
456 |
# Reset stage outputs at the start of generation
|
457 |
-
global stage_outputs
|
458 |
stage_outputs = {}
|
|
|
459 |
|
460 |
# Define constants
|
461 |
max_audio_retries = 5
|
@@ -619,9 +624,12 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
619 |
progress = 0
|
620 |
label = "Researching lecture topic..."
|
621 |
if not serpapi_key:
|
622 |
-
add_stage_output("Research", "No API key for research was provided, proceeding with slide generation.")
|
|
|
|
|
|
|
623 |
else:
|
624 |
-
add_stage_output("Research", "Starting research phase...")
|
625 |
yield (
|
626 |
html_with_progress(label, progress),
|
627 |
[]
|
@@ -669,9 +677,10 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
669 |
logger.info("Handoff from %s to %s", source, message.target)
|
670 |
if source == "research_agent" and message.target == "slide_agent":
|
671 |
progress = 25
|
672 |
-
label = "Slides
|
673 |
if hasattr(message, 'content'):
|
674 |
-
add_stage_output("Research", message.content)
|
|
|
675 |
yield (
|
676 |
html_with_progress(label, progress),
|
677 |
[]
|
@@ -684,7 +693,7 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
684 |
if extracted_json:
|
685 |
slides = extracted_json
|
686 |
logger.info("Extracted slides JSON from HandoffMessage context: %s", slides)
|
687 |
-
add_stage_output("Slides", json.dumps(slides, indent=2))
|
688 |
if slides is None or len(slides) != total_slides:
|
689 |
if slide_retry_count < max_retries:
|
690 |
slide_retry_count += 1
|
@@ -698,6 +707,7 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
698 |
continue
|
699 |
progress = 50
|
700 |
label = "Scripts: generating..."
|
|
|
701 |
yield (
|
702 |
html_with_progress(label, progress),
|
703 |
[]
|
@@ -710,9 +720,10 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
710 |
if extracted_json:
|
711 |
scripts = extracted_json
|
712 |
logger.info("Extracted scripts JSON from HandoffMessage context: %s", scripts)
|
713 |
-
add_stage_output("Scripts", json.dumps(scripts, indent=2))
|
714 |
progress = 75
|
715 |
label = "Review: in progress..."
|
|
|
716 |
yield (
|
717 |
html_with_progress(label, progress),
|
718 |
[]
|
@@ -725,7 +736,7 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
725 |
if extracted_json:
|
726 |
slides = extracted_json
|
727 |
logger.info("Slide Agent generated %d slides: %s", len(slides), slides)
|
728 |
-
add_stage_output("Slides", json.dumps(slides, indent=2))
|
729 |
if len(slides) != total_slides:
|
730 |
if slide_retry_count < max_retries:
|
731 |
slide_retry_count += 1
|
@@ -741,9 +752,9 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
741 |
html_files = create_slides(slides, title, instructor_name)
|
742 |
if not html_files:
|
743 |
logger.error("Failed to generate HTML slides")
|
744 |
-
add_stage_output("Slides", "Failed to generate HTML slides")
|
745 |
else:
|
746 |
-
add_stage_output("Slides", f"Successfully generated {len(html_files)} HTML slides")
|
747 |
progress = 50
|
748 |
label = "Scripts: generating..."
|
749 |
yield (
|
@@ -770,7 +781,7 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
770 |
if extracted_json:
|
771 |
scripts = extracted_json
|
772 |
logger.info("Script Agent generated scripts for %d slides: %s", len(scripts), scripts)
|
773 |
-
add_stage_output("Scripts", json.dumps(scripts, indent=2))
|
774 |
for i, script in enumerate(scripts):
|
775 |
script_file = os.path.join(OUTPUT_DIR, f"slide_{i+1}_script.txt")
|
776 |
try:
|
@@ -781,6 +792,7 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
781 |
except Exception as e:
|
782 |
logger.error("Error saving script to %s: %s", script_file, str(e))
|
783 |
add_stage_output("Scripts", f"Failed to save script for slide {i+1}: {str(e)}")
|
|
|
784 |
progress = 75
|
785 |
label = "Script complete. Reviewing lecture materials..."
|
786 |
yield (
|
@@ -805,7 +817,8 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
805 |
logger.info("Instructor Agent completed lecture review: %s", message.content)
|
806 |
progress = 90
|
807 |
label = "Lecture materials ready. Generating lecture speech..."
|
808 |
-
add_stage_output("Review", message.content)
|
|
|
809 |
file_paths = [f for f in os.listdir(OUTPUT_DIR) if f.endswith(('.md', '.txt'))]
|
810 |
file_paths.sort()
|
811 |
file_paths = [os.path.join(OUTPUT_DIR, f) for f in file_paths]
|
@@ -937,7 +950,7 @@ Example: 'Received {total_slides} slides, {total_slides} scripts, and HTML files
|
|
937 |
audio_urls[i] = None
|
938 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
939 |
label = f"Generating lecture speech for slide {i + 1}/{len(scripts)}..."
|
940 |
-
add_stage_output("Speech", f"Failed to generate
|
941 |
yield (
|
942 |
html_with_progress(label, progress),
|
943 |
file_paths,
|
@@ -1626,7 +1639,7 @@ with gr.Blocks(
|
|
1626 |
with gr.Group(elem_id="form-group"):
|
1627 |
title = gr.Textbox(label="Lecture Title", placeholder="e.g. Introduction to AI")
|
1628 |
lecture_content_description = gr.Textbox(label="Lecture Content Description", placeholder="e.g. Focus on recent advancements")
|
1629 |
-
lecture_type = gr.Dropdown(["Conference", "University", "High school"], label="Audience", value="
|
1630 |
lecture_style = gr.Dropdown(
|
1631 |
["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"],
|
1632 |
label="Lecture Style",
|
|
|
1 |
# Lectūra Research Demo: A Multi-Agent Tool for Self-taught Mastery.
|
2 |
+
# Author: Jaward Sesay | Github: https://github.com/Jaykef
|
3 |
# © Lectūra Labs 2025. All rights reserved.
|
4 |
import os
|
5 |
import json
|
|
|
172 |
|
173 |
# Track stage outputs
|
174 |
stage_outputs = {}
|
175 |
+
stage_status = {}
|
176 |
|
177 |
+
def add_stage_output(stage_name, output, status="Task In Progress"):
|
178 |
if stage_name not in stage_outputs:
|
179 |
stage_outputs[stage_name] = []
|
180 |
stage_outputs[stage_name].append(output)
|
181 |
+
stage_status[stage_name] = status
|
182 |
|
183 |
# Dynamic progress bar with accordion
|
184 |
def html_with_progress(label, progress):
|
185 |
accordion_html = ""
|
186 |
if stage_outputs:
|
187 |
accordion_html = """
|
188 |
+
<div style="width: 70%; margin-top: 20px; border: 1px solid #ddd; border-radius: 20px; overflow: hidden;">
|
189 |
"""
|
190 |
for stage_name, outputs in stage_outputs.items():
|
191 |
+
status_color = "#2196F3" if stage_status.get(stage_name) == "Task In Progress" else "#4CAF50"
|
192 |
accordion_html += f"""
|
193 |
<div style="border-bottom: 1px solid #ddd;">
|
194 |
+
<div style="padding: 10px; background-color: #f8f9fa; cursor: pointer; display: flex; justify-content: space-between; align-items: center;" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">
|
195 |
+
<h4 style="margin: 0; color: #555 !important;">{stage_name} Agent</h4>
|
196 |
+
<span style="color: {status_color}; font-weight: bold;">{stage_status.get(stage_name, 'Task In Progress')}</span>
|
197 |
</div>
|
198 |
+
<div style="padding: 10px; display: none; background-color: white; text-align: left;">
|
199 |
"""
|
200 |
for output in outputs:
|
201 |
accordion_html += f"""
|
202 |
+
<div style="margin-bottom: 10px; padding: 10px; background-color: #fff; border-radius: 4px;">
|
203 |
+
<pre style="margin: 0; white-space: pre-wrap; word-wrap: break-word; color: #555 !important;">{output}</pre>
|
204 |
</div>
|
205 |
"""
|
206 |
accordion_html += """
|
|
|
458 |
print(f"Received serpapi_key: '{serpapi_key}' (type: {type(serpapi_key)}, length: {len(serpapi_key) if serpapi_key else 0})")
|
459 |
|
460 |
# Reset stage outputs at the start of generation
|
461 |
+
global stage_outputs, stage_status
|
462 |
stage_outputs = {}
|
463 |
+
stage_status = {}
|
464 |
|
465 |
# Define constants
|
466 |
max_audio_retries = 5
|
|
|
624 |
progress = 0
|
625 |
label = "Researching lecture topic..."
|
626 |
if not serpapi_key:
|
627 |
+
add_stage_output("Research", "No API key for research was provided, proceeding with slide generation.", "No serpapi key provided for research, handing off to Slide Agent")
|
628 |
+
add_stage_output("Slides", "Starting slide generation...", "Task In Progress")
|
629 |
+
label = "Generating Slides..."
|
630 |
+
progress = 25
|
631 |
else:
|
632 |
+
add_stage_output("Research", "Starting research phase...", "Task In Progress")
|
633 |
yield (
|
634 |
html_with_progress(label, progress),
|
635 |
[]
|
|
|
677 |
logger.info("Handoff from %s to %s", source, message.target)
|
678 |
if source == "research_agent" and message.target == "slide_agent":
|
679 |
progress = 25
|
680 |
+
label = "Generating Slides..."
|
681 |
if hasattr(message, 'content'):
|
682 |
+
add_stage_output("Research", message.content, "Task Completed")
|
683 |
+
# Remove duplicate Slides stage output
|
684 |
yield (
|
685 |
html_with_progress(label, progress),
|
686 |
[]
|
|
|
693 |
if extracted_json:
|
694 |
slides = extracted_json
|
695 |
logger.info("Extracted slides JSON from HandoffMessage context: %s", slides)
|
696 |
+
add_stage_output("Slides", json.dumps(slides, indent=2), "Task Completed")
|
697 |
if slides is None or len(slides) != total_slides:
|
698 |
if slide_retry_count < max_retries:
|
699 |
slide_retry_count += 1
|
|
|
707 |
continue
|
708 |
progress = 50
|
709 |
label = "Scripts: generating..."
|
710 |
+
# Remove duplicate Scripts stage output
|
711 |
yield (
|
712 |
html_with_progress(label, progress),
|
713 |
[]
|
|
|
720 |
if extracted_json:
|
721 |
scripts = extracted_json
|
722 |
logger.info("Extracted scripts JSON from HandoffMessage context: %s", scripts)
|
723 |
+
add_stage_output("Scripts", json.dumps(scripts, indent=2), "Task Completed")
|
724 |
progress = 75
|
725 |
label = "Review: in progress..."
|
726 |
+
# Remove duplicate Review stage output
|
727 |
yield (
|
728 |
html_with_progress(label, progress),
|
729 |
[]
|
|
|
736 |
if extracted_json:
|
737 |
slides = extracted_json
|
738 |
logger.info("Slide Agent generated %d slides: %s", len(slides), slides)
|
739 |
+
add_stage_output("Slides", json.dumps(slides, indent=2), "Task In Progress")
|
740 |
if len(slides) != total_slides:
|
741 |
if slide_retry_count < max_retries:
|
742 |
slide_retry_count += 1
|
|
|
752 |
html_files = create_slides(slides, title, instructor_name)
|
753 |
if not html_files:
|
754 |
logger.error("Failed to generate HTML slides")
|
755 |
+
add_stage_output("Slides", "Failed to generate HTML slides", "Task Failed")
|
756 |
else:
|
757 |
+
add_stage_output("Slides", f"Successfully generated {len(html_files)} HTML slides", "Task Completed")
|
758 |
progress = 50
|
759 |
label = "Scripts: generating..."
|
760 |
yield (
|
|
|
781 |
if extracted_json:
|
782 |
scripts = extracted_json
|
783 |
logger.info("Script Agent generated scripts for %d slides: %s", len(scripts), scripts)
|
784 |
+
add_stage_output("Scripts", json.dumps(scripts, indent=2), "Task In Progress")
|
785 |
for i, script in enumerate(scripts):
|
786 |
script_file = os.path.join(OUTPUT_DIR, f"slide_{i+1}_script.txt")
|
787 |
try:
|
|
|
792 |
except Exception as e:
|
793 |
logger.error("Error saving script to %s: %s", script_file, str(e))
|
794 |
add_stage_output("Scripts", f"Failed to save script for slide {i+1}: {str(e)}")
|
795 |
+
add_stage_output("Scripts", "All scripts generated and saved", "Task Completed")
|
796 |
progress = 75
|
797 |
label = "Script complete. Reviewing lecture materials..."
|
798 |
yield (
|
|
|
817 |
logger.info("Instructor Agent completed lecture review: %s", message.content)
|
818 |
progress = 90
|
819 |
label = "Lecture materials ready. Generating lecture speech..."
|
820 |
+
add_stage_output("Review", message.content, "Task Completed")
|
821 |
+
add_stage_output("Speech", "Starting speech generation...", "Task In Progress")
|
822 |
file_paths = [f for f in os.listdir(OUTPUT_DIR) if f.endswith(('.md', '.txt'))]
|
823 |
file_paths.sort()
|
824 |
file_paths = [os.path.join(OUTPUT_DIR, f) for f in file_paths]
|
|
|
950 |
audio_urls[i] = None
|
951 |
progress = 90 + ((i + 1) / len(scripts)) * 10
|
952 |
label = f"Generating lecture speech for slide {i + 1}/{len(scripts)}..."
|
953 |
+
add_stage_output("Speech", f"Failed to generate speech for slide {i + 1}: {str(e)}")
|
954 |
yield (
|
955 |
html_with_progress(label, progress),
|
956 |
file_paths,
|
|
|
1639 |
with gr.Group(elem_id="form-group"):
|
1640 |
title = gr.Textbox(label="Lecture Title", placeholder="e.g. Introduction to AI")
|
1641 |
lecture_content_description = gr.Textbox(label="Lecture Content Description", placeholder="e.g. Focus on recent advancements")
|
1642 |
+
lecture_type = gr.Dropdown(["Personalized", "Conference", "University", "High school"], label="Audience", value="Personalized")
|
1643 |
lecture_style = gr.Dropdown(
|
1644 |
["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"],
|
1645 |
label="Lecture Style",
|