Download runtime/cell17.py from raymuz/NLLUniversalProfessionalEditor: direct link, hf CLI and curl.
- Browser
- Download file 29.2 kB
-
https://huggingface.co/spaces/raymuz/NLLUniversalProfessionalEditor/resolve/main/runtime/cell17.py
- Command line
-
hf download hf://spaces/raymuz/NLLUniversalProfessionalEditor/runtime/cell17.py
-
curl -L -o cell17.py https://huggingface.co/spaces/raymuz/NLLUniversalProfessionalEditor/resolve/main/runtime/cell17.py
29.2 kB
| #@title 9. PROFESSIONAL GRADIO UI v5.1 — FULL 30-RULE DIAGNOSTICS | |
| # Complete replacement for Cell 9. | |
| # Compatible with Cell 5 v4.1, Cell 5A v3.4, Cell 6 v5.1, | |
| # Cell 7 v4, Cell 7A v2, Cell 7B v2, Cell 8 v4, and Cell 8A v2. | |
| import json | |
| import html | |
| import re | |
| import tempfile | |
| import time | |
| import uuid | |
| from collections import Counter | |
| from pathlib import Path | |
| from dataclasses import asdict, is_dataclass | |
| import pandas as pd | |
| import gradio as gr | |
| # ===================================================================== | |
| # 1. PREFLIGHT | |
| # ===================================================================== | |
| EXPECTED_RULE_COUNT = 30 | |
| _REQUIRED_CELL9 = [ | |
| "ENGINE", "Engine", "Analyzer", "Generator", "Validator", "Ranker", | |
| "RULES", "PROFILES", "realization_issues", | |
| ] | |
| _missing_cell9 = [name for name in _REQUIRED_CELL9 if name not in globals()] | |
| if _missing_cell9: | |
| raise RuntimeError( | |
| "Cell 9 cannot run. Missing: " + ", ".join(_missing_cell9) | |
| + ". Run Cells 1–8A in order first." | |
| ) | |
| if len(RULES) != EXPECTED_RULE_COUNT: | |
| raise RuntimeError( | |
| f"Cell 9 expected {EXPECTED_RULE_COUNT} registered transformation " | |
| f"rules, but found {len(RULES)}. Rerun Cells 5, 5A, 6, 7, 7A, " | |
| "and 7B in order." | |
| ) | |
| MUST_PARAPHRASE_ACTIVE = ( | |
| getattr(Engine.one, "__name__", "") == "_one_with_must_paraphrase_v2" | |
| ) | |
| if not MUST_PARAPHRASE_ACTIVE: | |
| raise RuntimeError( | |
| "Cell 9 requires the Cell 7B v2 must-paraphrase orchestrator. " | |
| "Run Cell 7B v2 before Cell 9." | |
| ) | |
| # ===================================================================== | |
| # 2. DETERMINISTIC SETTINGS | |
| # ===================================================================== | |
| def _set_cfg_flag(name, value): | |
| if "CFG" in globals(): | |
| try: | |
| setattr(CFG, name, bool(value)) | |
| except Exception: | |
| pass | |
| def _apply_ui_settings(use_stanza, use_language_tool): | |
| # No generative/model-assisted candidate production in this UI. | |
| _set_cfg_flag("mlm", False) | |
| _set_cfg_flag("sts", False) | |
| _set_cfg_flag("nli", False) | |
| _set_cfg_flag("online", False) | |
| # Optional read-only analysis resources. | |
| _set_cfg_flag("stanza", use_stanza) | |
| _set_cfg_flag("lt", use_language_tool) | |
| # ===================================================================== | |
| # 3. GENERAL HELPERS | |
| # ===================================================================== | |
| def _json_safe(value): | |
| if value is None or isinstance(value, (str, int, float, bool)): | |
| return value | |
| if is_dataclass(value): | |
| return _json_safe(asdict(value)) | |
| if isinstance(value, dict): | |
| return {str(key): _json_safe(item) for key, item in value.items()} | |
| if isinstance(value, (list, tuple, set)): | |
| return [_json_safe(item) for item in value] | |
| if hasattr(value, "tolist"): | |
| try: | |
| return _json_safe(value.tolist()) | |
| except Exception: | |
| pass | |
| if hasattr(value, "item"): | |
| try: | |
| return _json_safe(value.item()) | |
| except Exception: | |
| pass | |
| if hasattr(value, "__dict__"): | |
| try: | |
| return _json_safe(vars(value)) | |
| except Exception: | |
| pass | |
| return str(value) | |
| def _file_path(uploaded_file): | |
| if uploaded_file is None: | |
| return None | |
| if isinstance(uploaded_file, str): | |
| return uploaded_file | |
| return getattr(uploaded_file, "name", None) or str(uploaded_file) | |
| def _read_input_text(typed_text, uploaded_file): | |
| typed_text = str(typed_text or "") | |
| if typed_text.strip(): | |
| return typed_text.strip(), "typed_text" | |
| path_value = _file_path(uploaded_file) | |
| if not path_value: | |
| raise gr.Error("Enter text or upload a TXT, DOCX, or text-based PDF.") | |
| path = Path(path_value) | |
| if not path.exists(): | |
| raise gr.Error("The uploaded file could not be found.") | |
| suffix = path.suffix.lower() | |
| if suffix == ".txt": | |
| return path.read_text(encoding="utf-8", errors="replace").strip(), path.name | |
| if suffix == ".docx": | |
| from docx import Document | |
| document = Document(str(path)) | |
| text = "\n\n".join(p.text for p in document.paragraphs).strip() | |
| return text, path.name | |
| if suffix == ".pdf": | |
| try: | |
| import fitz | |
| except Exception as exc: | |
| raise gr.Error("PyMuPDF is unavailable: " + str(exc)) | |
| pdf = fitz.open(str(path)) | |
| try: | |
| text = "\n\n".join(page.get_text("text") for page in pdf).strip() | |
| finally: | |
| pdf.close() | |
| if not text: | |
| raise gr.Error( | |
| "The PDF contains no extractable text. Scanned PDFs require OCR." | |
| ) | |
| return text, path.name | |
| raise gr.Error("Unsupported file type. Use TXT, DOCX, or text-based PDF.") | |
| def _trace(item): | |
| value = getattr(item, "diagnostics", None) | |
| if value is None: | |
| value = getattr(item, "trace", None) | |
| return dict(value or {}) | |
| def _checks(item): | |
| return dict(getattr(item, "checks", None) or {}) | |
| def _changed(item): | |
| return str(getattr(item, "original", "")) != str(getattr(item, "revised", "")) | |
| # ===================================================================== | |
| # 4. DIAGNOSTIC TABLES | |
| # ===================================================================== | |
| def _sentence_rows(result): | |
| rows = [] | |
| for number, item in enumerate(list(getattr(result, "sentences", []) or []), 1): | |
| trace = _trace(item) | |
| selected = trace.get("selected") or {} | |
| generated = trace.get("generated_by_lane", {}) or {} | |
| must = trace.get("must_paraphrase", {}) or {} | |
| attempts = must.get("attempts", []) or [] | |
| rows.append({ | |
| "Sentence": number, | |
| "Status": getattr(item, "status", ""), | |
| "Changed": _changed(item), | |
| "Requested mode": must.get("requested_mode", getattr(result, "mode", "")), | |
| "Accepted mode": must.get("accepted_mode", ""), | |
| "Attempts": must.get("attempt_count", len(attempts)), | |
| "Must-paraphrase outcome": must.get("outcome", ""), | |
| "Detected families": " | ".join(list(getattr(item, "families", []) or [])), | |
| "Selected family": getattr(item, "family", None) or selected.get("family") or "", | |
| "Selected lane": selected.get("lane", ""), | |
| "Selected score": selected.get("score"), | |
| "Minimal candidates": generated.get("Minimal", 0), | |
| "Balanced candidates": generated.get("Balanced", 0), | |
| "Structural candidates": generated.get("Structural", 0), | |
| "Before final filter": trace.get("generated_before_final_filter"), | |
| "After final filter": trace.get("generated_after_final_filter"), | |
| "Original": getattr(item, "original", ""), | |
| "Revised": getattr(item, "revised", ""), | |
| "Reason": getattr(item, "reason", ""), | |
| }) | |
| return rows | |
| def _routing_rows(result): | |
| rows = [] | |
| for number, item in enumerate(list(getattr(result, "sentences", []) or []), 1): | |
| trace = _trace(item) | |
| applicable = trace.get("applicable", {}) or {} | |
| generated = trace.get("generated_by_lane", {}) or {} | |
| rows.append({ | |
| "Sentence": number, | |
| "Status": getattr(item, "status", ""), | |
| "Detected families": " | ".join( | |
| trace.get("detected_families", []) | |
| or list(getattr(item, "families", []) or []) | |
| ), | |
| "Minimal rules": " | ".join(applicable.get("Minimal", []) or []), | |
| "Balanced rules": " | ".join(applicable.get("Balanced", []) or []), | |
| "Structural rules": " | ".join(applicable.get("Structural", []) or []), | |
| "Minimal generated": generated.get("Minimal", 0), | |
| "Balanced generated": generated.get("Balanced", 0), | |
| "Structural generated": generated.get("Structural", 0), | |
| "Before final filter": trace.get("generated_before_final_filter"), | |
| "After final filter": trace.get("generated_after_final_filter"), | |
| "Rule errors": json.dumps(_json_safe(trace.get("rule_errors", [])), ensure_ascii=False), | |
| "Terminal rejections": json.dumps(_json_safe(trace.get("rejections", {})), ensure_ascii=False), | |
| }) | |
| return rows | |
| def _realization_rows(result): | |
| rows = [] | |
| for sentence_number, item in enumerate(list(getattr(result, "sentences", []) or []), 1): | |
| for rejection_number, rejection in enumerate( | |
| list(_trace(item).get("realization_rejections", []) or []), 1 | |
| ): | |
| rows.append({ | |
| "Sentence": sentence_number, | |
| "Rejection": rejection_number, | |
| "Stage": rejection.get("stage", ""), | |
| "Lane": rejection.get("lane", ""), | |
| "Family/rule": rejection.get("rule", ""), | |
| "Issues": " | ".join(rejection.get("issues", []) or []), | |
| "Rejected text": rejection.get("text", ""), | |
| }) | |
| if not rows: | |
| rows.append({ | |
| "Sentence": 0, "Rejection": 0, "Stage": "", "Lane": "", | |
| "Family/rule": "", "Issues": "None", "Rejected text": "", | |
| }) | |
| return rows | |
| def _candidate_rows(result): | |
| rows = [] | |
| for sentence_number, item in enumerate(list(getattr(result, "sentences", []) or []), 1): | |
| candidates = list(_trace(item).get("candidate_traces", []) or []) | |
| if not candidates: | |
| rows.append({ | |
| "Sentence": sentence_number, | |
| "Candidate": 0, | |
| "Lane": "", | |
| "Family": "", | |
| "Accepted": False, | |
| "Score": None, | |
| "Change": None, | |
| "Failures": "", | |
| "Rejection reasons": "No candidate trace available", | |
| "Candidate text": "", | |
| }) | |
| continue | |
| for candidate_number, candidate in enumerate(candidates, 1): | |
| rows.append({ | |
| "Sentence": sentence_number, | |
| "Candidate": candidate_number, | |
| "Lane": candidate.get("lane", ""), | |
| "Family": candidate.get("family", ""), | |
| "Accepted": candidate.get("accepted", False), | |
| "Score": candidate.get("score"), | |
| "Change": candidate.get("change"), | |
| "Failures": " | ".join(candidate.get("failures", []) or []), | |
| "Rejection reasons": json.dumps( | |
| _json_safe(candidate.get("rejection_reasons", [])), | |
| ensure_ascii=False, | |
| ), | |
| "Candidate text": candidate.get("text", ""), | |
| }) | |
| return rows | |
| def _attempt_rows(result): | |
| rows = [] | |
| for sentence_number, item in enumerate(list(getattr(result, "sentences", []) or []), 1): | |
| must = _trace(item).get("must_paraphrase", {}) or {} | |
| attempts = must.get("attempts", []) or [] | |
| if not attempts: | |
| rows.append({ | |
| "Sentence": sentence_number, "Attempt": 0, "Mode": "", | |
| "Status": "", "Changed": False, "Selected family": "", | |
| "Failures": "", "Realization issues": "", | |
| "Generated after filter": None, "Revised": "", | |
| }) | |
| continue | |
| for attempt_number, attempt in enumerate(attempts, 1): | |
| rows.append({ | |
| "Sentence": sentence_number, | |
| "Attempt": attempt_number, | |
| "Mode": attempt.get("attempted_mode", ""), | |
| "Status": attempt.get("status", ""), | |
| "Changed": attempt.get("changed", False), | |
| "Selected family": attempt.get("selected_family", ""), | |
| "Failures": " | ".join(attempt.get("failures", []) or []), | |
| "Realization issues": " | ".join(attempt.get("realization_issues", []) or []), | |
| "Generated after filter": attempt.get("generated_after_final_filter"), | |
| "Revised": attempt.get("revised", ""), | |
| }) | |
| return rows | |
| def _finding_rows(result): | |
| rows = [] | |
| for finding in list(getattr(result, "findings", []) or []): | |
| safe = _json_safe(finding) | |
| rows.append(safe if isinstance(safe, dict) else {"Finding": safe}) | |
| return rows | |
| # ===================================================================== | |
| # 5. SUMMARY AND FULL PAYLOAD | |
| # ===================================================================== | |
| def _summary_markdown(result): | |
| sentences = list(getattr(result, "sentences", []) or []) | |
| total = len(sentences) | |
| successes = sum(getattr(item, "status", "") == "SUCCESS" for item in sentences) | |
| changed = sum(_changed(item) for item in sentences) | |
| statuses = Counter(getattr(item, "status", "UNKNOWN") for item in sentences) | |
| attempts = sum( | |
| int((_trace(item).get("must_paraphrase", {}) or {}).get("attempt_count", 0) or 0) | |
| for item in sentences | |
| ) | |
| preserved = sum( | |
| (_trace(item).get("must_paraphrase", {}) or {}).get("outcome") | |
| == "ORIGINAL_PRESERVED_AFTER_EXHAUSTIVE_SAFE_SEARCH" | |
| for item in sentences | |
| ) | |
| status_text = ", ".join(f"{name}: {count}" for name, count in sorted(statuses.items())) | |
| warnings = list(getattr(result, "warnings", []) or []) | |
| warning_text = "<br>".join(html.escape(str(x)) for x in warnings) if warnings else "None" | |
| return f""" | |
| ### Processing summary | |
| - **Discipline:** {getattr(result, 'discipline', '')} | |
| - **Routing evidence:** {', '.join(map(str, getattr(result, 'evidence', []) or [])) or 'default profile'} | |
| - **Writing Pattern Score:** {getattr(result, 'pattern_score', '')} — **{getattr(result, 'band', '')}** | |
| - **Registered transformation rules:** {len(RULES)} | |
| - **Must-paraphrase orchestrator:** {'Active' if MUST_PARAPHRASE_ACTIVE else 'Inactive'} | |
| - **Sentences processed:** {total} | |
| - **Successful selections:** {successes} | |
| - **Changed sentences:** {changed} | |
| - **Unchanged sentences:** {total - changed} | |
| - **Total bounded attempts:** {attempts} | |
| - **Preserved after exhaustive safe search:** {preserved} | |
| - **Terminal statuses:** {status_text or 'None'} | |
| - **Warnings:** {warning_text} | |
| > The Writing Pattern Score describes observable structural and stylistic characteristics only. It does not identify authorship. | |
| """ | |
| def _full_payload(result, source_name): | |
| sentences = [] | |
| for number, item in enumerate(list(getattr(result, "sentences", []) or []), 1): | |
| sentences.append({ | |
| "sentence": number, | |
| "status": getattr(item, "status", ""), | |
| "original": getattr(item, "original", ""), | |
| "revised": getattr(item, "revised", ""), | |
| "families": _json_safe(getattr(item, "families", [])), | |
| "selected_family": getattr(item, "family", None), | |
| "reason": getattr(item, "reason", ""), | |
| "checks": _json_safe(_checks(item)), | |
| "trace": _json_safe(_trace(item)), | |
| }) | |
| return { | |
| "source_name": source_name, | |
| "workflow": getattr(result, "workflow", ""), | |
| "mode": getattr(result, "mode", ""), | |
| "discipline": getattr(result, "discipline", ""), | |
| "routing_evidence": _json_safe(getattr(result, "evidence", [])), | |
| "writing_pattern_score": getattr(result, "pattern_score", None), | |
| "writing_pattern_band": getattr(result, "band", ""), | |
| "warnings": _json_safe(getattr(result, "warnings", [])), | |
| "stats": _json_safe(getattr(result, "stats", {})), | |
| "registered_rule_count": len(RULES), | |
| "must_paraphrase_active": MUST_PARAPHRASE_ACTIVE, | |
| "must_paraphrase_wrapper": getattr(Engine.one, "__name__", ""), | |
| "rewrite_configuration": { | |
| "deterministic_rules": True, | |
| "masked_language_model": False, | |
| "sts_model": False, | |
| "nli_model": False, | |
| "online_lexicon": False, | |
| "language_tool_optional": bool(getattr(globals().get("CFG", object()), "lt", False)), | |
| }, | |
| "registered_rules": [ | |
| { | |
| "name": registered.name, | |
| "lanes": sorted(registered.lanes), | |
| "families": sorted(registered.families), | |
| "priority": registered.priority, | |
| } | |
| for registered in RULES | |
| ], | |
| "sentences": sentences, | |
| } | |
| # ===================================================================== | |
| # 6. REPORTS | |
| # ===================================================================== | |
| def _write_reports(result, source_name, payload): | |
| folder = Path(tempfile.mkdtemp(prefix="nll_review_")) | |
| stem = Path(source_name or "nll_input").stem | |
| token = time.strftime("%Y%m%d_%H%M%S") + "_" + uuid.uuid4().hex[:6] | |
| txt_path = folder / f"{stem}_{token}_revised.txt" | |
| json_path = folder / f"{stem}_{token}_diagnostics.json" | |
| docx_path = folder / f"{stem}_{token}_review.docx" | |
| html_path = folder / f"{stem}_{token}_review.html" | |
| revised = str(getattr(result, "revised", "")) | |
| txt_path.write_text(revised, encoding="utf-8") | |
| json_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") | |
| from docx import Document | |
| document = Document() | |
| document.add_heading("NLL Universal Professional Editor — Review", level=0) | |
| document.add_paragraph("Discipline: " + str(getattr(result, "discipline", ""))) | |
| document.add_paragraph( | |
| "Writing Pattern Score: " + str(getattr(result, "pattern_score", "")) | |
| + " — " + str(getattr(result, "band", "")) | |
| ) | |
| document.add_paragraph( | |
| "The Writing Pattern Score describes observable structural and stylistic " | |
| "characteristics only. It does not identify authorship." | |
| ) | |
| document.add_heading("Revised text", level=1) | |
| for paragraph in re.split(r"\n\s*\n", revised): | |
| document.add_paragraph(paragraph) | |
| document.add_heading("Sentence review", level=1) | |
| for number, item in enumerate(list(getattr(result, "sentences", []) or []), 1): | |
| trace = _trace(item) | |
| document.add_heading(f"Sentence {number}: {getattr(item, 'status', '')}", level=2) | |
| document.add_paragraph("Original: " + str(getattr(item, "original", ""))) | |
| document.add_paragraph("Revised: " + str(getattr(item, "revised", ""))) | |
| document.add_paragraph("Reason: " + str(getattr(item, "reason", ""))) | |
| document.add_paragraph( | |
| "Must-paraphrase: " | |
| + json.dumps(_json_safe(trace.get("must_paraphrase", {})), ensure_ascii=False) | |
| ) | |
| document.save(str(docx_path)) | |
| rows = _sentence_rows(result) | |
| body = "".join( | |
| "<tr>" | |
| f"<td>{row['Sentence']}</td>" | |
| f"<td>{html.escape(str(row['Status']))}</td>" | |
| f"<td>{html.escape(str(row['Accepted mode']))}</td>" | |
| f"<td>{html.escape(str(row['Detected families']))}</td>" | |
| f"<td>{html.escape(str(row['Original']))}</td>" | |
| f"<td>{html.escape(str(row['Revised']))}</td>" | |
| f"<td>{html.escape(str(row['Reason']))}</td>" | |
| "</tr>" | |
| for row in rows | |
| ) | |
| html_path.write_text( | |
| f"""<!doctype html><html lang="en"><head><meta charset="utf-8"> | |
| <title>NLL Review</title><style> | |
| body{{font-family:Arial,sans-serif;max-width:1500px;margin:30px auto;color:#172033}} | |
| h1,h2{{color:#123b66}}table{{border-collapse:collapse;width:100%}} | |
| th,td{{border:1px solid #cbd5e1;padding:8px;vertical-align:top;text-align:left}} | |
| th{{background:#eaf2fb}}pre{{white-space:pre-wrap;background:#f8fafc;padding:12px}} | |
| </style></head><body> | |
| <h1>NLL Universal Professional Editor — Review</h1> | |
| <p><strong>Rules:</strong> {len(RULES)} | <strong>Must paraphrase:</strong> {MUST_PARAPHRASE_ACTIVE}</p> | |
| <h2>Revised text</h2><pre>{html.escape(revised)}</pre> | |
| <h2>Sentence review</h2><table><thead><tr><th>#</th><th>Status</th><th>Mode</th> | |
| <th>Families</th><th>Original</th><th>Revised</th><th>Reason</th></tr></thead> | |
| <tbody>{body}</tbody></table> | |
| <h2>Full diagnostics</h2><pre>{html.escape(json.dumps(payload, ensure_ascii=False, indent=2))}</pre> | |
| </body></html>""", | |
| encoding="utf-8", | |
| ) | |
| return [str(txt_path), str(docx_path), str(json_path), str(html_path)] | |
| # ===================================================================== | |
| # 7. MAIN CALLBACK | |
| # ===================================================================== | |
| def run_ui_with_full_diagnostics( | |
| typed_text, | |
| uploaded_file, | |
| workflow, | |
| mode, | |
| discipline, | |
| protected_terms, | |
| use_stanza, | |
| use_language_tool, | |
| progress=gr.Progress(), | |
| ): | |
| source_text, source_name = _read_input_text(typed_text, uploaded_file) | |
| if not source_text.strip(): | |
| raise gr.Error("No readable text was provided.") | |
| _apply_ui_settings(use_stanza, use_language_tool) | |
| progress(0.02, desc="Preparing request") | |
| try: | |
| result = ENGINE.process( | |
| source_text, workflow, mode, discipline, protected_terms, progress | |
| ) | |
| except TypeError: | |
| result = ENGINE.process( | |
| text=source_text, | |
| workflow=workflow, | |
| mode=mode, | |
| discipline=discipline, | |
| terms_text=protected_terms, | |
| progress=progress, | |
| ) | |
| progress(0.86, desc="Building diagnostics") | |
| payload = _full_payload(result, source_name) | |
| files = _write_reports(result, source_name, payload) | |
| progress(1.0, desc="Complete") | |
| return ( | |
| getattr(result, "revised", source_text), | |
| _summary_markdown(result), | |
| pd.DataFrame(_sentence_rows(result)), | |
| pd.DataFrame(_routing_rows(result)), | |
| pd.DataFrame(_attempt_rows(result)), | |
| pd.DataFrame(_realization_rows(result)), | |
| pd.DataFrame(_candidate_rows(result)), | |
| pd.DataFrame(_finding_rows(result)), | |
| payload, | |
| files, | |
| ) | |
| # ===================================================================== | |
| # 8. PROFESSIONAL GRADIO UI | |
| # ===================================================================== | |
| CELL9_CSS = """ | |
| .gradio-container { max-width: 1650px !important; } | |
| #nll-title { text-align:center; margin-bottom:4px; color:#123b66; } | |
| #nll-subtitle { text-align:center; color:#596779; margin-bottom:16px; } | |
| .nll-panel { border:1px solid #d5dee8; border-radius:14px; padding:12px; background:#fbfdff; } | |
| .nll-note { background:#fff8dc; border-left:4px solid #d7a100; padding:8px 12px; } | |
| .nll-ok { background:#e8f6ee; border-left:4px solid #238636; padding:8px 12px; } | |
| """ | |
| with gr.Blocks(css=CELL9_CSS, title="NLL Universal Professional Editor") as NLL_APP: | |
| gr.Markdown("# NLL Universal Professional Editor", elem_id="nll-title") | |
| gr.Markdown( | |
| "Deterministic, non-generative rewriting with must-paraphrase orchestration, " | |
| "meaning safeguards, and complete sentence/candidate diagnostics.", | |
| elem_id="nll-subtitle", | |
| ) | |
| gr.Markdown( | |
| f"**System ready:** {len(RULES)} rules | Must-paraphrase active | " | |
| "Cell 5A realization gate active | Cell 7 judge active", | |
| elem_classes="nll-ok", | |
| ) | |
| with gr.Row(equal_height=False): | |
| with gr.Column(scale=1, elem_classes="nll-panel"): | |
| gr.Markdown("## Input and controls") | |
| input_text = gr.Textbox( | |
| label="Text", | |
| lines=20, | |
| placeholder="Paste academic, contract, policy, technical, business, or professional text.", | |
| ) | |
| input_file = gr.File( | |
| label="Optional file upload", | |
| file_types=[".txt", ".docx", ".pdf"], | |
| type="filepath", | |
| ) | |
| workflow_control = gr.Radio( | |
| choices=["Scan Only", "Scan & Neutralize", "Edit / Paraphrase Only"], | |
| value="Edit / Paraphrase Only", | |
| label="Workflow", | |
| ) | |
| mode_control = gr.Radio( | |
| choices=["Minimal", "Balanced", "Structural"], | |
| value="Balanced", | |
| label="Preferred rewrite mode", | |
| ) | |
| discipline_control = gr.Dropdown( | |
| choices=["Auto Detect"] + list(PROFILES.keys()), | |
| value="Auto Detect", | |
| label="Discipline", | |
| ) | |
| protected_terms_control = gr.Textbox( | |
| label="Immutable/user-protected terms", | |
| placeholder="Comma, semicolon, or line-separated terms, e.g., WSC, TxDOT, ALCEF-RSF", | |
| lines=3, | |
| ) | |
| with gr.Accordion("Optional non-generative analysis resources", open=False): | |
| stanza_control = gr.Checkbox( | |
| value=bool(getattr(globals().get("CFG", object()), "stanza", False)), | |
| label="Stanza parsing support", | |
| ) | |
| language_tool_control = gr.Checkbox( | |
| value=bool(getattr(globals().get("CFG", object()), "lt", False)), | |
| label="Local LanguageTool comparison", | |
| ) | |
| analyze_button = gr.Button("Analyze / Revise", variant="primary", size="lg") | |
| gr.Markdown( | |
| "The selected mode is attempted first. If no safe change survives, " | |
| "the other modes are searched. The original is retained only after " | |
| "all safe deterministic options are exhausted.", | |
| elem_classes="nll-note", | |
| ) | |
| with gr.Column(scale=1, elem_classes="nll-panel"): | |
| gr.Markdown("## Revised output and review") | |
| revised_output = gr.Textbox(label="Revised output", lines=20) | |
| summary_output = gr.Markdown() | |
| with gr.Tabs(): | |
| with gr.Tab("Sentence comparison"): | |
| sentence_table = gr.Dataframe( | |
| label="Original, revised, mode, family, and judge outcome", | |
| interactive=False, | |
| wrap=True, | |
| ) | |
| with gr.Tab("Routing diagnostics"): | |
| routing_table = gr.Dataframe( | |
| label="Detected families, applicable rules, and generated candidates", | |
| interactive=False, | |
| wrap=True, | |
| ) | |
| with gr.Tab("Must-paraphrase attempts"): | |
| attempt_table = gr.Dataframe( | |
| label="Requested-mode-first judge and retry attempts", | |
| interactive=False, | |
| wrap=True, | |
| ) | |
| with gr.Tab("Realization rejections"): | |
| realization_table = gr.Dataframe( | |
| label="Candidates rejected by Cell 5A", | |
| interactive=False, | |
| wrap=True, | |
| ) | |
| with gr.Tab("Candidate diagnostics"): | |
| candidate_table = gr.Dataframe( | |
| label="Validated candidates, scores, and rejection reasons", | |
| interactive=False, | |
| wrap=True, | |
| ) | |
| with gr.Tab("Writing-pattern findings"): | |
| findings_table = gr.Dataframe( | |
| label="Observable structural and stylistic findings", | |
| interactive=False, | |
| wrap=True, | |
| ) | |
| with gr.Tab("Full JSON diagnostics"): | |
| diagnostic_json = gr.JSON(label="Complete diagnostic payload") | |
| with gr.Tab("Downloads"): | |
| download_output = gr.File( | |
| label="Download revised text and review reports", | |
| file_count="multiple", | |
| ) | |
| inputs = [ | |
| input_text, input_file, workflow_control, mode_control, | |
| discipline_control, protected_terms_control, | |
| stanza_control, language_tool_control, | |
| ] | |
| outputs = [ | |
| revised_output, summary_output, sentence_table, routing_table, | |
| attempt_table, realization_table, candidate_table, findings_table, | |
| diagnostic_json, download_output, | |
| ] | |
| analyze_button.click( | |
| fn=run_ui_with_full_diagnostics, | |
| inputs=inputs, | |
| outputs=outputs, | |
| show_progress="full", | |
| concurrency_limit=1, | |
| ) | |
| NLL_APP.queue(default_concurrency_limit=1, max_size=8) | |
| print("=" * 76) | |
| print("CELL 9 PROFESSIONAL GRADIO UI v5.1 LOADED") | |
| print("=" * 76) | |
| print("Aligned pipeline: 5 v4.1 -> 5A v3.4 -> 6 v5.1 -> 7 v4 -> 7A v2 -> 7B v2 -> 8 v4 -> 8A v2") | |
| print("Registered transformation rules:", len(RULES)) | |
| print("Expected rule count:", EXPECTED_RULE_COUNT) | |
| print("Must-paraphrase orchestrator: active") | |
| print("Cell 5A realization diagnostics: active") | |
| print("Sentence comparison: active") | |
| print("Routing diagnostics: active") | |
| print("Must-paraphrase attempt diagnostics: active") | |
| print("Candidate diagnostics: active") | |
| print("Writing Pattern Score terminology: active") | |
| print("LLM/MLM candidate generation: disabled") | |
| print("STS/NLI scoring: disabled") | |
| print("Online lexical fallback: disabled") | |
| print("TXT/DOCX/JSON/HTML downloads: active") | |
| print("=" * 76) | |
| # Gradio is mounted by the production ASGI app; do not launch here. |