redxican commited on
Commit
359f87d
·
verified ·
1 Parent(s): f8e5331

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +208 -33
app.py CHANGED
@@ -2,6 +2,14 @@
2
  """
3
  Turbo Air Viewer - Equipment Specification Database Viewer
4
  Enhanced version with product image extraction and display
 
 
 
 
 
 
 
 
5
  """
6
 
7
  import streamlit as st
@@ -20,6 +28,12 @@ from urllib.parse import quote
20
  from PIL import Image
21
  import fitz # PyMuPDF
22
  import tempfile
 
 
 
 
 
 
23
 
24
  # Streamlit page config MUST be first
25
  st.set_page_config(
@@ -306,7 +320,7 @@ def extract_pdf_thumbnail(pdf_url, model_name, max_width=300, max_height=400):
306
  return st.session_state.product_images[cache_key]
307
 
308
  try:
309
- # Download PDF to temporary file
310
  response = requests.get(pdf_url, timeout=30)
311
  if response.status_code == 200:
312
  with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
@@ -314,13 +328,12 @@ def extract_pdf_thumbnail(pdf_url, model_name, max_width=300, max_height=400):
314
  tmp_path = tmp_file.name
315
 
316
  # Open PDF and extract first page
317
- pdf_document = fitz.Document(tmp_path)
318
  first_page = pdf_document[0]
319
 
320
  # Render page as image (2x resolution for better quality)
321
  mat = fitz.Matrix(2, 2)
322
- display_list = first_page.get_displaylist()
323
- pix = display_list.get_pixmap(matrix=mat)
324
 
325
  # Convert to PIL Image
326
  img_data = pix.tobytes("png")
@@ -358,7 +371,7 @@ def extract_pdf_thumbnail(pdf_url, model_name, max_width=300, max_height=400):
358
  return img_base64
359
 
360
  except Exception as e:
361
- st.warning(f"Could not extract image for {model_name}: {str(e)}")
362
  return None
363
 
364
  # Cache functions
@@ -575,6 +588,141 @@ def export_bookmarked_models():
575
  df = pd.DataFrame(export_data)
576
  return df.to_csv(index=False)
577
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  def display_pdf_preview(file_path, model_name):
579
  """Display PDF inline in Streamlit app - optimized for HuggingFace Spaces"""
580
 
@@ -697,19 +845,12 @@ def get_high_accuracy_models(limit=8):
697
 
698
  return high_accuracy[:limit]
699
 
700
- # MAIN UI
701
- st.title("❄️ Turbo Air Equipment Viewer")
702
- st.caption("Professional Equipment Specification Database")
703
-
704
- # Get all models
705
- all_models = get_all_models()
706
-
707
- if not all_models:
708
- st.error("⚠️ No data found in database. Please ensure turbo_air_db.sqlite is available.")
709
- st.stop()
710
-
711
- # Bookmarks section
712
- if st.session_state.bookmarked_models:
713
  st.markdown("### 📌 Bookmarked Models")
714
 
715
  view_col1, view_col2, view_col3 = st.columns([2, 1, 1])
@@ -717,14 +858,11 @@ if st.session_state.bookmarked_models:
717
  st.markdown(f"**{len(st.session_state.bookmarked_models)} models selected**")
718
 
719
  with view_col3:
720
- # Toggle for text-only view
721
- if st.button(
722
- "📷 Show Images" if st.session_state.text_only_view else "📝 Text Only",
723
- key="toggle_view",
724
- use_container_width=True
725
- ):
726
  st.session_state.text_only_view = not st.session_state.text_only_view
727
- st.rerun()
728
 
729
  # Display bookmarked models with or without images
730
  if st.session_state.text_only_view:
@@ -770,11 +908,12 @@ if st.session_state.bookmarked_models:
770
  pdf_filename = model_data['file_path'].replace('\\', '/').split('/')[-1]
771
  pdf_url = f"https://huggingface.co/spaces/TurboAir/TurboAirViewer/resolve/main/pdfs/{pdf_filename}"
772
 
773
- # Show loading spinner while extracting image
774
- with st.spinner(f"Loading image for {model}..."):
775
- img_base64 = extract_pdf_thumbnail(pdf_url, model)
776
 
777
  if img_base64:
 
778
  st.markdown(
779
  f'<img src="data:image/png;base64,{img_base64}" '
780
  f'style="width:100%; max-height:200px; object-fit:contain;" '
@@ -782,7 +921,18 @@ if st.session_state.bookmarked_models:
782
  unsafe_allow_html=True
783
  )
784
  else:
785
- st.info("📄 No preview available")
 
 
 
 
 
 
 
 
 
 
 
786
 
787
  # Model info
788
  st.markdown(f"**{model}**")
@@ -797,7 +947,7 @@ if st.session_state.bookmarked_models:
797
 
798
  # Export section
799
  st.markdown("---")
800
- export_col1, export_col2 = st.columns(2)
801
 
802
  with export_col1:
803
  csv_data = export_bookmarked_models()
@@ -811,12 +961,37 @@ if st.session_state.bookmarked_models:
811
  )
