Spaces:
Runtime error
Runtime error
Disable local handwriting gracefully when packages absent
Browse files- app.py +28 -0
- static/index.html +25 -7
- static/styles.css +6 -0
app.py
CHANGED
|
@@ -75,6 +75,34 @@ async def index():
|
|
| 75 |
return FileResponse(str(INDEX_HTML))
|
| 76 |
|
| 77 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
@app.post("/api/upload")
|
| 79 |
async def upload(
|
| 80 |
file: UploadFile = File(...),
|
|
|
|
| 75 |
return FileResponse(str(INDEX_HTML))
|
| 76 |
|
| 77 |
|
| 78 |
+
# Cache the (deterministic) optional-feature probe so torch/transformers are
|
| 79 |
+
# imported at most once.
|
| 80 |
+
_CAPABILITIES: dict = {}
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
@app.get("/api/capabilities")
|
| 84 |
+
async def capabilities():
|
| 85 |
+
"""Report which optional features are usable in THIS deployment.
|
| 86 |
+
|
| 87 |
+
Local handwriting (TrOCR) needs torch + transformers, which the lean hosted
|
| 88 |
+
build omits. The front-end reads this to disable the handwriting toggle
|
| 89 |
+
gracefully and point users to online OCR, instead of letting a page fail
|
| 90 |
+
mid-extraction with a missing-dependency error.
|
| 91 |
+
"""
|
| 92 |
+
if "handwriting" not in _CAPABILITIES:
|
| 93 |
+
from pipeline.jobs import _EXECUTOR
|
| 94 |
+
from pipeline.handwriting import is_available
|
| 95 |
+
|
| 96 |
+
loop = asyncio.get_running_loop()
|
| 97 |
+
try:
|
| 98 |
+
_CAPABILITIES["handwriting"] = await loop.run_in_executor(
|
| 99 |
+
_EXECUTOR, is_available
|
| 100 |
+
)
|
| 101 |
+
except Exception:
|
| 102 |
+
_CAPABILITIES["handwriting"] = False
|
| 103 |
+
return {"handwriting": bool(_CAPABILITIES["handwriting"])}
|
| 104 |
+
|
| 105 |
+
|
| 106 |
@app.post("/api/upload")
|
| 107 |
async def upload(
|
| 108 |
file: UploadFile = File(...),
|
static/index.html
CHANGED
|
@@ -18,12 +18,29 @@
|
|
| 18 |
}
|
| 19 |
document.addEventListener("DOMContentLoaded", function () {
|
| 20 |
var btn = document.getElementById("themeToggle");
|
| 21 |
-
if (
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
});
|
| 28 |
})();
|
| 29 |
</script>
|
|
@@ -140,7 +157,7 @@
|
|
| 140 |
<svg class="accordion-chevron" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"></path></svg>
|
| 141 |
</summary>
|
| 142 |
<div class="accordion-body">
|
| 143 |
-
<div class="setting setting-row">
|
| 144 |
<div>
|
| 145 |
<label class="setting-label" for="handwritingToggle">Handwriting (local) <span class="beta-tag">beta</span></label>
|
| 146 |
<p class="hint">Read handwritten pages with a local model (English, ~73% on real cursive). Much slower; first use downloads a model once. For far better handwriting, use Online Gemini below instead — you don't need both.</p>
|
|
@@ -150,6 +167,7 @@
|
|
| 150 |
<span class="slider" aria-hidden="true"></span>
|
| 151 |
</label>
|
| 152 |
</div>
|
|
|
|
| 153 |
|
| 154 |
<div class="setting setting-row">
|
| 155 |
<div>
|
|
|
|
| 18 |
}
|
| 19 |
document.addEventListener("DOMContentLoaded", function () {
|
| 20 |
var btn = document.getElementById("themeToggle");
|
| 21 |
+
if (btn) {
|
| 22 |
+
btn.addEventListener("click", function () {
|
| 23 |
+
var next = root.getAttribute("data-theme") === "light" ? "dark" : "light";
|
| 24 |
+
root.setAttribute("data-theme", next);
|
| 25 |
+
try { localStorage.setItem("theme", next); } catch (e) {}
|
| 26 |
+
});
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
/* Disable local-handwriting gracefully where its packages aren't
|
| 30 |
+
installed (e.g. the lean hosted build) and point users to Gemini,
|
| 31 |
+
instead of letting a page fail mid-extraction. */
|
| 32 |
+
fetch("/api/capabilities")
|
| 33 |
+
.then(function (r) { return r.json(); })
|
| 34 |
+
.then(function (caps) {
|
| 35 |
+
if (!caps || caps.handwriting !== false) return;
|
| 36 |
+
var hw = document.getElementById("handwritingToggle");
|
| 37 |
+
if (hw) { hw.checked = false; hw.disabled = true; }
|
| 38 |
+
var row = document.getElementById("handwritingRow");
|
| 39 |
+
if (row) row.classList.add("is-unavailable");
|
| 40 |
+
var note = document.getElementById("hwUnavailable");
|
| 41 |
+
if (note) note.hidden = false;
|
| 42 |
+
})
|
| 43 |
+
.catch(function () {});
|
| 44 |
});
|
| 45 |
})();
|
| 46 |
</script>
|
|
|
|
| 157 |
<svg class="accordion-chevron" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"></path></svg>
|
| 158 |
</summary>
|
| 159 |
<div class="accordion-body">
|
| 160 |
+
<div class="setting setting-row" id="handwritingRow">
|
| 161 |
<div>
|
| 162 |
<label class="setting-label" for="handwritingToggle">Handwriting (local) <span class="beta-tag">beta</span></label>
|
| 163 |
<p class="hint">Read handwritten pages with a local model (English, ~73% on real cursive). Much slower; first use downloads a model once. For far better handwriting, use Online Gemini below instead — you don't need both.</p>
|
|
|
|
| 167 |
<span class="slider" aria-hidden="true"></span>
|
| 168 |
</label>
|
| 169 |
</div>
|
| 170 |
+
<p class="hint hw-note" id="hwUnavailable" hidden>Local handwriting isn't installed in this build — use <strong>Online vision OCR (Gemini)</strong> below for handwriting.</p>
|
| 171 |
|
| 172 |
<div class="setting setting-row">
|
| 173 |
<div>
|
static/styles.css
CHANGED
|
@@ -294,6 +294,12 @@ html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
|
|
| 294 |
}
|
| 295 |
.switch input:checked + .slider::before { transform: translateX(20px); }
|
| 296 |
.switch input:focus-visible + .slider { box-shadow: 0 0 0 3px var(--accent-ring); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
|
| 298 |
/* ---------------- Work area ---------------- */
|
| 299 |
.work { min-width: 0; display: flex; flex-direction: column; gap: 18px; }
|
|
|
|
| 294 |
}
|
| 295 |
.switch input:checked + .slider::before { transform: translateX(20px); }
|
| 296 |
.switch input:focus-visible + .slider { box-shadow: 0 0 0 3px var(--accent-ring); }
|
| 297 |
+
.switch input:disabled + .slider { opacity: 0.45; cursor: not-allowed; }
|
| 298 |
+
|
| 299 |
+
/* a feature that isn't available in this build */
|
| 300 |
+
.setting-row.is-unavailable .setting-label { color: var(--text-dim); }
|
| 301 |
+
.hw-note { color: var(--accent); }
|
| 302 |
+
.hw-note strong { color: var(--accent); font-weight: 700; }
|
| 303 |
|
| 304 |
/* ---------------- Work area ---------------- */
|
| 305 |
.work { min-width: 0; display: flex; flex-direction: column; gap: 18px; }
|