naman1102 commited on
Commit
e297e4a
Β·
1 Parent(s): ae53812

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +159 -56
app.py CHANGED
@@ -33,11 +33,9 @@ def write_repos_to_csv(repo_ids: List[str]) -> None:
33
  try:
34
  with open(CSV_FILE, mode="w", newline='', encoding="utf-8") as csvfile:
35
  writer = csv.writer(csvfile)
36
- writer.writerow(["repo id", "link", "strength", "weaknesses", "speciality", "relevance rating"])
37
  for repo_id in repo_ids:
38
- # Create Hugging Face Spaces link
39
- hf_link = f"https://huggingface.co/spaces/{repo_id}"
40
- writer.writerow([repo_id, hf_link, "", "", "", ""])
41
  logger.info(f"Wrote {len(repo_ids)} repo IDs to {CSV_FILE}")
42
  except Exception as e:
43
  logger.error(f"Error writing to CSV: {e}")
@@ -67,7 +65,6 @@ def read_csv_to_dataframe() -> pd.DataFrame:
67
  # Format text columns for better display
68
  if not df.empty:
69
  df['repo id'] = df['repo id'].apply(lambda x: format_text_for_dataframe(x, 50))
70
- # Keep link as is since it's a URL
71
  df['strength'] = df['strength'].apply(lambda x: format_text_for_dataframe(x, 180))
72
  df['weaknesses'] = df['weaknesses'].apply(lambda x: format_text_for_dataframe(x, 180))
73
  df['speciality'] = df['speciality'].apply(lambda x: format_text_for_dataframe(x, 150))
@@ -75,7 +72,7 @@ def read_csv_to_dataframe() -> pd.DataFrame:
75
 
76
  return df
77
  except FileNotFoundError:
78
- return pd.DataFrame(columns=["repo id", "link", "strength", "weaknesses", "speciality", "relevance rating"])
79
  except Exception as e:
80
  logger.error(f"Error reading CSV: {e}")
81
  return pd.DataFrame()
@@ -121,16 +118,28 @@ def analyze_and_update_single_repo(repo_id: str, user_requirements: str = "") ->
121
  df.at[idx, "weaknesses"] = llm_json.get("weaknesses", "")
122
  df.at[idx, "speciality"] = llm_json.get("speciality", "")
123
  df.at[idx, "relevance rating"] = llm_json.get("relevance rating", "")
124
- # Ensure link is present (in case it was added later)
125
- if "link" in df.columns and (pd.isna(df.at[idx, "link"]) or df.at[idx, "link"] == ""):
126
- df.at[idx, "link"] = f"https://huggingface.co/spaces/{repo_id}"
127
  repo_found_in_df = True
128
  break
129
 
130
  if not repo_found_in_df:
131
  logger.warning(f"Repo ID {repo_id} not found in CSV for updating.")
132
 
