Project_Mizan / app.py
KhaledTTarabay's picture
UI Update
e35d845
Raw
History Blame Contribute Delete
9.58 kB
import streamlit as st
import streamlit.components.v1 as components
from model.predict import predict
st.set_page_config(page_title="Mizan ميزان", layout="centered")
# ---------------------------------------------------------------------------
# Theme — parchment, ink, wax seal. Injected once for the whole app.
# ---------------------------------------------------------------------------
st.markdown(
"""
<style>
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;0,700;1,500&family=Amiri:wght@400;700&family=Spectral:wght@300;400;500&display=swap');
:root {
--parchment: #E8DFC8;
--ink: #2B2118;
--ink-soft: #5A4B38;
--wax-red: #8B2E20;
--seal-blue: #1F3A5F;
--gold: #A8843D;
}
.stApp {
background: #E8DFC8;
background-image:
radial-gradient(circle at 20% 15%, rgba(168,132,61,0.06) 0%, transparent 40%),
radial-gradient(circle at 80% 85%, rgba(139,46,32,0.05) 0%, transparent 45%);
}
.miz-masthead { text-align: center; margin: 1rem 0 0.2rem; }
.miz-masthead .ar { font-family: 'Amiri', serif; font-weight: 700; font-size: clamp(3rem, 10vw, 5rem); line-height: 1; color: #2B2118; }
.miz-masthead .en { font-family: 'Cormorant Garamond', serif; font-weight: 600; font-size: clamp(1rem, 3vw, 1.4rem); letter-spacing: 0.5em; text-transform: uppercase; color: #5A4B38; margin-top: 0.3rem; padding-left: 0.5em; }
.miz-rule { width: min(520px, 88vw); margin: 1.1rem auto 0.4rem; display: flex; align-items: center; gap: 0.8rem; color: #A8843D; }
.miz-rule .line { flex: 1; height: 1px; background: linear-gradient(to right, transparent, #A8843D); }
.miz-rule .line.r { background: linear-gradient(to left, transparent, #A8843D); }
.miz-rule .glyph { font-family: 'Amiri', serif; font-size: 1.4rem; opacity: 0.85; }
.miz-tagline { font-family: 'Cormorant Garamond', serif; font-style: italic; font-size: 1.1rem; color: #5A4B38; text-align: center; max-width: 30rem; margin: 0.5rem auto 1.5rem; line-height: 1.5; }
.miz-section { font-family: 'Cormorant Garamond', serif; font-weight: 600; text-transform: uppercase; letter-spacing: 0.28em; font-size: 0.8rem; color: #5A4B38; margin: 0.5rem 0 0.3rem; }
.stTabs [data-baseweb="tab"] { font-family: 'Cormorant Garamond', serif; font-size: 1.05rem; letter-spacing: 0.1em; color: #5A4B38; }
.stTabs [aria-selected="true"] { color: #2B2118; border-bottom-color: #8B2E20 !important; }
.stTextArea textarea { background: rgba(255,253,247,0.6) !important; border: 1px solid rgba(90,75,56,0.3) !important; border-radius: 2px !important; font-family: 'Amiri', serif !important; font-size: 1.3rem !important; line-height: 1.7 !important; color: #2B2118 !important; direction: rtl; }
.stTextArea textarea:focus { border-color: #A8843D !important; box-shadow: 0 0 0 3px rgba(168,132,61,0.15) !important; }
.stButton button { font-family: 'Cormorant Garamond', serif !important; font-weight: 600 !important; letter-spacing: 0.14em; text-transform: uppercase; color: #E8DFC8 !important; background: #2B2118 !important; border: none !important; border-radius: 2px !important; padding: 0.6rem 1.8rem !important; }
.stButton button:hover { background: #1d160f !important; }
#MainMenu, footer, header { visibility: hidden; }
</style>
""",
unsafe_allow_html=True,
)
# ---------------------------------------------------------------------------
# Masthead
# ---------------------------------------------------------------------------
st.markdown(
"""
<div class="miz-masthead">
<div class="ar">ميزان</div>
<div class="en">Mizan</div>
</div>
<div class="miz-rule"><span class="line"></span><span class="glyph">۞</span><span class="line r"></span></div>
<p class="miz-tagline">A scale for what we are told. Weigh a claim, and let the seal fall where it may.</p>
""",
unsafe_allow_html=True,
)
def render_verdict(label, certainty, confidence):
"""Render the wax-seal verdict as a self-contained HTML component."""
is_fake = label == "Fake"
accent = "#8B2E20" if is_fake else "#1F3A5F"
accent_deep = "#6E2014" if is_fake else "#16293f"
accent_light = "#d8a99e" if is_fake else "#9db4cf"
accent_text = "#eccfc8" if is_fake else "#cdd9e8"
mark = "\u2715" if is_fake else "\u2713"
sub = "FALSE" if is_fake else "CREDIBLE"
caption = "Sealed \u00b7 Disputed" if is_fake else "Sealed \u00b7 Authentic"
html = f"""
<!DOCTYPE html><html><head><meta charset="utf-8">
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;600;700&family=Amiri:wght@400;700&display=swap" rel="stylesheet">
<style>
body {{ margin:0; background:transparent; font-family:'Cormorant Garamond',serif; text-align:center; overflow:hidden; }}
.eyebrow {{ text-transform:uppercase; letter-spacing:0.4em; font-size:0.75rem; color:#5A4B38; opacity:0; animation:fade 0.6s ease 0.1s forwards; }}
.word {{ font-weight:700; font-size:clamp(2.4rem,8vw,3.6rem); line-height:1.05; color:#2B2118; opacity:0; animation:fade 0.7s ease 0.25s forwards; }}
.conf {{ font-family:'Amiri',serif; font-size:1.05rem; color:#5A4B38; margin-top:0.4rem; opacity:0; animation:fade 0.7s ease 0.45s forwards; }}
.seal {{ margin:1.6rem auto 0; width:140px; height:140px; transform:scale(2.4) rotate(-18deg); opacity:0; animation:stamp 0.5s cubic-bezier(0.2,1.4,0.4,1) 0.7s forwards; }}
.cap {{ text-transform:uppercase; letter-spacing:0.32em; font-size:0.7rem; color:#5A4B38; margin-top:0.9rem; opacity:0; animation:fade 0.6s ease 1.15s forwards; }}
@keyframes stamp {{ 0%{{transform:scale(2.4) rotate(-18deg);opacity:0;}} 60%{{opacity:1;}} 100%{{transform:scale(1) rotate(-6deg);opacity:1;}} }}
@keyframes fade {{ from{{opacity:0;transform:translateY(10px);}} to{{opacity:1;transform:translateY(0);}} }}
@media (prefers-reduced-motion: reduce) {{ * {{ animation-duration:0.01ms!important; animation-delay:0ms!important; }} }}
</style></head><body>
<div class="eyebrow">The Scale Has Spoken</div>
<div class="word">{label}</div>
<div class="conf">{certainty} \u00b7 Confidence {confidence}%</div>
<div class="seal">
<svg viewBox="0 0 150 150" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="wax" cx="40%" cy="35%" r="75%">
<stop offset="0%" stop-color="{accent}"/>
<stop offset="100%" stop-color="{accent_deep}"/>
</radialGradient>
<filter id="rough"><feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" result="n"/><feDisplacementMap in="SourceGraphic" in2="n" scale="3.2"/></filter>
</defs>
<g filter="url(#rough)">
<circle cx="75" cy="75" r="64" fill="url(#wax)"/>
<circle cx="75" cy="75" r="52" fill="none" stroke="{accent_light}" stroke-width="1.2" opacity="0.5"/>
</g>
<text x="75" y="62" text-anchor="middle" font-family="Amiri,serif" font-size="32" fill="{accent_text}" font-weight="700">{mark}</text>
<text x="75" y="92" text-anchor="middle" font-family="Cormorant Garamond,serif" font-size="14" letter-spacing="2" fill="{accent_text}" font-weight="600">VERDICT</text>
<text x="75" y="107" text-anchor="middle" font-family="Cormorant Garamond,serif" font-size="9" letter-spacing="3" fill="{accent_light}">{sub}</text>
</svg>
</div>
<div class="cap">{caption}</div>
</body></html>
"""
components.html(html, height=360)
# ---------------------------------------------------------------------------
# Tabs
# ---------------------------------------------------------------------------
tab1, tab2 = st.tabs(["Analyse", "Live Headlines"])
with tab1:
st.markdown('<div class="miz-section">Submit a claim for judgment</div>', unsafe_allow_html=True)
text_input = st.text_area("Paste a claim in Arabic", height=150, label_visibility="collapsed")
if st.button("Render Verdict"):
if text_input.strip() == "":
st.warning("Please enter a claim first.")
else:
results = predict(text_input)
if results["label"] == "Unsupported":
st.warning("Only Arabic is supported in this version.")
else:
render_verdict(results["label"], results["certainty"], results["confidence"])
st.caption(f"Detected language: {results['language'].upper()}")
with tab2:
st.markdown('<div class="miz-section">Live Headlines</div>', unsafe_allow_html=True)
if st.button("Fetch Headlines"):
from scraper.headlines import get_headlines
with st.spinner("Fetching headlines..."):
headlines = get_headlines()
for h in headlines:
result = predict(h["title"])
col1, col2 = st.columns([3, 1])
with col1:
st.markdown(f"**{h['title']}**")
st.caption(f"{h['source']} \u00b7 {h['language'].upper()}")
with col2:
if result["label"] == "Unsupported":
st.info("Arabic only")
elif result["label"] == "Fake":
st.error(f"{result['certainty']} Fake \u00b7 {result['confidence']}%")
else:
st.success(f"{result['certainty']} Credible \u00b7 {result['confidence']}%")
st.divider()