fedec65 commited on
Commit
c26d87b
Β·
verified Β·
1 Parent(s): 24a10ca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -58
app.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
  Dashboard Narrator - Powered by OpenRouter.ai
3
- A tool to analyze dashboard PDFs and generate comprehensive reports.
4
  """
5
 
6
  # Import required libraries
@@ -168,15 +168,17 @@ SUPPORTED_LANGUAGES = {
168
  }
169
  }
170
 
171
- # OpenRouter models
172
- DEFAULT_MODEL = "anthropic/claude-3.7-sonnet"
173
  OPENROUTER_MODELS = [
 
174
  "anthropic/claude-3.7-sonnet",
175
  "openai/gpt-4.1",
176
  "openai/o4-mini-high",
177
  "openai/gpt-4.1-mini",
178
- "moonshotai/kimi-vl-a3b-thinking:free",
179
  "google/gemini-2.5-pro-preview-03-25",
 
180
  "microsoft/phi-4-multimodal-instruct",
181
  "qwen/qwen2.5-vl-72b-instruct:free",
182
  "openrouter/optimus-alpha"
@@ -197,6 +199,39 @@ def extract_text_from_pdf(pdf_bytes):
197
  print(f"Error extracting text from PDF: {str(e)}")
198
  return ""
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  def divide_image_vertically(image, num_sections):
201
  """Divide an image vertically into sections."""
202
  width, height = image.size
@@ -256,17 +291,31 @@ def analyze_dashboard_section(client, model, section_number, total_sections, ima
256
  Focus exclusively on the visible section. Don't reference or speculate about unseen dashboard elements.\n
257
  Answer completely in {language['name']}.\n\n
258
  # Text extracted from the complete dashboard:\n
259
- {full_text[:10000]}
260
 
261
  # Image of this dashboard section:
262
  [BASE64 IMAGE: {encoded_image[:20]}...]
263
  This is a dashboard visualization showing various metrics and charts. Please analyze the content visible in this image.
264
  """
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  try:
267
  response = client.messages_create(
268
  model=model,
269
- messages=[{"role": "user", "content": section_prompt}],
270
  system=language['system_prompt'],
271
  temperature=0.1,
272
  max_tokens=10000
@@ -296,7 +345,7 @@ def create_comprehensive_report(client, model, section_analyses, full_text, lang
296
  8 Appendix: Monitoring Improvements - Move the monitoring suggestions to an appendix unless they're a primary focus\n\n
297
  Integrate information from all sections to create a coherent and complete report.\n\n
298
  # Text extracted from the complete dashboard:\n
299
- {full_text[:10000]}
300
  """
301
  try:
302
  response = client.messages_create(
@@ -376,8 +425,8 @@ def markdown_to_pdf(markdown_content, output_filename, language):
376
  print(f"PDF created successfully: {output_filename}")
377
  return output_filename
378
 
379
- def analyze_vertical_dashboard(client, model, pdf_bytes, language, goal_description=None, num_sections=4, dashboard_index=None):
380
- """Analyze a vertical dashboard by dividing it into sections."""
381
  dashboard_marker = f" {dashboard_index}" if dashboard_index is not None else ""
382
  total_dashboards = progress_tracker.total_dashboards if hasattr(progress_tracker, 'total_dashboards') else 1
383
  dashboard_progress_base = ((dashboard_index - 1) / total_dashboards * 100) if dashboard_index is not None else 0
@@ -386,23 +435,40 @@ def analyze_vertical_dashboard(client, model, pdf_bytes, language, goal_descript
386
  progress_tracker.update(dashboard_progress_base, f"πŸ–ΌοΈ Analyzing dashboard{dashboard_marker}...")
387
  print(f"πŸ–ΌοΈ Analyzing dashboard{dashboard_marker}...")
388
 
389
- progress_tracker.update(dashboard_progress_base + dashboard_progress_step * 0.1, f"πŸ“„ Extracting text from dashboard{dashboard_marker}...")
390
- print(f"πŸ“„ Extracting full text from PDF...")
391
- full_text = extract_text_from_pdf(pdf_bytes)
392
- if not full_text or len(full_text.strip()) < 100:
393
- print("⚠️ Limited text extracted from PDF. Analysis will rely primarily on images.")
 
 
 
 
 
394
  else:
395
- print(f"βœ… Extracted {len(full_text)} characters of text from PDF.")
396
 
 
397
  progress_tracker.update(dashboard_progress_base + dashboard_progress_step * 0.2, f"πŸ–ΌοΈ Converting dashboard{dashboard_marker} to images...")
398
- print("πŸ–ΌοΈ Converting PDF to images...")
 
399
  try:
400
- pdf_images = convert_from_bytes(pdf_bytes, dpi=150)
401
- if not pdf_images:
402
- print("❌ Unable to convert PDF to images.")
403
- return None, "Error: Unable to convert PDF to images."
404
- print(f"βœ… PDF converted to {len(pdf_images)} image pages.")
405
- main_image = pdf_images[0]
 
 
 
 
 
 
 
 
 
 
406
  print(f"Main image size: {main_image.width}x{main_image.height} pixels")
407
 
408
  progress_tracker.update(dashboard_progress_base + dashboard_progress_step * 0.3, f"Dividing dashboard{dashboard_marker} into {num_sections} sections...")
@@ -410,7 +476,7 @@ def analyze_vertical_dashboard(client, model, pdf_bytes, language, goal_descript
410
  image_sections = divide_image_vertically(main_image, num_sections)
411
  print(f"βœ… Image divided into {len(image_sections)} sections.")
412
  except Exception as e:
413
- print(f"❌ Error converting or dividing PDF: {str(e)}")
414
  return None, f"Error: {str(e)}"
415
 
416
  section_analyses = []
@@ -490,11 +556,11 @@ def get_available_models(api_key):
490
  print(f"Error fetching models: {str(e)}")
491
  return ["custom"] + OPENROUTER_MODELS
492
 
493
- def process_multiple_dashboards(api_key, pdf_files, language_code="it", goal_description=None, num_sections=4, model_name=DEFAULT_MODEL, custom_model=None):
494
- """Process multiple dashboard PDFs and create individual and comparative reports."""
495
  # Start progress tracking
496
  progress_tracker.start_processing()
497
- progress_tracker.total_dashboards = len(pdf_files)
498
 
499
  # Step 1: Initialize language settings and API client
500
  progress_tracker.update(1, "Initializing analysis...")
@@ -531,16 +597,17 @@ def process_multiple_dashboards(api_key, pdf_files, language_code="it", goal_des
531
  individual_reports = []
532
  individual_analyses = []
533
 
534
- for i, pdf_bytes in enumerate(pdf_files):
535
- dashboard_progress_base = (i / len(pdf_files) * 80) # 80% of progress for dashboard analysis
536
- progress_tracker.update(dashboard_progress_base, f"Processing dashboard {i+1}/{len(pdf_files)}...")
537
  print(f"\n{'#'*60}")
538
- print(f"Processing dashboard {i+1}/{len(pdf_files)}...")
539
 
540
  report, analysis = analyze_vertical_dashboard(
541
  client=client,
542
  model=model,
543
- pdf_bytes=pdf_bytes,
 
544
  language=language,
545
  goal_description=goal_description,
546
  num_sections=num_sections,
@@ -628,8 +695,8 @@ def process_multiple_dashboards(api_key, pdf_files, language_code="it", goal_des
628
  return combined_content, output_files, "βœ… Analysis completed successfully!"
629
 
630
  # Wrapper function for Gradio interface
631
- def process_dashboard(api_key, pdf_files, language_name, goal_description=None, num_sections=4, model_name=DEFAULT_MODEL, custom_model=None):
632
- """Process dashboard PDFs and generate reports (wrapper function for Gradio interface)."""
633
  # Start a thread to update progress
634
  progress_thread = threading.Thread(target=update_progress)
635
  progress_thread.daemon = True
@@ -642,31 +709,58 @@ def process_dashboard(api_key, pdf_files, language_name, goal_description=None,
642
  language_code = lang_data['code']
643
  break
644
 
645
- # Get the PDF file bytes
646
- pdf_bytes_list = []
647
- if pdf_files is not None:
648
- for pdf_file in pdf_files:
649
- if isinstance(pdf_file, dict) and 'name' in pdf_file:
650
- # Handle newer Gradio File component format
651
- with open(pdf_file['name'], 'rb') as f:
652
- pdf_bytes_list.append(f.read())
653
- elif isinstance(pdf_file, str):
654
- # Handle older Gradio File component format
655
- with open(pdf_file, 'rb') as f:
656
- pdf_bytes_list.append(f.read())
657
- else:
658
- # Just try to handle whatever format we got
659
- try:
660
- if hasattr(pdf_file, 'read'):
661
- pdf_bytes_list.append(pdf_file.read())
662
- except Exception as e:
663
- print(f"Error reading PDF file: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
 
665
  # Call the actual processing function
666
  try:
667
  combined_content, output_files, status = process_multiple_dashboards(
668
  api_key=api_key,
669
- pdf_files=pdf_bytes_list,
670
  language_code=language_code,
671
  goal_description=goal_description,
672
  num_sections=num_sections,
@@ -703,10 +797,14 @@ with gr.Blocks(title="Dashboard Narrator - Powered by OpenRouter.ai", theme=gr.t
703
  gr.Markdown("""
704
  # πŸ“Š Dashboard Narrator - Powered by OpenRouter.ai
705
  Unlock the hidden stories in your dashboards!<br>
706
- Dashboard Narrator leverages advanced AI models through OpenRouter.ai to dissect your PDF reports,<br>
707
  analyze each segment with expert precision, and craft comprehensive insights in your preferred language.<br><br>
708
  Turn complex data visualizations into clear, strategic recommendations and uncover trends you might have missed.<br>
709
  From executive summaries to detailed breakdowns, get the full narrative behind your numbers in just a few clicks.<br><br>
 
 
 
 
710
  """)
711
  with gr.Row():
712
  with gr.Column(scale=1):
@@ -744,9 +842,9 @@ with gr.Blocks(title="Dashboard Narrator - Powered by OpenRouter.ai", theme=gr.t
744
  placeholder="E.g., Analyze Q1 2024 sales KPIs..."
745
  )
746
 
747
- pdf_files = gr.File(
748
- label="Upload Dashboards (PDF)",
749
- file_types=[".pdf"],
750
  file_count="multiple"
751
  )
752
 
@@ -778,7 +876,7 @@ with gr.Blocks(title="Dashboard Narrator - Powered by OpenRouter.ai", theme=gr.t
778
  # Handle analyze button
779
  analyze_btn.click(
780
  fn=process_dashboard,
781
- inputs=[api_key, pdf_files, language, goal, num_sections, model_choice, custom_model],
782
  outputs=[output_md, output_files, output_status]
783
  )
784
 
 
1
  """
2
  Dashboard Narrator - Powered by OpenRouter.ai
3
+ A tool to analyze dashboard PDFs and images and generate comprehensive reports.
4
  """
5
 
6
  # Import required libraries
 
168
  }
169
  }
170
 
171
+ # OpenRouter models - Updated with new models
172
+ DEFAULT_MODEL = "anthropic/claude-sonnet-4"
173
  OPENROUTER_MODELS = [
174
+ "anthropic/claude-sonnet-4",
175
  "anthropic/claude-3.7-sonnet",
176
  "openai/gpt-4.1",
177
  "openai/o4-mini-high",
178
  "openai/gpt-4.1-mini",
179
+ "google/gemini-2.5-flash-preview-05-20",
180
  "google/gemini-2.5-pro-preview-03-25",
181
+ "moonshotai/kimi-vl-a3b-thinking:free",
182
  "microsoft/phi-4-multimodal-instruct",
183
  "qwen/qwen2.5-vl-72b-instruct:free",
184
  "openrouter/optimus-alpha"
 
199
  print(f"Error extracting text from PDF: {str(e)}")
200
  return ""
201
 
202
+ def get_file_type(file_path):
203
+ """Determine the file type based on file extension."""
204
+ if file_path.lower().endswith('.pdf'):
205
+ return 'pdf'
206
+ elif file_path.lower().endswith(('.png', '.jpg', '.jpeg')):
207
+ return 'image'
208
+ else:
209
+ return 'unknown'
210
+
211
+ def load_image_from_file(file_path):
212
+ """Load an image from file path."""
213
+ try:
214
+ image = Image.open(file_path)
215
+ # Convert to RGB if necessary
216
+ if image.mode != 'RGB':
217
+ image = image.convert('RGB')
218
+ return image
219
+ except Exception as e:
220
+ print(f"Error loading image from {file_path}: {str(e)}")
221
+ return None
222
+
223
+ def load_image_from_bytes(image_bytes):
224
+ """Load an image from bytes."""
225
+ try:
226
+ image = Image.open(io.BytesIO(image_bytes))
227
+ # Convert to RGB if necessary
228
+ if image.mode != 'RGB':
229
+ image = image.convert('RGB')
230
+ return image
231
+ except Exception as e:
232
+ print(f"Error loading image from bytes: {str(e)}")
233
+ return None
234
+
235
  def divide_image_vertically(image, num_sections):
236
  """Divide an image vertically into sections."""
237
  width, height = image.size
 
291
  Focus exclusively on the visible section. Don't reference or speculate about unseen dashboard elements.\n
292
  Answer completely in {language['name']}.\n\n
293
  # Text extracted from the complete dashboard:\n
294
+ {full_text[:10000] if full_text else "No text available for this image."}
295
 
296
  # Image of this dashboard section:
297
  [BASE64 IMAGE: {encoded_image[:20]}...]
298
  This is a dashboard visualization showing various metrics and charts. Please analyze the content visible in this image.
299
  """
300
 
301
+ # Create message with image for vision models
302
+ message_content = [
303
+ {
304
+ "type": "text",
305
+ "text": section_prompt
306
+ },
307
+ {
308
+ "type": "image_url",
309
+ "image_url": {
310
+ "url": f"data:image/png;base64,{encoded_image}"
311
+ }
312
+ }
313
+ ]
314
+
315
  try:
316
  response = client.messages_create(
317
  model=model,
318
+ messages=[{"role": "user", "content": message_content}],
319
  system=language['system_prompt'],
320
  temperature=0.1,
321
  max_tokens=10000
 
345
  8 Appendix: Monitoring Improvements - Move the monitoring suggestions to an appendix unless they're a primary focus\n\n
346
  Integrate information from all sections to create a coherent and complete report.\n\n
347
  # Text extracted from the complete dashboard:\n
348
+ {full_text[:10000] if full_text else "No text available for this image."}
349
  """
350
  try:
351
  response = client.messages_create(
 
425
  print(f"PDF created successfully: {output_filename}")
426
  return output_filename
427
 
428
+ def analyze_vertical_dashboard(client, model, file_data, file_type, language, goal_description=None, num_sections=4, dashboard_index=None):
429
+ """Analyze a vertical dashboard by dividing it into sections. Supports both PDF and image files."""
430
  dashboard_marker = f" {dashboard_index}" if dashboard_index is not None else ""
431
  total_dashboards = progress_tracker.total_dashboards if hasattr(progress_tracker, 'total_dashboards') else 1
432
  dashboard_progress_base = ((dashboard_index - 1) / total_dashboards * 100) if dashboard_index is not None else 0
 
435
  progress_tracker.update(dashboard_progress_base, f"πŸ–ΌοΈ Analyzing dashboard{dashboard_marker}...")
436
  print(f"πŸ–ΌοΈ Analyzing dashboard{dashboard_marker}...")
437
 
438
+ # Extract text if it's a PDF
439
+ full_text = ""
440
+ if file_type == 'pdf':
441
+ progress_tracker.update(dashboard_progress_base + dashboard_progress_step * 0.1, f"πŸ“„ Extracting text from dashboard{dashboard_marker}...")
442
+ print(f"πŸ“„ Extracting full text from PDF...")
443
+ full_text = extract_text_from_pdf(file_data)
444
+ if not full_text or len(full_text.strip()) < 100:
445
+ print("⚠️ Limited text extracted from PDF. Analysis will rely primarily on images.")
446
+ else:
447
+ print(f"βœ… Extracted {len(full_text)} characters of text from PDF.")
448
  else:
449
+ print("πŸ“„ Image file detected - no text extraction needed.")
450
 
451
+ # Convert to image(s)
452
  progress_tracker.update(dashboard_progress_base + dashboard_progress_step * 0.2, f"πŸ–ΌοΈ Converting dashboard{dashboard_marker} to images...")
453
+ print("πŸ–ΌοΈ Processing image...")
454
+
455
  try:
456
+ if file_type == 'pdf':
457
+ # Convert PDF to images
458
+ pdf_images = convert_from_bytes(file_data, dpi=150)
459
+ if not pdf_images:
460
+ print("❌ Unable to convert PDF to images.")
461
+ return None, "Error: Unable to convert PDF to images."
462
+ print(f"βœ… PDF converted to {len(pdf_images)} image pages.")
463
+ main_image = pdf_images[0]
464
+ else:
465
+ # Load image directly
466
+ main_image = load_image_from_bytes(file_data)
467
+ if main_image is None:
468
+ print("❌ Unable to load image.")
469
+ return None, "Error: Unable to load image."
470
+ print(f"βœ… Image loaded successfully.")
471
+
472
  print(f"Main image size: {main_image.width}x{main_image.height} pixels")
473
 
474
  progress_tracker.update(dashboard_progress_base + dashboard_progress_step * 0.3, f"Dividing dashboard{dashboard_marker} into {num_sections} sections...")
 
476
  image_sections = divide_image_vertically(main_image, num_sections)
477
  print(f"βœ… Image divided into {len(image_sections)} sections.")
478
  except Exception as e:
479
+ print(f"❌ Error processing image: {str(e)}")
480
  return None, f"Error: {str(e)}"
481
 
482
  section_analyses = []
 
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
562
  progress_tracker.start_processing()
563
+ progress_tracker.total_dashboards = len(files)
564
 
565
  # Step 1: Initialize language settings and API client
566
  progress_tracker.update(1, "Initializing analysis...")
 
597
  individual_reports = []
598
  individual_analyses = []
599
 
600
+ for i, (file_data, file_type) in enumerate(files):
601
+ dashboard_progress_base = (i / len(files) * 80) # 80% of progress for dashboard analysis
602
+ progress_tracker.update(dashboard_progress_base, f"Processing dashboard {i+1}/{len(files)}...")
603
  print(f"\n{'#'*60}")
604
+ print(f"Processing dashboard {i+1}/{len(files)} (Type: {file_type})...")
605
 
606
  report, analysis = analyze_vertical_dashboard(
607
  client=client,
608
  model=model,
609
+ file_data=file_data,
610
+ file_type=file_type,
611
  language=language,
612
  goal_description=goal_description,
613
  num_sections=num_sections,
 
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
701
  progress_thread = threading.Thread(target=update_progress)
702
  progress_thread.daemon = True
 
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(
762
  api_key=api_key,
763
+ files=processed_files,
764
  language_code=language_code,
765
  goal_description=goal_description,
766
  num_sections=num_sections,
 
797
  gr.Markdown("""
798
  # πŸ“Š Dashboard Narrator - Powered by OpenRouter.ai
799
  Unlock the hidden stories in your dashboards!<br>
800
+ Dashboard Narrator leverages advanced AI models through OpenRouter.ai to dissect your PDF reports and images,<br>
801
  analyze each segment with expert precision, and craft comprehensive insights in your preferred language.<br><br>
802
  Turn complex data visualizations into clear, strategic recommendations and uncover trends you might have missed.<br>
803
  From executive summaries to detailed breakdowns, get the full narrative behind your numbers in just a few clicks.<br><br>
804
+ **✨ New Features:**
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):
 
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
 
 
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