Deploy OvisOCR2 OCR demo
Browse files- .gitattributes +3 -0
- README.md +100 -4
- app.py +574 -0
- dist/assets/__vite-browser-external-DYxpcVy9-BIHI7g3E.js +1 -0
- dist/assets/index-5Ju-rOmV.js +0 -0
- dist/assets/index-CCa7-Cez.css +1 -0
- dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- dist/assets/manrope-cyrillic-wght-normal-Dvxsihut.woff2 +0 -0
- dist/assets/manrope-greek-wght-normal-DL7QRZyv.woff2 +0 -0
- dist/assets/manrope-latin-ext-wght-normal-Ch3YOpNY.woff2 +0 -0
- dist/assets/manrope-latin-wght-normal-DHIcAJRg.woff2 +0 -0
- dist/assets/manrope-vietnamese-wght-normal-usUDDRr7.woff2 +0 -0
- dist/brand/ovis-logo.png +0 -0
- dist/examples/handwriting-040745.jpg +3 -0
- dist/examples/handwriting-042609.jpg +3 -0
- dist/examples/page-9477c155-19b3-4f2d-ac18-ee3f8997d2f8.png +3 -0
- dist/examples/table-1305dae7.webp +0 -0
- dist/examples/table-3ba1139c.webp +0 -0
- dist/favicon.ico +0 -0
- dist/index.html +31 -0
- dist/vendor/mathjax/output/chtml/fonts/tex.js +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_AMS-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Bold.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Fraktur-Bold.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Fraktur-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Bold.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Italic.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-BoldItalic.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-Italic.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Bold.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Italic.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Script-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Size1-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Size2-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Size3-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Size4-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Vector-Bold.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Vector-Regular.woff +0 -0
- dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Zero.woff +0 -0
- dist/vendor/mathjax/tex-chtml-full.js +0 -0
- requirements.txt +5 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
dist/examples/handwriting-040745.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
dist/examples/handwriting-042609.jpg filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
dist/examples/page-9477c155-19b3-4f2d-ac18-ee3f8997d2f8.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,13 +1,109 @@
|
|
| 1 |
---
|
| 2 |
title: OvisOCR2
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: gray
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.20.0
|
| 8 |
-
python_version:
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: OvisOCR2
|
| 3 |
+
emoji: 🔎
|
| 4 |
+
colorFrom: blue
|
| 5 |
colorTo: gray
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.20.0
|
| 8 |
+
python_version: "3.12.12"
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
+
license: apache-2.0
|
| 12 |
+
hardware: zero-gpu
|
| 13 |
+
startup_duration_timeout: 30m
|
| 14 |
+
models:
|
| 15 |
+
- ATH-MaaS/OvisOCR2
|
| 16 |
+
preload_from_hub:
|
| 17 |
+
- ATH-MaaS/OvisOCR2
|
| 18 |
+
short_description: Stream structured Markdown from document images and PDFs.
|
| 19 |
---
|
| 20 |
|
| 21 |
+
# OvisOCR2 — document OCR demo
|
| 22 |
+
|
| 23 |
+
A focused image and PDF document parsing demo for
|
| 24 |
+
[ATH-MaaS/OvisOCR2](https://huggingface.co/ATH-MaaS/OvisOCR2). It preserves
|
| 25 |
+
reading order and emits Markdown with LaTeX formulas, HTML tables, and rendered
|
| 26 |
+
visual-region crops. Model tokens stream into the interface as they are generated.
|
| 27 |
+
PDFs are rasterized locally and processed one page at a time in Base mode. Each
|
| 28 |
+
page uses an independent streamed GPU request and the browser merges the results,
|
| 29 |
+
so long documents do not have to fit inside one ZeroGPU lease. This demo
|
| 30 |
+
intentionally does not expose a separate Long mode.
|
| 31 |
+
|
| 32 |
+
The five packaged examples cover two financial tables, two Chinese handwriting
|
| 33 |
+
pages, and one formula-rich OmniDocBench research-paper page. They are selectable
|
| 34 |
+
directly in the interface and are also covered by the local smoke-test workflow.
|
| 35 |
+
The workspace opens empty so examples remain an explicit user choice rather than
|
| 36 |
+
an automatic input.
|
| 37 |
+
|
| 38 |
+
## Local run
|
| 39 |
+
|
| 40 |
+
The verified local model directory is `/root/models/ATH-MaaS/OvisOCR2`.
|
| 41 |
+
|
| 42 |
+
```bash
|
| 43 |
+
conda env create -f environment.yml
|
| 44 |
+
conda activate ovisocr2-demo
|
| 45 |
+
npm ci
|
| 46 |
+
npm run build
|
| 47 |
+
CUDA_VISIBLE_DEVICES=0 \
|
| 48 |
+
OVISOCR_MODEL_PATH=/root/models/ATH-MaaS/OvisOCR2 \
|
| 49 |
+
python app.py
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
Open `http://127.0.0.1:7860`.
|
| 53 |
+
|
| 54 |
+
### DSW proxy
|
| 55 |
+
|
| 56 |
+
The server already listens on `0.0.0.0`. Set a DSW id and the public proxy URL
|
| 57 |
+
is generated from the same `PORT` automatically:
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
CUDA_VISIBLE_DEVICES=0 \
|
| 61 |
+
PORT=7869 \
|
| 62 |
+
OVISOCR_DSW_ID=1976932 \
|
| 63 |
+
OVISOCR_MODEL_PATH=/root/models/ATH-MaaS/OvisOCR2 \
|
| 64 |
+
python app.py
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
The corresponding URL is
|
| 68 |
+
`https://1976932-proxy-7869.dsw-gateway-cn-hangzhou.data.aliyun.com/`.
|
| 69 |
+
For a different gateway or an explicit public URL, set `OVISOCR_ROOT_PATH`
|
| 70 |
+
instead; it takes precedence over `OVISOCR_DSW_ID`.
|
| 71 |
+
|
| 72 |
+
DSW's per-port proxy uses its own hostname and serves at `/`, so the ASGI root
|
| 73 |
+
path stays empty. If an explicit URL contains a real subpath, such as
|
| 74 |
+
`https://example.test/ocr/`, the app passes only `/ocr` to Gradio's `root_path`;
|
| 75 |
+
passing the whole URL into this custom FastAPI server would break static mounts.
|
| 76 |
+
|
| 77 |
+
For a UI-only smoke that does not load the model:
|
| 78 |
+
|
| 79 |
+
```bash
|
| 80 |
+
OVISOCR_TEST_MODE=1 python app.py
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
## Hugging Face Space
|
| 84 |
+
|
| 85 |
+
The checked-in `dist/` directory is the production frontend, so the folder can
|
| 86 |
+
be uploaded directly to a Gradio Space without a Node build step. Select
|
| 87 |
+
ZeroGPU hardware in the Space settings. The README metadata pins Python 3.12.12
|
| 88 |
+
and Gradio 6.20.0
|
| 89 |
+
and declares `hardware: zero-gpu`; `app.py` decorates only the inference endpoint
|
| 90 |
+
with `@spaces.GPU` and loads the model on CUDA at module scope, following the
|
| 91 |
+
ZeroGPU loading contract.
|
| 92 |
+
|
| 93 |
+
On Hugging Face, the model source automatically falls back from the local path
|
| 94 |
+
to `ATH-MaaS/OvisOCR2`. PDFs are limited to 50 pages by default. Every page gets
|
| 95 |
+
its own ZeroGPU reservation while the frontend preserves already completed pages.
|
| 96 |
+
|
| 97 |
+
## Runtime knobs
|
| 98 |
+
|
| 99 |
+
- `OVISOCR_MODEL_PATH`: local directory or Hub model id.
|
| 100 |
+
- `OVISOCR_MAX_NEW_TOKENS`: defaults to `16384`, matching the model README inference configuration.
|
| 101 |
+
- `OVISOCR_MAX_PDF_PAGES`: defaults to `50`.
|
| 102 |
+
- `OVISOCR_PDF_RENDER_SCALE`: PDF rasterization scale, defaults to `2.0`.
|
| 103 |
+
- `OVISOCR_STREAM_MIN_CHARS`: minimum new characters between stream updates, defaults to `24`.
|
| 104 |
+
- `OVISOCR_STREAM_MAX_INTERVAL`: maximum seconds between available stream updates, defaults to `0.12`.
|
| 105 |
+
- `OVISOCR_GPU_DURATION`: per-page ZeroGPU reservation in seconds, defaults to `300`.
|
| 106 |
+
- `OVISOCR_ATTN_IMPLEMENTATION`: defaults to `sdpa`.
|
| 107 |
+
- `OVISOCR_DSW_ID`: generates the Hangzhou DSW proxy root using the active `PORT`.
|
| 108 |
+
- `OVISOCR_ROOT_PATH`: explicit proxy URL or ASGI path prefix; overrides `OVISOCR_DSW_ID`.
|
| 109 |
+
- `OVISOCR_TEST_MODE=1`: deterministic mock inference for UI/integration tests.
|
app.py
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import base64
|
| 4 |
+
import html
|
| 5 |
+
import io
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
+
import threading
|
| 9 |
+
import time
|
| 10 |
+
from collections.abc import Iterator
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Any, Callable
|
| 13 |
+
from urllib.parse import urlsplit
|
| 14 |
+
|
| 15 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 16 |
+
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
import spaces # Must be imported before torch on Hugging Face ZeroGPU.
|
| 20 |
+
except ImportError: # Local development uses a no-op decorator.
|
| 21 |
+
class _LocalSpaces:
|
| 22 |
+
@staticmethod
|
| 23 |
+
def GPU(*decorator_args: Any, **decorator_kwargs: Any) -> Callable:
|
| 24 |
+
def decorate(function: Callable) -> Callable:
|
| 25 |
+
return function
|
| 26 |
+
|
| 27 |
+
if decorator_args and callable(decorator_args[0]) and len(decorator_args) == 1:
|
| 28 |
+
return decorator_args[0]
|
| 29 |
+
return decorate
|
| 30 |
+
|
| 31 |
+
spaces = _LocalSpaces()
|
| 32 |
+
|
| 33 |
+
import gradio as gr
|
| 34 |
+
import fitz
|
| 35 |
+
import torch
|
| 36 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 37 |
+
from fastapi.responses import FileResponse, JSONResponse
|
| 38 |
+
from gradio.data_classes import FileData
|
| 39 |
+
from PIL import Image, ImageOps
|
| 40 |
+
from starlette.staticfiles import StaticFiles
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
BASE_DIR = Path(__file__).resolve().parent
|
| 44 |
+
DIST_DIR = BASE_DIR / "dist"
|
| 45 |
+
MODEL_ID = "ATH-MaaS/OvisOCR2"
|
| 46 |
+
LOCAL_MODEL_DEFAULT = Path("/root/models/ATH-MaaS/OvisOCR2")
|
| 47 |
+
TEST_MODE = os.getenv("OVISOCR_TEST_MODE", "0").lower() in {"1", "true", "yes"}
|
| 48 |
+
MODEL_SOURCE = os.getenv(
|
| 49 |
+
"OVISOCR_MODEL_PATH",
|
| 50 |
+
str(LOCAL_MODEL_DEFAULT if LOCAL_MODEL_DEFAULT.is_dir() else MODEL_ID),
|
| 51 |
+
)
|
| 52 |
+
MAX_NEW_TOKENS = int(os.getenv("OVISOCR_MAX_NEW_TOKENS", "16384"))
|
| 53 |
+
MAX_PDF_PAGES = int(os.getenv("OVISOCR_MAX_PDF_PAGES", "50"))
|
| 54 |
+
PDF_RENDER_SCALE = float(os.getenv("OVISOCR_PDF_RENDER_SCALE", "2.0"))
|
| 55 |
+
STREAM_MIN_CHARS = int(os.getenv("OVISOCR_STREAM_MIN_CHARS", "24"))
|
| 56 |
+
STREAM_MAX_INTERVAL = float(os.getenv("OVISOCR_STREAM_MAX_INTERVAL", "0.12"))
|
| 57 |
+
MIN_PIXELS = 448 * 448
|
| 58 |
+
MAX_PIXELS = 2880 * 2880
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def server_config() -> tuple[int, str | None, str | None]:
|
| 62 |
+
"""Resolve the port, ASGI path prefix, and optional public proxy URL."""
|
| 63 |
+
port = int(os.getenv("PORT", os.getenv("GRADIO_SERVER_PORT", "7860")))
|
| 64 |
+
configured_root = (
|
| 65 |
+
os.getenv("OVISOCR_ROOT_PATH", "").strip()
|
| 66 |
+
or os.getenv("GRADIO_ROOT_PATH", "").strip()
|
| 67 |
+
)
|
| 68 |
+
dsw_id = os.getenv("OVISOCR_DSW_ID", "").strip()
|
| 69 |
+
public_url = None
|
| 70 |
+
root_path = None
|
| 71 |
+
if configured_root.startswith(("http://", "https://")):
|
| 72 |
+
public_url = configured_root
|
| 73 |
+
path = urlsplit(configured_root).path.rstrip("/")
|
| 74 |
+
root_path = path or None
|
| 75 |
+
elif configured_root:
|
| 76 |
+
root_path = configured_root.rstrip("/") or None
|
| 77 |
+
elif dsw_id:
|
| 78 |
+
public_url = (
|
| 79 |
+
f"https://{dsw_id}-proxy-{port}."
|
| 80 |
+
"dsw-gateway-cn-hangzhou.data.aliyun.com/"
|
| 81 |
+
)
|
| 82 |
+
return port, root_path, public_url
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
SERVER_PORT, ROOT_PATH, PUBLIC_URL = server_config()
|
| 86 |
+
|
| 87 |
+
OCR_PROMPT = (
|
| 88 |
+
"\nExtract all readable content from the image in natural human reading order "
|
| 89 |
+
"and output the result as a single Markdown document. For charts or images, "
|
| 90 |
+
'represent them using an HTML image tag: <img src="images/bbox_{left}_{top}_{right}_{bottom}.jpg" />, '
|
| 91 |
+
"where left, top, right, bottom are bounding box coordinates scaled to [0, 1000). "
|
| 92 |
+
"Format formulas as LaTeX. Format tables as HTML: <table>...</table>. "
|
| 93 |
+
"Transcribe all other text as standard Markdown. Preserve the original text "
|
| 94 |
+
"without translation or paraphrasing."
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
BBOX_IMAGE_PATTERN = re.compile(
|
| 98 |
+
r'<img\s+src=["\']images/bbox_(\d+)_(\d+)_(\d+)_(\d+)\.jpg["\']\s*/?>',
|
| 99 |
+
flags=re.IGNORECASE,
|
| 100 |
+
)
|
| 101 |
+
UNMATERIALIZED_BBOX_IMAGE_PATTERN = re.compile(
|
| 102 |
+
r'<img\b[^>]*\bsrc=["\']images/bbox_[^"\']+["\'][^>]*>',
|
| 103 |
+
flags=re.IGNORECASE,
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
MOCK_MARKDOWN = r"""# 盈利预测、估值与评级
|
| 107 |
+
|
| 108 |
+
我们预测公司 2024—2026 年营业收入与归母净利润将保持稳健增长,当前股价对应估值如下。
|
| 109 |
+
|
| 110 |
+
<table>
|
| 111 |
+
<thead><tr><th>项目</th><th>2023A</th><th>2024E</th><th>2025E</th><th>2026E</th></tr></thead>
|
| 112 |
+
<tbody>
|
| 113 |
+
<tr><td>营业收入(百万元)</td><td>9,423</td><td>10,516</td><td>11,873</td><td>13,441</td></tr>
|
| 114 |
+
<tr><td>归母净利润(百万元)</td><td>1,267</td><td>1,452</td><td>1,681</td><td>1,946</td></tr>
|
| 115 |
+
<tr><td>每股收益(元)</td><td>1.02</td><td>1.17</td><td>1.36</td><td>1.57</td></tr>
|
| 116 |
+
<tr><td>市盈率</td><td>18.4</td><td>16.1</td><td>13.8</td><td>12.0</td></tr>
|
| 117 |
+
</tbody>
|
| 118 |
+
</table>
|
| 119 |
+
|
| 120 |
+
## 财务摘要
|
| 121 |
+
|
| 122 |
+
净资产收益率采用 $ROE = \frac{NP}{E}$ 计算;预计 2025 年利润同比增速为:
|
| 123 |
+
|
| 124 |
+
\[
|
| 125 |
+
g = \frac{1{,}681 - 1{,}452}{1{,}452} \times 100\% = 15.8\%.
|
| 126 |
+
\]
|
| 127 |
+
|
| 128 |
+
<img src="images/bbox_120_130_880_420.jpg" />
|
| 129 |
+
|
| 130 |
+
资料来源:公司公告,研究团队整理。"""
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
processor = None
|
| 134 |
+
model = None
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def _load_model() -> None:
|
| 138 |
+
global processor, model
|
| 139 |
+
if TEST_MODE:
|
| 140 |
+
return
|
| 141 |
+
|
| 142 |
+
from transformers import AutoProcessor, Qwen3_5ForConditionalGeneration
|
| 143 |
+
|
| 144 |
+
local_only = Path(MODEL_SOURCE).is_dir()
|
| 145 |
+
processor = AutoProcessor.from_pretrained(
|
| 146 |
+
MODEL_SOURCE,
|
| 147 |
+
min_pixels=MIN_PIXELS,
|
| 148 |
+
max_pixels=MAX_PIXELS,
|
| 149 |
+
local_files_only=local_only,
|
| 150 |
+
)
|
| 151 |
+
model = Qwen3_5ForConditionalGeneration.from_pretrained(
|
| 152 |
+
MODEL_SOURCE,
|
| 153 |
+
dtype=torch.bfloat16,
|
| 154 |
+
attn_implementation=os.getenv("OVISOCR_ATTN_IMPLEMENTATION", "sdpa"),
|
| 155 |
+
local_files_only=local_only,
|
| 156 |
+
).to("cuda")
|
| 157 |
+
model.eval()
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def clean_truncated_repeats(
|
| 161 |
+
text: str,
|
| 162 |
+
min_text_len: int = 8000,
|
| 163 |
+
max_period: int = 200,
|
| 164 |
+
min_period: int = 1,
|
| 165 |
+
min_repeat_chars: int = 100,
|
| 166 |
+
min_repeat_times: int = 5,
|
| 167 |
+
) -> str:
|
| 168 |
+
"""Remove a repeated suffix created when generation reaches its token ceiling."""
|
| 169 |
+
n = len(text)
|
| 170 |
+
if n < min_text_len:
|
| 171 |
+
return text
|
| 172 |
+
|
| 173 |
+
max_period = min(max_period, n - 1)
|
| 174 |
+
for unit_len in range(min_period, max_period + 1):
|
| 175 |
+
if text[n - 1] != text[n - 1 - unit_len]:
|
| 176 |
+
continue
|
| 177 |
+
match_len = 1
|
| 178 |
+
idx = n - 2
|
| 179 |
+
while idx >= unit_len and text[idx] == text[idx - unit_len]:
|
| 180 |
+
match_len += 1
|
| 181 |
+
idx -= 1
|
| 182 |
+
total_len = match_len + unit_len
|
| 183 |
+
repeat_times = total_len // unit_len
|
| 184 |
+
tail_len = total_len % unit_len
|
| 185 |
+
if repeat_times >= min_repeat_times and total_len >= min_repeat_chars:
|
| 186 |
+
return text[: n - total_len + unit_len] + text[n - tail_len :]
|
| 187 |
+
return text
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def materialize_bbox_images(markdown: str, page_image: Image.Image) -> str:
|
| 191 |
+
"""Replace bbox image placeholders in rendered output with safe data-URI crops.
|
| 192 |
+
|
| 193 |
+
Raw model Markdown is returned separately and remains unchanged.
|
| 194 |
+
"""
|
| 195 |
+
width, height = page_image.size
|
| 196 |
+
|
| 197 |
+
def replace(match: re.Match[str]) -> str:
|
| 198 |
+
left, top, right, bottom = (int(value) for value in match.groups())
|
| 199 |
+
x1 = max(0, min(width, round(left * width / 1000)))
|
| 200 |
+
y1 = max(0, min(height, round(top * height / 1000)))
|
| 201 |
+
x2 = max(0, min(width, round(right * width / 1000)))
|
| 202 |
+
y2 = max(0, min(height, round(bottom * height / 1000)))
|
| 203 |
+
if x2 <= x1 or y2 <= y1:
|
| 204 |
+
return match.group(0)
|
| 205 |
+
|
| 206 |
+
crop = page_image.crop((x1, y1, x2, y2)).convert("RGB")
|
| 207 |
+
crop.thumbnail((1600, 1600), Image.Resampling.LANCZOS)
|
| 208 |
+
buffer = io.BytesIO()
|
| 209 |
+
crop.save(buffer, format="JPEG", quality=88, optimize=True)
|
| 210 |
+
payload = base64.b64encode(buffer.getvalue()).decode("ascii")
|
| 211 |
+
return f'<img src="data:image/jpeg;base64,{payload}" alt="Visual region" />'
|
| 212 |
+
|
| 213 |
+
return neutralize_unmaterialized_bbox_images(BBOX_IMAGE_PATTERN.sub(replace, markdown))
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def neutralize_unmaterialized_bbox_images(markdown: str) -> str:
|
| 217 |
+
"""Render placeholder examples as code instead of issuing broken requests."""
|
| 218 |
+
|
| 219 |
+
def replace(match: re.Match[str]) -> str:
|
| 220 |
+
escaped = html.escape(match.group(0), quote=False)
|
| 221 |
+
return f'<code class="unresolved-image-reference">{escaped}</code>'
|
| 222 |
+
|
| 223 |
+
return UNMATERIALIZED_BBOX_IMAGE_PATTERN.sub(replace, markdown)
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def stream_safe_markdown(markdown: str) -> str:
|
| 227 |
+
"""Avoid broken image requests until a page's bbox crops are materialized."""
|
| 228 |
+
return neutralize_unmaterialized_bbox_images(
|
| 229 |
+
BBOX_IMAGE_PATTERN.sub(
|
| 230 |
+
'<div class="visual-placeholder">Preparing visual region…</div>',
|
| 231 |
+
markdown,
|
| 232 |
+
)
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def _file_path(file_data: FileData | dict[str, Any]) -> str:
|
| 237 |
+
if isinstance(file_data, dict):
|
| 238 |
+
path = file_data.get("path")
|
| 239 |
+
else:
|
| 240 |
+
path = getattr(file_data, "path", None)
|
| 241 |
+
if not path:
|
| 242 |
+
raise ValueError("No uploaded document was provided.")
|
| 243 |
+
return str(path)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def generation_token_ids(active_processor: Any) -> dict[str, int]:
|
| 247 |
+
"""Use tokenizer stop IDs; this checkpoint's config and tokenizer differ."""
|
| 248 |
+
tokenizer = active_processor.tokenizer
|
| 249 |
+
return {
|
| 250 |
+
"eos_token_id": int(tokenizer.eos_token_id),
|
| 251 |
+
"pad_token_id": int(tokenizer.pad_token_id),
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def document_info(path: str) -> tuple[str, int]:
|
| 256 |
+
suffix = Path(path).suffix.lower()
|
| 257 |
+
try:
|
| 258 |
+
with Path(path).open("rb") as file:
|
| 259 |
+
header = file.read(5)
|
| 260 |
+
except OSError as error:
|
| 261 |
+
raise ValueError("The uploaded document could not be read.") from error
|
| 262 |
+
|
| 263 |
+
# Browser uploads can arrive at Gradio as an extensionless temporary `blob`.
|
| 264 |
+
if suffix == ".pdf" or header == b"%PDF-":
|
| 265 |
+
with fitz.open(path) as document:
|
| 266 |
+
total_pages = document.page_count
|
| 267 |
+
if total_pages < 1:
|
| 268 |
+
raise ValueError("The uploaded PDF has no pages.")
|
| 269 |
+
if total_pages > MAX_PDF_PAGES:
|
| 270 |
+
raise ValueError(
|
| 271 |
+
f"This demo accepts up to {MAX_PDF_PAGES} PDF pages; received {total_pages}."
|
| 272 |
+
)
|
| 273 |
+
return "pdf", total_pages
|
| 274 |
+
|
| 275 |
+
try:
|
| 276 |
+
with Image.open(path) as source:
|
| 277 |
+
source.verify()
|
| 278 |
+
except Exception as error:
|
| 279 |
+
raise ValueError("Please upload a valid PNG, JPEG, WebP, or PDF file.") from error
|
| 280 |
+
return "image", 1
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def load_document_page(path: str, document_type: str, page_index: int) -> Image.Image:
|
| 284 |
+
if document_type == "pdf":
|
| 285 |
+
with fitz.open(path) as document:
|
| 286 |
+
page = document.load_page(page_index)
|
| 287 |
+
pixmap = page.get_pixmap(
|
| 288 |
+
matrix=fitz.Matrix(PDF_RENDER_SCALE, PDF_RENDER_SCALE),
|
| 289 |
+
colorspace=fitz.csRGB,
|
| 290 |
+
alpha=False,
|
| 291 |
+
)
|
| 292 |
+
return Image.frombytes("RGB", (pixmap.width, pixmap.height), pixmap.samples)
|
| 293 |
+
|
| 294 |
+
with Image.open(path) as source:
|
| 295 |
+
return ImageOps.exif_transpose(source).convert("RGB")
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def page_preview_data_uri(page_image: Image.Image) -> str:
|
| 299 |
+
preview = page_image.copy().convert("RGB")
|
| 300 |
+
preview.thumbnail((1400, 1800), Image.Resampling.LANCZOS)
|
| 301 |
+
buffer = io.BytesIO()
|
| 302 |
+
preview.save(buffer, format="JPEG", quality=82, optimize=True)
|
| 303 |
+
payload = base64.b64encode(buffer.getvalue()).decode("ascii")
|
| 304 |
+
return f"data:image/jpeg;base64,{payload}"
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def _model_inputs(page_image: Image.Image) -> Any:
|
| 308 |
+
if processor is None or model is None:
|
| 309 |
+
raise RuntimeError("OvisOCR2 is not loaded.")
|
| 310 |
+
|
| 311 |
+
messages = [
|
| 312 |
+
{
|
| 313 |
+
"role": "user",
|
| 314 |
+
"content": [
|
| 315 |
+
{"type": "image", "image": page_image},
|
| 316 |
+
{"type": "text", "text": OCR_PROMPT},
|
| 317 |
+
],
|
| 318 |
+
}
|
| 319 |
+
]
|
| 320 |
+
return processor.apply_chat_template(
|
| 321 |
+
messages,
|
| 322 |
+
tokenize=True,
|
| 323 |
+
add_generation_prompt=True,
|
| 324 |
+
return_dict=True,
|
| 325 |
+
return_tensors="pt",
|
| 326 |
+
enable_thinking=False,
|
| 327 |
+
).to(model.device)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def infer_stream(page_image: Image.Image) -> Iterator[str]:
|
| 331 |
+
if TEST_MODE:
|
| 332 |
+
for end in range(64, len(MOCK_MARKDOWN) + 64, 64):
|
| 333 |
+
yield MOCK_MARKDOWN[:end]
|
| 334 |
+
return
|
| 335 |
+
if processor is None or model is None:
|
| 336 |
+
raise RuntimeError("OvisOCR2 is not loaded.")
|
| 337 |
+
|
| 338 |
+
from transformers import TextIteratorStreamer
|
| 339 |
+
|
| 340 |
+
inputs = _model_inputs(page_image)
|
| 341 |
+
streamer = TextIteratorStreamer(
|
| 342 |
+
processor.tokenizer,
|
| 343 |
+
skip_prompt=True,
|
| 344 |
+
skip_special_tokens=True,
|
| 345 |
+
clean_up_tokenization_spaces=False,
|
| 346 |
+
timeout=30.0,
|
| 347 |
+
)
|
| 348 |
+
errors: list[BaseException] = []
|
| 349 |
+
|
| 350 |
+
def generate() -> None:
|
| 351 |
+
try:
|
| 352 |
+
with torch.inference_mode():
|
| 353 |
+
model.generate(
|
| 354 |
+
**inputs,
|
| 355 |
+
streamer=streamer,
|
| 356 |
+
max_new_tokens=MAX_NEW_TOKENS,
|
| 357 |
+
do_sample=False,
|
| 358 |
+
temperature=None,
|
| 359 |
+
top_p=None,
|
| 360 |
+
top_k=None,
|
| 361 |
+
**generation_token_ids(processor),
|
| 362 |
+
)
|
| 363 |
+
except BaseException as error:
|
| 364 |
+
errors.append(error)
|
| 365 |
+
streamer.on_finalized_text("", stream_end=True)
|
| 366 |
+
|
| 367 |
+
worker = threading.Thread(target=generate, name="ovisocr2-generate", daemon=True)
|
| 368 |
+
worker.start()
|
| 369 |
+
text = ""
|
| 370 |
+
last_yielded = ""
|
| 371 |
+
last_yield_time = time.monotonic()
|
| 372 |
+
for fragment in streamer:
|
| 373 |
+
text += fragment
|
| 374 |
+
now = time.monotonic()
|
| 375 |
+
if (
|
| 376 |
+
len(text) - len(last_yielded) >= STREAM_MIN_CHARS
|
| 377 |
+
or now - last_yield_time >= STREAM_MAX_INTERVAL
|
| 378 |
+
):
|
| 379 |
+
yield text
|
| 380 |
+
last_yielded = text
|
| 381 |
+
last_yield_time = now
|
| 382 |
+
|
| 383 |
+
worker.join()
|
| 384 |
+
if errors:
|
| 385 |
+
raise RuntimeError("Model generation failed.") from errors[0]
|
| 386 |
+
final_text = clean_truncated_repeats(text.strip())
|
| 387 |
+
if final_text and final_text != last_yielded:
|
| 388 |
+
yield final_text
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
def combine_pages(pages: list[dict[str, Any]], field: str) -> str:
|
| 392 |
+
if len(pages) <= 1:
|
| 393 |
+
return pages[0].get(field, "") if pages else ""
|
| 394 |
+
return "\n\n---\n\n".join(
|
| 395 |
+
f"<!-- Page {page['page_number']} -->\n\n{page.get(field, '')}" for page in pages
|
| 396 |
+
)
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def stream_payload(
|
| 400 |
+
*,
|
| 401 |
+
event: str,
|
| 402 |
+
pages: list[dict[str, Any]],
|
| 403 |
+
current_page: int,
|
| 404 |
+
total_pages: int,
|
| 405 |
+
document_type: str,
|
| 406 |
+
started: float,
|
| 407 |
+
page_preview: str | None = None,
|
| 408 |
+
) -> dict[str, Any]:
|
| 409 |
+
return {
|
| 410 |
+
"event": event,
|
| 411 |
+
"markdown": combine_pages(pages, "markdown"),
|
| 412 |
+
"render_markdown": combine_pages(pages, "render_markdown"),
|
| 413 |
+
"pages": pages,
|
| 414 |
+
"current_page": current_page,
|
| 415 |
+
"total_pages": total_pages,
|
| 416 |
+
"document_type": document_type,
|
| 417 |
+
"page_preview": page_preview,
|
| 418 |
+
"char_count": sum(len(page.get("markdown", "")) for page in pages),
|
| 419 |
+
"elapsed_seconds": round(time.perf_counter() - started, 3),
|
| 420 |
+
"model": MODEL_ID,
|
| 421 |
+
"backend": "mock" if TEST_MODE else "transformers",
|
| 422 |
+
"mode": "base",
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
def _gpu_duration(
|
| 427 |
+
image_path: FileData | dict[str, Any], page_index: int = 0
|
| 428 |
+
) -> int:
|
| 429 |
+
return int(os.getenv("OVISOCR_GPU_DURATION", "300"))
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
_load_model()
|
| 433 |
+
app = gr.Server()
|
| 434 |
+
app.add_middleware(
|
| 435 |
+
CORSMiddleware,
|
| 436 |
+
allow_origins=["http://127.0.0.1:4173", "http://localhost:4173"],
|
| 437 |
+
allow_credentials=True,
|
| 438 |
+
allow_methods=["*"],
|
| 439 |
+
allow_headers=["*"],
|
| 440 |
+
)
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
@app.api(name="run_ocr", concurrency_limit=1, time_limit=300)
|
| 444 |
+
@spaces.GPU(duration=_gpu_duration)
|
| 445 |
+
def run_ocr(image_path: FileData, page_index: int = 0) -> Iterator[dict[str, Any]]:
|
| 446 |
+
"""Stream one document page per GPU request.
|
| 447 |
+
|
| 448 |
+
The browser calls this endpoint sequentially for PDFs. Keeping each page in
|
| 449 |
+
its own request prevents long documents from exhausting one ZeroGPU lease.
|
| 450 |
+
"""
|
| 451 |
+
started = time.perf_counter()
|
| 452 |
+
path = _file_path(image_path)
|
| 453 |
+
document_type, total_pages = document_info(path)
|
| 454 |
+
page_index = int(page_index)
|
| 455 |
+
if page_index < 0 or page_index >= total_pages:
|
| 456 |
+
raise ValueError(
|
| 457 |
+
f"Requested PDF page {page_index + 1}, but this document has {total_pages} pages."
|
| 458 |
+
)
|
| 459 |
+
|
| 460 |
+
page_number = page_index + 1
|
| 461 |
+
page_image = load_document_page(path, document_type, page_index)
|
| 462 |
+
page_started = time.perf_counter()
|
| 463 |
+
current = {
|
| 464 |
+
"page_number": page_number,
|
| 465 |
+
"markdown": "",
|
| 466 |
+
"render_markdown": "",
|
| 467 |
+
"status": "streaming",
|
| 468 |
+
"elapsed_seconds": 0.0,
|
| 469 |
+
}
|
| 470 |
+
yield stream_payload(
|
| 471 |
+
event="page_start",
|
| 472 |
+
pages=[current],
|
| 473 |
+
current_page=page_number,
|
| 474 |
+
total_pages=total_pages,
|
| 475 |
+
document_type=document_type,
|
| 476 |
+
started=started,
|
| 477 |
+
page_preview=page_preview_data_uri(page_image),
|
| 478 |
+
)
|
| 479 |
+
|
| 480 |
+
markdown = ""
|
| 481 |
+
for partial in infer_stream(page_image):
|
| 482 |
+
markdown = partial
|
| 483 |
+
current = {
|
| 484 |
+
"page_number": page_number,
|
| 485 |
+
"markdown": markdown,
|
| 486 |
+
"render_markdown": stream_safe_markdown(markdown),
|
| 487 |
+
"status": "streaming",
|
| 488 |
+
"elapsed_seconds": round(time.perf_counter() - page_started, 3),
|
| 489 |
+
}
|
| 490 |
+
yield stream_payload(
|
| 491 |
+
event="stream",
|
| 492 |
+
pages=[current],
|
| 493 |
+
current_page=page_number,
|
| 494 |
+
total_pages=total_pages,
|
| 495 |
+
document_type=document_type,
|
| 496 |
+
started=started,
|
| 497 |
+
)
|
| 498 |
+
|
| 499 |
+
markdown = markdown.strip()
|
| 500 |
+
if not markdown:
|
| 501 |
+
raise RuntimeError(f"The model returned an empty result for page {page_number}.")
|
| 502 |
+
completed_page = {
|
| 503 |
+
"page_number": page_number,
|
| 504 |
+
"markdown": markdown,
|
| 505 |
+
"render_markdown": materialize_bbox_images(markdown, page_image),
|
| 506 |
+
"status": "complete",
|
| 507 |
+
"elapsed_seconds": round(time.perf_counter() - page_started, 3),
|
| 508 |
+
}
|
| 509 |
+
yield stream_payload(
|
| 510 |
+
event="page_complete",
|
| 511 |
+
pages=[completed_page],
|
| 512 |
+
current_page=page_number,
|
| 513 |
+
total_pages=total_pages,
|
| 514 |
+
document_type=document_type,
|
| 515 |
+
started=started,
|
| 516 |
+
)
|
| 517 |
+
|
| 518 |
+
yield stream_payload(
|
| 519 |
+
event="complete",
|
| 520 |
+
pages=[completed_page],
|
| 521 |
+
current_page=page_number,
|
| 522 |
+
total_pages=total_pages,
|
| 523 |
+
document_type=document_type,
|
| 524 |
+
started=started,
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
@app.get("/healthz")
|
| 529 |
+
def healthz() -> JSONResponse:
|
| 530 |
+
return JSONResponse(
|
| 531 |
+
{
|
| 532 |
+
"status": "ok",
|
| 533 |
+
"model": MODEL_ID,
|
| 534 |
+
"model_source": MODEL_SOURCE,
|
| 535 |
+
"backend": "mock" if TEST_MODE else "transformers",
|
| 536 |
+
"loaded": TEST_MODE or (processor is not None and model is not None),
|
| 537 |
+
"max_pdf_pages": MAX_PDF_PAGES,
|
| 538 |
+
"root_path": ROOT_PATH,
|
| 539 |
+
"public_url": PUBLIC_URL,
|
| 540 |
+
}
|
| 541 |
+
)
|
| 542 |
+
|
| 543 |
+
|
| 544 |
+
if DIST_DIR.is_dir():
|
| 545 |
+
for route, directory in (
|
| 546 |
+
("/assets", DIST_DIR / "assets"),
|
| 547 |
+
("/examples", DIST_DIR / "examples"),
|
| 548 |
+
("/brand", DIST_DIR / "brand"),
|
| 549 |
+
("/vendor", DIST_DIR / "vendor"),
|
| 550 |
+
):
|
| 551 |
+
if directory.is_dir():
|
| 552 |
+
app.mount(route, StaticFiles(directory=directory), name=route.strip("/").replace("/", "-"))
|
| 553 |
+
|
| 554 |
+
|
| 555 |
+
@app.get("/")
|
| 556 |
+
def homepage() -> FileResponse:
|
| 557 |
+
index_path = DIST_DIR / "index.html"
|
| 558 |
+
if not index_path.is_file():
|
| 559 |
+
raise RuntimeError("Frontend build missing. Run `npm run build` before launching app.py.")
|
| 560 |
+
return FileResponse(index_path)
|
| 561 |
+
|
| 562 |
+
|
| 563 |
+
@app.get("/favicon.ico")
|
| 564 |
+
def favicon() -> FileResponse:
|
| 565 |
+
return FileResponse(DIST_DIR / "favicon.ico")
|
| 566 |
+
|
| 567 |
+
|
| 568 |
+
if __name__ == "__main__":
|
| 569 |
+
app.launch(
|
| 570 |
+
server_name=os.getenv("GRADIO_SERVER_NAME", "0.0.0.0"),
|
| 571 |
+
server_port=SERVER_PORT,
|
| 572 |
+
root_path=ROOT_PATH,
|
| 573 |
+
show_error=True,
|
| 574 |
+
)
|
dist/assets/__vite-browser-external-DYxpcVy9-BIHI7g3E.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
const e={};export{e as default};
|
dist/assets/index-5Ju-rOmV.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
dist/assets/index-CCa7-Cez.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(data:font/woff2;base64,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) format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(./manrope-cyrillic-wght-normal-Dvxsihut.woff2) format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(./manrope-greek-wght-normal-DL7QRZyv.woff2) format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(./manrope-vietnamese-wght-normal-usUDDRr7.woff2) format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(./manrope-latin-ext-wght-normal-Ch3YOpNY.woff2) format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(./manrope-latin-wght-normal-DHIcAJRg.woff2) format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url(data:font/woff2;base64,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) format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url(./jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2) format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url(./jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2) format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url(./jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2) format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url(./jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2) format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url(./jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2) format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{font-family:Manrope Variable,Manrope,Arial,sans-serif;color:#080a0d;background:#fff;font-synthesis:none;--ink: #080a0d;--muted: #626872;--subtle: #8a9099;--soft: #f7f8fa;--canvas: #f3f5f8;--line: #e3e6ea;--line-strong: #cfd4da;--blue: #075ce8;--blue-deep: #0349cf;--blue-soft: #edf4ff;--green: #08a972;--red: #d42d3f}*{box-sizing:border-box}html{min-width:320px;background:#fff}body{margin:0;min-width:320px;min-height:100vh;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}button,input{font:inherit}button,a{-webkit-tap-highlight-color:transparent}button{color:inherit}.app-shell{min-height:100vh;background:#fff}.topbar{height:64px;padding:0 28px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);background:#fffffff5}.brand{display:inline-flex;align-items:center;gap:14px;color:var(--ink);text-decoration:none}.brand img{width:78px;height:auto;display:block}.brand-divider{width:1px;height:28px;background:var(--line)}.product-name{font-size:24px;line-height:1;font-weight:730;letter-spacing:-.045em}.product-name span{color:var(--blue)}.model-chip{margin-left:2px;padding:4px 9px 3px;border:1px solid #b9d3ff;border-radius:999px;color:var(--blue);background:var(--blue-soft);font-family:JetBrains Mono Variable,monospace;font-size:11px;font-weight:650;letter-spacing:-.02em}.topbar nav{display:flex;align-items:center;gap:32px}.topbar nav a{color:#333840;text-decoration:none;font-size:13px;font-weight:560}.topbar nav a:hover{color:var(--blue)}.main-content{width:min(100%,1600px);margin:0 auto;padding:22px 24px 32px}.intro-row{min-height:72px;display:flex;align-items:center;justify-content:space-between;gap:28px}.eyebrow{margin:0 0 5px;color:var(--blue);font-family:JetBrains Mono Variable,monospace;font-size:10px;font-weight:650;line-height:1.2;letter-spacing:.09em;text-transform:uppercase}h1,h2,p{text-wrap:pretty}h1{margin:0;font-size:clamp(21px,2.1vw,30px);line-height:1.16;letter-spacing:-.035em;font-weight:700}.intro-actions{display:flex;align-items:center;gap:22px;flex-shrink:0}.status-line{display:flex;align-items:center;gap:8px;color:var(--muted);font-family:JetBrains Mono Variable,monospace;font-size:10px;white-space:nowrap}.status-mode:before{content:"·";margin-right:8px;color:var(--line-strong)}.status-dot{width:7px;height:7px;border-radius:50%;background:var(--green);box-shadow:0 0 0 3px #08a9721a}.status-line.running .status-dot{background:var(--blue);box-shadow:0 0 0 3px #075ce81a;animation:pulse 1.25s ease-in-out infinite}.status-line.error .status-dot{background:var(--red);box-shadow:0 0 0 3px #d42d3f1a}.primary-button{height:42px;min-width:132px;padding:0 22px;border:0;border-radius:6px;display:inline-flex;align-items:center;justify-content:center;gap:10px;color:#fff;background:var(--blue);box-shadow:0 7px 18px #075ce82e;cursor:pointer;font-size:13px;font-weight:680;transition:background .14s ease,transform .14s ease,box-shadow .14s ease}.primary-button:hover:not(:disabled){background:var(--blue-deep);box-shadow:0 9px 22px #075ce83d;transform:translateY(-1px)}.primary-button:disabled{opacity:.58;cursor:not-allowed;box-shadow:none}.spinner{width:15px;height:15px;border:2px solid rgba(255,255,255,.38);border-top-color:#fff;border-radius:50%;animation:spin .8s linear infinite}.workspace{height:clamp(510px,61vh,650px);min-height:510px;display:grid;grid-template-columns:minmax(360px,.92fr) minmax(520px,1.22fr);border:1px solid var(--line);border-radius:8px;overflow:hidden;background:#fff}.document-panel,.result-panel{min-width:0;min-height:0;display:flex;flex-direction:column}.document-panel{border-right:1px solid var(--line)}.panel-toolbar,.result-toolbar{height:48px;min-height:48px;padding:0 16px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);background:#fffffffa}.panel-title{min-width:0;display:flex;align-items:center;gap:8px;color:#4a5059;font-size:11px}.panel-title>span{max-width:210px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.panel-title strong{margin-left:3px;padding:2px 6px;border-radius:4px;color:var(--green);background:#eaf9f3;font-family:JetBrains Mono Variable,monospace;font-size:8px;letter-spacing:.06em}.text-button,.copy-button,.zoom-toolbar button,.toast button{border:0;background:transparent;cursor:pointer}.text-button{display:inline-flex;align-items:center;gap:6px;color:var(--blue);font-size:11px;font-weight:620}.text-button:hover{color:var(--blue-deep)}.document-canvas{position:relative;min-height:0;flex:1;padding:40px 28px 24px;display:flex;align-items:center;justify-content:center;overflow:auto;background:var(--canvas)}.document-canvas:fullscreen{padding:72px;background:var(--canvas)}.document-canvas.dragging{background:var(--blue-soft)}.document-image{display:block;max-width:100%;height:auto;max-height:calc(100% - 8px);object-fit:contain;background:#fff;box-shadow:0 8px 22px #0f172a26,0 0 0 1px #0f172a0f;transition:width .15s ease}.document-pdf{width:min(88%,720px);height:calc(100% - 8px);min-height:360px;border:0;background:#fff;box-shadow:0 8px 22px #0f172a26,0 0 0 1px #0f172a0f}.pdf-fallback{height:100%;min-height:360px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--muted);background:#fff;text-align:center}.pdf-fallback strong{color:var(--ink)}.pdf-fallback span{max-width:260px;font-size:11px}.zoom-toolbar{position:absolute;z-index:2;top:14px;left:14px;height:32px;padding:0 6px;display:flex;align-items:center;gap:2px;border:1px solid var(--line);border-radius:6px;background:#fffffff0;box-shadow:0 2px 7px #0f172a12;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.zoom-toolbar button{width:28px;height:28px;display:grid;place-items:center;border-radius:4px;color:#5e646d}.zoom-toolbar button:hover{color:var(--blue);background:var(--blue-soft)}.zoom-toolbar>span:not(.toolbar-divider){min-width:38px;text-align:center;color:#444a53;font-family:JetBrains Mono Variable,monospace;font-size:10px}.zoom-toolbar .toolbar-divider{width:1px;height:16px;margin:0 4px;background:var(--line)}.drop-overlay{position:absolute;top:12px;right:12px;bottom:12px;left:12px;z-index:4;display:grid;place-items:center;border:2px dashed var(--blue);border-radius:8px;color:var(--blue);background:#edf4fff0;font-weight:700}.upload-empty{width:min(360px,80%);min-height:210px;border:1px dashed #aab6c7;border-radius:8px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--muted);background:#ffffff9e;cursor:pointer}.upload-empty strong{color:var(--ink)}.upload-empty span{font-size:11px}.result-toolbar{padding-left:18px}.tabs{height:100%;display:flex;align-items:stretch;gap:24px}.tabs button{position:relative;padding:0 4px;border:0;color:#666c75;background:transparent;cursor:pointer;font-size:11px;font-weight:600}.tabs button:after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:transparent}.tabs button.active{color:var(--blue)}.tabs button.active:after{background:var(--blue)}.copy-button{height:30px;padding:0 11px;border:1px solid #a9c9ff;border-radius:5px;display:inline-flex;align-items:center;gap:7px;color:var(--blue);font-size:10px;font-weight:650}.copy-button:hover:not(:disabled){background:var(--blue-soft)}.copy-button:disabled{opacity:.42;cursor:not-allowed}.page-strip{min-height:40px;padding:5px 12px;display:flex;align-items:center;gap:7px;border-bottom:1px solid var(--line);background:#fbfcfe}.page-pills{min-width:0;flex:1;display:flex;align-items:center;gap:6px;overflow-x:auto;scrollbar-width:thin}.page-pills button,.page-arrow{height:28px;border:1px solid var(--line);border-radius:5px;display:inline-flex;align-items:center;justify-content:center;gap:4px;color:#606771;background:#fff;cursor:pointer;font-family:JetBrains Mono Variable,monospace;font-size:9px;white-space:nowrap}.page-pills button{padding:0 9px}.page-pills button.active{border-color:#9fc2fb;color:var(--blue);background:var(--blue-soft)}.page-pills button:disabled{color:#adb3bc;background:#f6f7f9;cursor:default}.page-pills button svg{color:var(--green)}.page-arrow{width:28px;flex:0 0 auto}.page-arrow:hover:not(:disabled){color:var(--blue);border-color:#b8cff5}.page-arrow:disabled{opacity:.36;cursor:default}.result-content{min-height:0;flex:1;overflow:auto;background:#fff}.result-state{min-height:100%;padding:48px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:var(--muted)}.result-state.empty svg{margin-bottom:12px;color:#b4bbc4}.result-state strong{color:#31363d;font-size:14px}.result-state p{max-width:400px;margin:7px 0 0;font-size:11px;line-height:1.55}.processing-mark{width:46px;height:46px;margin-bottom:15px;display:grid;place-items:center;border:1px solid #cfe0ff;border-radius:50%;background:var(--blue-soft)}.processing-mark span{width:16px;height:16px;border:2px solid #bdd4ff;border-top-color:var(--blue);border-radius:50%;animation:spin .8s linear infinite}.stream-banner{position:sticky;z-index:3;top:0;min-height:34px;padding:7px 16px;display:flex;align-items:center;gap:7px;border-bottom:1px solid #d9e7ff;color:#50627b;background:#f1f6fff5;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);font-size:10px}.stream-banner>span:first-child{width:7px;height:7px;border-radius:50%;background:var(--blue);animation:pulse 1s ease-in-out infinite}.stream-banner strong{color:var(--blue);font-size:10px}.stream-banner>span:last-child{margin-left:auto;font-family:JetBrains Mono Variable,monospace;font-size:8px}.rendered-markdown{padding:24px 30px 46px;color:#20242a;font-size:14px;line-height:1.68}.rendered-markdown>:first-child{margin-top:0}.rendered-markdown h1,.rendered-markdown h2,.rendered-markdown h3,.rendered-markdown h4{margin:1.5em 0 .56em;color:#111318;line-height:1.25;letter-spacing:-.02em}.rendered-markdown h1{font-size:22px}.rendered-markdown h2{font-size:18px}.rendered-markdown h3{font-size:15px}.rendered-markdown h4{font-size:14px}.rendered-markdown p,.rendered-markdown ul,.rendered-markdown ol{margin:.7em 0}.rendered-markdown a{color:var(--blue)}.rendered-markdown blockquote{margin:1em 0;padding:1px 0 1px 16px;border-left:3px solid #bad3ff;color:#535a64}.rendered-markdown table{width:100%;margin:1.2em 0;border-collapse:collapse;display:table;overflow-x:auto;font-size:12px}.rendered-markdown th,.rendered-markdown td{padding:8px 10px;border:1px solid #dfe3e8;text-align:left;vertical-align:top}.rendered-markdown th{background:#f5f7fa;font-weight:700}.rendered-markdown pre{max-width:100%;padding:14px;overflow-x:auto;border:1px solid #dce3ec;border-radius:6px;white-space:pre;background:#f7f9fc}.rendered-markdown code{font-family:JetBrains Mono Variable,monospace;font-size:.9em}.rendered-markdown .unresolved-image-reference{padding:2px 5px;border:1px solid #dce3ec;border-radius:4px;color:#4b5f7a;background:#f5f7fa;overflow-wrap:anywhere}.rendered-markdown img{display:block;max-width:min(100%,620px);height:auto;margin:18px auto;border-radius:3px}.rendered-markdown mjx-container{max-width:100%;overflow-x:auto;overflow-y:hidden}.rendered-markdown .visual-placeholder{min-height:82px;margin:16px 0;border:1px dashed #b7ccec;border-radius:6px;display:grid;place-items:center;color:#6680a5;background:#f5f8fd;font-family:JetBrains Mono Variable,monospace;font-size:9px}.source-code{min-height:100%;margin:0;padding:24px 28px 48px;overflow:visible;color:#183254;background:#fbfcfe;font-family:JetBrains Mono Variable,monospace;font-size:11px;line-height:1.66;white-space:pre-wrap;overflow-wrap:anywhere;-moz-tab-size:2;tab-size:2}.examples-section{padding-top:18px}.examples-heading{margin-bottom:10px;display:flex;align-items:flex-end;justify-content:space-between}.examples-heading h2{margin:0;font-size:14px;line-height:1.2;letter-spacing:-.02em}.examples-heading>p{margin:0;color:var(--subtle);font-family:JetBrains Mono Variable,monospace;font-size:9px}.example-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px}.example-card{min-width:0;padding:0;border:1px solid var(--line);border-radius:7px;overflow:hidden;color:#4a5059;background:#fff;cursor:pointer;text-align:left;transition:border-color .14s ease,transform .14s ease,box-shadow .14s ease}.example-card:hover{border-color:#aac8f9;transform:translateY(-1px);box-shadow:0 7px 17px #0f172a0f}.example-card.active{border-color:var(--blue);box-shadow:0 0 0 1px var(--blue)}.example-image-wrap{position:relative;height:112px;display:block;overflow:hidden;background:var(--soft)}.example-card img{width:100%;height:100%;display:block;object-fit:cover;object-position:top center;transition:transform .18s ease}.example-card:hover img{transform:scale(1.018)}.selected-badge{position:absolute;left:8px;bottom:8px;padding:4px 7px;display:inline-flex;align-items:center;gap:4px;border-radius:4px;color:#fff;background:var(--blue);font-size:9px;font-weight:700}.example-label{min-height:36px;padding:9px 11px;display:flex;align-items:center;font-size:10px;font-weight:610}.file-input{position:fixed;width:1px;height:1px;opacity:0;pointer-events:none}.toast{position:fixed;z-index:30;right:22px;bottom:22px;min-width:220px;min-height:42px;padding:0 12px;display:flex;align-items:center;gap:8px;border:1px solid #243142;border-radius:6px;color:#fff;background:#111926;box-shadow:0 14px 35px #0f172a33;opacity:0;transform:translateY(8px);pointer-events:none;transition:opacity .14s ease,transform .14s ease;font-size:11px}.toast.show{opacity:1;transform:translateY(0);pointer-events:auto}.toast button{width:24px;height:24px;margin-left:auto;display:grid;place-items:center;color:#cbd5e1}button:focus-visible,a:focus-visible{outline:2px solid var(--blue);outline-offset:2px}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.42}}@media(max-width:1050px){.main-content{padding-inline:18px}.workspace{grid-template-columns:minmax(330px,.85fr) minmax(470px,1.15fr)}.status-mode{display:none}.example-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.example-image-wrap{height:112px}}@media(max-width:820px){.topbar{height:58px;padding:0 16px}.brand{gap:9px}.brand img{width:62px}.brand-divider{height:22px}.product-name{font-size:19px}.model-chip{display:none}.topbar nav{gap:16px}.topbar nav a{font-size:11px}.topbar nav a:last-child{display:none}.main-content{padding:15px 14px 24px}.intro-row{align-items:flex-start;flex-direction:column;gap:14px;padding-bottom:14px}.intro-actions{width:100%;justify-content:space-between;gap:12px}.status-line{min-width:0;white-space:normal}.primary-button{min-width:116px}.workspace{height:auto;min-height:0;grid-template-columns:1fr}.document-panel{border-right:0;border-bottom:1px solid var(--line)}.document-canvas,.result-content{min-height:430px;max-height:62vh}.example-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.examples-heading>p{display:none}}@media(max-width:480px){.topbar nav{display:none}.intro-actions{align-items:stretch;flex-direction:column}.primary-button{width:100%}.workspace{border-radius:6px}.panel-title>span{max-width:150px}.document-canvas{min-height:370px;padding-inline:16px}.result-content{min-height:400px}.rendered-markdown{padding:22px 18px 40px}.example-grid{gap:10px}.example-image-wrap{height:98px}.toast{left:14px;right:14px;bottom:14px}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}
|
dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2
ADDED
|
Binary file (12.1 kB). View file
|
|
|
dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2
ADDED
|
Binary file (9 kB). View file
|
|
|
dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2
ADDED
|
Binary file (15.2 kB). View file
|
|
|
dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2
ADDED
|
Binary file (40.4 kB). View file
|
|
|
dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2
ADDED
|
Binary file (7.5 kB). View file
|
|
|
dist/assets/manrope-cyrillic-wght-normal-Dvxsihut.woff2
ADDED
|
Binary file (14.5 kB). View file
|
|
|
dist/assets/manrope-greek-wght-normal-DL7QRZyv.woff2
ADDED
|
Binary file (9.44 kB). View file
|
|
|
dist/assets/manrope-latin-ext-wght-normal-Ch3YOpNY.woff2
ADDED
|
Binary file (15.1 kB). View file
|
|
|
dist/assets/manrope-latin-wght-normal-DHIcAJRg.woff2
ADDED
|
Binary file (24.8 kB). View file
|
|
|
dist/assets/manrope-vietnamese-wght-normal-usUDDRr7.woff2
ADDED
|
Binary file (8.52 kB). View file
|
|
|
dist/brand/ovis-logo.png
ADDED
|
dist/examples/handwriting-040745.jpg
ADDED
|
Git LFS Details
|
dist/examples/handwriting-042609.jpg
ADDED
|
Git LFS Details
|
dist/examples/page-9477c155-19b3-4f2d-ac18-ee3f8997d2f8.png
ADDED
|
Git LFS Details
|
dist/examples/table-1305dae7.webp
ADDED
|
dist/examples/table-3ba1139c.webp
ADDED
|
dist/favicon.ico
ADDED
|
|
dist/index.html
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<meta name="theme-color" content="#ffffff" />
|
| 7 |
+
<meta name="description" content="Streaming document parsing for images and PDFs with OvisOCR2." />
|
| 8 |
+
<link rel="icon" href="./favicon.ico" />
|
| 9 |
+
<title>OvisOCR2 — Document to Markdown</title>
|
| 10 |
+
<script>
|
| 11 |
+
window.MathJax = {
|
| 12 |
+
options: {
|
| 13 |
+
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'code', 'annotation', 'annotation-xml']
|
| 14 |
+
},
|
| 15 |
+
tex: {
|
| 16 |
+
inlineMath: [['\\(', '\\)'], ['$', '$']],
|
| 17 |
+
displayMath: [['\\[', '\\]']],
|
| 18 |
+
processEscapes: true
|
| 19 |
+
},
|
| 20 |
+
chtml: { matchFontHeight: false },
|
| 21 |
+
startup: { typeset: false }
|
| 22 |
+
};
|
| 23 |
+
</script>
|
| 24 |
+
<script defer src="./vendor/mathjax/tex-chtml-full.js"></script>
|
| 25 |
+
<script type="module" crossorigin src="./assets/index-5Ju-rOmV.js"></script>
|
| 26 |
+
<link rel="stylesheet" crossorigin href="./assets/index-CCa7-Cez.css">
|
| 27 |
+
</head>
|
| 28 |
+
<body>
|
| 29 |
+
<div id="root"></div>
|
| 30 |
+
</body>
|
| 31 |
+
</html>
|
dist/vendor/mathjax/output/chtml/fonts/tex.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_AMS-Regular.woff
ADDED
|
Binary file (40.8 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Bold.woff
ADDED
|
Binary file (9.91 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Regular.woff
ADDED
|
Binary file (9.6 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Fraktur-Bold.woff
ADDED
|
Binary file (22.3 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Fraktur-Regular.woff
ADDED
|
Binary file (21.5 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Bold.woff
ADDED
|
Binary file (34.5 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Italic.woff
ADDED
|
Binary file (20.8 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Main-Regular.woff
ADDED
|
Binary file (34.2 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-BoldItalic.woff
ADDED
|
Binary file (19.8 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-Italic.woff
ADDED
|
Binary file (19.4 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Math-Regular.woff
ADDED
|
Binary file (19.3 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Bold.woff
ADDED
|
Binary file (15.9 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Italic.woff
ADDED
|
Binary file (14.6 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_SansSerif-Regular.woff
ADDED
|
Binary file (12.7 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Script-Regular.woff
ADDED
|
Binary file (11.9 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Size1-Regular.woff
ADDED
|
Binary file (5.79 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Size2-Regular.woff
ADDED
|
Binary file (5.46 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Size3-Regular.woff
ADDED
|
Binary file (3.24 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Size4-Regular.woff
ADDED
|
Binary file (5.15 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff
ADDED
|
Binary file (17.6 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Vector-Bold.woff
ADDED
|
Binary file (1.12 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Vector-Regular.woff
ADDED
|
Binary file (1.14 kB). View file
|
|
|
dist/vendor/mathjax/output/chtml/fonts/woff-v2/MathJax_Zero.woff
ADDED
|
Binary file (1.37 kB). View file
|
|
|
dist/vendor/mathjax/tex-chtml-full.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
transformers>=5.13.0,<6
|
| 2 |
+
accelerate>=1.12.0
|
| 3 |
+
torchvision>=0.25.0,<0.27
|
| 4 |
+
Pillow>=10.0.0
|
| 5 |
+
PyMuPDF>=1.24.0,<2
|