No description
Find a file
Pier-Jean Malandrino 47e0d197b3 feat(#207): document-centric routing skeleton
Adds the routing scaffold for the doc-centric pivot. Each new route
renders a placeholder page until E3/E4/E5 implement them; legacy
routes (/studio, /documents, /history, /search, /reasoning) keep
working in parallel.

New routes (Vue Router, history mode):
  /docs                   library (placeholder, #211)
  /docs/new               import (placeholder, #214)
  /docs/:id?mode=         workspace (placeholder, #216)
  /index                  stores list (placeholder, 0.7.0)
  /index/:store           store detail (placeholder)
  /index/:store/query     RAG playground (placeholder)
  /runs                   run history (placeholder)
  /runs/:id               run detail (placeholder)

Mode parsing
- shared/routing/modes.ts: DocMode union ('ask'|'inspect'|'chunks'),
  parseMode() returns the default ('ask') for missing or unknown
  values. #210 will layer feature-flag-aware redirection on top.

Route names
- shared/routing/names.ts: ROUTES typed const so callers do
  router.push({ name: ROUTES.DOC_WORKSPACE, ... }) instead of
  stringly-typed names.

Pages
- ComingSoonShell shared component: card + back-home link, themed
  with existing CSS tokens.
- 8 thin placeholder pages (one per new route) that compose the shell
  and forward route params.
- i18n keys under comingSoon.* added in fr + en.

Tests
- shared/routing/modes.test.ts (10 cases): isDocMode + parseMode +
  ALL_MODES invariants.
- app/router/router.test.ts (5 cases): every doc-centric route
  resolves to a component, legacy routes still work, doc workspace
  receives id and parsed mode as props, unknown mode falls back to
  ask, unknown path redirects to home.

Routes table extracted to routes.ts so tests build a router with
createMemoryHistory() (no window required) instead of needing the
production createWebHistory() router.

Refs #207
2026-05-05 09:38:39 +02:00
.github fix(ci): install pytestarch in docling-compat workflow 2026-05-05 09:38:36 +02:00
docs docs(design): E2 design docs for 0.6.0 navigation refactor 2026-05-05 09:38:39 +02:00
document-parser chore(#206): backfill 0.6.0 doc-centric data model 2026-05-05 09:38:39 +02:00
e2e refactor(audit): remediate 0.5.0 audit findings — clean architecture, security, DRY, SOLID, perf 2026-04-29 14:00:00 +02:00
embedding-service fix(ci): install curl in embedding-service image for healthcheck 2026-04-10 22:22:50 +02:00
experiments/reasoning-trace feat(reasoning): reasoning-trace viewer v1 with SQLite-backed graph 2026-04-29 14:00:00 +02:00
frontend feat(#207): document-centric routing skeleton 2026-05-05 09:38:39 +02:00
.dockerignore feat: add E2E API tests with Karate V2 2026-04-08 13:47:03 +02:00
.editorconfig Add qualityt check and contributing doc 2026-03-21 15:34:54 +01:00
.env.example fix(nginx): make upload body size configurable via NGINX_MAX_BODY_SIZE env var 2026-04-30 11:38:14 +02:00
.gitignore chore: gitignore local-only artifacts on release/0.6.0 2026-05-05 09:38:39 +02:00
.trivyignore.yaml fix(ci): drop paths constraint from CVE-2026-40393 trivy ignore 2026-04-29 14:00:00 +02:00
CHANGELOG.md chore(release): 0.5.1 2026-04-30 11:38:14 +02:00
CODE_OF_CONDUCT.md ci: add release-gate pipeline, fix CI duplication, add OSS playbook docs 2026-04-09 13:20:43 +02:00
CONTRIBUTING.md feat(#180): feature-flag ingestion pipeline and add brainless one-liner Quick Start 2026-04-13 11:18:56 +02:00
docker-compose.dev.yml feat(neo4j): Day 1 — compose service, driver, schema bootstrap 2026-04-29 14:00:00 +02:00
docker-compose.ingestion.yml feat(#180): feature-flag ingestion pipeline and add brainless one-liner Quick Start 2026-04-13 11:18:56 +02:00
docker-compose.yml fix(nginx): make upload body size configurable via NGINX_MAX_BODY_SIZE env var 2026-04-30 11:38:14 +02:00
Dockerfile fix(nginx): move template outside sites-enabled to avoid nginx loading it raw 2026-04-30 12:15:46 +02:00
LICENSE Work on full Docker integration 2026-03-17 13:33:36 +01:00
mkdocs.yml fix(docs): also tolerate links.not_found for cross-tree audit links 2026-04-29 14:12:01 +02:00
nginx.conf.template fix(nginx): make upload body size configurable via NGINX_MAX_BODY_SIZE env var 2026-04-30 11:38:14 +02:00
README.md docs(readme): document NGINX_MAX_BODY_SIZE env var and nginx upload layer 2026-04-30 11:38:14 +02:00
SECURITY.md ci: add release-gate pipeline, fix CI duplication, add OSS playbook docs 2026-04-09 13:20:43 +02:00

Docling Studio

License: MIT Python Node Docling CI GitHub Stars

A visual document analysis studio powered by Docling. Upload a PDF, configure the extraction pipeline, and visualize the results — text, tables, images, formulas, bounding boxes — all from your browser.

Docling Studio — Presentation

Star History

Star History Chart

Features

  • Home page with quick upload and recent documents
  • PDF viewer with page navigation, bounding box overlay, and resizable results panel
  • Configurable Docling pipeline — OCR, table extraction, code/formula enrichment, picture classification & description, image generation
  • Bounding box visualization — color-coded element overlay directly on the PDF
  • Per-page results — right panel syncs with the current PDF page
  • Chunking — split extracted content into semantic chunks (hierarchical, hybrid, or page-based) with configurable token limits and inline editing
  • Ingestion pipeline — Docling → chunking → embedding → OpenSearch vector indexing (one-click from Studio)
  • Graph storage (Neo4j) — full DoclingDocument tree (sections, paragraphs, tables, pages, chunks) mirrored as a graph with PARENT_OF, NEXT, ON_PAGE, HAS_CHUNK, DERIVED_FROM relations, with an in-app graph view powered by Cytoscape.js
  • Markdown & HTML export of extracted content
  • Document management — upload, list, delete, search, filter by indexing status
  • Analysis history — re-visit and open past analyses
  • Upload limits — configurable max file size and max page count per document
  • Rate limiting — configurable requests per minute per IP
  • Dark / Light theme and FR / EN localization

Architecture

┌────────────┐         ┌──────────────────────┐
│  Frontend  │────────▶│   Document Parser    │
│  Vue 3     │  /api/* │ FastAPI + Docling    │
│  port 3000 │         │ SQLite + file storage│
└────────────┘         │   port 8000          │
                       └──────────────────────┘
Service Stack Role
frontend Vue 3, TypeScript, Vite, Pinia UI, PDF viewer, results display
document-parser FastAPI, Docling, SQLite, pdf2image REST API, document parsing, storage

Backend structure (hexagonal architecture — ports & adapters)

document-parser/
├── main.py                   # FastAPI app, CORS, lifespan
├── domain/                   # Pure domain — no HTTP, no DB
│   ├── models.py             # Document, AnalysisJob dataclasses
│   ├── ports.py              # Abstract protocols (converter, chunker)
│   └── value_objects.py      # ConversionResult, PageDetail, ChunkResult
├── api/                      # HTTP layer (FastAPI routers)
│   ├── schemas.py            # Pydantic DTOs (camelCase serialization)
│   ├── documents.py          # /api/documents endpoints
│   └── analyses.py           # /api/analyses endpoints
├── persistence/              # Data layer (SQLite via aiosqlite)
│   ├── database.py           # Connection management, schema init
│   ├── document_repo.py      # Document CRUD
│   └── analysis_repo.py      # AnalysisJob CRUD
├── services/                 # Use case orchestration
│   ├── document_service.py   # Upload, delete, preview
│   └── analysis_service.py   # Async Docling processing
└── tests/                    # 377 tests (pytest)

Frontend structure (feature-based)

frontend/src/
├── app/                      # App shell, router, global styles
├── pages/                    # Route-level pages
│   ├── HomePage.vue          # Landing page with upload & stats
│   ├── StudioPage.vue        # PDF viewer + config + results
│   ├── DocumentsPage.vue     # Document management
│   ├── HistoryPage.vue       # Past analyses
│   └── SettingsPage.vue      # Theme, language, API URL
├── features/                 # Feature modules
│   ├── analysis/             # Analysis store, API, bbox, UI components
│   ├── document/             # Document store, API, upload, list
│   ├── history/              # History store, API, navigation
│   └── settings/             # Settings store
└── shared/                   # Shared utilities (types, i18n, http, format)

Quick Start

One command, nothing else to install:

docker run -p 3000:3000 ghcr.io/scub-france/docling-studio:latest-local

Open http://localhost:3000, upload a PDF, and get results. That's it.

Note: The first analysis takes longer as Docling downloads its ML models (~400 MB). Subsequent runs are fast.

Image variants

Variant Image tag Size Description
local latest-local ~1.9 GB Full — runs Docling in-process, CPU-only
remote latest-remote ~270 MB Lightweight — delegates to an external Docling Serve instance

For remote mode:

docker run -p 3000:3000 \
  -e DOCLING_SERVE_URL=http://your-docling-serve:5001 \
  ghcr.io/scub-france/docling-studio:latest-remote

Docker Compose

git clone https://github.com/scub-france/Docling-Studio.git
cd Docling-Studio

# Simple mode (backend + frontend only)
docker compose up --build

# With ingestion pipeline (OpenSearch + embeddings)
docker compose --profile ingestion -f docker-compose.yml -f docker-compose.ingestion.yml up --build

Local Development

Backend (Python 3.12+):

cd document-parser
python -m venv .venv && source .venv/bin/activate

# Remote mode (lightweight)
pip install -r requirements.txt

# Local mode (with Docling)
pip install -r requirements-local.txt

uvicorn main:app --reload --port 8000

Frontend (Node 20+):

cd frontend
npm install
npm run dev

Running Tests

# Backend (377 tests)
cd document-parser
pip install pytest pytest-asyncio httpx
pytest tests/ -v

# Frontend (156 tests)
cd frontend
npm run test:run

Pipeline Options

These options map directly to Docling's PdfPipelineOptions. See the Docling documentation for details on each feature.

Option Default Description
do_ocr true OCR for scanned pages and embedded images
do_table_structure true Table detection and row/column reconstruction
table_mode accurate accurate (TableFormer) or fast
do_code_enrichment false Specialized OCR for code blocks
do_formula_enrichment false Math formula recognition (LaTeX output)
do_picture_classification false Classify images by type (chart, photo, diagram…)
do_picture_description false Generate image descriptions via VLM
generate_picture_images false Extract detected images as separate files
generate_page_images false Rasterize each page as an image
images_scale 1.0 Scale factor for generated images (0.110)

Configuration

All configuration is done via environment variables. See .env.example.

Variable Default Description
CONVERSION_ENGINE local local (in-process Docling) or remote (Docling Serve)
DOCLING_SERVE_URL http://localhost:5001 Docling Serve endpoint (remote mode only)
DOCLING_SERVE_API_KEY API key for Docling Serve (optional)
CORS_ORIGINS http://localhost:3000,... CORS allowed origins (comma-separated)
UPLOAD_DIR ./uploads File storage directory
DB_PATH ./data/docling_studio.db SQLite database path
CONVERSION_TIMEOUT 600 Max seconds for a single Docling conversion
BATCH_PAGE_SIZE 10 Pages per batch (0 = process all at once)
MAX_FILE_SIZE_MB 50 Maximum upload file size in MB (0 = unlimited)
MAX_PAGE_COUNT 0 Maximum number of pages per document (0 = unlimited)
NGINX_MAX_BODY_SIZE 200M Nginx request body limit — nginx format (200M, 0 = unlimited). Must be ≥ MAX_FILE_SIZE_MB.
RATE_LIMIT_RPM 100 Max requests per minute per IP (0 = disabled)

Upload Limits

Docling Studio enforces configurable limits on uploaded documents to protect the server against oversized files and long-running analyses:

  • MAX_FILE_SIZE_MB (default 50) — rejects uploads exceeding this size. Validated at two levels: early Content-Length check and streaming byte count.
  • MAX_PAGE_COUNT (default 0 = unlimited) — rejects documents with more pages than allowed. Useful on shared instances or Hugging Face Spaces to cap processing time.
  • NGINX_MAX_BODY_SIZE (default 200M) — nginx-level body cap, applied before the request reaches the backend. Defaults to 200M so MAX_FILE_SIZE_MB is always the effective limit. Use nginx format (50M, 1G, 0 for unlimited).

Both application limits are exposed in the /api/health endpoint so the frontend can display them to the user before upload. Set either to 0 to disable the corresponding check.

Ingestion Pipeline (opt-in)

Docling Studio can optionally index extracted chunks into OpenSearch for vector and full-text search. This requires two additional services (OpenSearch + embedding) and is disabled by default.

To enable ingestion with Docker Compose:

docker compose --profile ingestion \
  -f docker-compose.yml -f docker-compose.ingestion.yml \
  up --build

When ingestion is enabled, the UI shows:

  • An Ingest button in Studio to push chunks to OpenSearch
  • An OpenSearch connection status badge in the sidebar
  • Indexed / Not indexed filters on the Documents page
  • A Search page for full-text and vector search across indexed documents
Variable Default Description
OPENSEARCH_URL OpenSearch endpoint (empty = ingestion disabled)
EMBEDDING_URL Embedding service endpoint (empty = ingestion disabled)
EMBEDDING_DIMENSION 384 Vector dimension (must match embedding model)

Graph storage with Neo4j (opt-in)

Docling Studio can mirror the full DoclingDocument tree into a Neo4j graph: sections, paragraphs, tables, figures, pages, and chunks all become first-class nodes connected by HAS_ROOT, PARENT_OF, NEXT, ON_PAGE, HAS_CHUNK, and DERIVED_FROM edges. This enables queries that are impossible with a flat chunk store — navigating a document's outline, finding all tables under a given section, or tracing a chunk back to its source elements.

Enable Neo4j with the ingestion profile (it ships alongside OpenSearch):

docker compose --profile ingestion \
  -f docker-compose.yml -f docker-compose.ingestion.yml \
  up --build

The Neo4j Browser is available at http://localhost:7474 (user neo4j, password changeme by default).

Schema at a glance

graph TD
    D[Document] -->|HAS_ROOT| SH[SectionHeader]
    D -->|HAS_CHUNK| C[Chunk]
    SH -->|PARENT_OF| P[Paragraph]
    SH -->|PARENT_OF| T[Table]
    P -->|NEXT| T
    P -->|ON_PAGE| PG[Page]
    T -->|ON_PAGE| PG
    C -->|DERIVED_FROM| P
    C -->|DERIVED_FROM| T

Example Cypher queries

Find all "Methods" sections across documents (impossible in vector-only stores):

MATCH (d:Document)-[:HAS_ROOT]->(:Element)-[:PARENT_OF*]->(s:SectionHeader)
WHERE toLower(s.text) CONTAINS 'method'
RETURN d.title, s.text, s.level

Get the parent section and sibling elements of a chunk (context for RAG):

MATCH (c:Chunk {id: $chunk_id})-[:DERIVED_FROM]->(e:Element)
MATCH (e)<-[:PARENT_OF]-(parent:Element)-[:PARENT_OF]->(sibling:Element)
RETURN parent, collect(sibling) AS siblings

List all tables from documents ingested from an invoices/ path:

MATCH (d:Document)-[:HAS_ROOT]->(:Element)-[:PARENT_OF*]->(t:Table)
WHERE d.source_uri CONTAINS 'invoices/'
RETURN d.title, t.caption, t.cells_json
Variable Default Description
NEO4J_URI Neo4j Bolt endpoint (empty = graph storage disabled)
NEO4J_USER neo4j Neo4j username
NEO4J_PASSWORD changeme Neo4j password

The in-app Graph tab (under Results) renders the per-document graph with Cytoscape.js (see ADR-001 for the library choice). Documents with more than 200 pages return HTTP 413 from GET /api/documents/{id}/graph; pagination ships in v0.6.

Live Reasoning (opt-in, R&D)

Docling Studio can run docling-agent's Chunkless RAG loop against an analyzed document and return a full reasoning trace — the path the agent walked through the document outline, with the section reference / rationale / answer for each iteration. The trace is overlaid on the document graph so you can see how the agent navigated the structure.

Disabled by default — pulls heavy deps (docling-agent, mellea, ~60 MB) and needs a reachable Ollama instance with the target model already pulled.

Enable

export REASONING_ENABLED=true
export OLLAMA_HOST=http://localhost:11434      # default
export REASONING_MODEL_ID=gpt-oss:20b           # any model already pulled in Ollama
# Optional, future-proof — only "ollama" is realizable today (see Architecture below):
export LLM_PROVIDER_TYPE=ollama

Then pip install docling-agent mellea (or use the local Docker image which bundles them) and restart the backend. The frontend reads reasoningAvailable from /api/health and hides the Reasoning sidebar entry when the runner isn't wired — so users never click through to a 503.

Variable Default Description
REASONING_ENABLED false Master switch — true to enable the live runner
OLLAMA_HOST http://localhost:11434 Ollama daemon URL
REASONING_MODEL_ID gpt-oss:20b Default model id (per-call override allowed via the API)
LLM_PROVIDER_TYPE ollama LLM backend selector — only ollama is supported today

Architecture

The reasoning subsystem is wired through a ReasoningRunner port (document-parser/domain/ports.py) and an LLMProvider abstraction:

  • domain/ports.py defines ReasoningRunner, LLMProvider, ReasoningParseError (no third-party imports)
  • domain/value_objects.py defines LLMProviderType, ReasoningResult, ReasoningIteration
  • infra/llm/ollama_provider.py implements LLMProvider for Ollama
  • infra/docling_agent_reasoning.py implements ReasoningRunner using docling-agent + mellea — all upstream coupling is here, including the _rag_loop workaround tracked at docling-agent#26
  • api/reasoning.py consumes app.state.reasoning_runner — zero coupling to docling-agent

This makes alternate LLM backends a question of adding new LLMProvider adapters once docling-agent (or a replacement) supports them upstream.

CI / Release

GitHub Actions pipelines (see .github/workflows/):

Workflow Trigger What it does
CI push to main, pull requests Lint + type check + Backend tests + Frontend tests + build
Release push tag v* Build & push two multi-arch Docker images (remote + local) to ghcr.io
Docs push to main (docs changes) Build & deploy MkDocs to GitHub Pages

We follow Semantic Versioning with a simplified Git Flow. See CONTRIBUTING.md for the full release process.

Performance & System Requirements

Document type Pages Approx. time (CPU)
Simple report 510 ~30s1 min
Research paper 1030 ~12 min
Large document 100+ ~25 min

Docker Desktop settings

Remote image Local image
Image size ~270 MB ~1.9 GB
Memory 2 GB 6 GB (recommended 8 GB+)
CPUs 2 4 (recommended 8+)

Platform support

All Docker images are multi-arch (linux/amd64 + linux/arm64). No GPU required.

Tech Stack

  • Frontend: Vue 3, TypeScript, Vite, Pinia, DOMPurify
  • Backend: FastAPI, Docling 2.x, SQLite (aiosqlite), pdf2image
  • CI: GitHub Actions
  • Infra: Docker Compose + Nginx

Contributing

Contributions are welcome! Please open an issue first to discuss what you'd like to change.

License

MIT — Pier-Jean Malandrino