|
|
import os |
|
|
import sys |
|
|
import time |
|
|
import subprocess |
|
|
import urllib.request |
|
|
import socket |
|
|
import gradio as gr |
|
|
import http.client |
|
|
import urllib.parse |
|
|
import xml.etree.ElementTree as ET |
|
|
import tarfile |
|
|
import shutil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
REPO_URL = "https://github.com/Automattic/atd-server-next.git" |
|
|
SERVER_DIR = "atd-server-next" |
|
|
MODELS_DIR = os.path.join(SERVER_DIR, "models") |
|
|
JAVA_DIR = "jdk8" |
|
|
HOST = "127.0.0.1" |
|
|
PORT = 1049 |
|
|
|
|
|
|
|
|
JAVA_URL = "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u392-b08/OpenJDK8U-jre_x64_linux_hotspot_8u392b08.tar.gz" |
|
|
JAVA_BIN = os.path.join(os.getcwd(), JAVA_DIR, "bin", "java") |
|
|
|
|
|
MODEL_BASE_URL = "https://openatd.svn.wordpress.org/atd-server/models/" |
|
|
MODEL_FILES = [ |
|
|
"cnetwork.bin", "cnetwork2.bin", "dictionary.txt", "edits.bin", |
|
|
"endings.bin", "hnetwork.bin", "hnetwork2.bin", "hnetwork4.bin", |
|
|
"lexicon.bin", "model.bin", "model.zip", "network3f.bin", |
|
|
"network3p.bin", "not_misspelled.txt", "stringpool.bin", "trigrams.bin" |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def install_java8(): |
|
|
"""Downloads and installs portable Java 8.""" |
|
|
print("--- [PHASE -1] CHECKING JAVA RUNTIME ---") |
|
|
|
|
|
|
|
|
if os.path.exists(JAVA_BIN): |
|
|
print(f"Portable Java 8 found at: {JAVA_BIN}") |
|
|
return |
|
|
|
|
|
print("Java 8 not found. Downloading portable runtime (approx 40MB)...") |
|
|
tar_path = "java8.tar.gz" |
|
|
|
|
|
try: |
|
|
|
|
|
urllib.request.urlretrieve(JAVA_URL, tar_path) |
|
|
print("Download complete. Extracting...") |
|
|
|
|
|
|
|
|
with tarfile.open(tar_path, "r:gz") as tar: |
|
|
|
|
|
root_name = tar.getnames()[0].split('/')[0] |
|
|
tar.extractall() |
|
|
|
|
|
|
|
|
if os.path.exists(JAVA_DIR): |
|
|
shutil.rmtree(JAVA_DIR) |
|
|
os.rename(root_name, JAVA_DIR) |
|
|
|
|
|
|
|
|
os.remove(tar_path) |
|
|
|
|
|
|
|
|
subprocess.run(["chmod", "+x", JAVA_BIN], check=True) |
|
|
|
|
|
print(f"Java 8 successfully installed to {JAVA_DIR}") |
|
|
|
|
|
except Exception as e: |
|
|
print(f"FATAL: Failed to install Java 8. Error: {e}") |
|
|
sys.exit(1) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def setup_server(): |
|
|
|
|
|
install_java8() |
|
|
|
|
|
print("\n--- [PHASE 0] CHECKING REPOSITORY ---") |
|
|
if not os.path.exists(SERVER_DIR): |
|
|
print(f"Cloning {REPO_URL}...") |
|
|
subprocess.run(["git", "clone", "--depth", "1", REPO_URL, SERVER_DIR], check=True) |
|
|
|
|
|
print("\n--- [PHASE 1] CHECKING MODELS ---") |
|
|
if not os.path.exists(MODELS_DIR): |
|
|
os.makedirs(MODELS_DIR, exist_ok=True) |
|
|
|
|
|
for filename in MODEL_FILES: |
|
|
filepath = os.path.join(MODELS_DIR, filename) |
|
|
if not os.path.exists(filepath): |
|
|
print(f"Downloading {filename}...") |
|
|
try: |
|
|
urllib.request.urlretrieve(MODEL_BASE_URL + filename, filepath) |
|
|
except Exception as e: |
|
|
print(f" -> FAILED: {e}") |
|
|
|
|
|
print("\n--- [PHASE 2] COMPILING RULES ---") |
|
|
|
|
|
if not os.path.exists(os.path.join(SERVER_DIR, "models", "grammar.bin")): |
|
|
print("Compiling rules using Java 8...") |
|
|
|
|
|
cp = "lib/sleep.jar:lib/moconti.jar:lib/spellutils.jar" |
|
|
|
|
|
try: |
|
|
subprocess.run( |
|
|
[ |
|
|
JAVA_BIN, |
|
|
"-Datd.lowmem=true", |
|
|
"-Xmx1024M", |
|
|
"-classpath", cp, |
|
|
"sleep.console.TextConsole", |
|
|
"utils/rules/rules.sl" |
|
|
], |
|
|
cwd=SERVER_DIR, |
|
|
check=True |
|
|
) |
|
|
print("Rules compiled successfully.") |
|
|
except subprocess.CalledProcessError as e: |
|
|
print(f"Rule compilation warning: {e}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start_backend(): |
|
|
print("\n--- [PHASE 3] STARTING SERVER ---") |
|
|
|
|
|
classpath = "lib/sleep.jar:lib/moconti.jar:lib/spellutils.jar" |
|
|
sleep_cp = "lib:service/code" |
|
|
|
|
|
cmd = [ |
|
|
JAVA_BIN, |
|
|
"-Dfile.encoding=UTF-8", |
|
|
"-XX:+AggressiveHeap", |
|
|
"-XX:+UseParallelGC", |
|
|
"-Datd.lowmem=true", |
|
|
"-Dbind.interface=127.0.0.1", |
|
|
f"-Dserver.port={PORT}", |
|
|
f"-Dsleep.classpath={sleep_cp}", |
|
|
"-Dsleep.debug=24", |
|
|
"-classpath", classpath, |
|
|
"httpd.Moconti", |
|
|
"atdconfig.sl" |
|
|
] |
|
|
|
|
|
print(f"Launching with: {JAVA_BIN}") |
|
|
return subprocess.Popen(cmd, cwd=SERVER_DIR) |
|
|
|
|
|
def wait_for_port(timeout=60): |
|
|
print(f"Waiting for port {PORT}...") |
|
|
start = time.time() |
|
|
while time.time() - start < timeout: |
|
|
try: |
|
|
with socket.create_connection((HOST, PORT), timeout=1): |
|
|
print("Server is Online!") |
|
|
return True |
|
|
except (ConnectionRefusedError, OSError): |
|
|
time.sleep(1) |
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AtDClient: |
|
|
def check_document(self, text): |
|
|
try: |
|
|
conn = http.client.HTTPConnection(HOST, PORT, timeout=5) |
|
|
params = urllib.parse.urlencode({'key': 'gradio', 'data': text}) |
|
|
headers = {"Content-Type": "application/x-www-form-urlencoded"} |
|
|
conn.request("POST", "/checkDocument", params, headers) |
|
|
resp = conn.getresponse() |
|
|
if resp.status != 200: return [] |
|
|
|
|
|
xml_text = resp.read().decode('utf-8', errors='ignore') |
|
|
if not xml_text.strip().startswith("<"): return [] |
|
|
|
|
|
root = ET.fromstring(xml_text) |
|
|
errors = [] |
|
|
for e in root.findall('error'): |
|
|
err = { |
|
|
'string': e.find('string').text, |
|
|
'description': e.find('description').text, |
|
|
'type': e.find('type').text, |
|
|
'precontext': e.find('precontext').text or "", |
|
|
'suggestions': [] |
|
|
} |
|
|
sug = e.find('suggestions') |
|
|
if sug is not None: |
|
|
err['suggestions'] = [o.text for o in sug.findall('option') if o.text] |
|
|
errors.append(err) |
|
|
return errors |
|
|
except Exception as e: |
|
|
print(f"Client Error: {e}") |
|
|
return [] |
|
|
|
|
|
client = AtDClient() |
|
|
|
|
|
def analyze_text(text): |
|
|
if not text.strip(): return [] |
|
|
errors = client.check_document(text) |
|
|
output = [] |
|
|
last_pos = 0 |
|
|
|
|
|
for err in errors: |
|
|
word = err['string'] |
|
|
search_start = last_pos |
|
|
if err['precontext']: |
|
|
context_idx = text.find(err['precontext'], last_pos) |
|
|
if context_idx != -1: |
|
|
search_start = context_idx + len(err['precontext']) |
|
|
|
|
|
idx = text.find(word, search_start) |
|
|
if idx != -1: |
|
|
if idx > last_pos: |
|
|
output.append((text[last_pos:idx], None)) |
|
|
|
|
|
label = f"{err['type']}: {err['description']}" |
|
|
if err['suggestions']: |
|
|
label += f" -> {', '.join(err['suggestions'][:3])}" |
|
|
|
|
|
output.append((text[idx:idx+len(word)], label)) |
|
|
last_pos = idx + len(word) |
|
|
|
|
|
if last_pos < len(text): |
|
|
output.append((text[last_pos:], None)) |
|
|
return output |
|
|
|
|
|
if __name__ == "__main__": |
|
|
setup_server() |
|
|
server_proc = start_backend() |
|
|
|
|
|
time.sleep(5) |
|
|
if server_proc.poll() is not None: |
|
|
print("FATAL: Java server exited immediately.") |
|
|
sys.exit(1) |
|
|
|
|
|
if wait_for_port(timeout=120): |
|
|
with gr.Blocks(title="AtD Self-Hosted") as demo: |
|
|
gr.Markdown("# 🛡️ After The Deadline") |
|
|
gr.Markdown("Running on Portable Java 8") |
|
|
|
|
|
with gr.Row(): |
|
|
inp = gr.Textbox(label="Input", placeholder="Type here... e.g., I has a error.", lines=6) |
|
|
out = gr.HighlightedText(label="Analysis", combine_adjacent=True) |
|
|
|
|
|
btn = gr.Button("Check Text", variant="primary") |
|
|
btn.click(analyze_text, inputs=inp, outputs=out) |
|
|
|
|
|
demo.launch(server_name="0.0.0.0", server_port=7860) |
|
|
else: |
|
|
print("FATAL: Server did not start (Timeout).") |
|
|
server_proc.kill() |