Spaces:
Running
Running
unknown
Claude Opus 5.5
Faster and steadier: jobs survive silent connection drops, Journal Finder warm start, parallel lookups and caches, fast engine up to FAST_MAX_WORDS
1f241b9 Download formatter_tab.py from Rasheed/Test_AI_writing: direct link, hf CLI and curl.
- Browser
- Download file 13.9 kB
-
https://huggingface.co/spaces/Rasheed/Test_AI_writing/resolve/main/formatter_tab.py
- Command line
-
hf download hf://spaces/Rasheed/Test_AI_writing/formatter_tab.py
-
curl -L -o formatter_tab.py https://huggingface.co/spaces/Rasheed/Test_AI_writing/resolve/main/formatter_tab.py
13.9 kB
| """Journal Formatter tab: arrange a manuscript into a journal's format (Word + an Overleaf-ready LaTeX project). | |
| The work is done by the FormatMatch engine in ./formatmatch: it reads the manuscript (.docx, .pdf, LaTeX .tex / .zip | |
| - an Overleaf "Download source" zip works -, Markdown), builds the target format from a journal name, a publisher | |
| house style, the journal's Word template or its author guidelines, and writes the paper again in that format with | |
| a compliance report. Equations, algorithms and complex tables are carried over untouched. | |
| Overleaf: the LaTeX project is a zip with main.tex at its top level, refs.bib and figures/, using classes that | |
| Overleaf's TeX Live has (IEEEtran, elsarticle, llncs, sn-jnl ...). The customer downloads it and uploads it to | |
| Overleaf himself (New Project -> Upload Project); the page explains how. | |
| """ | |
| import html | |
| import os | |
| import re | |
| import tempfile | |
| import time | |
| from pathlib import Path | |
| import gradio as gr | |
| from tools_common import download_row | |
| TOOL = "Journal Formatter" # job key: a reconnecting page picks up its own running job | |
| try: | |
| from formatmatch.engine import (FormatSource, build_spec, fetch_guidelines_url, format_manuscript, | |
| guidelines_text_from_file, learn_template) | |
| from formatmatch.read_any import SUPPORTED | |
| from formatmatch.spec import PRESETS, lookup_journal, parse_guidelines | |
| AVAILABLE, WHY = True, "" | |
| except Exception as _err: # the suite must start even if this tool cannot load | |
| AVAILABLE, WHY = False, f"{type(_err).__name__}: {_err}" | |
| PRESETS, SUPPORTED = {}, [] | |
| OUT_DIR = Path(tempfile.gettempdir()) / "cs_outputs" | |
| AUTO = "Recognise from the journal name" | |
| REF_STYLES = ["(from the journal)", "ieee", "apa", "harvard", "emerald-harvard", "vancouver", "ama", "chicago", | |
| "springer-basic", "springer-vancouver", "lncs", "mdpi", "elsevier-numeric", "mla"] | |
| CITE_MODES = ["(from the journal)", "numeric", "superscript", "author-year", "author-year-nocomma"] | |
| MAX_MB = float(os.getenv("FORMATTER_MAX_MB", "40")) | |
| def _preset_choices(): | |
| return [AUTO] + [PRESETS[k]["name"] for k in PRESETS] | |
| def _preset_key(label): | |
| return next((k for k in PRESETS if PRESETS[k]["name"] == label), None) | |
| def overleaf_html(zip_path): | |
| """How to use the LaTeX project in Overleaf. The customer downloads the zip and uploads it there himself: | |
| there is no button that sends it to Overleaf.""" | |
| if not zip_path or not os.path.exists(zip_path): | |
| return "" | |
| return ('<div class="cs-gauge-cap" style="text-align:left">🍃 <b>To use it in Overleaf:</b> download the LaTeX ' | |
| 'project above, then in Overleaf choose <b>New Project → Upload Project</b> and select the .zip. It ' | |
| 'opens with <code>main.tex</code>, the references in <code>refs.bib</code> and the figures in ' | |
| '<code>figures/</code>; compile with pdfLaTeX.</div>') | |
| def _data(path): | |
| p = Path(str(path)) | |
| if p.stat().st_size > MAX_MB * 1024 * 1024: | |
| raise gr.Error(f"That file is larger than {MAX_MB:.0f} MB. Remove very large images or split the document.") | |
| return p.name, p.read_bytes() | |
| def format_doc(manuscript, journal, style, template, guidelines, guidelines_url, want_docx, want_latex, crossref, | |
| ref_style, cite_mode, progress=gr.Progress()): | |
| if not manuscript: | |
| raise gr.Error("Upload your manuscript first.") | |
| journal, guidelines, guidelines_url = (journal or "").strip(), (guidelines or "").strip(), (guidelines_url or "").strip() | |
| if not (journal or template or guidelines or guidelines_url or _preset_key(style)): | |
| raise gr.Error("Tell the tool the target format: type the journal's name, choose a publisher style, or " | |
| "upload the journal's Word template.") | |
| if not (want_docx or want_latex): | |
| raise gr.Error("Choose at least one output: Word or LaTeX.") | |
| name, data = _data(manuscript) | |
| src, found = FormatSource(), [] | |
| progress(0.03, desc="Working out the target format") | |
| if journal: | |
| try: | |
| src.journal = lookup_journal(journal) | |
| if src.journal.get("publisher"): | |
| found.append(f"{src.journal.get('title') or journal} — {src.journal['publisher']}") | |
| except Exception as err: # the lookup is a convenience (Crossref), never required | |
| print(f"[formatter] journal lookup failed: {err}", flush=True) | |
| src.preset = _preset_key(style) or (src.journal or {}).get("preset") | |
| if template: | |
| try: | |
| ov, prof, notes = learn_template(*_data(template)) | |
| src.template_overrides, src.profile = ov, prof | |
| found += list(notes)[:6] | |
| except Exception as err: | |
| raise gr.Error(f"The template could not be read: {err}") | |
| text = guidelines | |
| if guidelines_url: | |
| try: | |
| text += "\n" + fetch_guidelines_url(guidelines_url) | |
| except Exception as err: | |
| found.append(f"The guidelines page could not be opened ({err}); paste its text instead.") | |
| if text.strip(): | |
| src.guidelines = parse_guidelines(text) | |
| found += list(src.guidelines.get("_found", []))[:6] | |
| manual = {} | |
| if ref_style and not ref_style.startswith("("): | |
| manual["references"] = {"style": ref_style} | |
| if cite_mode and not cite_mode.startswith("("): | |
| manual["citations"] = {"mode": cite_mode} | |
| src.manual = manual | |
| if not (src.preset or src.template_overrides or src.guidelines): | |
| raise gr.Error(f"“{journal}” was not recognised as a known publisher. Choose the closest publisher style, " | |
| "or upload the journal's Word template or author guidelines.") | |
| spec = build_spec(src) | |
| try: | |
| res = format_manuscript(name, data, spec, src.profile, bool(want_docx), bool(want_latex), bool(crossref), | |
| progress=lambda f, m: progress(0.05 + 0.9 * min(1.0, f), desc=m)) | |
| except RuntimeError as err: # e.g. LaTeX input without pandoc on the server | |
| raise gr.Error(str(err)) | |
| except Exception as err: | |
| print(f"[formatter] failed on {name}: {type(err).__name__}: {err}", flush=True) | |
| raise gr.Error(f"This manuscript could not be formatted ({type(err).__name__}). If it is a PDF, try the " | |
| "Word or LaTeX source instead.") | |
| job = Path(tempfile.mkdtemp(prefix="format_", dir=str(OUT_DIR))) | |
| base = re.sub(r"[^A-Za-z0-9_-]+", "_", os.path.splitext(name)[0]).strip("_") or "manuscript" | |
| paths = {} | |
| for key, blob, fname in (("docx", res.docx, f"{base}_formatted.docx"), ("zip", res.latex_zip, f"{base}_latex.zip"), | |
| ("md", res.report_md.encode("utf-8"), f"{base}_format_report.md")): | |
| if blob: | |
| (job / fname).write_bytes(blob) | |
| paths[key] = str(job / fname) | |
| checks = res.report.checks | |
| n_ok = sum(1 for _, ok, _ in checks if ok) | |
| print(f"[formatter] {name} -> {spec.get('name', 'custom')}: {n_ok}/{len(checks)} checks, " | |
| f"{len(res.report.changes)} changes, {len(res.report.warnings)} warnings", flush=True) | |
| notes = "".join(f"<li>{html.escape(str(f))}</li>" for f in found[:8]) | |
| status = (f'<div class="cs-status cs-done">✅ <b>Formatted as {html.escape(str(spec.get("name", "custom format")))}' | |
| f'</b> — {n_ok} of {len(checks)} checks passed, {len(res.report.changes)} changes made, ' | |
| f'{len(res.report.warnings)} point(s) need your attention.' | |
| + (f'<ul style="margin:6px 0 0 18px">{notes}</ul>' if notes else "") + "</div>") | |
| buttons = download_row((paths.get("docx"), "Download Word document"), (paths.get("zip"), "Download LaTeX project"), | |
| (paths.get("md"), "Download report")) | |
| return (status, buttons, paths.get("docx"), paths.get("zip"), paths.get("md"), | |
| overleaf_html(paths.get("zip")) if paths.get("zip") else "", res.report_md) | |
| def build(ui=None): | |
| """The tab's interface (call inside `with gr.Tab(...)`). `ui` carries the suite's shared look: its animated | |
| status panel (wait, run_live, job) so this tab shows the same logo animation and progress bar as the others; | |
| without it (tests, stand-alone use) Gradio's own progress display is used.""" | |
| gr.HTML('<div class="cs-intro"><b>Journal Formatter.</b> Upload your manuscript and name the journal. You get ' | |
| 'it back arranged in that journal\'s format as a Word file and as a LaTeX project you can upload to ' | |
| 'Overleaf, with a report of what was changed. Equations, algorithms and tables are carried over ' | |
| 'untouched.<div class="cs-flow"><span>1 · Upload the manuscript</span><span>2 · Name the journal</span>' | |
| '<span>3 · Download Word / LaTeX for Overleaf</span></div></div>') | |
| if not AVAILABLE: | |
| gr.HTML(f'<div class="cs-status cs-err">⚠️ The formatter could not be loaded on this server ' | |
| f'({html.escape(WHY)}).</div>') | |
| return | |
| with gr.Row(equal_height=False): | |
| with gr.Column(elem_classes="cs-card"): | |
| ms = gr.File(label="Manuscript (.docx, .pdf, LaTeX .tex or .zip incl. an Overleaf source zip, .md)", | |
| file_types=["." + e for e in SUPPORTED], type="filepath") | |
| journal = gr.Textbox(label="Target journal or conference", | |
| placeholder="e.g. IEEE Access, Expert Systems with Applications") | |
| style = gr.Dropdown(_preset_choices(), value=AUTO, label="Publisher style", | |
| info="Leave as it is when you typed the journal's name; choose one if the journal " | |
| "is not recognised.") | |
| with gr.Accordion("📄 I have the journal's template or author guidelines (optional, most accurate)", | |
| open=False): | |
| template = gr.File(label="Journal Word template (.docx / .dotx) or a published sample paper (.pdf)", | |
| file_types=[".docx", ".dotx", ".dotm", ".doc", ".pdf"], type="filepath") | |
| guidelines = gr.Textbox(label="Author guidelines (paste the text)", lines=4) | |
| guidelines_url = gr.Textbox(label="…or the address of the guidelines page", | |
| placeholder="https://…/author-guidelines") | |
| with gr.Accordion("⚙️ More options (not needed)", open=False): | |
| with gr.Row(): | |
| want_docx = gr.Checkbox(value=True, label="Word (.docx)") | |
| want_latex = gr.Checkbox(value=True, label="LaTeX project for Overleaf (.zip)") | |
| crossref = gr.Checkbox(value=False, label="Check references online (slower)", | |
| info="Fills in missing volume, pages and DOI from Crossref") | |
| with gr.Row(): | |
| ref_style = gr.Dropdown(REF_STYLES, value=REF_STYLES[0], label="Reference style") | |
| cite_mode = gr.Dropdown(CITE_MODES, value=CITE_MODES[0], label="In-text citations") | |
| go = gr.Button("Format my manuscript", variant="primary", size="lg") | |
| with gr.Column(elem_classes="cs-card"): | |
| status = gr.HTML('<div class="cs-status cs-idle">Upload a manuscript, name the journal and press ' | |
| '<b>Format my manuscript</b>.</div>') | |
| dl = gr.HTML() # download buttons that carry the files themselves | |
| f_docx = gr.File(label="Formatted Word document", interactive=False) | |
| f_zip = gr.File(label="LaTeX project (.zip) for Overleaf", interactive=False) | |
| overleaf = gr.HTML() | |
| f_md = gr.File(label="Formatting report", interactive=False) | |
| with gr.Accordion("Formatting report", open=False): | |
| report = gr.Markdown() | |
| inputs = [ms, journal, style, template, guidelines, guidelines_url, want_docx, want_latex, crossref, ref_style, | |
| cite_mode] | |
| outputs = [status, dl, f_docx, f_zip, f_md, overleaf, report] | |
| if ui: # the suite's branded status panel (logo animation, bar) | |
| def core(*args): # args = the inputs + the job's progress reporter | |
| return format_doc(*args[:-1], progress=args[-1]) | |
| def run(*args, request: gr.Request = None): | |
| *args, user = args # the signed-in user (or None) comes last | |
| t0, last = time.time(), None | |
| name = os.path.basename(str(args[0])) if args[0] else "" | |
| try: | |
| for frame in ui["run_live"](core, tuple(args), len(outputs), 0, user=user, request=request, | |
| tool=TOOL): | |
| last = frame | |
| yield frame | |
| except Exception as err: # shown to the customer by the status panel; log it too | |
| ui["log"](user, request, "Journal Formatter", True, {"name": name}, time.time() - t0, "error", | |
| str(getattr(err, "message", err))) | |
| raise | |
| out = last[2] or last[3] if last and len(last) > 3 else None # Word file, else the LaTeX zip | |
| ui["log"](user, request, "Journal Formatter", True, | |
| {"name": name, "output": out if isinstance(out, str) else None}, time.time() - t0, "ok") | |
| go.click(lambda: ui["wait"], None, status, queue=False, **ui["hid"]).then( | |
| run, inputs + [ui["user"]], outputs, **ui["job"](status)) | |
| else: | |
| go.click(format_doc, inputs, outputs, api_name=False, show_progress="full", | |
| concurrency_limit=int(os.getenv("FORMATTER_CONCURRENCY", "3"))) | |