Spaces:
Sleeping
Sleeping
| # ============================================================================== | |
| # 🛡️ FraudGuard Myanmar AI - Final Master Pipeline | |
| # ============================================================================== | |
| import gradio as gr | |
| import requests | |
| import whois | |
| import re | |
| import feedparser | |
| import os | |
| import numpy as np | |
| from datetime import datetime | |
| from difflib import SequenceMatcher | |
| from PIL import Image, ImageChops | |
| from sentence_transformers import SentenceTransformer, util | |
| import torch | |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification | |
| from io import BytesIO | |
| from bs4 import BeautifulSoup | |
| # --- 1. MODEL LOADING --- | |
| print("System Initializing... Loading All AI Components.") | |
| MODEL_NAME = "Poe255M/myanmar-fraud-detection-final" | |
| try: | |
| # Hugging Face မှ Model နှင့် Tokenizer ကို တိုက်ရိုက်ယူခြင်း | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) | |
| nlp_model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME) | |
| print(f"✅ Custom Myanmar AI Model Loaded from Hugging Face: {MODEL_NAME}") | |
| except Exception as e: | |
| print(f"⚠️ သတိပေးချက်: Online Model ကို မတွေ့ပါ။ Base Model ကို ယာယီသုံးထားပါမည်။ Error: {e}") | |
| tokenizer = AutoTokenizer.from_pretrained("xlm-roberta-base") | |
| nlp_model = AutoModelForSequenceClassification.from_pretrained("xlm-roberta-base", num_labels=2) | |
| device = 'cuda' if torch.cuda.is_available() else 'cpu' | |
| nlp_model.to(device) | |
| nlp_model.eval() | |
| print("Loading CLIP model for multi-modal verification...") | |
| clip_model = SentenceTransformer('clip-ViT-B-32') | |
| # --- 2. CORE LOGIC MODULES --- | |
| class VerificationSystem: | |
| def translate_to_en(text): | |
| """CLIP Model ဖြင့် ပုံကိုစစ်ဆေးရန်အတွက်သာ သုံးမည်""" | |
| try: | |
| url = f"https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q={text}" | |
| res = requests.get(url, timeout=5).json() | |
| return "".join([s[0] for s in res[0]]) | |
| except: | |
| return text | |
| def verify_headline_match(user_text, url): | |
| """User ရိုက်တဲ့စာနဲ့ URL ထဲက မူရင်းခေါင်းစဉ် တူ၊ မတူ စစ်ဆေးရန်""" | |
| if not url or not url.startswith("http"): | |
| return 100, "N/A" | |
| try: | |
| headers = {'User-Agent': 'Mozilla/5.0'} | |
| res = requests.get(url, headers=headers, timeout=10) | |
| soup = BeautifulSoup(res.text, 'html.parser') | |
| original_title = "" | |
| h1 = soup.find('h1') | |
| if h1: | |
| original_title = h1.get_text().strip() | |
| elif soup.title: | |
| original_title = soup.title.get_text().strip() | |
| if not original_title: | |
| return 50, "Could not extract title from source" | |
| similarity = SequenceMatcher(None, user_text.lower(), original_title.lower()).ratio() | |
| match_score = similarity * 100 | |
| msg = f"✅ Original Title: {original_title[:60]}..." if match_score > 60 else f"⚠️ Mismatch! Source Title: {original_title[:60]}..." | |
| return match_score, msg | |
| except: | |
| return 50, "Error Fetching Source Title" | |
| def perform_ela(image_path, quality=90): | |
| if not image_path: return 100, "No image" | |
| try: | |
| original = Image.open(image_path).convert('RGB') | |
| resaved_path = "temp_forensic.jpg" | |
| original.save(resaved_path, 'JPEG', quality=quality) | |
| resaved = Image.open(resaved_path) | |
| ela_diff = ImageChops.difference(original, resaved) | |
| stat = np.array(ela_diff).mean() | |
| if os.path.exists(resaved_path): os.remove(resaved_path) | |
| if stat > 1.2: | |
| score = max(10, 100 - (stat * 40)) | |
| return score, "⚠️ Tampering Detected" | |
| elif stat > 0.8: | |
| score = max(50, 100 - (stat * 20)) | |
| return score, "🟡 Minor Edits/Low Quality" | |
| else: | |
| score = min(100, 100 - (stat * 5)) | |
| return score, "✅ Consistent Pixels" | |
| except: return 50, "Scan Error" | |
| def extract_image_from_url(url): | |
| if not url: return None | |
| try: | |
| headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'} | |
| if any(url.lower().endswith(ext) for ext in ['.jpg', '.jpeg', '.png', '.webp']): | |
| res = requests.get(url, headers=headers, timeout=10) | |
| img = Image.open(BytesIO(res.content)).convert('RGB') | |
| path = "temp_url_image.jpg" | |
| img.save(path) | |
| return path | |
| response = requests.get(url, headers=headers, timeout=10) | |
| soup = BeautifulSoup(response.text, 'html.parser') | |
| img_tag = soup.find("meta", property="og:image") or soup.find("meta", attrs={"name": "twitter:image"}) | |
| img_url = img_tag["content"] if img_tag and img_tag.has_attr("content") else None | |
| if not img_url: | |
| first_img = soup.find("img") | |
| img_url = first_img["src"] if first_img and first_img.has_attr("src") else None | |
| if img_url: | |
| if img_url.startswith('//'): img_url = 'https:' + img_url | |
| elif img_url.startswith('/'): | |
| from urllib.parse import urljoin | |
| img_url = urljoin(url, img_url) | |
| img_res = requests.get(img_url, headers=headers, timeout=10) | |
| img = Image.open(BytesIO(img_res.content)).convert('RGB') | |
| path = "temp_extracted_image.jpg" | |
| img.save(path) | |
| return path | |
| except: return None | |
| return None | |
| def get_image_text_similarity(image_path, text, url="", url_image_path=None): | |
| if not image_path or not text: return 50, "Incomplete Data" | |
| try: | |
| is_myanmar = bool(re.search(r'[\u1000-\u109F]', text)) | |
| processed_text = VerificationSystem.translate_to_en(text) if is_myanmar else text | |
| img_obj = Image.open(image_path) | |
| img_emb = clip_model.encode(img_obj) | |
| text_emb = clip_model.encode([processed_text]) | |
| similarity = util.cos_sim(img_emb, text_emb).item() | |
| is_trusted_source = False | |
| trusted_domains = ["bbc.com", "reuters.com", "apnews.com", "voanews.com", "rfa.org", "nytimes.com"] | |
| if url: | |
| for d in trusted_domains: | |
| if d in url.lower(): | |
| is_trusted_source = True | |
| break | |
| is_text_match = similarity >= 0.22 | |
| if is_text_match: | |
| final_score = min(100, similarity * 240) | |
| msg = "✅ စာသားနှင့် ပုံ ကိုက်ညီမှုရှိသည်" if is_myanmar else "✅ Context Matches" | |
| else: | |
| if is_trusted_source: | |
| final_score = 55.0 | |
| msg = "ℹ️ သတင်းရင်းမြစ်မှာ ယုံကြည်ရသော်လည်း ပုံမှာ သရုပ်ပြပုံ (Illustrative) သာ ဖြစ်နိုင်ပါသည်" if is_myanmar else "ℹ️ Trusted source but image may be illustrative" | |
| else: | |
| final_score = max(10, similarity * 130) | |
| msg = "❌ စာသားနှင့် ပုံ မကိုက်ညီပါ" if is_myanmar else "❌ Content Mismatch" | |
| img_match_pct = 0 | |
| has_url_img = False | |
| if url_image_path and os.path.exists(url_image_path): | |
| has_url_img = True | |
| url_img_emb = clip_model.encode(Image.open(url_image_path)) | |
| img_similarity = util.cos_sim(img_emb, url_img_emb).item() | |
| img_match_pct = img_similarity * 100 | |
| if img_match_pct > 80: | |
| if not is_text_match: | |
| final_score = 65.0 if is_trusted_source else 45.0 | |
| msg = "⚠️ Link ပါပုံနှင့် တူသော်လည်း ခေါင်းစဉ်နှင့် တိုက်ရိုက်မသက်ဆိုင်ပါ" if is_myanmar else "⚠️ Image matches Source but Mismatches Headline" | |
| else: | |
| final_score = min(100, final_score + 15) | |
| msg = "✅ မူရင်းသတင်းပါပုံဖြစ်ပြီး စာသားနှင့်လည်း ကိုက်ညီပါသည်" if is_myanmar else "✅ Verified Source Image Match" | |
| elif img_match_pct < 50: | |
| final_score = max(10, final_score - 25) | |
| msg += " | ⚠️ Link ထဲမှ မူရင်းပုံမဟုတ်ပါ" if is_myanmar else " | ⚠️ Not the Original Image from Link" | |
| if has_url_img and not is_text_match and img_match_pct < 50 and not is_trusted_source: | |
| final_score = 10.5 | |
| msg = "🚨 သတင်းအချက်အလက်အားလုံး လွဲမှားနေပါသည် (High Risk)" if is_myanmar else "🚨 High Risk: Complete Information Mismatch" | |
| return round(max(5, final_score), 2), msg | |
| except Exception as e: | |
| return 50, f"Verification Error: {str(e)}" | |
| def get_source_score(url): | |
| if not url or not url.startswith("http"): | |
| return 30, "Missing/Invalid URL Source" | |
| try: | |
| domain_search = re.search(r'https?://([A-Za-z0-9.-]+)', url) | |
| if not domain_search: return 30, "Invalid Domain" | |
| domain = domain_search.group(1).lower() | |
| # --- ၁။ တရားဝင် သတင်းဌာနကြီးများ (Hard News) --- | |
| trusted_news = ["bbc.com", "reuters.com", "rfa.org", "voanews.com", "dvb.no", "irrawaddy.com", "myanmar-now.org", "khitthitnews.com", "mizzima.com"] | |
| # --- ၂။ နာမည်ကြီး အနုပညာ/ဆယ်လီ မီဒီယာများ (Cele Media) --- | |
| trusted_cele = ["myanmarcelebrity.com", "popularmyanmar.com", "celegabar.com", "shwemon.com", "celeyatkwat.com"] | |
| # (က) သတင်းဌာနကြီးများ စစ်ဆေးခြင်း | |
| for m in trusted_news: | |
| if domain == m or domain.endswith("." + m): | |
| return 100, f"Verified News Media ({domain})" | |
| # (ခ) အနုပညာသတင်းဌာနများ စစ်ဆေးခြင်း | |
| for c in trusted_cele: | |
| if domain == c or domain.endswith("." + c): | |
| return 90, f"Verified Entertainment Media ({domain})" | |
| # --- ၃။ Social Media Links (Facebook, Instagram) စစ်ဆေးခြင်း --- | |
| # ဆယ်လီသတင်းအများစုသည် Social Media ပေါ်တွင်သာ ရှိတတ်သဖြင့် သီးသန့်စစ်ဆေးမည် | |
| if "facebook.com" in domain or "instagram.com" in domain: | |
| path = url.split(domain)[-1].lower() | |
| # Official Page ဟု ယူဆနိုင်သော လက္ခဏာများ (ဥပမာ - facebook.com/naytoe.official) | |
| if "official" in path or "original" in path or "verified" in path: | |
| return 85, "Likely Official Social Media Account" | |
| # Facebook Group သို့မဟုတ် Video Link သီးသန့်ဖြစ်နေလျှင် (သတင်းတုဖြန့်ရန် အသုံးများသော နေရာများ) | |
| elif "/groups/" in path or "/watch/" in path or "reel" in path: | |
| return 40, "Social Media Group/Video (Unverified Origin)" | |
| # သာမန် Page သို့မဟုတ် Profile ဖြစ်လျှင် (ကြားနေအမှတ်ပေးမည်) | |
| else: | |
| return 60, "General Social Media Source (Needs Cross-check)" | |
| # --- ၄။ အမည်မသိ Domain များကို WHOIS ဖြင့် သက်တမ်းစစ်ခြင်း (Fake Sites များကို ဖမ်းရန်) --- | |
| w = whois.whois(domain) | |
| creation_date = w.creation_date | |
| if isinstance(creation_date, list): | |
| creation_date = creation_date[0] | |
| if creation_date: | |
| from datetime import datetime | |
| age_days = (datetime.now() - creation_date).days | |
| age_months = age_days // 30 | |
| if age_days < 180: | |
| return 15, f"⚠️ Very New/Suspicious Site (Age: {age_months} months)" | |
| elif age_days < 730: | |
| return 50, f"Neutral Site (Age: {age_months} months)" | |
| else: | |
| return 80, f"Established Site (Age: {age_months // 12} years)" | |
| else: | |
| return 30, "Unknown Identity (No Creation Date)" | |
| except Exception as e: | |
| return 20, "Hidden/Suspicious Source Identity" | |
| def get_nlp_prediction(text): | |
| if not text or len(text.split()) < 3: return 10.0 | |
| # ၁။ AI Model မှ ရလဒ်ယူခြင်း | |
| inputs = tokenizer(text, max_length=512, padding="max_length", truncation=True, return_tensors="pt").to(device) | |
| import torch | |
| with torch.no_grad(): | |
| outputs = nlp_model(**inputs) | |
| probs = torch.nn.functional.softmax(outputs.logits, dim=-1)[0] | |
| real_score = probs[0].item() * 100 | |
| text_lower = text.lower() | |
| penalty = 0 | |
| # (က) Health Scam ဖမ်းရန် (WHO/သုတေသန နာမည်သုံးပြီး လိမ်ထားလျှင်) | |
| health_keywords = ["ကျန်းမာရေး", "ရောဂါ", "ဆေး", "who", "သုတေသန", "အတည်ပြု"] | |
| if any(kw in text_lower for kw in health_keywords): | |
| scam_words = ["၁၀၀%", "အာမခံ", "ချက်ချင်း", "လျှို့ဝှက်ချက်", "မဖြစ်မနေ", "ပျောက်ကင်း", "လုံးဝကာကွယ်", "အထူးသဖြင့်", "ပိုမိုထိရောက်"] | |
| if any(sw in text_lower for sw in scam_words): | |
| penalty += 35 # ကျန်းမာရေးလိမ်လည်မှုဖြစ်၍ အမှတ်များများလျှော့မည် | |
| # (ခ) Cele News မှန်လျှင် Penalty မထိစေရန် ကာကွယ်ခြင်း | |
| cele_keywords = ["မင်းသား", "မင်းသမီး", "အဆိုတော်", "သရုပ်ဆောင်", "ဆယ်လီ", "လက်ထပ်", "celebrity"] | |
| is_cele = any(kw in text_lower for kw in cele_keywords) | |
| # ၂။ Case 2 & 3 အတွက် အထူး Red Flags (Heuristics) - (ညီမ၏ မူလ Code အတိုင်း) | |
| urgency_patterns = [ | |
| "ပိတ်သိမ်းသွားမည်", "ပိတ်သိမ်းတော့မယ်", "အမြန်ဆုံး", "လက်ဆင့်ကမ်း", | |
| "အတွင်းသတင်း", "Update ပြုလုပ်ရပါမည်", "Personal Information", | |
| "အကောင့်ပိတ်သိမ်း", "ယာယီပိတ်သိမ်း", "မယုံနိုင်စရာ", "၁၀၀% အမှန်" | |
| ] | |
| if is_cele: | |
| # ဆယ်လီသတင်းဆိုလျှင် "မယုံနိုင်စရာ" ကဲ့သို့သော စကားလုံးများအတွက် Penalty မပေးတော့ပါ။ | |
| # Clickbait လင့်ခ်နှိပ်ခိုင်းတာမျိုးကိုပဲ သီးသန့်စစ်ပါမည်။ | |
| clickbait_patterns = ["ဗီဒီယိုကြည့်ရန်", "ရှယ်ထား", "လင့်ခ်ဝင်ကြည့်", "ဖုန်းဘေလ်", "လက်ဆောင်"] | |
| for pattern in clickbait_patterns: | |
| if pattern in text_lower: | |
| penalty += 20 | |
| else: | |
| for pattern in urgency_patterns: | |
| if pattern in text: | |
| penalty += 20 # တစ်ခုပါတိုင်း ၂၀% လျှော့ချမည် | |
| # ၃။ AI က ၉၀% ကျော် အစစ်လို့ပြောရင်တောင် Penalty ပါရင် Score ကို ချက်ချင်းချမည် | |
| final_score = real_score - penalty | |
| # ၄။ Logic Correction (Case 2/3 Fix) | |
| # AI က သိပ်မသေချာဘူး (၇၅% အောက်) ဆိုရင် 'သံသယဖြစ်ဖွယ်' ဘက်ကို ပိုပို့မည် | |
| if final_score < 75:final_score = final_score * 0.6 # Score ကို ထပ်လျှော့ချခြင်း | |
| return round(max(5, final_score), 2) | |
| def check_rss_similarity(headline, url=""): | |
| import requests | |
| if not headline or len(headline) < 10: return 0 | |
| trusted_domains = [ | |
| "bbc.com/burmese", "rfa.org/burmese", "burmese.voanews.com", | |
| "mizzima.com", "khitthitnews.org", "dvb.no", "myanmar-now.org", | |
| "reuters.com", "apnews.com", "nytimes.com", "cnn.com", | |
| "theguardian.com", "aljazeera.com", "dw.com", "france24.com", | |
| "bloomberg.com", "wsj.com", "forbes.com","myanmarcelebrity.com" | |
| ] | |
| base_bonus = 0 | |
| if url: | |
| for domain in trusted_domains: | |
| if domain in url.lower(): | |
| base_bonus = 90 | |
| break | |
| feeds = [ | |
| "https://burmese.voanews.com/api/z$y_iqve_t", | |
| "https://www.rfa.org/burmese/rss2.xml", | |
| "https://www.bbc.com/burmese/index.xml", | |
| "https://burmese.dvb.no/feed", | |
| "https://www.mizzimaburmese.com/rss", | |
| "https://www.khitthitnews.com/feed", | |
| "https://burmese.irrawaddy.com/feed", | |
| "https://www.myanmarcelebrity.com/feeds/posts/default?alt=rss", | |
| "https://popularmyanmar.com/feed/" | |
| ] | |
| headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'} | |
| max_sim = 0 | |
| clean_headline = re.sub(r'[^\\u1000-\\u109F a-zA-Z0-9]', ' ', headline).lower() | |
| headline_keywords = set([w for w in clean_headline.split() if len(w) > 2]) | |
| for f_url in feeds: | |
| try: | |
| response = requests.get(f_url, headers=headers, timeout=5) | |
| if response.status_code == 200: | |
| feed = feedparser.parse(response.content) | |
| for entry in feed.entries: | |
| seq_sim = SequenceMatcher(None, headline.lower(), entry.title.lower()).ratio() | |
| entry_clean = re.sub(r'[^\\u1000-\\u109F a-zA-Z0-9]', ' ', entry.title).lower() | |
| entry_keywords = set([w for w in entry_clean.split() if len(w) > 2]) | |
| common = headline_keywords.intersection(entry_keywords) | |
| keyword_sim = len(common) / len(headline_keywords) if headline_keywords else 0 | |
| max_sim = max(max_sim, seq_sim, keyword_sim) | |
| if max_sim > 0.85: break | |
| except: continue | |
| rss_val = min(100, max_sim * 160) | |
| final_score = max(base_bonus, rss_val) | |
| return final_score | |
| # --- 3. MASTER ENGINE (Final Presentation Version) --- | |
| def master_detector_v12(text, url, image): | |
| try: | |
| v = VerificationSystem() | |
| # ၁။ 🚨 Empty Input Check (စာသားမပါလျှင်) | |
| if not text or not text.strip(): | |
| return "<div style='color:#dc2626; padding:20px; text-align:center; background:#fef2f2; border-radius:10px; border:1px solid #fca5a5;'><b>⚠️ ကျေးဇူးပြု၍ စစ်ဆေးလိုသော သတင်းစာသားကို ထည့်သွင်းပါ။</b></div>" | |
| # ၂။ 🚨 Short Text Check ("I don't know" Logic - စာသားတိုလွန်းလျှင်) | |
| words = text.split() | |
| if len(words) < 10: | |
| return f""" | |
| <div style='color:#d97706; padding:20px; text-align:center; background:#fffbeb; border-radius:10px; border:1px solid #fcd34d;'> | |
| <b style='font-size:1.1rem;'>⚠️ အချက်အလက် မလုံလောက်ပါ (Insufficient Information)</b><br><br> | |
| သင်ထည့်သွင်းထားသော စာသားမှာ <b>({len(words)} လုံးသာ)</b> ရှိပြီး တိုတောင်းလွန်းပါသည်။ <br> | |
| AI မှ တိကျစွာ ဆန်းစစ်နိုင်ရန်အတွက် အနည်းဆုံး စကားလုံး (၁၀) လုံးနှင့်အထက် ပါဝင်သော သတင်းအပြည့်အစုံကို ထည့်သွင်းပေးပါ။ | |
| </div> | |
| """ | |
| # URL ပါ၊ မပါ စစ်ဆေးခြင်း | |
| has_url = bool(url and url.strip() != "") | |
| is_myanmar = bool(re.search(r'[\u1000-\u109F]', text)) | |
| # --- 📊 3. UI Progress Bar ဖန်တီးပေးသော Helper Function --- | |
| def create_bar(label, value, bar_color, is_mm, nlp_label_local, custom_msg=None): | |
| if custom_msg: | |
| explainer = custom_msg | |
| else: | |
| if label == "Source Trust": | |
| if not has_url: | |
| explainer = "ℹ️ URL လင့်ခ် မပါဝင်သဖြင့် သတင်းရင်းမြစ်ကို အတည်ပြု၍ မရပါ။" if is_mm else "ℹ️ No URL provided. Source unverified." | |
| bar_color = "#94a3b8" # URL မပါလျှင် ခဲရောင်ပြမည် | |
| else: | |
| explainer = ("✅ ယုံကြည်ရသော ရင်းမြစ်ဖြစ်သည်။" if value > 75 else "⚠️ ရင်းမြစ် မတည်ငြိမ်ခြင်း (သို့) ဒိုမိန်းသက်တမ်း နုနယ်ခြင်း။") if is_mm else ("✅ Verified source." if value > 75 else "⚠️ Low authority source.") | |
| elif label == "Global Consistency": | |
| explainer = ("✅ အခြားမီဒီယာများတွင်လည်း ဖော်ပြထားသည်။" if value > 60 else "ℹ️ အခြားသတင်းဌာနများတွင် အတည်ပြုချက် မတွေ့ရသေးပါ။") if is_mm else ("✅ Corroborated." if value > 60 else "ℹ️ Not corroborated yet.") | |
| elif label == "AI Pattern Analysis": | |
| explainer = ("✅ AI မှ သတင်းမှန် အရေးအသားဟု ဆုံးဖြတ်သည်။" if nlp_label_local == "Real" else "⚠️ AI မှ သတင်းတု/Clickbait ဟု သတ်မှတ်သည်။" if nlp_label_local == "Fake" else "ℹ️ AI အတွက် ဆုံးဖြတ်ရန် ခက်ခဲသော ရောထွေးနေသည့် အရေးအသားဖြစ်သည်။") if is_mm else ("✅ Legitimate pattern." if nlp_label_local == "Real" else "⚠️ Misinformation pattern." if nlp_label_local == "Fake" else "ℹ️ Neutral/Mixed pattern.") | |
| elif label == "Image Integrity": | |
| explainer = ("✅ ပုံရိပ်မှာ မူရင်းအတိုင်းဖြစ်ပြီး ပြင်ဆင်မှု မတွေ့ရပါ။" if value > 85 else "⚠️ ပုံရိပ်ကို ပြုပြင်ထားသော လက္ခဏာရှိသည်။") if is_mm else ("✅ No tampering." if value > 85 else "⚠️ Potential tampering.") | |
| elif label == "Visual Context": | |
| explainer = ("✅ ပုံနှင့်စာသား ကိုက်ညီမှုရှိသည်။" if value > 72 else "⚠️ ပုံနှင့်စာသား တစ်ခြားစီဖြစ်နေသည်။") if is_mm else ("✅ Context matches." if value > 72 else "⚠️ Context mismatch.") | |
| else: explainer = "" | |
| return f""" | |
| <div style="margin-bottom: 12px;"> | |
| <div style="display: flex; justify-content: space-between; font-size: 0.8rem; color: #475569; margin-bottom: 3px;"> | |
| <span style="font-weight:600;">{label}</span><span>{value:.1f}%</span> | |
| </div> | |
| <div style="width: 100%; background: #e2e8f0; border-radius: 10px; height: 7px; overflow: hidden;"> | |
| <div style="width: {value}%; background: {bar_color}; height: 100%; border-radius: 10px;"></div> | |
| </div> | |
| <div style="font-size: 0.72rem; color: #1e293b; margin-top: 3px; line-height: 1.3; font-weight: 500;">{explainer}</div> | |
| </div>""" | |
| # --- 🔍 4. Core Metrics Calculations --- | |
| url_image = v.extract_image_from_url(url) if has_url else None | |
| src_score, src_msg = v.get_source_score(url) if has_url else (0, "No URL") | |
| rss_score = v.check_rss_similarity(text, url=url) | |
| nlp_score = v.get_nlp_prediction(text) | |
| # NLP Score Labeling | |
| if nlp_score >= 65: | |
| nlp_label = "Real"; nlp_val = nlp_score; ai_c = "#10b981" | |
| elif nlp_score <= 40: | |
| nlp_label = "Fake"; nlp_val = 100 - nlp_score; ai_c = "#dc2626" | |
| else: | |
| nlp_label = "Neutral"; nlp_val = nlp_score; ai_c = "#94a3b8" | |
| # --- 🖼️ 5. Image Processing & Zero-shot Classification --- | |
| image_bars_html = "" | |
| img_msg_extra = "" | |
| sim_msg = "" | |
| if image: | |
| # ပြည်တွင်း/ပြည်ပ မြင်ကွင်းခွဲခြားခြင်း (Zero-shot CLIP) | |
| try: | |
| img_obj = Image.open(image) | |
| loc_prompts = [ | |
| "a photo taken in Myanmar, Burmese streets, pagodas, Asian people, Myanmar culture", | |
| "a photo taken in a foreign country, Western people, foreign streets, Europe, America, Africa, Middle East" | |
| ] | |
| loc_embs = clip_model.encode(loc_prompts) | |
| img_emb_local = clip_model.encode(img_obj) | |
| loc_scores = util.cos_sim(img_emb_local, loc_embs)[0] | |
| if loc_scores[1] > loc_scores[0] + 0.02: | |
| img_msg_extra = "<br><span style='color:#dc2626; font-weight:600;'>🌍 AI Visual Scan: ဤပုံသည် ပြည်ပနိုင်ငံမှ မြင်ကွင်းဖြစ်နိုင်ခြေများပါသည်။ (Foreign Image Detected)</span>" | |
| elif loc_scores[0] > loc_scores[1] + 0.02: | |
| img_msg_extra = "<br><span style='color:#059669; font-weight:600;'>🇲🇲 AI Visual Scan: ဤပုံသည် ပြည်တွင်းမှ မြင်ကွင်းဖြစ်နိုင်ခြေများပါသည်။ (Domestic Image)</span>" | |
| except Exception as e: | |
| pass # Error တက်လျှင် ကျော်သွားမည် | |
| img_ela_score, _ = v.perform_ela(image) | |
| img_sim_score, sim_msg_original = v.get_image_text_similarity(image, text, url=url, url_image_path=url_image) | |
| # Combine image similarity message with location detection | |
| sim_msg = sim_msg_original + img_msg_extra | |
| # ⚖️ Dynamic Weighting for Image Case | |
| if has_url: | |
| weights = {'source': 0.20, 'nlp': 0.35, 'rss': 0.15, 'ela': 0.10, 'sim': 0.20} | |
| else: | |
| # URL မပါလျှင် Source ကို 0 ထားပြီး AI နှင့် Image ကို အလေးပေးမည် | |
| weights = {'source': 0.0, 'nlp': 0.45, 'rss': 0.15, 'ela': 0.15, 'sim': 0.25} | |
| final = (src_score * weights['source']) + (nlp_score * weights['nlp']) + \ | |
| (rss_score * weights['rss']) + (img_ela_score * weights['ela']) + \ | |
| (img_sim_score * weights['sim']) | |
| image_bars_html = f""" | |
| {create_bar("Image Integrity", img_ela_score, "#f59e0b", is_myanmar, nlp_label)} | |
| {create_bar("Visual Context", img_sim_score, "#06b6d4", is_myanmar, nlp_label, custom_msg=sim_msg)} | |
| """ | |
| else: | |
| # ⚖️ Dynamic Weighting for Text-Only Case | |
| if has_url: | |
| weights = {'source': 0.35, 'nlp': 0.50, 'rss': 0.15} | |
| else: | |
| # ပုံရော၊ URL ရော မပါလျှင် NLP ကို 80% အထိ အလေးပေးမည် | |
| weights = {'source': 0.0, 'nlp': 0.80, 'rss': 0.20} | |
| final = (src_score * weights['source']) + (nlp_score * weights['nlp']) + (rss_score * weights['rss']) | |
| img_msg = "ℹ️ ပုံမပါဝင်သည့်အတွက် Visual Analysis မပြုလုပ်ပါ။" if is_myanmar else "ℹ️ No image for visual analysis." | |
| image_bars_html = f'<div style="padding:12px; background:#f1f5f9; border-radius:8px; font-size:0.75rem; color:#475569; text-align:center;">{img_msg}</div>' | |
| # --- 🌟 5.1 Fact-Verification Override (The Veto Power - For Cele News ONLY) 🌟 --- | |
| is_corroborated = rss_score >= 70 | |
| cele_keywords = ["မင်းသား", "မင်းသမီး", "အဆိုတော်", "သရုပ်ဆောင်", "ဆယ်လီ", "လက်ထပ်", "celebrity"] | |
| is_cele_news_context = any(kw in text.lower() for kw in cele_keywords) | |
| if is_corroborated and is_cele_news_context: | |
| final = max(final, 85.0) | |
| if nlp_label == "Fake": | |
| nlp_label = "Real (Overridden by Cele Fact-Check)" | |
| # --- 🗂️ 6. Final Status & Dynamic Color Logic --- | |
| if final >= 75: | |
| main_bg, accent_c, status = "#ecfdf5", "#059669", "✅ ယုံကြည်စိတ်ချရသော သတင်း (RELIABLE VERDICT)" | |
| elif final >= 45 and final < 75: | |
| main_bg, accent_c, status = "#f8fafc", "#64748b", "⚖️ အတည်ပြုရန်ခက်ခဲသော သတင်း (INCONCLUSIVE / NEUTRAL)" | |
| else: | |
| main_bg, accent_c, status = "#fef2f2", "#dc2626", "🚨 သတင်းတု / အန္တရာယ်ရှိသောသတင်း (FAKE / HIGH RISK)" | |
| # --- 📝 7. Analysis & Recommendations --- | |
| analysis_header = "🔎 အသေးစိတ် ဆန်းစစ်ချက်" if is_myanmar else "🔎 Detailed Reasoning" | |
| tips_header = "🛡️ အကြံပြုချက်နှင့် သတိပြုရန်" if is_myanmar else "🛡️ Recommendations" | |
| reasons = [] | |
| tips = [] | |
| if final >= 75: | |
| # 🌟 Cele News Veto အလုပ်လုပ်ခဲ့လျှင် Message ပြောင်းပြမည် 🌟 | |
| if is_corroborated and is_cele_news_context: | |
| reasons.append("🌟 <b>Cele News Verified:</b> ဤအနုပညာသတင်းကို အခြားသော တရားဝင် မီဒီယာများတွင်ပါ အတိအကျ ဖော်ပြထားသဖြင့် သတင်းအမှန်ဖြစ်ကြောင်း အတည်ပြုပါသည်။") | |
| else: | |
| reasons.append("✅ သတင်းရင်းမြစ်နှင့် အချက်အလက်များ ခိုင်မာမှုရှိသည်။") | |
| tips.append("💡 ဤသတင်းသည် ယုံကြည်စိတ်ချရသဖြင့် ဝေမျှနိုင်ပါသည်။") | |
| elif final >= 45 and final < 75: | |
| reasons.append("⚖️ အချက်အလက်များမှာ အမှန်နှင့် အမှား ရောထွေးနေနိုင်ပါသည်။ (သို့) လုံလောက်သော သက်သေအထောက်အထား မတွေ့ရသေးပါ။") | |
| tips.append("💡 ဤသတင်းကို ချက်ချင်းမယုံကြည်ဘဲ အခြားတရားဝင် မီဒီယာကြီးများတွင် ထပ်မံစစ်ဆေးရန် အကြံပြုအပ်ပါသည်။") | |
| else: | |
| if nlp_label == "Fake": reasons.append("⚠️ AI စနစ်မှ ဤစာသားသည် သတင်းအတု/Clickbait ပုံစံဖြစ်နေကြောင်း တွေ့ရှိရသည်။") | |
| if not has_url: | |
| reasons.append("ℹ️ သတင်းရင်းမြစ် (URL) ထည့်သွင်းထားခြင်း မရှိသဖြင့် မူရင်းရင်းမြစ်ကို အတည်ပြုရန် ခက်ခဲပါသည်။") | |
| elif src_score < 40: | |
| reasons.append(f"❌ သတင်းရင်းမြစ် ({src_msg}) သည် စိတ်မချရပါ။") | |
| tips.append("💡 သတင်းအမှားဖြစ်နိုင်ခြေ အလွန်များသဖြင့် အခြားသူများထံ ဆက်လက်မဝေမျှရန် အသိပေးအပ်ပါသည်။") | |
| reasons_html = "".join([f"<li style='margin-bottom:5px;'>{r}</li>" for r in reasons]) | |
| tips_html = "".join([f"<li style='margin-bottom:6px;'>{t}</li>" for t in tips]) | |
| # --- 🌐 8. Final HTML Output | |
| return f""" | |
| <div style="background: {main_bg}; padding: 22px; border-radius: 15px; border: 1px solid {accent_c}33; font-family: sans-serif; box-shadow: 0 4px 6px rgba(0,0,0,0.1);"> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> | |
| <h3 style="margin: 0; color: {accent_c}; font-size: 1.15rem; font-weight: 800;">{status}</h3> | |
| <span style="background: {accent_c}; color: white; padding: 5px 15px; border-radius: 25px; font-weight: 800;">{final:.1f}%</span> | |
| </div> | |
| <div style="background: white; padding: 18px; border-radius: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.03);"> | |
| <b style="font-size: 0.85rem; color: #1e293b; display: block; margin-bottom: 15px; text-transform: uppercase;">📊 Verification Metrics:</b> | |
| {create_bar("Source Trust", src_score, "#3b82f6", is_myanmar, nlp_label)} | |
| {create_bar("Global Consistency", rss_score, "#8b5cf6", is_myanmar, nlp_label)} | |
| {create_bar("AI Pattern Analysis", nlp_val, ai_c, is_myanmar, nlp_label)} | |
| <hr style="border: 0; border-top: 1px solid #f1f5f9; margin: 18px 0;"> | |
| {image_bars_html} | |
| </div> | |
| <div style="background: {("#f0fdf4" if final >= 75 else "#f8fafc" if final >= 45 else "#fef2f2")}; padding: 15px; border-radius: 10px; border-left: 5px solid {accent_c}; margin-top: 15px;"> | |
| <b style="color: {accent_c}; display: block; margin-bottom: 8px;">{analysis_header}</b> | |
| <ul style="margin: 0; padding-left: 20px; color: #1e293b; font-size: 0.95rem;">{reasons_html if reasons_html else "<li>Analysis complete.</li>"}</ul> | |
| </div> | |
| <div style="background: #eff6ff; padding: 15px; border-radius: 10px; border-left: 5px solid #2563eb; margin-top: 12px;"> | |
| <b style="color: #1e40af; display: block; margin-bottom: 8px;">{tips_header}</b> | |
| <ul style="margin: 0; padding-left: 20px; color: #1e3a8a; font-size: 0.95rem;">{tips_html}</ul> | |
| </div> | |
| </div> | |
| """ | |
| except Exception as e: | |
| import traceback | |
| return f"<div style='color:red; padding:20px; border:1px solid red; border-radius:10px;'><b>System Error:</b> {str(e)}<br><pre style='font-size:0.7rem;'>{traceback.format_exc()}</pre></div>" | |
| # --- 4. MODERN UI DESIGN --- | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); | |
| @import url('https://mmwebfonts.comquas.com/fonts/?font=pyidaungsu'); | |
| .gradio-container { background-color: #f9fafb !important; font-family: 'Inter', 'Pyidaungsu' !important; } | |
| .card { background: white !important; border-radius: 20px !important; border: 1px solid #e5e7eb !important; padding: 30px !important; } | |
| textarea { font-family: 'Pyidaungsu', sans-serif !important; font-size: 16px !important; } | |
| .btn-primary { background: #111827 !important; color: white !important; border-radius: 10px !important; font-weight: 600 !important; } | |
| """ | |
| with gr.Blocks(css=custom_css) as demo: | |
| gr.HTML("<div style='text-align: center; padding: 20px;'><h1>🛡️ Fake News Detection (Powered by Myanmar AI)</h1></div>") | |
| with gr.Tabs(): | |
| with gr.TabItem("🔍 Verifier Dashboard"): | |
| with gr.Row(): | |
| with gr.Column(scale=5, elem_classes="card"): | |
| txt = gr.Textbox(label="News Content", lines=8, placeholder="သတင်းစာသားကို ဤနေရာတွင် ထည့်ပါ...") | |
| url = gr.Textbox(label="Source URL (Optional)") | |
| img = gr.Image(label="Image Attachment", type="filepath") | |
| with gr.Row(): | |
| clear = gr.Button("Reset Fields") | |
| submit = gr.Button("Analyze News", variant="primary") | |
| with gr.Column(scale=5): | |
| empty_html = "<div style='text-align: center; padding: 100px; color: #9ca3af;'>Ready for analysis...</div>" | |
| output = gr.HTML(value=empty_html) | |
| with gr.TabItem("ℹ️ About System"): | |
| with gr.Column(elem_classes="card"): | |
| gr.Markdown(r""" | |
| # 🛡️ Fake News Detection System Methodology | |
| ### 📄 1. Project Overview & System Rationale | |
| ဤ Project သည် သတင်းတု (Fake News) များကို ရှာဖွေရာတွင် စာသားတင်မကဘဲ Context အားလုံးကို ခြုံငုံကြည့်သည့် **Multi-modal Framework** တစ်ခုဖြစ်သည်။ ဤစနစ်သည် သတင်းတစ်ခု၏ စစ်မှန်မှုကို အချက် (၄) ချက်ဖြင့် တိုင်းတာပါသည်။ | |
| 1. **Linguistic Style:** အရေးအသားပုံစံမှာ ဝါဒဖြန့်စာသားဖြစ်နေသလား? | |
| 2. **Metadata & Source:** သတင်းလာရာ ရင်းမြစ်က ယုံကြည်ရသလား? | |
| 3. **Visual Integrity:** သတင်းတွင်ပါသော ပုံသည် ပြုပြင်ထားသလား သို့မဟုတ် စာသားနှင့် ကိုက်ညီမှုရှိသလား? | |
| 4. **Global consistency:** အခြားသော မီဒီယာကြီးများမှာ ဖော်ပြထားခြင်းရှိသလား? | |
| --- | |
| ### 🧠 2. Core Algorithms & Methodology | |
| #### **A. Transformer-based Classification (XLM-RoBERTa)** | |
| * **Algorithm:** *XLM-RoBERTa (Cross-lingual Language Model)* | |
| * **Implementation:** ဤ Model သည် မြန်မာစာ သတင်းမှန်နှင့် သတင်းအတု Data ထောင်ပေါင်းများစွာကို ကိုယ်တိုင် သင်ယူ (Fine-tuned) ထားသော ကိုယ်ပိုင် AI စနစ်ဖြစ်သည်။ ဘာသာပြန်စရာမလိုဘဲ မြန်မာစာကို တိုက်ရိုက် နားလည်စစ်ဆေးနိုင်သည်။ | |
| #### **B. Visual Forensics (ELA & CLIP)** | |
| * **Error Level Analysis (ELA):** JPEG ပုံရိပ်တစ်ခုကို ပြန်သိမ်းသည့်အခါ ပြုပြင်ထားသော Pixel များသည် Error Level ကွဲပြားသွားခြင်းကို အခြေခံ၍ ပုံပြင်/မပြင်ကို စစ်ဆေးသည်။ | |
| * **CLIP (Contrastive Language-Image Pre-training):** NLP နည်းပညာကို အသုံးပြု၍ စာသားထဲတွင် ပါဝင်သော "အကြောင်းအရာ" နှင့် ရုပ်ပုံထဲတွင် မြင်တွေ့ရသော "အမြင်အာရုံဆိုင်ရာ သဘောတရား (Visual Concept)" တို့၏ ကိုက်ညီမှုကို Text-Image Embedding Alignment နည်းလမ်းဖြင့် တိုင်းတာသည်။ | |
| #### **C. Source Verification & RSS Matching** | |
| * **Whois Analysis:** Domain ၏ သက်တမ်းကို စစ်ဆေးသည်။ သတင်းအတုဆိုဒ်အများစုမှာ သက်တမ်း (၆) လအောက်သာ ရှိတတ်သည်။ | |
| * **RSS Feed Comparison:** BBC, RFA, VOA စသည့် ယုံကြည်ရသော သတင်းဌာနကြီးများ၏ လက်ရှိသတင်းခေါင်းစဉ်များနှင့် သင့်သတင်းကို တိုက်ဆိုင်စစ်ဆေးပြီး အခြားမီဒီယာတွင် ပါ၊ မပါ ဆုံးဖြတ်သည်။ | |
| --- | |
| ### ⚙️ 3. Mathematical Scoring Model | |
| စနစ်မှ ရရှိလာသော Metrics တစ်ခုချင်းစီကို အောက်ပါ **Weighted Average Formula** ဖြင့် ပေါင်းစပ်ကာ Confidence Score ထုတ်ပေးပါသည်။ | |
| $$Score = (W_{nlp} \cdot NLP) + (W_{src} \cdot Source) + (W_{rss} \cdot RSS) + (W_{vis} \cdot Visual)$$ | |
| | Metric | Weight (With Image) | Weight (Text Only) | | |
| | :--- | :--- | :--- | | |
| | **AI Pattern (NLP)** | 30% | 45% | | |
| | **Source Authority** | 25% | 45% | | |
| | **Visual Forensics** | 10% | - | | |
| | **Content Consistency**| 20% | - | | |
| | **Global Consensus** | 15% | 10% | | |
| --- | |
| ### 🎯 4. Project Deliverables | |
| * ✅ **Hybrid Detection:** စာသားရော ပုံပါ စစ်ဆေးနိုင်ခြင်း။ | |
| * ✅ **Evidence-Based Reasoning:** အဖြေတစ်ခုတည်း မဟုတ်ဘဲ အကြောင်းပြချက်ပါ ဖော်ပြခြင်း။ | |
| * ✅ **Burmese Language Support:** မြန်မာစာသားများကို တိုက်ရိုက် နားလည်ထောက်ပံ့ပေးခြင်း။ | |
| """) | |
| submit.click(master_detector_v12, inputs=[txt, url, img], outputs=output) | |
| clear.click(lambda: ["", "", None, empty_html], outputs=[txt, url, img, output]) | |
| if __name__ == "__main__": | |
| demo.launch(share=True) |