Ghada-99-Ragab's picture
Upload 8 files
1fe445a verified
Raw History Blame Contribute Delete
25.6 kB
"""Islamic Content Verifier: Gradio interface around the verification pipeline in ``verifier.py``.
Run locally with ``python app.py``. The rendering helpers are plain functions that return HTML, so they can be
tested without Gradio; Gradio is imported only when the interface is built.
"""
from __future__ import annotations
import html
import json
import logging
import os
import threading
from pathlib import Path
from typing import List, Optional
from verifier import MAX_INPUT_CHARS, IslamicContentVerifier
logger = logging.getLogger(__name__)
EXAMPLES_PATH = Path(__file__).resolve().parent / "demo" / "examples.json"
DEFAULT_MODEL_DIR = Path(__file__).resolve().parent / "models" / "span_detector"
# --------------------------------------------------------------------------------------------------------------
# Pipeline access
# --------------------------------------------------------------------------------------------------------------
_pipeline: Optional[IslamicContentVerifier] = None
_pipeline_lock = threading.Lock()
def get_pipeline() -> IslamicContentVerifier:
"""Create the pipeline once (the corpus index builds in a few seconds) and reuse it for every request."""
global _pipeline
with _pipeline_lock:
if _pipeline is None:
model_dir = os.environ.get("ICV_DETECTOR_MODEL") or (str(DEFAULT_MODEL_DIR) if DEFAULT_MODEL_DIR.is_dir() else None)
# With a trained model folder the BERT detector is used; if it cannot be loaded the rules detector takes over.
_pipeline = IslamicContentVerifier(detector="auto" if model_dir else "rules", model_dir=model_dir)
return _pipeline
def load_examples(path: Path = EXAMPLES_PATH) -> List[dict]:
try:
with open(path, encoding="utf-8") as handle:
return json.load(handle)
except (OSError, json.JSONDecodeError):
logger.exception("Could not load demo examples from %s", path)
return []
# --------------------------------------------------------------------------------------------------------------
# HTML rendering
# --------------------------------------------------------------------------------------------------------------
_e = html.escape
GROUP_LABEL = {
"verified": ("موثّق", "Verified"),
"mismatch": ("غير مطابق", "Mismatch"),
"review": ("يحتاج مراجعة بشرية", "Needs Human Review"),
}
TYPE_LABEL = {"Ayah": ("آية قرآنية", "Quran"), "Hadith": ("حديث نبوي", "Hadith")}
INDICATORS = [
("composite", "الدرجة المركبة", "Composite score"),
("coverage", "تغطية الكلمات", "Word coverage"),
("lcs_ratio", "التسلسل النصي", "Sequence (LCS)"),
("token_overlap", "تداخل الكلمات", "Token overlap"),
("edit_sim", "تشابه الأحرف", "Character similarity"),
("diacritic_sim", "مع التشكيل", "With diacritics"),
]
def _pct(value: float) -> str:
return f"{round(value * 100)}%"
def _source_label(source: dict) -> str:
if source["type"] == "Quran":
start = source.get("ayah", source.get("ayah_start"))
end = source.get("ayah_end", start)
verses = f"{start}" if start == end else f"{start}–{end}"
return f"سورة {source['surah_name']} — الآية {verses}"
return f"حديث رقم {source['hadithID']} — {source['title']}"
def _truncate(text: str, limit: int) -> str:
return text if len(text) <= limit else text[:limit].rstrip() + " …"
def _diff_html(comparison: dict) -> str:
"""Word-level diff: green = words only in the source, red = words only in the quotation."""
parts = []
for op in comparison["word_diff"]:
if op["op"] == "equal":
parts.append(f'<span class="w-eq">{_e(op["span"])}</span>')
else:
if op["span"]:
parts.append(f'<span class="w-extra" title="في الاقتباس فقط · only in the quotation">{_e(op["span"])}</span>')
if op["source"]:
parts.append(f'<span class="w-missing" title="في المصدر فقط · only in the source">{_e(op["source"])}</span>')
return " ".join(parts)
def _indicator_table(signals: Optional[dict]) -> str:
if not signals:
return ""
rows = []
for key, ar, en in INDICATORS:
value = float(signals.get(key, 0.0))
rows.append(
f'<div class="ind"><div class="ind-name">{ar}<small>{en}</small></div>'
f'<div class="bar"><span style="width:{_pct(value)}"></span></div><div class="ind-val">{_pct(value)}</div></div>'
)
substring = "نعم · Yes" if signals.get("is_substring") else "لا · No"
rows.append(f'<div class="ind"><div class="ind-name">احتواء كامل<small>Contained in source</small></div><div class="ind-val wide">{substring}</div></div>')
return '<div class="indicators">' + "".join(rows) + "</div>"
def _evidence_panel(span: dict) -> str:
evidence, verification = span["evidence"], span["verification"]
blocks = [
f'<div class="ev-row"><b>الاقتباس المكتشف · Detected quotation</b><p class="quote">{_e(span["text"])}</p></div>',
f'<div class="ev-row"><b>النوع · Type</b><p>{TYPE_LABEL[span["type"]][0]} · {TYPE_LABEL[span["type"]][1]}</p></div>',
]
if evidence:
comparison = evidence["comparison"]
blocks += [
f'<div class="ev-row"><b>المصدر المرشح · Candidate source</b><p>{_e(_source_label(evidence["source"]))}</p></div>',
f'<div class="ev-row"><b>نص المصدر الأصلي · Original source text</b><p class="quote">{_e(comparison["source_excerpt"])}</p></div>',
f'<div class="ev-row"><b>المقارنة · Comparison</b><p class="diff">{_diff_html(comparison)}</p>'
f'<p class="legend"><span class="w-extra">في الاقتباس فقط</span> <span class="w-missing">في المصدر فقط</span> '
f'· تشابه الكلمات {_pct(comparison["word_similarity"])}</p></div>',
f'<div class="ev-row"><b>مؤشرات التحقق · Similarity indicators</b>{_indicator_table(evidence.get("signals"))}</div>',
]
else:
blocks.append('<div class="ev-row"><b>المصدر · Source</b><p>لم يُسترجع أي مصدر مرشح · No candidate source was retrieved.</p></div>')
blocks += [
f'<div class="ev-row"><b>الثقة · Confidence</b><p>{_pct(verification["confidence"])}</p>'
f'<p class="legend en">verdict: {_e(verification["verdict"])} · method: {_e(verification["method"])} · '
f'candidates checked: {verification["n_candidates"]}</p></div>',
f'<div class="ev-row"><b>القرار · Decision</b><p>{_e(span["status_ar"])}<br><span class="en">{_e(span["status_en"])}</span></p></div>',
f'<div class="ev-row"><b>سبب القرار · Decision reason</b><p class="en">{_e(span["reason"])}</p></div>',
]
return "".join(blocks)
def _card(span: dict) -> str:
group = span["group"]
ar_label, en_label = GROUP_LABEL[group]
type_ar, type_en = TYPE_LABEL[span["type"]]
evidence = span["evidence"]
confidence = span["verification"]["confidence"]
source_html = comparison_html = ""
if evidence:
comparison = evidence["comparison"]
source_html = (
f'<div class="field"><label>المصدر المرشح · Candidate source</label><p>{_e(_source_label(evidence["source"]))}</p></div>'
f'<div class="field"><label>نص المصدر الأصلي · Original source text</label>'
f'<p class="quote small">{_e(_truncate(comparison["source_excerpt"], 360))}</p></div>'
)
comparison_html = (
f'<div class="field"><label>المقارنة · Comparison</label><p class="diff">{_diff_html(comparison)}</p></div>'
)
else:
source_html = '<div class="field"><label>المصدر المرشح · Candidate source</label><p>لا يوجد · None found</p></div>'
action = ""
correction, suggestion = span["correction"], span["suggestion"]
if correction:
action = (
'<div class="action ok"><b>تصحيح مدعوم بالمصدر · Source-backed correction</b>'
f'<p class="quote">{_e(correction["display_text"])}</p>'
f'<p class="legend">{_e(_source_label({"type": "Quran", **correction["source"]}) if correction["source"]["type"] == "Quran" else _source_label(correction["source"]))}'
f' · قوة المطابقة {_pct(correction["match_strength"])}</p></div>'
)
elif group == "review":
closest = ""
if suggestion:
src = suggestion["source"]
label = _source_label({"type": "Quran", **src}) if src["type"] == "Quran" else _source_label(src)
closest = (f'<p class="legend">أقرب مصدر وُجد للمراجِع (ليس تصحيحًا آليًا) · Closest source for the reviewer (not an automatic correction): '
f'{_e(label)}</p><p class="quote small">{_e(_truncate(suggestion["display_text"], 360))}</p>')
action = (
'<div class="action warn"><b>الأدلة غير كافية للتصحيح الآلي.</b> يُوصى بالمراجعة البشرية.'
'<p class="en">Insufficient evidence for automatic correction. Human review is recommended.</p>' + closest + "</div>"
)
elif span["status"] == "UNSUPPORTED":
action = (
'<div class="action bad"><b>لا يوجد مصدر مطابق في المراجع المتاحة للنظام.</b> لم يُقترح أي نص بديل.'
'<p class="en">No matching source in the corpus. No replacement text is proposed.</p></div>'
)
return f"""
<div class="qcard {group}">
<div class="qhead">
<span class="idx">{span["id"]}</span>
<span class="badge type">{type_ar} · {type_en}</span>
<span class="badge st {group}">{ar_label} · {en_label}</span>
<span class="conf">الثقة · Confidence <b>{_pct(confidence)}</b></span>
</div>
<div class="field"><label>الاقتباس المكتشف · Detected quotation</label><p class="quote">{_e(span["text"])}</p></div>
{source_html}
{comparison_html}
<div class="field"><label>القرار · Decision</label><p>{_e(span["status_ar"])}<br><span class="en">{_e(span["status_en"])}</span></p></div>
{action}
<details class="evidence"><summary>عرض الدليل · View Evidence</summary><div class="ev-body">{_evidence_panel(span)}</div></details>
</div>"""
def _summary(summary: dict) -> str:
tiles = [
(summary["n_spans"], "إجمالي الاقتباسات", "Total quotations", ""),
(summary["n_ayah"], "آيات قرآنية", "Quranic", ""),
(summary["n_hadith"], "أحاديث", "Hadith", ""),
(summary["VERIFIED"], "موثّق", "Verified", "verified"),
(summary["CORRECTED"] + summary["UNSUPPORTED"], "غير مطابق", "Mismatch", "mismatch"),
(summary["HUMAN_REVIEW"], "يحتاج مراجعة بشرية", "Needs Human Review", "review"),
]
return '<div class="summary">' + "".join(
f'<div class="tile {cls}{" zero" if value == 0 and cls else ""}"><b>{value}</b><span>{ar}</span><small>{en}</small></div>'
for value, ar, en, cls in tiles
) + "</div>"
def _highlighted_text(result: dict) -> str:
text, pieces, cursor = result["input_text"], [], 0
for span in result["spans"]:
pieces.append(_e(text[cursor:span["start"]]))
pieces.append(f'<mark class="{span["group"]}" title="{_e(span["status_en"])}">{_e(text[span["start"]:span["end"]])}</mark>')
cursor = span["end"]
pieces.append(_e(text[cursor:]))
legend = (
'<div class="hl-legend"><mark class="verified">موثّق · Verified</mark><mark class="mismatch">غير مطابق · Mismatch</mark>'
'<mark class="review">مراجعة بشرية · Review</mark></div>'
)
return (
'<div class="highlight"><label>النص مع الاقتباسات المكتشفة · Text with detected quotations</label><p>'
+ "".join(pieces) + "</p>" + legend + "</div>"
)
def render_results(result: dict) -> str:
"""HTML report for a pipeline result."""
if not result["spans"]:
return (
'<div class="notice">لم يُعثر على اقتباسات قرآنية أو حديثية في هذا النص. '
'<span class="en">No Quranic or Hadith quotations were detected. Quotations are currently detected when they are '
'enclosed in quotation marks or brackets and introduced by a typical phrase (see Limitations).</span></div>'
)
names = {"RuleDetector": ("القواعد", "Rule-based"), "BertDetector": ("نموذج BERT", "BERT model")}
ar, en = names.get(result["detector"], (result["detector"], result["detector"]))
chip = f'<div class="det-chip">الكاشف · Detector: <b>{ar} · {en}</b></div>'
return chip + _summary(result["summary"]) + _highlighted_text(result) + "".join(_card(s) for s in result["spans"])
def render_message(message_ar: str, message_en: str, kind: str = "info") -> str:
return f'<div class="notice {kind}">{_e(message_ar)}<span class="en">{_e(message_en)}</span></div>'
def verify_text(text: str) -> str:
"""Gradio handler: validate the input, run the pipeline and return HTML (never raises)."""
if not text or not text.strip():
return render_message("الرجاء إدخال نص للتحقق منه.", "Please enter a text to verify.", "warn")
try:
return render_results(get_pipeline().analyze(text))
except ValueError:
return render_message(
f"النص طويل جدًا (الحد الأقصى {MAX_INPUT_CHARS} حرف).", f"The text is too long (maximum {MAX_INPUT_CHARS} characters).", "warn"
)
except Exception:
logger.exception("Verification failed")
return render_message("حدث خطأ غير متوقع أثناء التحقق.", "An unexpected error occurred during verification.", "bad")
# --------------------------------------------------------------------------------------------------------------
# Interface
# --------------------------------------------------------------------------------------------------------------
_PATTERN = (
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E"
"%3Cg fill='none' stroke='%2322d3ee' stroke-opacity='0.07' stroke-width='1'%3E"
"%3Crect x='20' y='20' width='40' height='40'/%3E%3Crect x='20' y='20' width='40' height='40' transform='rotate(45 40 40)'/%3E"
"%3C/g%3E%3C/svg%3E\")"
)
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&family=Amiri:wght@400;700&display=swap');
.gradio-container {
--body-background-fill:#0a1128; --block-background-fill:#101a38; --block-border-color:#1e2d57;
--input-background-fill:#0d1631; --body-text-color:#e8eefc; --block-label-text-color:#9fb3da;
--button-primary-background-fill:#14b8a6; --button-primary-background-fill-hover:#22d3ee; --button-primary-text-color:#04202a;
--button-secondary-background-fill:#16244a; --button-secondary-text-color:#e8eefc; --button-secondary-border-color:#2a3d73;
background:#0a1128 PATTERN; font-family:'Cairo','Segoe UI',Tahoma,sans-serif; max-width:1080px !important; color:#e8eefc;
}
.hero { text-align:center; padding:34px 12px 18px; direction:rtl; }
.hero .eyebrow { color:#22d3ee; letter-spacing:.14em; font-size:.8rem; text-transform:uppercase; }
.hero h1 { font-size:2.3rem; margin:.3rem 0 .1rem; color:#fff; }
.hero h2 { font-size:1.05rem; font-weight:400; color:#9fb3da; margin:0 0 .9rem; direction:ltr; }
.hero p { max-width:760px; margin:.3rem auto; color:#c7d4f0; line-height:1.9; }
.hero .en { direction:ltr; color:#8ea3cf; font-size:.92rem; }
.input-area textarea { direction:rtl; text-align:right; font-family:'Amiri','Cairo',serif !important; font-size:1.2rem !important; line-height:2 !important; }
.btn-row button { font-weight:700 !important; border-radius:12px !important; }
.disclaimer { font-size:.82rem; color:#8ea3cf; text-align:center; padding:12px 8px; direction:rtl; line-height:1.8; }
.disclaimer .en { display:block; direction:ltr; }
.results { direction:rtl; text-align:right; }
.results .en, .notice .en { display:block; direction:ltr; text-align:left; color:#8ea3cf; font-size:.88rem; font-weight:400; }
.summary { display:grid; grid-template-columns:repeat(auto-fit,minmax(130px,1fr)); gap:10px; margin:6px 0 14px; }
.tile { background:#101a38; border:1px solid #1e2d57; border-radius:14px; padding:12px; text-align:center; }
.tile b { display:block; font-size:1.8rem; color:#fff; } .tile span { display:block; font-size:.9rem; } .tile small { color:#8ea3cf; }
.tile.verified { border-color:#14b8a6; } .tile.verified b { color:#2dd4bf; }
.tile.mismatch { border-color:#f43f5e; } .tile.mismatch b { color:#fb7185; }
.tile.review { border-color:#f59e0b; } .tile.review b { color:#fbbf24; }
.highlight { background:#0d1631; border:1px solid #1e2d57; border-radius:14px; padding:14px 16px; margin-bottom:14px; }
.highlight label, .field label { display:block; color:#7e93c0; font-size:.78rem; margin-bottom:4px; }
.highlight p { font-family:'Amiri','Cairo',serif; font-size:1.15rem; line-height:2.1; margin:0; white-space:pre-wrap; }
mark { color:#fff; border-radius:6px; padding:1px 4px; }
mark.verified { background:rgba(20,184,166,.35); } mark.mismatch { background:rgba(244,63,94,.35); } mark.review { background:rgba(245,158,11,.35); }
.qcard { background:#101a38; border:1px solid #1e2d57; border-inline-start:5px solid #3b4b7a; border-radius:16px; padding:16px 18px; margin:12px 0; }
.qcard.verified { border-inline-start-color:#14b8a6; } .qcard.mismatch { border-inline-start-color:#f43f5e; } .qcard.review { border-inline-start-color:#f59e0b; }
.qhead { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:10px; }
.idx { background:#1b2a55; border-radius:50%; width:28px; height:28px; display:inline-flex; align-items:center; justify-content:center; font-weight:700; }
.badge { padding:3px 12px; border-radius:999px; font-size:.82rem; background:#16244a; border:1px solid #2a3d73; }
.badge.st.verified { background:rgba(20,184,166,.18); border-color:#14b8a6; color:#5eead4; }
.badge.st.mismatch { background:rgba(244,63,94,.16); border-color:#f43f5e; color:#fda4af; }
.badge.st.review { background:rgba(245,158,11,.16); border-color:#f59e0b; color:#fcd34d; }
.conf { margin-inline-start:auto; color:#9fb3da; font-size:.88rem; } .conf b { color:#22d3ee; }
.field { margin:8px 0; } .field p { margin:0; line-height:1.8; }
.quote { font-family:'Amiri','Cairo',serif; font-size:1.2rem; line-height:2.1; color:#fff; } .quote.small { font-size:1.05rem; color:#d6e0f7; }
.diff { font-family:'Amiri','Cairo',serif; font-size:1.1rem; line-height:2.1; }
.w-extra { background:rgba(244,63,94,.25); border-radius:4px; padding:0 3px; text-decoration:line-through; }
.w-missing { background:rgba(20,184,166,.28); border-radius:4px; padding:0 3px; }
.legend { color:#8ea3cf; font-size:.82rem; margin:4px 0 0; } .legend .w-extra { text-decoration:none; }
.tile.zero { opacity:.5; }
.det-chip { text-align:center; color:#8ea3cf; font-size:.82rem; margin:2px 0 8px; } .det-chip b { color:#22d3ee; font-weight:600; }
.hl-legend { display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; font-size:.78rem; } .hl-legend mark { padding:2px 10px; }
.flow { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:8px; margin:16px 0 4px; direction:rtl; }
.flow span { background:#101a38; border:1px solid #1e2d57; border-radius:999px; padding:5px 14px; font-size:.85rem; color:#cfe0ff; }
.flow span small { color:#7e93c0; margin-inline-start:6px; direction:ltr; display:inline-block; }
.flow i { color:#22d3ee; font-style:normal; }
.hero .mark { width:52px; height:52px; margin:0 auto 6px; display:block; }
.hero h1 { background:linear-gradient(90deg,#fff,#a5f3fc); -webkit-background-clip:text; background-clip:text; color:transparent; }
.action { border-radius:12px; padding:10px 14px; margin-top:10px; line-height:1.8; }
.action.ok { background:rgba(20,184,166,.12); border:1px solid #14b8a6; }
.action.warn { background:rgba(245,158,11,.12); border:1px solid #f59e0b; }
.action.bad { background:rgba(244,63,94,.12); border:1px solid #f43f5e; }
.evidence { margin-top:12px; border-top:1px dashed #2a3d73; padding-top:8px; }
.evidence summary { cursor:pointer; color:#22d3ee; font-weight:700; }
.ev-row { margin:10px 0; } .ev-row b { color:#9fb3da; font-size:.85rem; } .ev-row p { margin:2px 0; }
.indicators { display:grid; gap:6px; margin-top:6px; }
.ind { display:grid; grid-template-columns:150px 1fr 48px; gap:10px; align-items:center; font-size:.85rem; }
.ind-name small { display:block; color:#7e93c0; font-size:.72rem; direction:ltr; text-align:right; }
.bar { background:#0d1631; border-radius:999px; height:8px; overflow:hidden; } .bar span { display:block; height:100%; background:linear-gradient(90deg,#14b8a6,#22d3ee); }
.ind-val { text-align:left; direction:ltr; color:#cfe0ff; } .ind-val.wide { grid-column:2 / span 2; text-align:right; }
.notice { background:#101a38; border:1px solid #1e2d57; border-radius:14px; padding:16px; direction:rtl; line-height:1.9; }
.notice.warn { border-color:#f59e0b; } .notice.bad { border-color:#f43f5e; }
@media (max-width:640px){ .hero h1{font-size:1.7rem;} .ind{grid-template-columns:110px 1fr 40px;} }
""".replace("PATTERN", _PATTERN)
HERO = """
<div class="hero">
<svg class="mark" viewBox="0 0 52 52" fill="none" stroke="#22d3ee" stroke-width="1.6" aria-hidden="true">
<rect x="11" y="11" width="30" height="30"/><rect x="11" y="11" width="30" height="30" transform="rotate(45 26 26)"/>
<circle cx="26" cy="26" r="6" fill="#14b8a6" stroke="none"/></svg>
<div class="eyebrow">Islamic Content Verifier</div>
<h1>مُدقِّق المحتوى الإسلامي</h1>
<h2>Verify Quranic and Hadith quotations with evidence.</h2>
<p>الصق نصًا ولّده نموذج لغوي، وسيكتشف النظام الآيات والأحاديث الواردة فيه، ويسترجع نصوصها من المصادر، ويقارنها كلمةً بكلمة،
ثم يعرض الدليل. وإذا لم تكفِ الأدلة فلن يختلق تصحيحًا، بل يحيل الحالة إلى المراجعة البشرية.</p>
<p class="en">Paste text generated by a language model. The system detects Quran and Hadith quotations, retrieves the source texts,
compares them word by word and shows the evidence. When evidence is insufficient it never invents a correction: it recommends human review.</p>
<div class="flow"><span>كشف<small>Detect</small></span><i>←</i><span>استرجاع<small>Retrieve</small></span><i>←</i>
<span>تحقق<small>Verify</small></span><i>←</i><span>دليل<small>Evidence</small></span><i>←</i><span>قرار<small>Decide</small></span></div>
</div>
"""
DISCLAIMER = """
<div class="disclaimer">أداة مساعدة للتدقيق النصي وليست فتوى ولا بديلًا عن المراجعة المتخصصة. النتائج مبنية على مراجع القرآن والكتب الستة المضمّنة فقط.
<span class="en">A text-verification aid, not a religious ruling and not a substitute for expert review. Results rely only on the bundled Quran and Six Books corpora.</span></div>
"""
PLACEHOLDER = "الصق هنا الرد الذي ولّده النموذج اللغوي، ويفضَّل أن تكون الاقتباسات بين علامات تنصيص بعد عبارة مثل «قال الله تعالى» أو «قال رسول الله ﷺ»…"
def build_interface():
import gradio as gr
examples = load_examples()
def next_example(index: int):
if not examples:
return "", 0
return examples[index % len(examples)]["text"], (index + 1) % len(examples)
with gr.Blocks(title="Islamic Content Verifier", css=CSS, theme=gr.themes.Base(primary_hue="teal", neutral_hue="slate")) as demo:
gr.HTML(HERO)
example_index = gr.State(0)
text_input = gr.Textbox(
label="النص المراد التحقق منه · Text to verify", lines=9, max_lines=24, placeholder=PLACEHOLDER,
rtl=True, elem_classes="input-area",
)
with gr.Row(elem_classes="btn-row"):
verify_button = gr.Button("تحقق من النص · Verify Text", variant="primary", scale=3)
example_button = gr.Button("جرّب مثالًا · Try an Example", variant="secondary", scale=2)
results = gr.HTML(elem_classes="results")
gr.HTML(DISCLAIMER)
verify_button.click(verify_text, inputs=text_input, outputs=results)
text_input.submit(verify_text, inputs=text_input, outputs=results)
example_button.click(next_example, inputs=example_index, outputs=[text_input, example_index]).then(
verify_text, inputs=text_input, outputs=results
)
return demo
def main() -> None:
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
get_pipeline() # build the index before serving the first request
build_interface().queue().launch(share=os.environ.get("ICV_SHARE") == "1") # ICV_SHARE=1 -> temporary public link (e.g. on Colab)
if __name__ == "__main__":
main()