UTKARSH MISHRA
Initial commit: InfoClassifier project
e5e36db
Raw History Blame Contribute Delete
32.3 kB
"""
ui.py β€” Information Integrity Analyzer
One input. Automatic pipeline. Rich verdict output.
"""
# Load .env first so all API keys are available before any module reads os.getenv()
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
import re
import time
from typing import Generator
import gradio as gr
from data_collector import CollectionRequest, DataCollector
from environment import InformationIntegrityEnv
from explainability import ExplainabilityEngine, build_reasoning_from_episode
from models import Action
from tasks.graders import GraderFactory
try:
from reasoning import DeterministicReasoningEngine, reason_over_claims
from retrieval import RetrievalPipeline
from decision_engine import make_moderation_decisions
_REASONING_AVAILABLE = True
except Exception:
_REASONING_AVAILABLE = False
collector = DataCollector()
explainer = ExplainabilityEngine()
# ─────────────────────────────────────────────────────────────────────────────
# Claim extractor
# ─────────────────────────────────────────────────────────────────────────────
_SIGNALS = re.compile(
r"\b(confirm|prove|show|reveal|say|claim|report|find|cause|lead to|linked to|"
r"associated with|increase|decrease|never|always|all|every|none|proven|fact|"
r"truth|confirmed|vaccine|virus|cancer|disease|drug|radiation|5g|covid|"
r"lockdown|percent|%|study|research|scientist|doctor|expert|government|"
r"permanently|alter|spread|hide|secret|conspiracy|banned|cure|prevent)\b",
re.IGNORECASE,
)
_SKIP = re.compile(
r"^(share|follow|click|subscribe|watch|read|download|buy|sign up|please|"
r"thank you|breaking:|update:|alert:|warning:)",
re.IGNORECASE,
)
def extract_claims(text: str) -> list[str]:
sentences = re.split(r"(?<=[.!?])\s+|\s*[|‒–—]\s*", text.strip())
claims, seen = [], set()
for s in sentences:
s = s.strip().strip("\"'.,!?")
if len(s) < 12 or len(s) > 280:
continue
if _SKIP.match(s):
continue
if _SIGNALS.search(s) and s.lower() not in seen:
seen.add(s.lower())
claims.append(s)
if not claims:
claims = [s.strip() for s in sentences if len(s.strip()) >= 15][:3]
return claims[:6]
# ─────────────────────────────────────────────────────────────────────────────
# HTML card builders
# ─────────────────────────────────────────────────────────────────────────────
def _progress_html(stage: str, pct: int, msg: str) -> str:
stages = ["Extracting claims", "Searching sources", "Running analysis", "Complete"]
dots = "".join(
f'<span style="display:inline-block;width:10px;height:10px;border-radius:50%;'
f'margin:0 4px;background:{"#4f8ef7" if i <= stages.index(stage) else "#dde3ee"}'
f'"></span>'
for i, s in enumerate(stages)
if (i <= stages.index(stage)) or True
)
return f"""
<div style="font-family:'Inter',sans-serif;padding:28px 32px;background:#f8faff;
border-radius:16px;border:1px solid #e0e7ff;">
<div style="font-size:15px;color:#5c6bc0;margin-bottom:14px;font-weight:600;">
{stage}… &nbsp; <span style="font-weight:400;color:#8e99c5">{msg}</span>
</div>
<div style="background:#e8edf8;border-radius:8px;height:8px;overflow:hidden;">
<div style="background:linear-gradient(90deg,#4f8ef7,#7c6af7);height:100%;
width:{pct}%;transition:width 0.4s ease;border-radius:8px;"></div>
</div>
</div>"""
def _verdict_color(decision: str) -> tuple[str, str, str]:
"""Returns (bg_gradient, text_color, icon)."""
return {
"remove": ("linear-gradient(135deg,#ff4444,#d32f2f)", "#fff", "🚫"),
"flag": ("linear-gradient(135deg,#ff9800,#e65100)", "#fff", "⚠️"),
"allow": ("linear-gradient(135deg,#43a047,#1b5e20)", "#fff", "βœ…"),
}.get(decision, ("linear-gradient(135deg,#607d8b,#37474f)", "#fff", "❓"))
def _reliability_bar(score: float) -> str:
filled = int(score * 12)
color = "#e53935" if score < 0.4 else "#fb8c00" if score < 0.7 else "#43a047"
bar = f'<span style="color:{color}">{"β–ˆ"*filled}</span>' \
f'<span style="color:#e0e0e0">{"β–ˆ"*(12-filled)}</span>'
return f'<span style="font-family:monospace;font-size:12px">{bar}</span> ' \
f'<span style="font-size:12px;color:#666">{score:.2f}</span>'
def _stance_badge(stance: str) -> str:
cfg = {
"contradicting": ("#fdecea", "#c62828", "βœ— Contradicts"),
"supporting": ("#e8f5e9", "#2e7d32", "βœ“ Supports"),
"neutral": ("#f3f4f6", "#555", "Β· Neutral"),
}.get(stance, ("#f3f4f6", "#555", stance))
bg, fg, label = cfg
return (f'<span style="background:{bg};color:{fg};padding:2px 8px;'
f'border-radius:12px;font-size:12px;font-weight:600">{label}</span>')
def _claim_badge(status: str) -> str:
cfg = {
"false": ("#fdecea", "#c62828", "FALSE"),
"true": ("#e8f5e9", "#2e7d32", "TRUE"),
"partially_true": ("#fff8e1", "#e65100", "PARTIAL"),
"unknown": ("#f3f4f6", "#555", "UNKNOWN"),
"unverified": ("#f3f4f6", "#9e9e9e", "UNVERIFIED"),
}.get(status.lower() if isinstance(status, str) else "unverified",
("#f3f4f6", "#555", status))
bg, fg, label = cfg
return (f'<span style="background:{bg};color:{fg};padding:3px 10px;'
f'border-radius:12px;font-size:11px;font-weight:700;'
f'letter-spacing:.5px">{label}</span>')
def build_result_html(statement: str, scenario: dict, obs, result, explanation: str,
per_claim_reasoning: dict | None = None) -> str:
decision = result.decision or "unknown"
grad, txt_col, icon = _verdict_color(decision)
correct_badge = (
'<span style="background:#e8f5e9;color:#2e7d32;padding:3px 10px;'
'border-radius:12px;font-size:12px;font-weight:600">βœ“ Correct</span>'
if result.correct else
'<span style="background:#fdecea;color:#c62828;padding:3px 10px;'
'border-radius:12px;font-size:12px;font-weight:600">βœ— Incorrect</span>'
)
# ── Claims section
claims_html = ""
for c in obs.claims:
status = c.verification_status
if hasattr(status, "value"):
status = status.value
conf_pct = int(c.confidence * 100)
claims_html += f"""
<div style="display:flex;align-items:flex-start;gap:12px;padding:12px 0;
border-bottom:1px solid #f0f0f0;">
<div style="flex:1;font-size:14px;color:#333;line-height:1.5">{c.text}</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:4px;
white-space:nowrap">
{_claim_badge(status)}
<span style="font-size:11px;color:#999">conf {conf_pct}%</span>
</div>
</div>"""
# ── Sources section (top 6 by reliability)
sources = sorted(scenario["sources"], key=lambda s: -s["reliability"])[:6]
sources_html = ""
for s in sources:
domain = re.sub(r"https?://(www\.)?", "", s["url"]).split("/")[0]
sources_html += f"""
<div style="padding:12px;border-radius:10px;background:#fafafa;
border:1px solid #eee;margin-bottom:8px">
<div style="display:flex;justify-content:space-between;align-items:center;
flex-wrap:wrap;gap:6px">
<span style="font-size:13px;font-weight:600;color:#333">{s['title'][:65]}</span>
{_stance_badge(s['stance'])}
</div>
<div style="font-size:12px;color:#888;margin:4px 0">{domain} Β· {s['source_type']}</div>
<div style="font-size:12px;color:#555;line-height:1.5;margin:6px 0">
{s['text'][:180]}{'…' if len(s['text'])>180 else ''}
</div>
<div>Reliability: {_reliability_bar(s['reliability'])}</div>
</div>"""
if not sources_html:
sources_html = '<p style="color:#999;font-style:italic">No sources fetched β€” try adding API keys for richer results.</p>'
# ── Reward breakdown
bd = result.reward_breakdown
reward_rows = ""
for label, val in [
("Claim verification", bd.verify_claim_reward),
("Source checks", bd.check_source_reward),
("Multi-source bonus", bd.multi_source_bonus),
("Decision accuracy", bd.decision_reward),
("Premature penalty", bd.premature_penalty),
("Redundancy penalty", bd.redundant_penalty),
("Inefficiency", bd.inefficiency_penalty),
("Over-aggression", bd.aggression_penalty),
]:
if val == 0:
continue
color = "#2e7d32" if val > 0 else "#c62828"
reward_rows += f"""
<div style="display:flex;justify-content:space-between;padding:5px 0;
border-bottom:1px solid #f5f5f5;font-size:13px">
<span style="color:#555">{label}</span>
<span style="color:{color};font-weight:600">{val:+.2f}</span>
</div>"""
# ── Grader sub-scores
grader = GraderFactory.get(scenario["difficulty"])
gbd = grader.score_breakdown(
decision = result.decision or "none",
ground_truth = result.ground_truth,
sources_checked = list(range(result.sources_checked)),
claims_verified = list(range(result.claims_verified)),
step_count = result.steps_taken,
all_source_ids = [s["id"] for s in scenario["sources"]],
all_claim_ids = [c["id"] for c in scenario["claims"]],
)
grader_bars = ""
for sub_label, sub_val in [
("Decision", gbd["decision_sub_score"]),
("Evidence", gbd["evidence_sub_score"]),
("Reasoning", gbd["reasoning_sub_score"]),
("Efficiency", gbd["efficiency_sub_score"]),
]:
pct = int(sub_val * 100)
colr = "#e53935" if pct < 50 else "#fb8c00" if pct < 75 else "#43a047"
grader_bars += f"""
<div style="margin-bottom:8px">
<div style="display:flex;justify-content:space-between;font-size:12px;
color:#555;margin-bottom:3px">
<span>{sub_label}</span><span style="color:{colr};font-weight:600">{pct}%</span>
</div>
<div style="background:#eee;border-radius:4px;height:6px">
<div style="background:{colr};width:{pct}%;height:100%;border-radius:4px"></div>
</div>
</div>"""
# ── Deterministic Reasoning breakdown (new)
reasoning_html = ""
if per_claim_reasoning:
reasoning_html = """
<div style="background:#fff;border-radius:14px;border:1px solid #e8ecf4;
padding:20px 24px;margin-bottom:20px">
<div style="font-size:11px;font-weight:700;color:#9ca3af;letter-spacing:1px;
text-transform:uppercase;margin-bottom:14px">
🧠 Deterministic Reasoning Engine
</div>"""
for claim_text, r in per_claim_reasoning.items():
score = r["evidence_score"]
conf = r["confidence"]
dec = r["decision"]
n_sup = r["n_supporting"]
n_con = r["n_contradicting"]
n_neu = r["n_neutral"]
dec_col = {"allow": "#2e7d32", "flag": "#e65100", "remove": "#c62828"}.get(dec, "#555")
score_col = "#c62828" if score < -0.3 else "#2e7d32" if score > 0.3 else "#e65100"
reasoning_html += f"""
<div style="background:#f8faff;border-radius:10px;padding:14px 18px;margin-bottom:12px;
border-left:4px solid {dec_col}">
<div style="font-size:13px;color:#444;font-style:italic;margin-bottom:8px">
"{claim_text[:100]}{'…' if len(claim_text)>100 else ''}"
</div>
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:8px">
<span style="background:{dec_col};color:#fff;padding:2px 10px;border-radius:10px;
font-size:12px;font-weight:700;text-transform:uppercase">{dec}</span>
<span style="font-size:12px;color:{score_col};font-weight:600">
Score: {score:+.3f}</span>
<span style="font-size:12px;color:#888">Confidence: {conf}</span>
<span style="font-size:12px;color:#2e7d32">β–² {n_sup} supporting</span>
<span style="font-size:12px;color:#c62828">β–Ό {n_con} contradicting</span>
<span style="font-size:12px;color:#888">Β· {n_neu} neutral</span>
</div>"""
# Safety rules fired
if r.get("safety_rules_fired"):
for rule in r["safety_rules_fired"]:
rule_name = rule.split(":")[0]
rule_desc = rule[len(rule_name)+1:].strip()
reasoning_html += f"""
<div style="font-size:11px;color:#e65100;background:#fff8e1;padding:4px 10px;
border-radius:6px;margin-bottom:4px">
⚠ <b>{rule_name}</b>: {rule_desc}
</div>"""
# Source breakdown (top 4)
sb = r.get("source_breakdown", [])[:4]
if sb:
reasoning_html += '<div style="margin-top:8px">'
for src in sb:
contrib = src["contribution"]
c_col = "#c62828" if contrib < 0 else "#2e7d32" if contrib > 0 else "#888"
reasoning_html += f"""
<div style="display:flex;justify-content:space-between;font-size:11px;
color:#555;padding:2px 0;border-bottom:1px solid #eee">
<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
max-width:420px">{src['title'][:70]}</span>
<span style="margin:0 8px">{_stance_badge(src['stance'])}</span>
<span style="font-weight:600;color:{c_col};white-space:nowrap">
{contrib:+.3f}</span>
</div>"""
reasoning_html += "</div>"
reasoning_html += "</div>"
reasoning_html += "</div>"
# ── Explanation (pre-formatted)
expl_lines = explanation.replace("<", "&lt;").replace(">", "&gt;")
expl_html = f'<pre style="font-family:inherit;font-size:13px;line-height:1.7;' \
f'white-space:pre-wrap;margin:0;color:#333">{expl_lines}</pre>'
return f"""
<div style="font-family:'Inter',system-ui,sans-serif;max-width:960px;margin:0 auto">
<!-- VERDICT BANNER -->
<div style="background:{grad};border-radius:16px;padding:28px 32px;
margin-bottom:24px;display:flex;align-items:center;gap:20px">
<div style="font-size:52px">{icon}</div>
<div>
<div style="color:rgba(255,255,255,.75);font-size:13px;
font-weight:600;letter-spacing:1px;text-transform:uppercase">
Moderation Verdict
</div>
<div style="color:{txt_col};font-size:36px;font-weight:800;
line-height:1.1;text-transform:uppercase">{decision}</div>
<div style="color:rgba(255,255,255,.85);font-size:13px;margin-top:4px">
Difficulty: <b>{scenario['difficulty']}</b> &nbsp;Β·&nbsp;
Steps: <b>{result.steps_taken}</b> &nbsp;Β·&nbsp;
Reward: <b>{result.total_reward:+.2f}</b> &nbsp;Β·&nbsp;
Grader: <b>{result.grader_score:.0%}</b>
&nbsp;&nbsp;{correct_badge}
</div>
</div>
</div>
<!-- STATEMENT -->
<div style="background:#fff;border-radius:14px;border:1px solid #e8ecf4;
padding:20px 24px;margin-bottom:20px">
<div style="font-size:11px;font-weight:700;color:#9ca3af;letter-spacing:1px;
text-transform:uppercase;margin-bottom:8px">Statement Reviewed</div>
<div style="font-size:15px;color:#1f2937;line-height:1.6;
font-style:italic">"{statement}"</div>
</div>
<!-- CLAIMS + SOURCES (two col) -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:20px">
<!-- Claims -->
<div style="background:#fff;border-radius:14px;border:1px solid #e8ecf4;
padding:20px 24px">
<div style="font-size:11px;font-weight:700;color:#9ca3af;letter-spacing:1px;
text-transform:uppercase;margin-bottom:12px">
Claims Verified ({len(obs.claims)})
</div>
{claims_html}
</div>
<!-- Scores -->
<div style="background:#fff;border-radius:14px;border:1px solid #e8ecf4;
padding:20px 24px">
<div style="font-size:11px;font-weight:700;color:#9ca3af;letter-spacing:1px;
text-transform:uppercase;margin-bottom:14px">Grader Score Breakdown</div>
{grader_bars}
<div style="border-top:1px solid #eee;padding-top:12px;margin-top:4px">
<div style="font-size:11px;font-weight:700;color:#9ca3af;letter-spacing:1px;
text-transform:uppercase;margin-bottom:8px">Reward Breakdown</div>
{reward_rows}
<div style="display:flex;justify-content:space-between;padding:8px 0;
font-size:14px;font-weight:700">
<span>Total Reward</span>
<span style="color:{'#2e7d32' if result.total_reward>0 else '#c62828'}">
{result.total_reward:+.2f}
</span>
</div>
</div>
</div>
</div>
<!-- SOURCES -->
<div style="background:#fff;border-radius:14px;border:1px solid #e8ecf4;
padding:20px 24px;margin-bottom:20px">
<div style="font-size:11px;font-weight:700;color:#9ca3af;letter-spacing:1px;
text-transform:uppercase;margin-bottom:14px">
Evidence Sources ({len(scenario['sources'])} found)
</div>
{sources_html}
</div>
{reasoning_html}
<!-- EXPLANATION -->
<div style="background:#fff;border-radius:14px;border:1px solid #e8ecf4;
padding:20px 24px">
<div style="font-size:11px;font-weight:700;color:#9ca3af;letter-spacing:1px;
text-transform:uppercase;margin-bottom:14px">
πŸ“‹ Full Reasoning & Explanation
</div>
<div style="background:#f8faff;border-radius:10px;padding:16px 20px">
{expl_html}
</div>
</div>
</div>
"""
# ─────────────────────────────────────────────────────────────────────────────
# Main pipeline (streaming generator)
# ─────────────────────────────────────────────────────────────────────────────
def analyze(statement: str) -> Generator:
"""
Difficulty is NOT a UI input.
The UI is a demo tool; evaluation difficulty is controlled exclusively
by the backend API (/reset?difficulty=...) and inference.py.
DataCollector always uses 'medium' here β€” it only affects evidence
retrieval depth, not grading.
"""
statement = statement.strip()
if not statement:
yield "<p style='color:#c62828;padding:20px'>⚠️ Please enter a statement to analyze.</p>"
return
# Stage 1 β€” Extract claims
yield _progress_html("Extracting claims", 10, "detecting verifiable assertions…")
claims = extract_claims(statement)
if not claims:
claims = [statement]
claim_list = " β€’ ".join(f"<em>{c[:60]}</em>" for c in claims)
yield _progress_html("Searching sources", 30,
f"found {len(claims)} claim(s): {claim_list[:120]}…")
# Stage 2 β€” Collect evidence
# Difficulty is fixed to "medium" here β€” the UI does NOT control evaluation logic.
# To run a proper benchmark, use: python inference.py --benchmark
try:
req = CollectionRequest(content=statement, claims=claims,
difficulty="medium")
scenario = collector.collect(req)
except Exception as e:
yield f"<p style='color:#c62828;padding:20px'>❌ Evidence collection failed: {e}</p>"
return
n_src = len(scenario["sources"])
yield _progress_html("Running analysis", 65,
f"fetched {n_src} source(s) β€” running moderation episode…")
# Stage 3 β€” Run episode
env = InformationIntegrityEnv(scenario=scenario, max_steps=20)
obs = env.reset()
per_claim_reasoning = None # populated by reasoning engine at decide step
# Auto-agent: verify all claims β†’ check all sources β†’ decide
for claim in obs.claims:
try:
obs, _, done, _ = env.step(
Action(type="verify_claim", claim_id=claim.id,
reasoning="auto-verify"))
if done:
break
except Exception:
pass
if not env.done:
for src in obs.available_sources:
try:
obs, _, done, _ = env.step(
Action(type="check_source", source_id=src.id,
reasoning="auto-check"))
if done:
break
except Exception:
pass
if not env.done:
# Use DeterministicReasoningEngine if available, else weighted fallback
if _REASONING_AVAILABLE:
try:
pipeline = RetrievalPipeline()
engine = DeterministicReasoningEngine()
raw_sources = [
{
"id": s.id, "title": s.title, "text": s.text,
"url": s.url, "stance": s.stance,
"reliability": s.reliability, "source_type": s.source_type,
}
for s in obs.available_sources
]
claim_texts = [c.text for c in obs.claims]
claim_results_map = pipeline.retrieve_for_all_claims(claim_texts, raw_sources)
agg_decision, per_claim_reasoning = reason_over_claims(claim_results_map, engine)
# Convert StructuredReasoning objects β†’ dicts for the HTML builder
per_claim_reasoning = {ct: r.to_dict() for ct, r in per_claim_reasoning.items()}
# ── decision_engine: recalibrate each claim's confidence & decision ──
de_inputs = []
for ct, r in per_claim_reasoning.items():
src_list = claim_results_map.get(ct, [])
de_inputs.append({
"claim": ct,
"status": "FALSE" if r["decision"] == "remove"
else "TRUE" if r["decision"] == "allow"
else "UNKNOWN",
"confidence": r.get("evidence_score", 0.0),
"supporting_sources": r["n_supporting"],
"contradicting_sources": r["n_contradicting"],
"neutral_sources": r["n_neutral"],
"avg_source_reliability": (
sum(s.reliability for s in src_list) / len(src_list)
if src_list else 0.5
),
})
de_result = make_moderation_decisions(de_inputs)
# Map content decision back to env decision format
_de_map = {"AUTO_REMOVE": "remove", "ALLOW": "allow", "FLAG_FOR_REVIEW": "flag"}
decision = _de_map.get(de_result["content_decision"], agg_decision)
# Inject decision_engine output into per_claim_reasoning for display
for entry in de_result["per_claim"]:
ct = entry["claim"]
if ct in per_claim_reasoning:
per_claim_reasoning[ct]["de_decision"] = entry["decision"]
per_claim_reasoning[ct]["de_confidence"] = entry["final_confidence"]
per_claim_reasoning[ct]["de_reason"] = entry["reason"]
per_claim_reasoning[ct]["de_pattern"] = entry.get("pattern_matched")
except Exception:
per_claim_reasoning = None
# Fallback to weighted ratio
contra = sum(s.reliability for s in obs.available_sources if s.stance == "contradicting")
supp = sum(s.reliability for s in obs.available_sources if s.stance == "supporting")
total = contra + supp or 1.0
ratio = supp / total
decision = "remove" if ratio < 0.25 else "flag" if ratio < 0.60 else "allow"
else:
per_claim_reasoning = None
contra = sum(s.reliability for s in obs.available_sources if s.stance == "contradicting")
supp = sum(s.reliability for s in obs.available_sources if s.stance == "supporting")
total = contra + supp or 1.0
ratio = supp / total
decision = "remove" if ratio < 0.25 else "flag" if ratio < 0.60 else "allow"
try:
obs, _, _, _ = env.step(
Action(type="decide", decision=decision,
reasoning="deterministic engine decision"))
except Exception:
pass
else:
per_claim_reasoning = None
yield _progress_html("Complete", 95, "generating explanation…")
# Stage 4 β€” Build result
result = env.episode_result()
reasoning = build_reasoning_from_episode(
obs=obs,
decision=result.decision or "unknown",
episode_id=env._episode_id,
difficulty=scenario["difficulty"],
)
explanation = explainer.generate_explanation(reasoning)
# Use engine's per_claim_reasoning if captured, else env's cached reasoning
if per_claim_reasoning is None and env._last_reasoning:
per_claim_reasoning = {
ct: r.to_dict() for ct, r in env._last_reasoning.items()
}
yield build_result_html(statement, scenario, obs, result, explanation,
per_claim_reasoning=per_claim_reasoning)
# ─────────────────────────────────────────────────────────────────────────────
# UI Layout
# ─────────────────────────────────────────────────────────────────────────────
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
body, .gradio-container { font-family: 'Inter', system-ui, sans-serif !important; background: #f0f4ff !important; }
.gr-button-primary { background: linear-gradient(135deg,#4f8ef7,#7c6af7) !important;
border:none !important; font-weight:700 !important;
font-size:16px !important; padding:14px 36px !important;
border-radius:12px !important; color:white !important; }
.gr-button-primary:hover { opacity:.9 !important; transform:translateY(-1px); }
textarea { font-size:15px !important; border-radius:12px !important;
border:2px solid #e0e7ff !important; padding:14px !important; }
textarea:focus { border-color:#4f8ef7 !important; box-shadow:0 0 0 3px rgba(79,142,247,.15) !important; }
.gr-form { background:transparent !important; }
.gr-box { background:transparent !important; border:none !important; box-shadow:none !important; }
#component-0 { background: #f0f4ff !important; }
"""
EXAMPLES = [
"5G towers confirmed to spread COVID-19. Scientists reveal radiation activates coronavirus. Governments hiding the truth.",
"Doctors confirm: drinking 3 cups of coffee daily prevents Alzheimer's in 100% of patients with zero side effects.",
"New government report proves immigration increases violent crime by 40% in all affected regions. Multiple studies confirm.",
"mRNA vaccines permanently alter human DNA and cause infertility according to multiple top doctors and researchers.",
"There will be a national lockdown starting next week. Government sources confirmed mandatory quarantine for all cities.",
"Eating chocolate every day reduces heart attack risk by 50% β€” scientists confirm in landmark study.",
]
with gr.Blocks(title="Information Integrity Analyzer") as demo:
gr.HTML("""
<div style="text-align:center;padding:40px 20px 20px;font-family:'Inter',sans-serif">
<div style="font-size:36px;margin-bottom:8px">πŸ›‘οΈ</div>
<h1 style="font-size:28px;font-weight:800;color:#1e2a4a;margin:0 0 8px">
Information Integrity Analyzer
</h1>
<p style="color:#6b7280;font-size:15px;max-width:520px;margin:0 auto">
Paste any statement or claim. The system automatically extracts assertions,
searches real evidence sources, and delivers a moderation verdict with full explanation.
</p>
<p style="color:#9ca3af;font-size:12px;margin-top:8px">
Demo mode β€” for benchmarking across difficulty tiers run:
<code>python inference.py --benchmark</code>
</p>
</div>
""")
with gr.Row(elem_id="main-row"):
with gr.Column(scale=1, min_width=640):
statement_box = gr.Textbox(
label="",
placeholder=(
"Paste any statement, post, or claim here…\n\n"
"e.g. \"5G towers confirmed to spread COVID-19. "
"Governments hiding the truth.\""
),
lines=4,
max_lines=8,
show_label=False,
)
analyze_btn = gr.Button(
"πŸ” Analyze Statement",
variant="primary",
)
gr.HTML("<div style='margin:8px 0 4px;font-size:12px;color:#9ca3af;font-weight:600;letter-spacing:.5px;text-transform:uppercase'>Try an example</div>")
with gr.Row():
for ex_text in EXAMPLES[:3]:
btn = gr.Button(ex_text[:45] + "…", size="sm", scale=1)
btn.click(fn=lambda t=ex_text: t, outputs=[statement_box])
with gr.Row():
for ex_text in EXAMPLES[3:]:
btn = gr.Button(ex_text[:45] + "…", size="sm", scale=1)
btn.click(fn=lambda t=ex_text: t, outputs=[statement_box])
result_html = gr.HTML(
value="<div style='padding:32px;text-align:center;color:#9ca3af;font-size:15px'>"
"Results will appear here after analysis.</div>"
)
analyze_btn.click(
fn=analyze,
inputs=[statement_box],
outputs=[result_html],
)
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860,
show_error=True,
css=CSS,
theme=gr.themes.Soft(
primary_hue="indigo",
secondary_hue="purple",
neutral_hue="slate",
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
),
)