"""Open Spell — Gradio interface for fast, open spell correction.""" from __future__ import annotations import time import gradio as gr from spelling.corrector import SpellingCorrector _corrector = SpellingCorrector() def _timed(fn, *args, **kwargs): start = time.perf_counter() out = fn(*args, **kwargs) elapsed_ms = (time.perf_counter() - start) * 1000 return out, f"{elapsed_ms:.2f} ms" def run_spell(text: str, edit_distance: int): if not text: return "", "0.00 ms", [] corrected, t = _timed(_corrector.correct, text, max_edit_distance=int(edit_distance)) diff = [[d.original, d.corrected, d.edit_distance, f"{d.confidence:.2f}"] for d in _corrector.diff(text, corrected)] return corrected, t, diff def run_grammar(text: str, edit_distance: int): if not text: return "", "0.00 ms" corrected, t = _timed(_corrector.correct_compound, text, max_edit_distance=int(edit_distance)) return corrected, t def run_phonetic(text: str, edit_distance: int): if not text: return "", "0.00 ms" corrected, t = _timed(_corrector.correct_phonetic, text, max_edit_distance=int(edit_distance)) return corrected, t def run_domain(text: str, terms: str, edit_distance: int): if not text: return "", "0.00 ms", "0 terms loaded" added = _corrector.add_domain_terms(terms.splitlines()) if terms else 0 corrected, t = _timed(_corrector.correct, text, max_edit_distance=int(edit_distance)) return corrected, t, f"{added} term(s) added this run" def run_slang(text: str): if not text: return "", "0.00 ms" normalized, t = _timed(_corrector.normalize_slang, text) return normalized, t THEME = gr.themes.Soft(primary_hue="blue", secondary_hue="green") with gr.Blocks(title="Open Spell", theme=THEME) as demo: gr.Markdown( """ # 📝 Open Spell **Fast, open, accurate spell correction. No LLM. No GPU. No API keys.** Powered by [SymSpell](https://github.com/wolfgarbe/SymSpell) — ~1 ms per word on CPU. """ ) with gr.Tab("Spell"): gr.Markdown("Single-word typo correction. Preserves capitalization and punctuation.") with gr.Row(): spell_in = gr.Textbox(label="Input", placeholder="I lik aples", lines=3) spell_out = gr.Textbox(label="Corrected", lines=3, interactive=False) with gr.Row(): spell_edit = gr.Slider(1, 3, value=2, step=1, label="Max edit distance") spell_time = gr.Textbox(label="Latency", interactive=False, scale=1) spell_btn = gr.Button("Correct", variant="primary") spell_diff = gr.Dataframe( headers=["Original", "Corrected", "Edits", "Confidence"], label="Per-word changes", interactive=False, ) spell_btn.click(run_spell, [spell_in, spell_edit], [spell_out, spell_time, spell_diff]) gr.Examples( [["I lik aples", 2], ["The qick brwn fox", 2], ["Recieve the comittee", 2]], inputs=[spell_in, spell_edit], ) with gr.Tab("Grammar + Spell"): gr.Markdown("Handles missing spaces, extra spaces, and typos in one pass. Uses bigram model.") with gr.Row(): gram_in = gr.Textbox(label="Input", placeholder="whereis thelove", lines=3) gram_out = gr.Textbox(label="Corrected", lines=3, interactive=False) with gr.Row(): gram_edit = gr.Slider(1, 3, value=2, step=1, label="Max edit distance") gram_time = gr.Textbox(label="Latency", interactive=False, scale=1) gram_btn = gr.Button("Correct", variant="primary") gram_btn.click(run_grammar, [gram_in, gram_edit], [gram_out, gram_time]) gr.Examples( [["whereis thelove", 2], ["i amgoing tothestore", 2], ["thequickbrownfox", 2]], inputs=[gram_in, gram_edit], ) with gr.Tab("Domain"): gr.Markdown( "Add custom vocabulary (one term per line) so domain words outrank common-word neighbors. " "Useful for medical, legal, brand-name, or technical text." ) with gr.Row(): with gr.Column(): dom_in = gr.Textbox(label="Input", placeholder="pateint has hypertension", lines=3) dom_terms = gr.Textbox( label="Custom dictionary (one term per line)", placeholder="hypertension\nmyocardial\nibuprofen", lines=6, ) with gr.Column(): dom_out = gr.Textbox(label="Corrected", lines=3, interactive=False) dom_status = gr.Textbox(label="Dictionary status", interactive=False) with gr.Row(): dom_edit = gr.Slider(1, 3, value=2, step=1, label="Max edit distance") dom_time = gr.Textbox(label="Latency", interactive=False, scale=1) dom_btn = gr.Button("Correct", variant="primary") dom_btn.click(run_domain, [dom_in, dom_terms, dom_edit], [dom_out, dom_time, dom_status]) with gr.Tab("Phonetic"): gr.Markdown( "Matches by Metaphone code, so sounds-like misspellings get reranked. Great for accessibility " "and dyslexia-friendly correction." ) with gr.Row(): ph_in = gr.Textbox(label="Input", placeholder="fone numbr nite", lines=3) ph_out = gr.Textbox(label="Corrected", lines=3, interactive=False) with gr.Row(): ph_edit = gr.Slider(1, 4, value=3, step=1, label="Max edit distance") ph_time = gr.Textbox(label="Latency", interactive=False, scale=1) ph_btn = gr.Button("Correct", variant="primary") ph_btn.click(run_phonetic, [ph_in, ph_edit], [ph_out, ph_time]) gr.Examples( [["fone numbr", 3], ["nite skool", 3], ["thanx for ur halp", 3]], inputs=[ph_in, ph_edit], ) with gr.Tab("Slang"): gr.Markdown( "Expands popular SMS / internet slang into natural conversational English. " "Context-dependent slang (`bet`, `fire`, `fam`, `sus`, `lit`) is deliberately left alone." ) with gr.Row(): sl_in = gr.Textbox( label="Input", placeholder="omg idk btw fyi imo this is the best", lines=3, ) sl_out = gr.Textbox(label="Natural English", lines=3, interactive=False) sl_time = gr.Textbox(label="Latency", interactive=False) sl_btn = gr.Button("Normalize", variant="primary") sl_btn.click(run_slang, [sl_in], [sl_out, sl_time]) gr.Examples( [ ["omg im gonna lemme know asap"], ["idk btw fyi imo this is the best"], ["tldr the meeting is at 3pm tbh"], ["wyd rn wanna grab coffee"], ["finna head out brb"], ["gotta finish this asap ngl"], ], inputs=[sl_in], ) gr.Markdown( """ --- **Open source · MIT licensed · No telemetry** · [GitHub-equivalent: see Files tab](.) """ ) if __name__ == "__main__": demo.launch()