Jaward commited on
Commit
203b906
·
verified ·
1 Parent(s): 65a26d2

visualized agents progress on tasks

Browse files
Files changed (1) hide show
  1. app.py +35 -22
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: 25px; overflow: hidden;">
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: #f8f9fa; border-radius: 4px;">
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: generating..."
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 audio for slide {i + 1}: {str(e)}")
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="University")
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",