Spaces:
Paused
Paused
unknown Claude Opus 5.5 commited on
Commit ·
cdcade6
1
Parent(s): e7bb050
Open in Overleaf: respect Overleaf's request size limit
Browse filesOverleaf answered a posted project with "Something went wrong ... there
was a problem with your request": HTTP 413. Measured limit: a 1.98 MB
request is accepted, 2.13 MB is refused, i.e. a zip of about 1.4 MB once
it is base64 in a form. The cut-off had been a guessed 6 MB.
- The button now measures the real request and posts the project only
when it stays under OVERLEAF_POST_MB (1.8): a zip up to about 1.2 MB.
- A larger project is handed over by its public address on the Space
(snip_uri=https://...), which Overleaf downloads itself.
- With neither possible the page says so and explains the manual upload.
- Test that every posted project fits the limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- formatter_tab.py +15 -9
- tests/test_tools_tabs.py +15 -3
formatter_tab.py
CHANGED
|
@@ -8,7 +8,8 @@ a compliance report. Equations, algorithms and complex tables are carried over u
|
|
| 8 |
Overleaf: the LaTeX project is a zip with main.tex at its top level, refs.bib and figures/, using classes that
|
| 9 |
Overleaf's TeX Live has (IEEEtran, elsarticle, llncs, sn-jnl ...). "Open in Overleaf" posts that zip to Overleaf
|
| 10 |
itself (overleaf.com/devs: snip_uri as a base64 data URL), so it works on a private Space and on a local run; only
|
| 11 |
-
a
|
|
|
|
| 12 |
"""
|
| 13 |
import html
|
| 14 |
import os
|
|
@@ -54,8 +55,11 @@ def public_url(path):
|
|
| 54 |
|
| 55 |
|
| 56 |
OVERLEAF = "https://www.overleaf.com/docs" # Overleaf's "Open in Overleaf" API (overleaf.com/devs)
|
| 57 |
-
#
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
|
| 61 |
def overleaf_html(zip_path):
|
|
@@ -70,10 +74,11 @@ def overleaf_html(zip_path):
|
|
| 70 |
'<code>refs.bib</code> and the figures in <code>figures/</code>; compile with pdfLaTeX.</div>')
|
| 71 |
if not zip_path or not os.path.exists(zip_path):
|
| 72 |
return ""
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
| 77 |
return (f'<form action="{OVERLEAF}" method="post" target="_blank" style="margin:0">'
|
| 78 |
f'<input type="hidden" name="snip_uri" value="{data}">'
|
| 79 |
'<input type="hidden" name="engine" value="pdflatex">'
|
|
@@ -81,8 +86,9 @@ def overleaf_html(zip_path):
|
|
| 81 |
'<button type="submit" class="cs-dl" style="border:0;cursor:pointer;font:inherit">'
|
| 82 |
'🍃 Open in Overleaf</button></form>' + manual)
|
| 83 |
url = public_url(zip_path)
|
| 84 |
-
if not url:
|
| 85 |
-
return
|
|
|
|
| 86 |
link = f"{OVERLEAF}?engine=pdflatex&main_document=main.tex&snip_uri=" + quote(url, safe="")
|
| 87 |
return (f'<a class="cs-dl" target="_blank" rel="noopener" href="{html.escape(link, quote=True)}">'
|
| 88 |
f'🍃 Open in Overleaf</a>' + manual)
|
|
|
|
| 8 |
Overleaf: the LaTeX project is a zip with main.tex at its top level, refs.bib and figures/, using classes that
|
| 9 |
Overleaf's TeX Live has (IEEEtran, elsarticle, llncs, sn-jnl ...). "Open in Overleaf" posts that zip to Overleaf
|
| 10 |
itself (overleaf.com/devs: snip_uri as a base64 data URL), so it works on a private Space and on a local run; only
|
| 11 |
+
a project too large for Overleaf to accept in one request (a zip above about 1.2 MB) is handed over by its address on
|
| 12 |
+
the Space (SPACE_HOST) instead.
|
| 13 |
"""
|
| 14 |
import html
|
| 15 |
import os
|
|
|
|
| 55 |
|
| 56 |
|
| 57 |
OVERLEAF = "https://www.overleaf.com/docs" # Overleaf's "Open in Overleaf" API (overleaf.com/devs)
|
| 58 |
+
# Overleaf answers a posted request above about 2 MB with "Something went wrong ... problem with your request"
|
| 59 |
+
# (HTTP 413; measured: 1.98 MB accepted, 2.13 MB refused). The project is base64 inside a form, which makes it
|
| 60 |
+
# about 1.42 times its size on the wire, so only a zip whose REQUEST stays under OVERLEAF_POST_MB is posted
|
| 61 |
+
# (a zip up to roughly 1.2 MB); a larger one is handed to Overleaf by its address on the Space.
|
| 62 |
+
OVERLEAF_POST_MB = float(os.getenv("OVERLEAF_POST_MB", "1.8"))
|
| 63 |
|
| 64 |
|
| 65 |
def overleaf_html(zip_path):
|
|
|
|
| 74 |
'<code>refs.bib</code> and the figures in <code>figures/</code>; compile with pdfLaTeX.</div>')
|
| 75 |
if not zip_path or not os.path.exists(zip_path):
|
| 76 |
return ""
|
| 77 |
+
import base64
|
| 78 |
+
from urllib.parse import urlencode
|
| 79 |
+
data = "data:application/zip;base64," + base64.b64encode(Path(zip_path).read_bytes()).decode("ascii")
|
| 80 |
+
request_mb = len(urlencode({"snip_uri": data, "engine": "pdflatex", "main_document": "main.tex"})) / 1e6
|
| 81 |
+
if request_mb <= OVERLEAF_POST_MB:
|
| 82 |
return (f'<form action="{OVERLEAF}" method="post" target="_blank" style="margin:0">'
|
| 83 |
f'<input type="hidden" name="snip_uri" value="{data}">'
|
| 84 |
'<input type="hidden" name="engine" value="pdflatex">'
|
|
|
|
| 86 |
'<button type="submit" class="cs-dl" style="border:0;cursor:pointer;font:inherit">'
|
| 87 |
'🍃 Open in Overleaf</button></form>' + manual)
|
| 88 |
url = public_url(zip_path)
|
| 89 |
+
if not url: # too large to post and no public address: by hand
|
| 90 |
+
return ('<div class="cs-gauge-cap" style="text-align:left">This project is too large to send to Overleaf '
|
| 91 |
+
'directly.</div>' + manual)
|
| 92 |
link = f"{OVERLEAF}?engine=pdflatex&main_document=main.tex&snip_uri=" + quote(url, safe="")
|
| 93 |
return (f'<a class="cs-dl" target="_blank" rel="noopener" href="{html.escape(link, quote=True)}">'
|
| 94 |
f'🍃 Open in Overleaf</a>' + manual)
|
tests/test_tools_tabs.py
CHANGED
|
@@ -81,8 +81,8 @@ def test_overleaf_button_posts_the_project_itself(tmp_path):
|
|
| 81 |
|
| 82 |
|
| 83 |
def test_a_large_project_goes_by_its_address_or_by_hand(tmp_path, monkeypatch):
|
| 84 |
-
p = _zip(tmp_path, kb=
|
| 85 |
-
|
| 86 |
monkeypatch.setenv("SPACE_HOST", "rasheed-ai-writing-suite.hf.space")
|
| 87 |
h = FMT.overleaf_html(p)
|
| 88 |
assert "<form" not in h and "snip_uri=https%3A%2F%2Frasheed-ai-writing-suite.hf.space%2Fgradio_api%2Ffile%3D" in h
|
|
@@ -90,10 +90,22 @@ def test_a_large_project_goes_by_its_address_or_by_hand(tmp_path, monkeypatch):
|
|
| 90 |
monkeypatch.delenv("SPACE_HOST")
|
| 91 |
monkeypatch.delenv("PUBLIC_HOST", raising=False)
|
| 92 |
h = FMT.overleaf_html(p)
|
| 93 |
-
assert "Open in Overleaf" not in h and "Upload Project" in h
|
| 94 |
assert FMT.overleaf_html(None) == ""
|
| 95 |
|
| 96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
# ---------------------------------------------------------------- finder
|
| 98 |
def test_finder_files_and_data_are_in_place():
|
| 99 |
assert FND.AVAILABLE
|
|
|
|
| 81 |
|
| 82 |
|
| 83 |
def test_a_large_project_goes_by_its_address_or_by_hand(tmp_path, monkeypatch):
|
| 84 |
+
p = _zip(tmp_path, kb=2000) # a 2 MB project: Overleaf refuses it as a posted form
|
| 85 |
+
assert FMT.OVERLEAF_POST_MB < 2.0
|
| 86 |
monkeypatch.setenv("SPACE_HOST", "rasheed-ai-writing-suite.hf.space")
|
| 87 |
h = FMT.overleaf_html(p)
|
| 88 |
assert "<form" not in h and "snip_uri=https%3A%2F%2Frasheed-ai-writing-suite.hf.space%2Fgradio_api%2Ffile%3D" in h
|
|
|
|
| 90 |
monkeypatch.delenv("SPACE_HOST")
|
| 91 |
monkeypatch.delenv("PUBLIC_HOST", raising=False)
|
| 92 |
h = FMT.overleaf_html(p)
|
| 93 |
+
assert "Open in Overleaf" not in h and "too large to send" in h and "Upload Project" in h
|
| 94 |
assert FMT.overleaf_html(None) == ""
|
| 95 |
|
| 96 |
|
| 97 |
+
def test_a_posted_project_stays_under_overleafs_request_limit(tmp_path):
|
| 98 |
+
from urllib.parse import urlencode
|
| 99 |
+
for kb in (200, 900, 1200): # every project that is posted fits Overleaf's 2 MB
|
| 100 |
+
h = FMT.overleaf_html(_zip(tmp_path, kb=kb))
|
| 101 |
+
if "<form" in h:
|
| 102 |
+
data = h.split('name="snip_uri" value="', 1)[1].split('"', 1)[0]
|
| 103 |
+
body = len(urlencode({"snip_uri": data, "engine": "pdflatex", "main_document": "main.tex"}))
|
| 104 |
+
assert body <= 1.9e6, kb
|
| 105 |
+
assert "<form" in FMT.overleaf_html(_zip(tmp_path, kb=200))
|
| 106 |
+
assert "<form" not in FMT.overleaf_html(_zip(tmp_path, kb=1600))
|
| 107 |
+
|
| 108 |
+
|
| 109 |
# ---------------------------------------------------------------- finder
|
| 110 |
def test_finder_files_and_data_are_in_place():
|
| 111 |
assert FND.AVAILABLE
|