# app.py
import os
import html
import difflib
from typing import List, Tuple
import gradio as gr
# --- LanguageTool (rule-based grammar checker) ---
import language_tool_python
# --- Transformers model for grammar correction ---
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
import torch
LT_LANG = "en-US"
# Инициализаци (нэг удаа)
tool = language_tool_python.LanguageTool(LT_LANG)
# Жижиг, CPU-д ээлтэй T5 correction загвар
MODEL_NAME = "vennify/t5-base-grammar-correction" # HF дээрх нийтлэг GEC загвар
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME)
model.eval()
def t5_correct(text: str, max_new_tokens: int = 128) -> str:
"""
T5 загвараар англи бичвэрийн зассан хувилбар гаргана.
"""
if not text.strip():
return ""
# Зарим GEC-T5 загваруудад тусгай prefix хэрэглэдэг. Энэ загварт prefix шаардлагагүй.
inputs = tokenizer([text], return_tensors="pt", truncation=True)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=max_new_tokens,
num_beams=4,
early_stopping=True
)
out = tokenizer.decode(outputs[0], skip_special_tokens=True)
return out.strip()
def lt_find_issues(text: str):
"""
LanguageTool ашиглан дүрмийн асуудлууд, тайлбар, санал болгох засваруудыг буцаана.
"""
matches = tool.check(text)
rows = []
for m in matches:
err = text[m.offset : m.offset + m.errorLength]
repls = ", ".join(m.replacements[:5]) if m.replacements else ""
rows.append(
{
"Error": err,
"Message": m.message,
"Rule": m.ruleId,
"Suggestions": repls,
"Start": m.offset,
"Length": m.errorLength,
}
)
return rows
def highlight_html(text: str, rows: List[dict]) -> str:
"""
Олдсон алдаануудыг текст дээр таг ашиглан тэмдэглэсэн HTML үүсгэнэ.
Давхцах highlight үүсэхээс сэргийлж баруунаас нь эхлэн wrap хийнэ.
"""
if not rows:
return f"{html.escape(text)}
"
# Баруунаас нь wrap хийхийн тулд эхлээд offset-ээр эрэмбэлнэ.
rows_sorted = sorted(rows, key=lambda r: r["Start"], reverse=True)
buf = text
for r in rows_sorted:
s = r["Start"]
e = s + r["Length"]
if s < 0 or e > len(buf): # хамгаалалт
continue
frag = html.escape(buf[s:e])
tip = html.escape(f'{r["Message"]} | Suggestions: {r.get("Suggestions","")}')
wrapped = f"{frag}"
buf = html.escape(buf[:s]) + wrapped + html.escape(buf[e:])
# Дээр нь escape давхардахгүй байх үүднээс буферийг дахин unescape хийхгүй.
# Учир нь бид хэсгүүдийг аль хэдийн escape хийсэн.
# Гэхдээ бид буферийн үлдсэн escape-г зөв үлдээхийн тулд дараах жижиг тохируулга:
# html.escape аль хэдийн хийсэн тул тагийг буцааж сэргээе.
buf = buf.replace("<mark", "")
return f"{buf}
"
def diff_html(a: str, b: str) -> str:
"""
Эх бичвэр (a) ба зассан бичвэр (b)-ийн ялгааг HTML хэлбэрээр харуулна.
"""
a_esc = html.escape(a)
b_esc = html.escape(b)
diff = difflib.ndiff(a_esc.split(), b_esc.split())
parts = []
for token in diff:
if token.startswith("+ "):
parts.append(f"{token[2:]}")
elif token.startswith("- "):
parts.append(f"{token[2:]}")
elif token.startswith("? "):
# туслах мөр – алгасъя
continue
else:
parts.append(token[2:])
return "" + " ".join(parts) + "
"
def pipeline(text: str):
"""
Нэг товчоор гурван үр дүн:
1) LanguageTool-ийн алдааны хүснэгт
2) Алдаатай хэсгийг highlight хийсэн HTML
3) T5 загвараар зассан хувилбар + diff
"""
text = (text or "").strip()
if not text:
return "", [], "", "", ""
# 1) LanguageTool
rows = lt_find_issues(text)
table_rows = [
[r["Error"], r["Message"], r["Rule"], r["Suggestions"]] for r in rows
]
highlighted = highlight_html(text, rows)
# 2) T5 correction
corrected = t5_correct(text)
# 3) Diff
dhtml = diff_html(text, corrected)
return corrected, table_rows, highlighted, dhtml, f"Found {len(rows)} potential issue(s)."
with gr.Blocks(title="Grammar Classroom (HF Space)") as demo:
gr.Markdown(
"""
# 🧑🏫 English Grammar Classroom
- **Rule-based check** (LanguageTool): errors, explanations, suggestions
- **AI correction** (T5): corrected version
- **Diff view**: see changes compared to your original
"""
)
with gr.Row():
inp = gr.Textbox(
label="Enter English text",
placeholder="Paste or type your sentence/paragraph here…",
lines=7,
)
run_btn = gr.Button("Check & Correct", variant="primary")
gr.Markdown("### ✅ AI-corrected Text")
corrected_out = gr.Textbox(label="Corrected", lines=6)
gr.Markdown("### 📋 Grammar Issues (LanguageTool)")
issues_df = gr.Dataframe(
headers=["Error", "Message", "Rule", "Suggestions"],
datatype=["str", "str", "str", "str"],
wrap=True,
interactive=False,
row_count=(0, "dynamic"),
)
issues_summary = gr.Markdown()
gr.Markdown("### ✨ Highlighted Issues")
highlighted_out = gr.HTML()
gr.Markdown("### 🔍 Diff (Original vs Corrected)")
diff_out = gr.HTML()
run_btn.click(
fn=pipeline,
inputs=[inp],
outputs=[corrected_out, issues_df, highlighted_out, diff_out, issues_summary],
)
if __name__ == "__main__":
demo.launch()