fedec65 commited on
Commit
4f16978
·
verified ·
1 Parent(s): c26d87b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +211 -90
app.py CHANGED
@@ -18,6 +18,8 @@ from weasyprint import HTML, CSS
18
  from weasyprint.text.fonts import FontConfiguration
19
  from pdf2image import convert_from_bytes
20
  import gradio as gr
 
 
21
 
22
  # Create a global progress tracker
23
  class ProgressTracker:
@@ -556,6 +558,78 @@ def get_available_models(api_key):
556
  print(f"Error fetching models: {str(e)}")
557
  return ["custom"] + OPENROUTER_MODELS
558
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  def process_multiple_dashboards(api_key, files, language_code="it", goal_description=None, num_sections=4, model_name=DEFAULT_MODEL, custom_model=None):
560
  """Process multiple dashboard files (PDF/images) and create individual and comparative reports."""
561
  # Start progress tracking
@@ -621,38 +695,10 @@ def process_multiple_dashboards(api_key, files, language_code="it", goal_descrip
621
  else:
622
  print(f"❌ Analysis of dashboard {i+1} failed.")
623
 
624
- # For Hugging Face Space: use tmp directory for file output
625
- tmp_dir = "/tmp"
626
- if not os.path.exists(tmp_dir):
627
- os.makedirs(tmp_dir, exist_ok=True)
628
-
629
- # Step 3: Generate output files
630
- progress_tracker.update(80, "Generating output files...")
631
- timestamp = time.strftime("%Y%m%d_%H%M%S")
632
- output_files = []
633
-
634
- # Create individual report files
635
- for i, report in enumerate(individual_reports):
636
- file_progress = 80 + (i / len(individual_reports) * 10) # 10% for creating files
637
- progress_tracker.update(file_progress, f"Creating files for dashboard {i+1}...")
638
-
639
- md_filename = os.path.join(tmp_dir, f"dashboard_{i+1}_{language['code']}_{timestamp}.md")
640
- pdf_filename = os.path.join(tmp_dir, f"dashboard_{i+1}_{language['code']}_{timestamp}.pdf")
641
-
642
- with open(md_filename, 'w', encoding='utf-8') as f:
643
- f.write(report)
644
- output_files.append(md_filename)
645
-
646
- try:
647
- pdf_path = markdown_to_pdf(report, pdf_filename, language)
648
- output_files.append(pdf_filename)
649
- except Exception as e:
650
- print(f"⚠️ Error converting dashboard {i+1} to PDF: {str(e)}")
651
-
652
- # If there are multiple dashboards, create a comparative report
653
  comparative_report = None
654
  if len(individual_reports) > 1:
655
- progress_tracker.update(90, "Creating comparative analysis...")
656
  print("\n" + "#"*60)
657
  print("Creating comparative analysis of all dashboards...")
658
 
@@ -667,34 +713,39 @@ def process_multiple_dashboards(api_key, files, language_code="it", goal_descrip
667
  language=language,
668
  goal_description=goal_description
669
  )
 
 
 
 
 
 
 
670
 
671
- # Save comparative report
672
- progress_tracker.update(95, "Saving comparative analysis files...")
673
- comparative_md = os.path.join(tmp_dir, f"comparative_analysis_{language['code']}_{timestamp}.md")
674
- comparative_pdf = os.path.join(tmp_dir, f"comparative_analysis_{language['code']}_{timestamp}.pdf")
675
-
676
- with open(comparative_md, 'w', encoding='utf-8') as f:
677
- f.write(comparative_report)
678
- output_files.append(comparative_md)
679
 
680
- try:
681
- pdf_path = markdown_to_pdf(comparative_report, comparative_pdf, language)
682
- output_files.append(comparative_pdf)
683
- except Exception as e:
684
- print(f"⚠️ Error converting comparative report to PDF: {str(e)}")
 
685
 
686
  # Complete progress tracking
687
  progress_tracker.update(100, "✅ Analysis completed successfully!")
688
  progress_tracker.end_processing()
689
 
690
- # Return the combined report content and all output files
691
  combined_content = "\n\n---\n\n".join(individual_reports)
692
  if len(individual_reports) > 1 and comparative_report:
693
  combined_content += f"\n\n{'='*80}\n\n# COMPARATIVE ANALYSIS\n\n{comparative_report}"
694
 
695
  return combined_content, output_files, "✅ Analysis completed successfully!"
696
 
697
- # Wrapper function for Gradio interface
698
  def process_dashboard(api_key, files, language_name, goal_description=None, num_sections=4, model_name=DEFAULT_MODEL, custom_model=None):
699
  """Process dashboard files (PDF/images) and generate reports (wrapper function for Gradio interface)."""
700
  # Start a thread to update progress
@@ -709,53 +760,86 @@ def process_dashboard(api_key, files, language_name, goal_description=None, num_
709
  language_code = lang_data['code']
710
  break
711
 
712
- # Process the uploaded files
 
 
 
 
 
 
 
 
 
 
 
 
 
713
  processed_files = []
714
  if files is not None:
715
- for file in files:
716
  try:
717
- # Handle different Gradio file formats
718
  file_path = None
719
- if isinstance(file, dict) and 'name' in file:
720
- # Newer Gradio File component format
721
- file_path = file['name']
 
 
 
 
722
  elif isinstance(file, str):
723
- # Older Gradio File component format
724
  file_path = file
725
  else:
726
  # Try to get the path from the file object
727
- if hasattr(file, 'name'):
728
- file_path = file.name
729
- elif hasattr(file, 'path'):
730
- file_path = file.path
731
 
732
- if file_path:
733
- # Determine file type
734
- file_type = get_file_type(file_path)
735
-
736
- if file_type == 'unknown':
737
- print(f"⚠️ Unsupported file type for {file_path}")
738
- continue
739
-
740
- # Read file data
 
 
 
 
 
 
 
 
741
  with open(file_path, 'rb') as f:
742
  file_data = f.read()
743
 
 
 
 
 
744
  processed_files.append((file_data, file_type))
745
- print(f"✅ Processed {file_path} as {file_type}")
746
- else:
747
- print(f"⚠️ Could not determine file path for uploaded file")
 
 
748
 
749
  except Exception as e:
750
- print(f"❌ Error processing uploaded file: {str(e)}")
751
  continue
752
 
753
  if not processed_files:
754
  error_message = "No valid files were uploaded or processed."
755
- progress_tracker.update(100, error_message)
756
  progress_tracker.end_processing()
757
  return None, None, error_message
758
 
 
 
759
  # Call the actual processing function
760
  try:
761
  combined_content, output_files, status = process_multiple_dashboards(
@@ -767,11 +851,28 @@ def process_dashboard(api_key, files, language_name, goal_description=None, num_
767
  model_name=model_name,
768
  custom_model=custom_model
769
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
770
  return combined_content, output_files, status
 
771
  except Exception as e:
772
  error_message = f"Error processing dashboards: {str(e)}"
773
- print(error_message)
774
- progress_tracker.update(100, error_message)
775
  progress_tracker.end_processing()
776
  return None, None, error_message
777
 
@@ -792,7 +893,7 @@ def refresh_models(api_key):
792
  print(f"Error refreshing models: {str(e)}")
793
  return gr.Dropdown(choices=["custom"] + OPENROUTER_MODELS, value=DEFAULT_MODEL)
794
 
795
- # Define the Gradio interface
796
  with gr.Blocks(title="Dashboard Narrator - Powered by OpenRouter.ai", theme=gr.themes.Soft()) as demo:
797
  gr.Markdown("""
798
  # 📊 Dashboard Narrator - Powered by OpenRouter.ai
@@ -805,28 +906,37 @@ with gr.Blocks(title="Dashboard Narrator - Powered by OpenRouter.ai", theme=gr.t
805
  - Support for PNG and JPG image analysis
806
  - Enhanced with Claude Sonnet 4 and Gemini 2.5 Flash models
807
  - Multi-format dashboard analysis capabilities
 
808
  """)
809
  with gr.Row():
810
  with gr.Column(scale=1):
811
- api_key = gr.Textbox(label="OpenRouter API Key", placeholder="Enter your OpenRouter API key...", type="password")
 
 
 
 
 
812
  refresh_btn = gr.Button("🔄 Refresh Available Models", size="sm")
813
 
814
  model_choice = gr.Dropdown(
815
  choices=["custom"] + OPENROUTER_MODELS,
816
  value=DEFAULT_MODEL,
817
- label="Select Model"
 
818
  )
819
 
820
  custom_model = gr.Textbox(
821
  label="Custom Model ID",
822
  placeholder="Enter custom model ID (e.g., anthropic/claude-3-opus:latest)...",
823
- visible=False
 
824
  )
825
 
826
  language = gr.Dropdown(
827
  choices=["Italiano", "English", "Français", "Español", "Deutsch"],
828
  value="English",
829
- label="Report Language"
 
830
  )
831
 
832
  num_sections = gr.Slider(
@@ -834,30 +944,40 @@ with gr.Blocks(title="Dashboard Narrator - Powered by OpenRouter.ai", theme=gr.t
834
  maximum=10,
835
  value=4,
836
  step=1,
837
- label="Number of Vertical Sections per Dashboard"
 
838
  )
839
 
840
  goal = gr.Textbox(
841
  label="Analysis Goal (optional)",
842
- placeholder="E.g., Analyze Q1 2024 sales KPIs..."
 
843
  )
844
 
845
  files = gr.File(
846
  label="Upload Dashboards (PDF, PNG, JPG)",
847
  file_types=[".pdf", ".png", ".jpg", ".jpeg"],
848
- file_count="multiple"
 
849
  )
850
 
851
- analyze_btn = gr.Button("🔍 Analyze Dashboards", variant="primary")
852
 
853
  with gr.Column(scale=2):
854
  with gr.Tab("Report"):
855
- output_md = gr.Markdown(label="Analysis Report", value="")
856
- with gr.Tab("Output Files"):
857
- output_files = gr.File(label="Download Files")
858
- output_status = gr.Textbox(label="Progress", placeholder="Upload dashboards and press Analyze...", interactive=False)
859
- # Progress component doesn't accept label in Gradio 5.21.0
860
- progress_bar = gr.Progress()
 
 
 
 
 
 
 
861
 
862
  # Handle model dropdown change
863
  model_choice.change(
@@ -873,13 +993,14 @@ with gr.Blocks(title="Dashboard Narrator - Powered by OpenRouter.ai", theme=gr.t
873
  outputs=model_choice,
874
  )
875
 
876
- # Handle analyze button
877
  analyze_btn.click(
878
  fn=process_dashboard,
879
  inputs=[api_key, files, language, goal, num_sections, model_choice, custom_model],
880
- outputs=[output_md, output_files, output_status]
 
881
  )
882
 
883
  # Launch the app
884
  if __name__ == "__main__":
885
- demo.launch()
 
18
  from weasyprint.text.fonts import FontConfiguration
19
  from pdf2image import convert_from_bytes
20
  import gradio as gr
21
+ import tempfile
22
+ import shutil
23
 
24
  # Create a global progress tracker
25
  class ProgressTracker:
 
558
  print(f"Error fetching models: {str(e)}")
559
  return ["custom"] + OPENROUTER_MODELS
560
 
561
+ # FIXED: Improved file handling for Gradio compatibility
562
+ def create_output_files(individual_reports, comparative_report, language, timestamp):
563
+ """Create output files with proper Gradio compatibility."""
564
+ output_files = []
565
+
566
+ try:
567
+ # Create a temporary directory that Gradio can access
568
+ temp_dir = tempfile.mkdtemp()
569
+ print(f"Created temporary directory: {temp_dir}")
570
+
571
+ # Create individual report files
572
+ for i, report in enumerate(individual_reports):
573
+ if report and report.strip(): # Only create files for valid reports
574
+ # Create markdown file
575
+ md_filename = os.path.join(temp_dir, f"dashboard_{i+1}_{language['code']}_{timestamp}.md")
576
+ try:
577
+ with open(md_filename, 'w', encoding='utf-8') as f:
578
+ f.write(report)
579
+ if os.path.exists(md_filename) and os.path.getsize(md_filename) > 0:
580
+ output_files.append(md_filename)
581
+ print(f"✅ Created markdown file: {md_filename}")
582
+ else:
583
+ print(f"⚠️ Failed to create valid markdown file for dashboard {i+1}")
584
+ except Exception as e:
585
+ print(f"❌ Error creating markdown file for dashboard {i+1}: {str(e)}")
586
+
587
+ # Create PDF file
588
+ pdf_filename = os.path.join(temp_dir, f"dashboard_{i+1}_{language['code']}_{timestamp}.pdf")
589
+ try:
590
+ pdf_path = markdown_to_pdf(report, pdf_filename, language)
591
+ if os.path.exists(pdf_filename) and os.path.getsize(pdf_filename) > 0:
592
+ output_files.append(pdf_filename)
593
+ print(f"✅ Created PDF file: {pdf_filename}")
594
+ else:
595
+ print(f"⚠️ Failed to create valid PDF file for dashboard {i+1}")
596
+ except Exception as e:
597
+ print(f"❌ Error creating PDF file for dashboard {i+1}: {str(e)}")
598
+
599
+ # Create comparative report if available
600
+ if comparative_report and comparative_report.strip():
601
+ # Create comparative markdown file
602
+ comparative_md = os.path.join(temp_dir, f"comparative_analysis_{language['code']}_{timestamp}.md")
603
+ try:
604
+ with open(comparative_md, 'w', encoding='utf-8') as f:
605
+ f.write(comparative_report)
606
+ if os.path.exists(comparative_md) and os.path.getsize(comparative_md) > 0:
607
+ output_files.append(comparative_md)
608
+ print(f"✅ Created comparative markdown file: {comparative_md}")
609
+ else:
610
+ print(f"⚠️ Failed to create valid comparative markdown file")
611
+ except Exception as e:
612
+ print(f"❌ Error creating comparative markdown file: {str(e)}")
613
+
614
+ # Create comparative PDF file
615
+ comparative_pdf = os.path.join(temp_dir, f"comparative_analysis_{language['code']}_{timestamp}.pdf")
616
+ try:
617
+ pdf_path = markdown_to_pdf(comparative_report, comparative_pdf, language)
618
+ if os.path.exists(comparative_pdf) and os.path.getsize(comparative_pdf) > 0:
619
+ output_files.append(comparative_pdf)
620
+ print(f"✅ Created comparative PDF file: {comparative_pdf}")
621
+ else:
622
+ print(f"⚠️ Failed to create valid comparative PDF file")
623
+ except Exception as e:
624
+ print(f"❌ Error creating comparative PDF file: {str(e)}")
625
+
626
+ print(f"Total output files created: {len(output_files)}")
627
+ return output_files
628
+
629
+ except Exception as e:
630
+ print(f"❌ Error in create_output_files: {str(e)}")
631
+ return []
632
+
633
  def process_multiple_dashboards(api_key, files, language_code="it", goal_description=None, num_sections=4, model_name=DEFAULT_MODEL, custom_model=None):
634
  """Process multiple dashboard files (PDF/images) and create individual and comparative reports."""
635
  # Start progress tracking
 
695
  else:
696
  print(f"❌ Analysis of dashboard {i+1} failed.")
697
 
698
+ # Step 3: Generate comparative report if multiple dashboards
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
699
  comparative_report = None
700
  if len(individual_reports) > 1:
701
+ progress_tracker.update(80, "Creating comparative analysis...")
702
  print("\n" + "#"*60)
703
  print("Creating comparative analysis of all dashboards...")
704
 
 
713
  language=language,
714
  goal_description=goal_description
715
  )
716
+
717
+ # Step 4: Create output files with improved handling
718
+ progress_tracker.update(90, "Creating output files...")
719
+ timestamp = time.strftime("%Y%m%d_%H%M%S")
720
+
721
+ try:
722
+ output_files = create_output_files(individual_reports, comparative_report, language, timestamp)
723
 
724
+ if not output_files:
725
+ error_msg = "No output files were created successfully."
726
+ progress_tracker.update(100, f"⚠️ {error_msg}")
727
+ progress_tracker.end_processing()
728
+ return None, None, error_msg
 
 
 
729
 
730
+ except Exception as e:
731
+ error_msg = f"Error creating output files: {str(e)}"
732
+ print(f"❌ {error_msg}")
733
+ progress_tracker.update(100, f"❌ {error_msg}")
734
+ progress_tracker.end_processing()
735
+ return None, None, error_msg
736
 
737
  # Complete progress tracking
738
  progress_tracker.update(100, "✅ Analysis completed successfully!")
739
  progress_tracker.end_processing()
740
 
741
+ # Return the combined report content and all output files
742
  combined_content = "\n\n---\n\n".join(individual_reports)
743
  if len(individual_reports) > 1 and comparative_report:
744
  combined_content += f"\n\n{'='*80}\n\n# COMPARATIVE ANALYSIS\n\n{comparative_report}"
745
 
746
  return combined_content, output_files, "✅ Analysis completed successfully!"
747
 
748
+ # FIXED: Improved wrapper function for Gradio interface
749
  def process_dashboard(api_key, files, language_name, goal_description=None, num_sections=4, model_name=DEFAULT_MODEL, custom_model=None):
750
  """Process dashboard files (PDF/images) and generate reports (wrapper function for Gradio interface)."""
751
  # Start a thread to update progress
 
760
  language_code = lang_data['code']
761
  break
762
 
763
+ # Validate inputs
764
+ if not api_key or not api_key.strip():
765
+ error_message = "API key is required."
766
+ progress_tracker.update(100, f"❌ {error_message}")
767
+ progress_tracker.end_processing()
768
+ return None, None, error_message
769
+
770
+ if not files or len(files) == 0:
771
+ error_message = "No files uploaded."
772
+ progress_tracker.update(100, f"❌ {error_message}")
773
+ progress_tracker.end_processing()
774
+ return None, None, error_message
775
+
776
+ # Process the uploaded files with improved handling
777
  processed_files = []
778
  if files is not None:
779
+ for i, file in enumerate(files):
780
  try:
781
+ # Handle different Gradio file formats more robustly
782
  file_path = None
783
+
784
+ if isinstance(file, dict):
785
+ # Handle new Gradio File component format
786
+ if 'name' in file:
787
+ file_path = file['name']
788
+ elif 'path' in file:
789
+ file_path = file['path']
790
  elif isinstance(file, str):
791
+ # Handle string file paths
792
  file_path = file
793
  else:
794
  # Try to get the path from the file object
795
+ for attr in ['name', 'path', 'file_path']:
796
+ if hasattr(file, attr):
797
+ file_path = getattr(file, attr)
798
+ break
799
 
800
+ if not file_path:
801
+ print(f"⚠️ Could not determine file path for uploaded file {i+1}")
802
+ continue
803
+
804
+ if not os.path.exists(file_path):
805
+ print(f"⚠️ File does not exist: {file_path}")
806
+ continue
807
+
808
+ # Determine file type
809
+ file_type = get_file_type(file_path)
810
+
811
+ if file_type == 'unknown':
812
+ print(f"⚠️ Unsupported file type for {file_path}")
813
+ continue
814
+
815
+ # Read file data
816
+ try:
817
  with open(file_path, 'rb') as f:
818
  file_data = f.read()
819
 
820
+ if len(file_data) == 0:
821
+ print(f"⚠️ Empty file: {file_path}")
822
+ continue
823
+
824
  processed_files.append((file_data, file_type))
825
+ print(f"✅ Processed {file_path} as {file_type} ({len(file_data)} bytes)")
826
+
827
+ except Exception as e:
828
+ print(f"❌ Error reading file {file_path}: {str(e)}")
829
+ continue
830
 
831
  except Exception as e:
832
+ print(f"❌ Error processing uploaded file {i+1}: {str(e)}")
833
  continue
834
 
835
  if not processed_files:
836
  error_message = "No valid files were uploaded or processed."
837
+ progress_tracker.update(100, f"❌ {error_message}")
838
  progress_tracker.end_processing()
839
  return None, None, error_message
840
 
841
+ print(f"Successfully processed {len(processed_files)} files for analysis")
842
+
843
  # Call the actual processing function
844
  try:
845
  combined_content, output_files, status = process_multiple_dashboards(
 
851
  model_name=model_name,
852
  custom_model=custom_model
853
  )
854
+
855
+ # Validate output files exist and are accessible
856
+ if output_files:
857
+ valid_files = []
858
+ for file_path in output_files:
859
+ if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
860
+ valid_files.append(file_path)
861
+ else:
862
+ print(f"⚠️ Output file not found or empty: {file_path}")
863
+
864
+ if valid_files:
865
+ print(f"✅ Returning {len(valid_files)} valid output files")
866
+ return combined_content, valid_files, status
867
+ else:
868
+ return combined_content, None, "Analysis completed but no downloadable files were created."
869
+
870
  return combined_content, output_files, status
871
+
872
  except Exception as e:
873
  error_message = f"Error processing dashboards: {str(e)}"
874
+ print(f"❌ {error_message}")
875
+ progress_tracker.update(100, f"❌ {error_message}")
876
  progress_tracker.end_processing()
877
  return None, None, error_message
878
 
 
893
  print(f"Error refreshing models: {str(e)}")
894
  return gr.Dropdown(choices=["custom"] + OPENROUTER_MODELS, value=DEFAULT_MODEL)
895
 
896
+ # Define the Gradio interface with improved error handling
897
  with gr.Blocks(title="Dashboard Narrator - Powered by OpenRouter.ai", theme=gr.themes.Soft()) as demo:
898
  gr.Markdown("""
899
  # 📊 Dashboard Narrator - Powered by OpenRouter.ai
 
906
  - Support for PNG and JPG image analysis
907
  - Enhanced with Claude Sonnet 4 and Gemini 2.5 Flash models
908
  - Multi-format dashboard analysis capabilities
909
+ - Improved file download functionality
910
  """)
911
  with gr.Row():
912
  with gr.Column(scale=1):
913
+ api_key = gr.Textbox(
914
+ label="OpenRouter API Key",
915
+ placeholder="Enter your OpenRouter API key...",
916
+ type="password",
917
+ info="Required - Get your API key from OpenRouter.ai"
918
+ )
919
  refresh_btn = gr.Button("🔄 Refresh Available Models", size="sm")
920
 
921
  model_choice = gr.Dropdown(
922
  choices=["custom"] + OPENROUTER_MODELS,
923
  value=DEFAULT_MODEL,
924
+ label="Select Model",
925
+ info="Choose an AI model for analysis"
926
  )
927
 
928
  custom_model = gr.Textbox(
929
  label="Custom Model ID",
930
  placeholder="Enter custom model ID (e.g., anthropic/claude-3-opus:latest)...",
931
+ visible=False,
932
+ info="Only shown when 'custom' is selected above"
933
  )
934
 
935
  language = gr.Dropdown(
936
  choices=["Italiano", "English", "Français", "Español", "Deutsch"],
937
  value="English",
938
+ label="Report Language",
939
+ info="Language for the generated reports"
940
  )
941
 
942
  num_sections = gr.Slider(
 
944
  maximum=10,
945
  value=4,
946
  step=1,
947
+ label="Number of Vertical Sections per Dashboard",
948
+ info="How many sections to divide each dashboard into for analysis"
949
  )
950
 
951
  goal = gr.Textbox(
952
  label="Analysis Goal (optional)",
953
+ placeholder="E.g., Analyze Q1 2024 sales KPIs...",
954
+ info="Specify what you want to focus on in the analysis"
955
  )
956
 
957
  files = gr.File(
958
  label="Upload Dashboards (PDF, PNG, JPG)",
959
  file_types=[".pdf", ".png", ".jpg", ".jpeg"],
960
+ file_count="multiple",
961
+ info="Upload one or more dashboard files for analysis"
962
  )
963
 
964
+ analyze_btn = gr.Button("🔍 Analyze Dashboards", variant="primary", size="lg")
965
 
966
  with gr.Column(scale=2):
967
  with gr.Tab("Report"):
968
+ output_md = gr.Markdown(label="Analysis Report", value="Upload dashboards and click Analyze to get started...")
969
+ with gr.Tab("Download Files"):
970
+ output_files = gr.File(
971
+ label="Download Generated Reports",
972
+ file_count="multiple",
973
+ info="Generated markdown and PDF files will appear here after analysis"
974
+ )
975
+ output_status = gr.Textbox(
976
+ label="Status",
977
+ placeholder="Upload dashboards and press Analyze to begin...",
978
+ interactive=False,
979
+ info="Analysis progress and status updates"
980
+ )
981
 
982
  # Handle model dropdown change
983
  model_choice.change(
 
993
  outputs=model_choice,
994
  )
995
 
996
+ # Handle analyze button with improved error handling
997
  analyze_btn.click(
998
  fn=process_dashboard,
999
  inputs=[api_key, files, language, goal, num_sections, model_choice, custom_model],
1000
+ outputs=[output_md, output_files, output_status],
1001
+ show_progress=True
1002
  )
1003
 
1004
  # Launch the app
1005
  if __name__ == "__main__":
1006
+ demo.launch(share=True, show_error=True)