812
 
813
  with export_col2:
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  if st.button("🗑️ Clear All", use_container_width=True):
815
  st.session_state.bookmarked_models = []
816
  st.session_state.product_images = {} # Clear image cache too
817
  st.rerun()
818
- else:
819
- st.info("📌 No models bookmarked yet. Select models to create your custom list!")
 
 
 
 
 
 
 
 
 
 
 
 
820
 
821
  # Main content area
822
  col1, col2 = st.columns([1, 3])
@@ -826,8 +1001,8 @@ with col1:
826
  st.write("• View PDF spec sheets")
827
  st.write("• Bookmark models for lists")
828
  st.write("• Toggle image/text view")
 
829
  st.write("• Google search finds prices")
830
- st.write("• Export includes all specs")
831
 
832
  with col2:
833
  st.markdown('### 🔍 Model Search')
 
2
  """
3
  Turbo Air Viewer - Equipment Specification Database Viewer
4
  Enhanced version with product image extraction and display
5
+
6
+ Required dependencies (add to requirements.txt):
7
+ - streamlit
8
+ - pandas
9
+ - requests
10
+ - Pillow
11
+ - PyMuPDF
12
+ - reportlab
13
  """
14
 
15
  import streamlit as st
 
28
  from PIL import Image
29
  import fitz # PyMuPDF
30
  import tempfile
31
+ from reportlab.lib import colors
32
+ from reportlab.lib.pagesizes import letter, landscape
33
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
34
+ from reportlab.lib.units import inch
35
+ from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image as RLImage, PageBreak
36
+ from reportlab.lib.enums import TA_CENTER
37
 
38
  # Streamlit page config MUST be first