133
- df.to_csv(CSV_FILE, index=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  logger.info(f"Successfully analyzed and updated CSV for {repo_id}")
135
  return combined_content, summary, df
136
 
@@ -284,15 +293,13 @@ def create_ui() -> gr.Blocks:
284
  .gr-dataframe th:nth-child(1),
285
  .gr-dataframe td:nth-child(1) { width: 15%; }
286
  .gr-dataframe th:nth-child(2),
287
- .gr-dataframe td:nth-child(2) { width: 15%; }
288
  .gr-dataframe th:nth-child(3),
289
- .gr-dataframe td:nth-child(3) { width: 20%; }
290
  .gr-dataframe th:nth-child(4),
291
  .gr-dataframe td:nth-child(4) { width: 20%; }
292
  .gr-dataframe th:nth-child(5),
293
  .gr-dataframe td:nth-child(5) { width: 15%; }
294
- .gr-dataframe th:nth-child(6),
295
- .gr-dataframe td:nth-child(6) { width: 15%; }
296
 
297
  /* Make repository names clickable */
298
  .gr-dataframe td:nth-child(1) {
@@ -308,18 +315,9 @@ def create_ui() -> gr.Blocks:
308
  transform: scale(1.02);
309
  }
310
 
311
- /* Make links clickable and styled */
312
- .gr-dataframe td:nth-child(2) {
313
- cursor: pointer;
314
- color: #667eea;
315
- text-decoration: underline;
316
- font-size: 0.9rem;
317
- transition: all 0.3s ease;
318
- }
319
-
320
- .gr-dataframe td:nth-child(2):hover {
321
- background-color: rgba(102, 126, 234, 0.1);
322
- color: #764ba2;
323
  }
324
 
325
  .gr-dataframe tbody tr:hover {
@@ -435,11 +433,27 @@ def create_ui() -> gr.Blocks:
435
  gr.Markdown("### πŸ“Š Results Dashboard")
436
  gr.Markdown("πŸ’‘ **Tip:** Click on any repository name to explore it in detail!")
437
  df_output = gr.Dataframe(
438
- headers=["Repository", "Link", "Strengths", "Weaknesses", "Speciality", "Relevance"],
439
  wrap=True,
440
  interactive=False # Prevent editing but allow selection
441
  )
442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  # --- Chatbot Tab ---
444
  with gr.TabItem("πŸ€– AI Assistant", id="chatbot_tab"):
445
  gr.Markdown("### πŸ’¬ Intelligent Repository Discovery")
@@ -620,15 +634,14 @@ def create_ui() -> gr.Blocks:
620
  status = "Status: Keywords extracted. User requirements saved for analysis."
621
  return final_keywords_str, status, user_requirements
622
 
623
- def handle_dataframe_select(evt: gr.SelectData, df_data) -> Tuple[str, Any]:
624
- """Handle dataframe row selection and navigate to repo explorer."""
625
  print(f"DEBUG: Selection event triggered!")
626
  print(f"DEBUG: evt = {evt}")
627
  print(f"DEBUG: df_data type = {type(df_data)}")
628
- print(f"DEBUG: df_data = {df_data}")
629
 
630
  if evt is None:
631
- return "", gr.update()
632
 
633
  try:
634
  # Get the selected row and column from the event
@@ -636,27 +649,23 @@ def create_ui() -> gr.Blocks:
636
  col_idx = evt.index[1]
637
  print(f"DEBUG: Selected row {row_idx}, column {col_idx}")
638
 
 
 
 
 
 
639
  # Handle pandas DataFrame
640
  if isinstance(df_data, pd.DataFrame) and not df_data.empty and row_idx < len(df_data):
641
-
642
- # If link column (column 1) is clicked, open the URL
643
- if col_idx == 1 and "link" in df_data.columns:
644
- link_url = df_data.iloc[row_idx, 1] # Second column contains link
645
- print(f"DEBUG: Link clicked: {link_url}")
646
- if link_url and str(link_url).strip() and str(link_url).startswith('http'):
647
- # Return JavaScript to open link in new tab
648
- js_code = f"window.open('{link_url}', '_blank');"
649
- return "", gr.update()
650
-
651
- # For other columns, get the repository ID from the first column (repo id)
652
  repo_id = df_data.iloc[row_idx, 0] # First column contains repo id
653
  print(f"DEBUG: Extracted repo_id = '{repo_id}'")
654
 
655
  # Only proceed if we actually have a repository ID
656
  if repo_id and str(repo_id).strip() and str(repo_id).strip() != 'nan':
657
  clean_repo_id = str(repo_id).strip()
658
- logger.info(f"Navigating to repo explorer for repository: {clean_repo_id}")
659
- return clean_repo_id, gr.update(selected="repo_explorer_tab")
 
660
  else:
661
  print(f"DEBUG: df_data is not a DataFrame or row_idx {row_idx} out of range")
662
 
@@ -664,7 +673,7 @@ def create_ui() -> gr.Blocks:
664
  print(f"DEBUG: Exception occurred: {e}")
665
  logger.error(f"Error handling dataframe selection: {e}")
666
 
667
- return "", gr.update()
668
 
669
  def handle_analyze_all_repos(repo_ids: List[str], user_requirements: str, progress=gr.Progress()) -> Tuple[pd.DataFrame, str, str]:
670
  """Analyzes all repositories in the CSV file with progress tracking."""
@@ -681,6 +690,7 @@ def create_ui() -> gr.Blocks:
681
  all_summaries = []
682
  successful_analyses = 0
683
  failed_analyses = 0
 
684
 
685
  for i, repo_id in enumerate(repo_ids):
686
  # Update progress
@@ -692,32 +702,84 @@ def create_ui() -> gr.Blocks:
692
 
693
  # Analyze the repository
694
  content, summary, df = analyze_and_update_single_repo(repo_id, user_requirements)
695
- all_summaries.append(f"βœ… {repo_id}: Analysis completed")
696
- successful_analyses += 1
697
 
698
- # Small delay to show progress (optional)
699
- time.sleep(0.1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
700
 
701
  except Exception as e:
702
  logger.error(f"Error analyzing {repo_id}: {e}")
703
  all_summaries.append(f"❌ {repo_id}: Error - {str(e)[:100]}...")
704
  failed_analyses += 1
 
 
705
 
706
  # Complete the progress
707
  progress(1.0, desc="Batch analysis completed!")
708
 
709
- # Final status
710
  final_status = f"πŸŽ‰ Batch Analysis Complete!\nβœ… Successful: {successful_analyses}/{total_repos}\n❌ Failed: {failed_analyses}/{total_repos}"
 
 
711
 
712
  # Create progress summary
713
- progress_summary = "\n".join(all_summaries[-10:]) # Show last 10 entries
714
- if len(all_summaries) > 10:
715
- progress_summary = f"... (showing last 10 of {len(all_summaries)} repositories)\n" + progress_summary
716
 
717
- # Get updated dataframe
718
  updated_df = read_csv_to_dataframe()
719
 
720
- logger.info(f"Batch analysis completed: {successful_analyses} successful, {failed_analyses} failed")
721
  return updated_df, final_status, progress_summary
722
 
723
  except Exception as e:
@@ -725,6 +787,31 @@ def create_ui() -> gr.Blocks:
725
  error_status = f"❌ Batch analysis failed: {e}"
726
  return read_csv_to_dataframe(), error_status, ""
727
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728
  # --- Component Event Wiring ---
729
 
730
  # Initialize chatbot with welcome message on app load
@@ -800,11 +887,27 @@ def create_ui() -> gr.Blocks:
800
  # Repo Explorer Tab
801
  setup_repo_explorer_events(repo_components, repo_states)
802
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
803
  # Add dataframe selection event
804
  df_output.select(
805
  fn=handle_dataframe_select,
806
  inputs=[df_output],
807
- outputs=[repo_components["repo_explorer_input"], tabs]
808
  )
809
 
810
  return app
 
33
  try:
34
  with open(CSV_FILE, mode="w", newline='', encoding="utf-8") as csvfile:
35
  writer = csv.writer(csvfile)
36
+ writer.writerow(["repo id", "strength", "weaknesses", "speciality", "relevance rating"])
37
  for repo_id in repo_ids:
38
+ writer.writerow([repo_id, "", "", "", ""])
 
 
39
  logger.info(f"Wrote {len(repo_ids)} repo IDs to {CSV_FILE}")
40
  except Exception as e:
41
  logger.error(f"Error writing to CSV: {e}")
 
65
  # Format text columns for better display
66
  if not df.empty:
67
  df['repo id'] = df['repo id'].apply(lambda x: format_text_for_dataframe(x, 50))
 
68
  df['strength'] = df['strength'].apply(lambda x: format_text_for_dataframe(x, 180))
69
  df['weaknesses'] = df['weaknesses'].apply(lambda x: format_text_for_dataframe(x, 180))
70
  df['speciality'] = df['speciality'].apply(lambda x: format_text_for_dataframe(x, 150))
 
72
 
73
  return df
74
  except FileNotFoundError:
75
+ return pd.DataFrame(columns=["repo id", "strength", "weaknesses", "speciality", "relevance rating"])
76
  except Exception as e:
77
  logger.error(f"Error reading CSV: {e}")
78
  return pd.DataFrame()
 
118
  df.at[idx, "weaknesses"] = llm_json.get("weaknesses", "")
119
  df.at[idx, "speciality"] = llm_json.get("speciality", "")
120
  df.at[idx, "relevance rating"] = llm_json.get("relevance rating", "")
 
 
 
121
  repo_found_in_df = True
122
  break
123
 
124
  if not repo_found_in_df:
125
  logger.warning(f"Repo ID {repo_id} not found in CSV for updating.")
126
 
127
+ # Write CSV with better error handling and flushing
128
+ try:
129
+ df.to_csv(CSV_FILE, index=False)
130
+ # Force file system flush
131
+ os.sync() if hasattr(os, 'sync') else None
132
+ logger.info(f"Successfully updated CSV for {repo_id}")
133
+ except Exception as csv_error:
134
+ logger.error(f"Failed to write CSV for {repo_id}: {csv_error}")
135
+ # Try once more with a small delay
136
+ time.sleep(0.2)
137
+ try:
138
+ df.to_csv(CSV_FILE, index=False)
139
+ logger.info(f"Successfully updated CSV for {repo_id} on retry")
140
+ except Exception as retry_error:
141
+ logger.error(f"Failed to write CSV for {repo_id} on retry: {retry_error}")
142
+
143
  logger.info(f"Successfully analyzed and updated CSV for {repo_id}")
144
  return combined_content, summary, df
145
 
 
293
  .gr-dataframe th:nth-child(1),
294
  .gr-dataframe td:nth-child(1) { width: 15%; }
295
  .gr-dataframe th:nth-child(2),
296
+ .gr-dataframe td:nth-child(2) { width: 25%; }
297
  .gr-dataframe th:nth-child(3),
298
+ .gr-dataframe td:nth-child(3) { width: 25%; }
299
  .gr-dataframe th:nth-child(4),
300
  .gr-dataframe td:nth-child(4) { width: 20%; }
301
  .gr-dataframe th:nth-child(5),
302
  .gr-dataframe td:nth-child(5) { width: 15%; }
 
 
303
 
304
  /* Make repository names clickable */
305
  .gr-dataframe td:nth-child(1) {
 
315
  transform: scale(1.02);
316
  }
317
 
318
+ /* Remove hover effect from other cells */
319
+ .gr-dataframe td:nth-child(n+2) {
320
+ cursor: default;
 
 
 
 
 
 
 
 
 
321
  }
322
 
323
  .gr-dataframe tbody tr:hover {
 
433
  gr.Markdown("### πŸ“Š Results Dashboard")
434
  gr.Markdown("πŸ’‘ **Tip:** Click on any repository name to explore it in detail!")
435
  df_output = gr.Dataframe(
436
+ headers=["Repository", "Strengths", "Weaknesses", "Speciality", "Relevance"],
437
  wrap=True,
438
  interactive=False # Prevent editing but allow selection
439
  )
440
 
441
+ # Modal popup for repository action selection
442
+ with gr.Row():
443
+ with gr.Column():
444
+ repo_action_modal = gr.Column(visible=False)
445
+ with repo_action_modal:
446
+ gr.Markdown("### πŸ”— Repository Actions")
447
+ selected_repo_display = gr.Textbox(
448
+ label="Selected Repository",
449
+ interactive=False,
450
+ info="Choose what you'd like to do with this repository"
451
+ )
452
+ with gr.Row():
453
+ visit_repo_btn = gr.Button("🌐 Visit Hugging Face Space", variant="primary", size="lg")
454
+ explore_repo_btn = gr.Button("πŸ” Open in Repo Explorer", variant="secondary", size="lg")
455
+ cancel_modal_btn = gr.Button("❌ Cancel", size="lg")
456
+
457
  # --- Chatbot Tab ---
458
  with gr.TabItem("πŸ€– AI Assistant", id="chatbot_tab"):
459
  gr.Markdown("### πŸ’¬ Intelligent Repository Discovery")
 
634
  status = "Status: Keywords extracted. User requirements saved for analysis."
635
  return final_keywords_str, status, user_requirements
636
 
637
+ def handle_dataframe_select(evt: gr.SelectData, df_data) -> Tuple[str, Any, Any]:
638
+ """Handle dataframe row selection - only repo ID column triggers modal."""
639
  print(f"DEBUG: Selection event triggered!")
640
  print(f"DEBUG: evt = {evt}")
641
  print(f"DEBUG: df_data type = {type(df_data)}")
 
642
 
643
  if evt is None:
644
+ return "", gr.update(visible=False), gr.update()
645
 
646
  try:
647
  # Get the selected row and column from the event
 
649
  col_idx = evt.index[1]
650
  print(f"DEBUG: Selected row {row_idx}, column {col_idx}")
651
 
652
+ # Only respond to clicks on the repo ID column (column 0)
653
+ if col_idx != 0:
654
+ print(f"DEBUG: Clicked on column {col_idx}, ignoring (only repo ID column responds)")
655
+ return "", gr.update(visible=False), gr.update()
656
+
657
  # Handle pandas DataFrame
658
  if isinstance(df_data, pd.DataFrame) and not df_data.empty and row_idx < len(df_data):
659
+ # Get the repository ID from the first column
 
 
 
 
 
 
 
 
 
 
660
  repo_id = df_data.iloc[row_idx, 0] # First column contains repo id
661
  print(f"DEBUG: Extracted repo_id = '{repo_id}'")
662
 
663
  # Only proceed if we actually have a repository ID
664
  if repo_id and str(repo_id).strip() and str(repo_id).strip() != 'nan':
665
  clean_repo_id = str(repo_id).strip()
666
+ logger.info(f"Showing modal for repository: {clean_repo_id}")
667
+ # Show modal and populate selected repo
668
+ return clean_repo_id, gr.update(visible=True), gr.update()
669
  else:
670
  print(f"DEBUG: df_data is not a DataFrame or row_idx {row_idx} out of range")
671
 
 
673
  print(f"DEBUG: Exception occurred: {e}")
674
  logger.error(f"Error handling dataframe selection: {e}")
675
 
676
+ return "", gr.update(visible=False), gr.update()
677
 
678
  def handle_analyze_all_repos(repo_ids: List[str], user_requirements: str, progress=gr.Progress()) -> Tuple[pd.DataFrame, str, str]:
679
  """Analyzes all repositories in the CSV file with progress tracking."""
 
690
  all_summaries = []
691
  successful_analyses = 0
692
  failed_analyses = 0
693
+ csv_update_failures = 0
694
 
695
  for i, repo_id in enumerate(repo_ids):
696
  # Update progress
 
702
 
703
  # Analyze the repository
704
  content, summary, df = analyze_and_update_single_repo(repo_id, user_requirements)
 
 
705
 
706
+ # Verify the CSV was actually updated by checking if the repo has analysis data
707
+ updated_df = read_csv_to_dataframe()
708
+ repo_updated = False
709
+
710
+ for idx, row in updated_df.iterrows():
711
+ if row["repo id"] == repo_id:
712
+ # Check if any analysis field is populated
713
+ if (row.get("strength", "").strip() or
714
+ row.get("weaknesses", "").strip() or
715
+ row.get("speciality", "").strip() or
716
+ row.get("relevance rating", "").strip()):
717
+ repo_updated = True
718
+ break
719
+
720
+ if repo_updated:
721
+ all_summaries.append(f"βœ… {repo_id}: Analysis completed & CSV updated")
722
+ successful_analyses += 1
723
+ else:
724
+ # CSV update failed - try once more
725
+ logger.warning(f"CSV update failed for {repo_id}, attempting retry...")
726
+ time.sleep(0.5) # Wait a bit longer
727
+
728
+ # Force re-read and re-update
729
+ df_retry = read_csv_to_dataframe()
730
+ retry_success = False
731
+
732
+ # Re-parse the analysis if available
733
+ if summary and "JSON extraction: SUCCESS" in summary:
734
+ # Extract the analysis from summary - this is a fallback
735
+ logger.info(f"Attempting to re-update CSV for {repo_id}")
736
+ content_retry, summary_retry, df_retry = analyze_and_update_single_repo(repo_id, user_requirements)
737
+
738
+ # Check again
739
+ final_df = read_csv_to_dataframe()
740
+ for idx, row in final_df.iterrows():
741
+ if row["repo id"] == repo_id:
742
+ if (row.get("strength", "").strip() or
743
+ row.get("weaknesses", "").strip() or
744
+ row.get("speciality", "").strip() or
745
+ row.get("relevance rating", "").strip()):
746
+ retry_success = True
747
+ break
748
+
749
+ if retry_success:
750
+ all_summaries.append(f"βœ… {repo_id}: Analysis completed & CSV updated (retry)")
751
+ successful_analyses += 1
752
+ else:
753
+ all_summaries.append(f"⚠️ {repo_id}: Analysis completed but CSV update failed")
754
+ csv_update_failures += 1
755
+
756
+ # Longer delay to prevent file conflicts
757
+ time.sleep(0.3)
758
 
759
  except Exception as e:
760
  logger.error(f"Error analyzing {repo_id}: {e}")
761
  all_summaries.append(f"❌ {repo_id}: Error - {str(e)[:100]}...")
762
  failed_analyses += 1
763
+ # Still wait to prevent rapid failures
764
+ time.sleep(0.2)
765
 
766
  # Complete the progress
767
  progress(1.0, desc="Batch analysis completed!")
768
 
769
+ # Final status with detailed breakdown
770
  final_status = f"πŸŽ‰ Batch Analysis Complete!\nβœ… Successful: {successful_analyses}/{total_repos}\n❌ Failed: {failed_analyses}/{total_repos}"
771
+ if csv_update_failures > 0:
772
+ final_status += f"\n⚠️ CSV Update Issues: {csv_update_failures}/{total_repos}"
773
 
774
  # Create progress summary
775
+ progress_summary = "\n".join(all_summaries[-15:]) # Show last 15 entries
776
+ if len(all_summaries) > 15:
777
+ progress_summary = f"... (showing last 15 of {len(all_summaries)} repositories)\n" + progress_summary
778
 
779
+ # Get final updated dataframe
780
  updated_df = read_csv_to_dataframe()
781
 
782
+ logger.info(f"Batch analysis completed: {successful_analyses} successful, {failed_analyses} failed, {csv_update_failures} CSV update issues")
783
  return updated_df, final_status, progress_summary
784
 
785
  except Exception as e:
 
787
  error_status = f"❌ Batch analysis failed: {e}"
788
  return read_csv_to_dataframe(), error_status, ""
789
 
790
+ def handle_visit_repo(repo_id: str) -> Tuple[Any, str]:
791
+ """Handle visiting the Hugging Face Space for the repository."""
792
+ if repo_id and repo_id.strip():
793
+ hf_url = f"https://huggingface.co/spaces/{repo_id.strip()}"
794
+ logger.info(f"User chose to visit: {hf_url}")
795
+ # Use JavaScript to open URL in new tab
796
+ js_code = f"""
797
+ <script>
798
+ window.open('{hf_url}', '_blank');
799
+ </script>
800
+ """
801
+ return gr.update(visible=False), f"🌐 Opening: {hf_url}"
802
+ return gr.update(visible=False), ""
803
+
804
+ def handle_explore_repo(repo_id: str) -> Tuple[Any, Any, str]:
805
+ """Handle navigating to the repo explorer for the repository."""
806
+ if repo_id and repo_id.strip():
807
+ logger.info(f"User chose to explore: {repo_id.strip()}")
808
+ return gr.update(visible=False), gr.update(selected="repo_explorer_tab"), repo_id.strip()
809
+ return gr.update(visible=False), gr.update(), ""
810
+
811
+ def handle_cancel_modal() -> Any:
812
+ """Handle closing the modal."""
813
+ return gr.update(visible=False)
814
+
815
  # --- Component Event Wiring ---
816
 
817
  # Initialize chatbot with welcome message on app load
 
887
  # Repo Explorer Tab
888
  setup_repo_explorer_events(repo_components, repo_states)
889
 
890
+ # Modal button events
891
+ visit_repo_btn.click(
892
+ fn=handle_visit_repo,
893
+ inputs=[selected_repo_display],
894
+ outputs=[repo_action_modal, selected_repo_display]
895
+ )
896
+ explore_repo_btn.click(
897
+ fn=handle_explore_repo,
898
+ inputs=[selected_repo_display],
899
+ outputs=[repo_action_modal, tabs, repo_components["repo_explorer_input"]]
900
+ )
901
+ cancel_modal_btn.click(
902
+ fn=handle_cancel_modal,
903
+ outputs=[repo_action_modal]
904
+ )
905
+
906
  # Add dataframe selection event
907
  df_output.select(
908
  fn=handle_dataframe_select,
909
  inputs=[df_output],
910
+ outputs=[selected_repo_display, repo_action_modal, tabs]
911
  )
912
 
913
  return app