akshit4857 commited on
Commit
0ee5b32
·
verified ·
1 Parent(s): 7388dac

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +132 -193
src/streamlit_app.py CHANGED
@@ -1,11 +1,12 @@
1
  """
2
  Review Validator - Advanced Edition
3
- With explainability graphs + PDF report download
4
  """
5
 
6
  import os
7
  import io
8
  import time
 
9
  import numpy as np
10
  import streamlit as st
11
  from transformers import pipeline, logging as hf_logging
@@ -82,7 +83,26 @@ def get_token():
82
 
83
  return None
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  HF_TOKEN = get_token()
 
86
 
87
  # --- Custom CSS ---
88
  def inject_custom_css():
@@ -152,6 +172,26 @@ def inject_custom_css():
152
  margin-top: 8px;
153
  }
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  .stButton>button {
156
  border-radius: 30px;
157
  font-weight: bold;
@@ -443,6 +483,75 @@ def get_image_from_url(url):
443
  except Exception:
444
  return None
445
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
  # --- Plotting ---
447
 
448
  def breakdown_chart(stats):
@@ -498,10 +607,11 @@ def word_freq_chart(stats):
498
 
499
  # --- PDF REPORT GENERATION ---
500
 
501
- def generate_pdf_report(platform, review_text, text_res, text_stats, image_info):
502
  """
503
  Returns PDF bytes. Requires ReportLab.
504
  image_info: dict or None
 
505
  """
506
  buffer = io.BytesIO()
507
  c = canvas.Canvas(buffer, pagesize=A4)
@@ -558,6 +668,21 @@ def generate_pdf_report(platform, review_text, text_res, text_stats, image_info)
558
  write_line(f"Caption (approx): {image_info['caption']}")
559
  y -= 10
560
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
  c.showPage()
562
  c.save()
563
  pdf_bytes = buffer.getvalue()
@@ -571,7 +696,7 @@ def landing_page():
571
  <div class="hero-box">
572
  <div class="hero-title">🛡️ Review Validator</div>
573
  <div class="hero-subtitle">
574
- Advanced AI-powered review and image analysis with graphs, explainability, and exportable reports.
575
  </div>
576
  </div>
577
  """, unsafe_allow_html=True)
@@ -596,9 +721,9 @@ def landing_page():
596
  with c3:
597
  st.markdown("""
598
  <div class="feature-card">
599
- <span class="emoji-icon">📊</span>
600
- <h3>Explainable Reports</h3>
601
- <p>Graphs, breakdowns, explanations, and PDF report downloads for sharing.</p>
602
  </div>
603
  """, unsafe_allow_html=True)
604
 
@@ -693,190 +818,4 @@ def detector_page(squad, warnings_text=None):
693
  color = "red" if bot_score > 50 else "green"
694
  k1.markdown(
695
  f"""<div class="stat-box">
696
- <div class="stat-num" style="color:{color}">{bot_score:.0f}%</div>
697
- <div class="stat-txt">AI-Likeness</div>
698
- </div>""",
699
- unsafe_allow_html=True
700
- )
701
- k2.markdown(
702
- f"""<div class="stat-box">
703
- <div class="stat-num">{grammar_score:.0f}%</div>
704
- <div class="stat-txt">Grammar Quality</div>
705
- </div>""",
706
- unsafe_allow_html=True
707
- )
708
- k3.markdown(
709
- f"""<div class="stat-box">
710
- <div class="stat-num">{mood_label}</div>
711
- <div class="stat-txt">Sentiment</div>
712
- </div>""",
713
- unsafe_allow_html=True
714
- )
715
-
716
- st.write("")
717
- g1, g2, g3 = st.columns(3)
718
- with g1:
719
- st.markdown("#### 📊 Scores")
720
- fig = breakdown_chart(res)
721
- st.pyplot(fig, use_container_width=True)
722
- with g2:
723
- st.markdown("#### 📏 Sentence Lengths")
724
- fig2 = sentence_length_chart(stats)
725
- st.pyplot(fig2, use_container_width=True)
726
- with g3:
727
- st.markdown("#### 🔤 Top Words")
728
- fig3 = word_freq_chart(stats)
729
- st.pyplot(fig3, use_container_width=True)
730
-
731
- st.markdown("#### 💡 Verdict & Explanation")
732
- if verdict_type == "error":
733
- st.error(verdict_text)
734
- elif verdict_type == "warning":
735
- st.warning(verdict_text)
736
- else:
737
- st.success(verdict_text)
738
-
739
- reasons = explain_text(res, stats, strict_mode_saved)
740
- for r in reasons:
741
- st.markdown(f"- {r}")
742
-
743
- st.markdown(
744
- "<small>Note: These scores and explanations are signals, not absolute proof. "
745
- "Always combine them with your own judgement.</small>",
746
- unsafe_allow_html=True
747
- )
748
-
749
- # PDF report button
750
- st.write("")
751
- if HAVE_REPORTLAB:
752
- img_info_for_pdf = st.session_state.get("img_res_for_pdf", None)
753
- pdf_bytes = generate_pdf_report(
754
- platform_saved,
755
- review_text_saved,
756
- res,
757
- stats,
758
- img_info_for_pdf
759
- )
760
- st.download_button(
761
- "📄 Download PDF Report",
762
- data=pdf_bytes,
763
- file_name="review_validator_report.pdf",
764
- mime="application/pdf",
765
- )
766
- else:
767
- st.info("PDF report requires reportlab. Add `reportlab` to requirements.txt to enable export.")
768
-
769
- # --- IMAGE TAB ---
770
- with tab2:
771
- col_in, col_view = st.columns([1, 1])
772
-
773
- with col_in:
774
- st.markdown("#### Step 1: Provide Image")
775
- method = st.radio(
776
- "Input Method",
777
- ["Paste URL", "Upload File"],
778
- horizontal=True,
779
- label_visibility="collapsed"
780
- )
781
-
782
- with st.form("image_form"):
783
- img_file = None
784
- img_url = None
785
-
786
- if method == "Paste URL":
787
- img_url = st.text_input("Paste Image Link:")
788
- else:
789
- img_file = st.file_uploader("Upload Image", type=['jpg', 'jpeg', 'png'])
790
-
791
- strict_img = st.checkbox("Use Strict AI Mode for Images", value=True)
792
- submitted = st.form_submit_button("Scan Image", type="primary")
793
-
794
- if submitted:
795
- target_img = None
796
- if method == "Paste URL" and img_url:
797
- target_img = get_image_from_url(img_url)
798
- elif method == "Upload File" and img_file:
799
- try:
800
- target_img = Image.open(img_file).convert("RGB")
801
- except Exception:
802
- target_img = None
803
-
804
- if target_img is None:
805
- st.error("Could not read image. Try another link or file.")
806
- else:
807
- with st.spinner("Scanning image..."):
808
- data = check_image(target_img, squad)
809
- st.session_state['img_res'] = (data, strict_img)
810
- st.session_state['current_img'] = target_img
811
- # store a simplified version for PDF report
812
- st.session_state['img_res_for_pdf'] = data
813
-
814
- with col_view:
815
- if 'current_img' in st.session_state:
816
- st.image(
817
- st.session_state['current_img'],
818
- use_column_width=True,
819
- caption="Analyzed Image"
820
- )
821
-
822
- if 'img_res' in st.session_state:
823
- data, strict_img = st.session_state['img_res']
824
- ai_score = data['ai_chance']
825
- caption = data['caption']
826
-
827
- st.markdown("#### Step 2: Analysis Results")
828
-
829
- st.markdown(f"""
830
- <div class="analysis-box">
831
- <strong>👁️ Visual Caption (approx):</strong><br>
832
- <em>{caption}</em>
833
- </div>
834
- """, unsafe_allow_html=True)
835
-
836
- st.write("")
837
-
838
- if strict_img:
839
- t_high = 90
840
- t_mid = 70
841
- else:
842
- t_high = 80
843
- t_mid = 60
844
-
845
- if ai_score >= t_high:
846
- st.error(f"🤖 Very likely AI-generated ({ai_score:.0f}% AI score)")
847
- elif ai_score >= t_mid:
848
- st.warning(f"🤔 Suspicious / possibly AI ({ai_score:.0f}% AI score)")
849
- else:
850
- st.success(f"📸 Likely real photo ({100 - ai_score:.0f}% real score)")
851
-
852
- st.markdown("**Detector Details:**")
853
- st.progress(ai_score / 100.0, text=f"AI probability: {ai_score:.1f}%")
854
-
855
- with st.expander("See raw detector scores"):
856
- st.write(f"Image AI Score (model): {ai_score:.1f}%")
857
-
858
- # --- MAIN CONTROLLER ---
859
- def main():
860
- inject_custom_css()
861
-
862
- if 'page' not in st.session_state:
863
- st.session_state['page'] = 'landing'
864
-
865
- with st.spinner("Loading AI models (first run can take some time)..."):
866
- squad, err = load_ai_squad()
867
-
868
- if squad is None:
869
- st.error(err or "Failed to load models.")
870
- st.stop()
871
-
872
- warnings_text = None
873
- if err:
874
- warnings_text = "Some features may be limited:<br>" + err.replace("\n", "<br>")
875
-
876
- if st.session_state['page'] == 'landing':
877
- landing_page()
878
- else:
879
- detector_page(squad, warnings_text=warnings_text)
880
-
881
- if __name__ == "__main__":
882
- main()
 
1
  """
2
  Review Validator - Advanced Edition
3
+ With explainability graphs + PDF report download + Google Reverse Image Search
4
  """
5
 
6
  import os
7
  import io
8
  import time
9
+ import base64
10
  import numpy as np
11
  import streamlit as st
12
  from transformers import pipeline, logging as hf_logging
 
83
 
84
  return None
85
 
86
+ def get_serpapi_key():
87
+ """
88
+ Safely retrieves SERPAPI_KEY.
89
+ Priority 1: Env var
90
+ Priority 2: Streamlit Secrets
91
+ """
92
+ key = os.environ.get("SERPAPI_KEY")
93
+ if key:
94
+ return key
95
+
96
+ try:
97
+ if hasattr(st, "secrets") and "SERPAPI_KEY" in st.secrets:
98
+ return st.secrets["SERPAPI_KEY"]
99
+ except Exception:
100
+ pass
101
+
102
+ return None
103
+
104
  HF_TOKEN = get_token()
105
+ SERPAPI_KEY = get_serpapi_key()
106
 
107
  # --- Custom CSS ---
108
  def inject_custom_css():
 
172
  margin-top: 8px;
173
  }
174
 
175
+ .reverse-search-box {
176
+ background: #f0fff4;
177
+ border-left: 5px solid #48bb78;
178
+ padding: 15px;
179
+ border-radius: 5px;
180
+ margin-top: 15px;
181
+ }
182
+
183
+ .result-item {
184
+ background: white;
185
+ padding: 12px;
186
+ border-radius: 8px;
187
+ margin: 8px 0;
188
+ border: 1px solid #e2e8f0;
189
+ transition: box-shadow 0.2s;
190
+ }
191
+ .result-item:hover {
192
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
193
+ }
194
+
195
  .stButton>button {
196
  border-radius: 30px;
197
  font-weight: bold;
 
483
  except Exception:
484
  return None
485
 
486
+ # --- GOOGLE REVERSE IMAGE SEARCH (SerpAPI) ---
487
+ def reverse_image_search(image_obj):
488
+ """
489
+ Performs Google reverse image search using SerpAPI.
490
+
491
+ Args:
492
+ image_obj: PIL Image object
493
+
494
+ Returns:
495
+ dict with 'success', 'results', 'error' keys
496
+ """
497
+ if not SERPAPI_KEY:
498
+ return {
499
+ "success": False,
500
+ "error": "SERPAPI_KEY not configured. Add it to secrets or environment variables.",
501
+ "results": []
502
+ }
503
+
504
+ try:
505
+ # Convert PIL Image to base64
506
+ buffered = io.BytesIO()
507
+ image_obj.save(buffered, format="JPEG")
508
+ img_str = base64.b64encode(buffered.getvalue()).decode()
509
+
510
+ # SerpAPI endpoint for Google Lens (reverse image search)
511
+ url = "https://serpapi.com/search"
512
+
513
+ params = {
514
+ "engine": "google_lens",
515
+ "api_key": SERPAPI_KEY,
516
+ "url": f"data:image/jpeg;base64,{img_str}"
517
+ }
518
+
519
+ response = requests.get(url, params=params, timeout=15)
520
+
521
+ if response.status_code != 200:
522
+ return {
523
+ "success": False,
524
+ "error": f"SerpAPI returned status code {response.status_code}",
525
+ "results": []
526
+ }
527
+
528
+ data = response.json()
529
+
530
+ # Extract visual matches
531
+ visual_matches = data.get("visual_matches", [])
532
+
533
+ results = []
534
+ for match in visual_matches[:10]: # Limit to top 10 results
535
+ results.append({
536
+ "title": match.get("title", "No title"),
537
+ "link": match.get("link", ""),
538
+ "source": match.get("source", "Unknown"),
539
+ "thumbnail": match.get("thumbnail", "")
540
+ })
541
+
542
+ return {
543
+ "success": True,
544
+ "results": results,
545
+ "error": None
546
+ }
547
+
548
+ except Exception as e:
549
+ return {
550
+ "success": False,
551
+ "error": f"Reverse search failed: {str(e)}",
552
+ "results": []
553
+ }
554
+
555
  # --- Plotting ---
556
 
557
  def breakdown_chart(stats):
 
607
 
608
  # --- PDF REPORT GENERATION ---
609
 
610
+ def generate_pdf_report(platform, review_text, text_res, text_stats, image_info, reverse_search_data=None):
611
  """
612
  Returns PDF bytes. Requires ReportLab.
613
  image_info: dict or None
614
+ reverse_search_data: dict with reverse search results or None
615
  """
616
  buffer = io.BytesIO()
617
  c = canvas.Canvas(buffer, pagesize=A4)
 
668
  write_line(f"Caption (approx): {image_info['caption']}")
669
  y -= 10
670
 
671
+ # Reverse search results
672
+ if reverse_search_data and reverse_search_data.get('success'):
673
+ write_line("=== Reverse Image Search Results ===", font="Helvetica-Bold", size=12)
674
+ results = reverse_search_data.get('results', [])
675
+ if results:
676
+ write_line(f"Found {len(results)} matches online:")
677
+ for i, result in enumerate(results[:5], 1):
678
+ write_line(f"{i}. {result['title']}")
679
+ write_line(f" Source: {result['source']}")
680
+ write_line(f" Link: {result['link']}")
681
+ y -= 5
682
+ else:
683
+ write_line("No matches found.")
684
+ y -= 10
685
+
686
  c.showPage()
687
  c.save()
688
  pdf_bytes = buffer.getvalue()
 
696
  <div class="hero-box">
697
  <div class="hero-title">🛡️ Review Validator</div>
698
  <div class="hero-subtitle">
699
+ Advanced AI-powered review and image analysis with graphs, explainability, reverse image search, and exportable reports.
700
  </div>
701
  </div>
702
  """, unsafe_allow_html=True)
 
721
  with c3:
722
  st.markdown("""
723
  <div class="feature-card">
724
+ <span class="emoji-icon">🔍</span>
725
+ <h3>Reverse Image Search</h3>
726
+ <p>Find where the image appears online using Google reverse image search.</p>
727
  </div>
728
  """, unsafe_allow_html=True)
729
 
 
818
  color = "red" if bot_score > 50 else "green"
819
  k1.markdown(
820
  f"""<div class="stat-box">
821
+ <div class="stat-num" style="color:{color}">{bot_score:.0f}%