39
  st.set_page_config(
 
320
  return st.session_state.product_images[cache_key]
321
 
322
  try:
323
+ # Download PDF to temporary file (silently)
324
  response = requests.get(pdf_url, timeout=30)
325
  if response.status_code == 200:
326
  with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
 
328
  tmp_path = tmp_file.name
329
 
330
  # Open PDF and extract first page
331
+ pdf_document = fitz.open(tmp_path)
332
  first_page = pdf_document[0]
333
 
334
  # Render page as image (2x resolution for better quality)
335
  mat = fitz.Matrix(2, 2)
336
+ pix = first_page.get_pixmap(matrix=mat)
 
337
 
338
  # Convert to PIL Image
339
  img_data = pix.tobytes("png")
 
371
  return img_base64
372
 
373
  except Exception as e:
374
+ # Silently fail - don't show warnings
375
  return None
376
 
377
  # Cache functions
 
588
  df = pd.DataFrame(export_data)
589
  return df.to_csv(index=False)
590
 
591
+ def export_bookmarked_models_pdf():
592
+ """Export bookmarked models to PDF with images and specifications"""
593
+ if not st.session_state.bookmarked_models:
594
+ return None
595
+
596
+ # Create PDF in memory
597
+ buffer = io.BytesIO()
598
+ doc = SimpleDocTemplate(buffer, pagesize=landscape(letter),
599
+ topMargin=0.5*inch, bottomMargin=0.5*inch,
600
+ leftMargin=0.5*inch, rightMargin=0.5*inch)
601
+
602
+ # Container for the 'Flowable' objects
603
+ elements = []
604
+
605
+ # Styles
606
+ styles = getSampleStyleSheet()
607
+ title_style = ParagraphStyle(
608
+ 'CustomTitle',
609
+ parent=styles['Heading1'],
610
+ fontSize=24,
611
+ textColor=colors.HexColor('#4CAF50'),
612
+ spaceAfter=30,
613
+ alignment=TA_CENTER
614
+ )
615
+
616
+ model_title_style = ParagraphStyle(
617
+ 'ModelTitle',
618
+ parent=styles['Heading2'],
619
+ fontSize=16,
620
+ textColor=colors.HexColor('#333333'),
621
+ spaceAfter=12
622
+ )
623
+
624
+ # Title
625
+ elements.append(Paragraph("Turbo Air Equipment Selection Report", title_style))
626
+ elements.append(Paragraph(f"Generated: {datetime.now().strftime('%B %d, %Y at %I:%M %p')}",
627
+ styles['Normal']))
628
+ elements.append(Spacer(1, 0.5*inch))
629
+
630
+ # Process each bookmarked model
631
+ for idx, model in enumerate(st.session_state.bookmarked_models):
632
+ if idx > 0:
633
+ elements.append(PageBreak())
634
+
635
+ model_data = get_model_data(model)
636
+ if not model_data:
637
+ continue
638
+
639
+ # Model header
640
+ elements.append(Paragraph(f"{model} - {get_product_type(model)}", model_title_style))
641
+
642
+ # Try to get product image
643
+ if model_data.get('file_path'):
644
+ pdf_filename = model_data['file_path'].replace('\\', '/').split('/')[-1]
645
+ pdf_url = f"https://huggingface.co/spaces/TurboAir/TurboAirViewer/resolve/main/pdfs/{pdf_filename}"
646
+
647
+ # Get cached image or extract it
648
+ cache_key = f"thumb_{model}"
649
+ img_base64 = st.session_state.product_images.get(cache_key)
650
+
651
+ if not img_base64:
652
+ img_base64 = extract_pdf_thumbnail(pdf_url, model, max_width=200, max_height=250)
653
+
654
+ if img_base64:
655
+ # Convert base64 to image for PDF
656
+ img_data = base64.b64decode(img_base64)
657
+ img = RLImage(io.BytesIO(img_data), width=2*inch, height=2.5*inch)
658
+ elements.append(img)
659
+ elements.append(Spacer(1, 0.2*inch))
660
+
661
+ # Specifications table
662
+ specs = model_data['data'].get('specs', {})
663
+ specs = clean_spec_data(specs)
664
+
665
+ # Create specifications data for table
666
+ spec_data = [['Specification', 'Value']]
667
+
668
+ if specs.get('voltage') and specs.get('voltage') != 'N/A':
669
+ spec_data.append(['Voltage', specs['voltage']])
670
+ if specs.get('amperage') and specs.get('amperage') != 'N/A':
671
+ spec_data.append(['Amperage', specs['amperage']])
672
+ if specs.get('phase') and specs.get('phase') != 'N/A':
673
+ spec_data.append(['Phase', specs['phase']])
674
+ if specs.get('frequency') and specs.get('frequency') != 'N/A':
675
+ spec_data.append(['Frequency', specs['frequency']])
676
+ if specs.get('dimensions') and specs.get('dimensions') != 'N/A':
677
+ spec_data.append(['Dimensions', specs['dimensions']])
678
+ if specs.get('weight') and specs.get('weight') != 'N/A':
679
+ spec_data.append(['Weight', specs['weight']])
680
+ if specs.get('capacity') and specs.get('capacity') != 'N/A':
681
+ spec_data.append(['Capacity', specs['capacity']])
682
+ if specs.get('refrigerant') and specs.get('refrigerant') != 'N/A':
683
+ spec_data.append(['Refrigerant', specs['refrigerant']])
684
+ if specs.get('temperature_range') and specs.get('temperature_range') != 'N/A':
685
+ spec_data.append(['Temperature Range', specs['temperature_range']])
686
+ if specs.get('compressor') and specs.get('compressor') != 'N/A':
687
+ spec_data.append(['Compressor', specs['compressor']])
688
+ if specs.get('btu') and specs.get('btu') != 'N/A':
689
+ spec_data.append(['BTU', specs['btu']])
690
+
691
+ if len(spec_data) > 1:
692
+ # Create table
693
+ spec_table = Table(spec_data, colWidths=[2.5*inch, 4*inch])
694
+ spec_table.setStyle(TableStyle([
695
+ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#4CAF50')),
696
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
697
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
698
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
699
+ ('FONTSIZE', (0, 0), (-1, 0), 12),
700
+ ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
701
+ ('BACKGROUND', (0, 1), (-1, -1), colors.beige),
702
+ ('GRID', (0, 0), (-1, -1), 1, colors.black),
703
+ ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
704
+ ('FONTSIZE', (0, 1), (-1, -1), 10),
705
+ ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f0f0f0')]),
706
+ ]))
707
+ elements.append(spec_table)
708
+ elements.append(Spacer(1, 0.3*inch))
709
+
710
+ # Features
711
+ features = model_data['data'].get('features', [])
712
+ if features:
713
+ elements.append(Paragraph("<b>Features:</b>", styles['Heading3']))
714
+ for feature in features:
715
+ elements.append(Paragraph(f"• {feature}", styles['Normal']))
716
+ elements.append(Spacer(1, 0.2*inch))
717
+
718
+ # Source info
719
+ elements.append(Paragraph(f"<i>Source: {model_data['filename']}</i>", styles['Normal']))
720
+
721
+ # Build PDF
722
+ doc.build(elements)
723
+ buffer.seek(0)
724
+ return buffer.getvalue()
725
+
726
  def display_pdf_preview(file_path, model_name):
727
  """Display PDF inline in Streamlit app - optimized for HuggingFace Spaces"""
728
 
 
845
 
846
  return high_accuracy[:limit]
847
 
848
+ def display_bookmarked_models():
849
+ """Display bookmarked models section with optimized toggle"""
850
+ if not st.session_state.bookmarked_models:
851
+ st.info("📌 No models bookmarked yet. Select models to create your custom list!")
852
+ return
853
+
 
 
 
 
 
 
 
854
  st.markdown("### 📌 Bookmarked Models")
855
 
856
  view_col1, view_col2, view_col3 = st.columns([2, 1, 1])
 
858
  st.markdown(f"**{len(st.session_state.bookmarked_models)} models selected**")
859
 
860
  with view_col3:
861
+ # Toggle for text-only view - optimized to avoid loading
862
+ toggle_label = "📷 Show Images" if st.session_state.text_only_view else "📝 Text Only"
863
+ if st.button(toggle_label, key="toggle_view", use_container_width=True):
 
 
 
864
  st.session_state.text_only_view = not st.session_state.text_only_view
865
+ # Don't use st.rerun() to avoid loading screen
866
 
867
  # Display bookmarked models with or without images
868
  if st.session_state.text_only_view:
 
908
  pdf_filename = model_data['file_path'].replace('\\', '/').split('/')[-1]
909
  pdf_url = f"https://huggingface.co/spaces/TurboAir/TurboAirViewer/resolve/main/pdfs/{pdf_filename}"
910
 
911
+ # Check if image is already cached
912
+ cache_key = f"thumb_{model}"
913
+ img_base64 = st.session_state.product_images.get(cache_key)
914
 
915
  if img_base64:
916
+ # Use cached image
917
  st.markdown(
918
  f'<img src="data:image/png;base64,{img_base64}" '
919
  f'style="width:100%; max-height:200px; object-fit:contain;" '
 
921
  unsafe_allow_html=True
922
  )
923
  else:
924
+ # Extract image with minimal loading indication
925
+ img_base64 = extract_pdf_thumbnail(pdf_url, model)
926
+
927
+ if img_base64:
928
+ st.markdown(
929
+ f'<img src="data:image/png;base64,{img_base64}" '
930
+ f'style="width:100%; max-height:200px; object-fit:contain;" '
931
+ f'class="bookmark-image">',
932
+ unsafe_allow_html=True
933
+ )
934
+ else:
935
+ st.info("📄 No preview available")
936
 
937
  # Model info
938
  st.markdown(f"**{model}**")
 
947
 
948
  # Export section
949
  st.markdown("---")
950
+ export_col1, export_col2, export_col3 = st.columns(3)
951
 
952
  with export_col1:
953
  csv_data = export_bookmarked_models()
 
961
  )
