"""GigaCheck demo: classify text and localize AI-written spans. Two tabs back two Mistral-7B models from the LLMTrace / GigaCheck project: a binary human/AI classifier and a fine-grained AI-span detector. """ from __future__ import annotations import gradio as gr from loguru import logger from models import classify_text, detect_intervals from rendering import build_classifier_card, build_detector_card from styles import CSS, THEME from validation import CONFIG, validate_text LANG_NOTE = ( '
⚠️ These models work with English and ' f"Russian text only. Enter between {CONFIG.min_words} and " f"{CONFIG.max_words} words.
" ) CLASSIFIER_EXAMPLES = [ # human [ "Netflix has laid off around 300 people across a number of departments. The company also laid off 150 workers back in May." ], # ai [ "@GOP @PamBondi Maybe we should talk about Gordon Sondland, who had ZERO experience or qualifications to be an Ambassador to the EU but since he donated a million dollars to trump he got the job anyway. I'm outraged!" ], # ai [ "В прекрасное утро, поразмыслив о том, как множество радостей приносит нам природа, я вспомнила о своей милой питомице, мадам Леске, и о ее необъяснимой страсти к дыне. \"Как же,\" подумала я, \"этот нежный плод вмещает в себе не только великолепие вкуса, но и целую кладезь полезных веществ\". И вдруг осенило меня: разве не заслуживает моя преданная спутница опробовать хоть крохотный кусочек этого плода?" ], ] CLASSIFIER_EXAMPLE_LABELS = ["EN · human", "EN · AI", "RU · AI"] DETECTOR_EXAMPLES = [ [ "The critic's review of the recent publication was scathing. The book failed miserably in portraying the harmful subjective discourses associated with the hegemony of the political system." ], [ "Университет Шеффилд Холлем имеет в Шеффилде два кампуса. Один из них — это Городской кампус, который находится в центре города. Второй кампус, Коллегиальный, расположен на юго-западе Шеффилда. Оба кампуса предлагают современные учебные здания и ресурсы для студентов. История университета Шеффилд Холлем началась в 1843 году с основания Шеффилдской школы дизайна. В 1960-х годах несколько независимых колледжей (включая Школу дизайна) объединились в Шеффилдский Политехникум, с 1976 года — Шеффилдский городской политехникум, с 1992 года — Университет Шеффилд Холлем." ], ] DETECTOR_EXAMPLE_LABELS = ["EN · example", "RU · example"] def update_counter(text: str) -> str: """Render a live word counter, flagging out-of-bounds input in red. Args: text: Current textbox content. Returns: An HTML snippet showing the word count and any validation message. """ result = validate_text(text) bad = (not result.ok) and result.word_count > 0 css_class = "gc-counter gc-bad" if bad else "gc-counter" note = f" — {result.message}" if bad else "" return ( f'
{result.word_count} / ' f"{CONFIG.max_words} words{note}
" ) def run_classifier(text: str) -> str: """Validate then classify the text, returning a result card. Args: text: User-supplied text. Returns: HTML for the classifier result card. Raises: gr.Error: If the text falls outside the configured word bounds. """ result = validate_text(text) if not result.ok: raise gr.Error(result.message) label, p_human, p_ai = classify_text(text) logger.info("classifier: label={} p_human={:.3f}", label, p_human) return build_classifier_card(label, p_human, p_ai) def run_detector(text: str, conf_threshold: float) -> str: """Validate then run AI-span detection, returning a result card. Args: text: User-supplied text. conf_threshold: Confidence threshold for keeping a span. Returns: HTML for the detector result card. Raises: gr.Error: If the text falls outside the configured word bounds. """ result = validate_text(text) if not result.ok: raise gr.Error(result.message) intervals = detect_intervals(text, conf_threshold) logger.info("detector: {} interval(s) at thresh={}", len(intervals), conf_threshold) return build_detector_card(text, intervals) def build_classifier_tab() -> None: """Build the classifier tab UI and wire its events.""" gr.HTML(LANG_NOTE) text_in = gr.Textbox( label="Text", placeholder="Paste English or Russian text to classify…", lines=8, ) counter = gr.HTML(update_counter("")) analyze_btn = gr.Button("Analyze", variant="primary") output = gr.HTML() gr.Examples( examples=CLASSIFIER_EXAMPLES, inputs=[text_in], label="Examples", example_labels=CLASSIFIER_EXAMPLE_LABELS, ) text_in.change(update_counter, inputs=[text_in], outputs=[counter]) analyze_btn.click(run_classifier, inputs=[text_in], outputs=[output]) def build_detector_tab() -> None: """Build the detector tab UI and wire its events.""" gr.HTML(LANG_NOTE) text_in = gr.Textbox( label="Text", placeholder="Paste English or Russian text to scan for AI-written spans…", lines=8, ) counter = gr.HTML(update_counter("")) conf = gr.Slider( label="Confidence threshold", minimum=0.0, maximum=1.0, value=CONFIG.default_conf_threshold, step=0.05, ) detect_btn = gr.Button("Detect", variant="primary") output = gr.HTML() gr.Examples( examples=DETECTOR_EXAMPLES, inputs=[text_in], label="Examples", example_labels=DETECTOR_EXAMPLE_LABELS, ) text_in.change(update_counter, inputs=[text_in], outputs=[counter]) detect_btn.click(run_detector, inputs=[text_in, conf], outputs=[output]) def build_demo() -> gr.Blocks: """Assemble the full Gradio demo. Returns: The configured :class:`gradio.Blocks` application. """ with gr.Blocks(theme=THEME, css=CSS, title="GigaCheck") as demo: gr.HTML( '

GigaCheck

' "

Detect AI-generated text — binary classification and " "fine-grained span localization

" ) with gr.Tabs(): with gr.Tab("Classifier"): gr.Markdown( "Binary **human / AI** classifier — " "[GigaCheck-Classifier-Multi]" "(https://huggingface.co/iitolstykh/GigaCheck-Classifier-Multi)" ) build_classifier_tab() with gr.Tab("Detector"): gr.Markdown( "Fine-grained **AI-span detector** — " "[GigaCheck-Detector-Multi]" "(https://huggingface.co/iitolstykh/GigaCheck-Detector-Multi)" ) build_detector_tab() return demo if __name__ == "__main__": build_demo().queue(max_size=50).launch()