962
 
963
  with export_col2:
964
+ # PDF export button
965
+ pdf_data = export_bookmarked_models_pdf()
966
+ if pdf_data:
967
+ st.download_button(
968
+ "📄 Export PDF",
969
+ data=pdf_data,
970
+ file_name=f"turbo_air_report_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
971
+ mime="application/pdf",
972
+ use_container_width=True,
973
+ type="primary"
974
+ )
975
+
976
+ with export_col3:
977
  if st.button("🗑️ Clear All", use_container_width=True):
978
  st.session_state.bookmarked_models = []
979
  st.session_state.product_images = {} # Clear image cache too
980
  st.rerun()
981
+
982
+ # MAIN UI
983
+ st.title("❄️ Turbo Air Equipment Viewer")
984
+ st.caption("Professional Equipment Specification Database")
985
+
986
+ # Get all models
987
+ all_models = get_all_models()
988
+
989
+ if not all_models:
990
+ st.error("⚠️ No data found in database. Please ensure turbo_air_db.sqlite is available.")
991
+ st.stop()
992
+
993
+ # Bookmarks section
994
+ display_bookmarked_models()
995
 
996
  # Main content area
997
  col1, col2 = st.columns([1, 3])
 
1001
  st.write("• View PDF spec sheets")
1002
  st.write("• Bookmark models for lists")
1003
  st.write("• Toggle image/text view")
1004
+ st.write("• Export to CSV or PDF")
1005
  st.write("• Google search finds prices")
 
1006
 
1007
  with col2:
1008
  st.markdown('### 🔍 Model Search')