Skip to content

Commit fdc3c84

Browse files
artizclaude
andcommitted
serve: docling.rs page shell for the playground, and an OpenAPI description
The playground looked nothing like the wasm demo page — a bare 🦆 heading, no logo, no way back to the project — and the API had no machine-readable description at all, so pointing Swagger UI, Redoc or a client generator at a running server was impossible. - index.html adopts the demo page's shell verbatim: docling's deep-blue bar (#003778) with the logo, the name/tagline block, and buttons to the OpenAPI document and GitHub on the right, over a centered main column. The palette variables keep the existing light/dark handling. - New GET /openapi.yaml: an OpenAPI 3.1 description of the real API — both request bodies (multipart upload, JSON URL), every ConvertOptions field with its enum/default, the four response shapes, and the error statuses. Linked from the header, the lede, the endpoint table and the curl examples. - New GET /logo.svg, the same logo the demo page uses. Both assets are include_str!'d into the binary, so a server still needs no static-file directory. - Test: both routes answer with the right content types, and the spec mentions every route the router serves and every option ConvertOptions accepts — the drift that would otherwise go unnoticed. - README: the serve section mentions /openapi.yaml, and its screenshot is regenerated on the new design (a real picture_classification.pdf run through the form, gallery included). Verified against a running server: /, /logo.svg, /openapi.yaml and /v1/config answer 200 with the expected content types, the served spec parses as YAML (5 paths), and uploads still convert to Markdown and JSON. Signed-off-by: artiz <artem.kustikov@gmail.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EY5KAiquN4YpVf2PXEQkVT
1 parent 3e23b49 commit fdc3c84

7 files changed

Lines changed: 568 additions & 10 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ cold call on the test fixtures); a semaphore bounds concurrent conversions.
117117
Markdown responses stream (chunked transfer); `/health` + `/ready` suit
118118
container probes, and SIGTERM drains in-flight requests before exit.
119119
`GET /` serves API docs plus an interactive test form — upload or URL in,
120-
streamed result out, with extracted pictures rendered below the text:
120+
streamed result out, with extracted pictures rendered below the text — and
121+
`GET /openapi.yaml` describes the whole API (OpenAPI 3.1), so Swagger UI,
122+
Redoc or a client generator can be pointed straight at a running server:
121123

122124
<p align="center">
123125
<img src="docs/assets/serve-form.png" alt="docling-serve test form: a converted image with the Markdown result and a gallery of extracted pictures" width="720">

crates/docling-serve/src/index.html

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,37 @@
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<title>docling-serve — HTTP conversion API</title>
7+
<link rel="icon" href="/logo.svg">
78
<style>
8-
:root { --fg: #1a1a1a; --muted: #6a6a6a; --line: #e2e2e2; --accent: #0969da; --bg: #ffffff; --panel: #f6f8fa; }
9+
/* Same shell as the wasm demo page (crates/docling-wasm/www/index.html):
10+
docling's deep-blue bar with the logo, a GitHub link on the right, and a
11+
centered main column — one look across the project's two web surfaces. */
12+
:root {
13+
color-scheme: light dark;
14+
--brand: #003778; --accent: #0094ff;
15+
--fg: #1a1a1a; --muted: #6a6a6a; --line: #e2e2e2; --bg: #ffffff; --panel: #f6f8fa;
16+
}
917
@media (prefers-color-scheme: dark) {
10-
:root { --fg: #e6e6e6; --muted: #9a9a9a; --line: #333; --accent: #58a6ff; --bg: #0d1117; --panel: #161b22; }
18+
:root { --fg: #e6e6e6; --muted: #9a9a9a; --line: #333; --bg: #0d1117; --panel: #161b22; }
1119
}
1220
* { box-sizing: border-box; }
13-
body { margin: 0 auto; max-width: 60rem; padding: 2rem 1.25rem 4rem; font: 16px/1.55 system-ui, sans-serif; color: var(--fg); background: var(--bg); }
14-
h1 { font-size: 1.6rem; margin: 0 0 .25rem; }
15-
h1 .duck { font-size: 1.3rem; }
21+
body { margin: 0; font: 16px/1.55 system-ui, sans-serif; color: var(--fg); background: var(--bg); }
22+
header {
23+
background: var(--brand); color: #fff;
24+
display: flex; align-items: center; gap: .8rem;
25+
padding: .55rem 1.2rem; box-shadow: 0 2px 6px rgba(0, 0, 0, .25);
26+
}
27+
header img { width: 38px; height: 38px; }
28+
header .name { font-size: 1.15rem; font-weight: 600; }
29+
header .tag { font-size: .8rem; opacity: .75; }
30+
header nav { margin-left: auto; display: flex; gap: .5rem; }
31+
header nav a {
32+
color: #fff; text-decoration: none; font-size: .85rem;
33+
border: 1px solid rgba(255, 255, 255, .4); border-radius: 6px; padding: .25rem .7rem;
34+
}
35+
header nav a:hover { background: rgba(255, 255, 255, .12); }
36+
main { max-width: 62rem; margin: 1.4rem auto; padding: 0 1rem 3rem; }
37+
a { color: var(--accent); }
1638
h2 { font-size: 1.15rem; margin: 2.2rem 0 .6rem; border-bottom: 1px solid var(--line); padding-bottom: .3rem; }
1739
p.sub { color: var(--muted); margin: 0 0 1.5rem; }
1840
code, pre { font: 13px/1.5 ui-monospace, "SF Mono", Menlo, Consolas, monospace; }
@@ -27,6 +49,7 @@
2749
select, input[type=text] { padding: .35rem .5rem; border: 1px solid var(--line); border-radius: 6px; background: var(--bg); color: var(--fg); }
2850
input[type=text] { flex: 1; min-width: 16rem; }
2951
button { padding: .45rem 1.1rem; border: 0; border-radius: 6px; background: var(--accent); color: #fff; font-weight: 600; cursor: pointer; }
52+
code a { color: inherit; }
3053
button:disabled { opacity: .5; cursor: wait; }
3154
.out { margin-top: 1rem; }
3255
.out pre { max-height: 24rem; overflow: auto; white-space: pre-wrap; }
@@ -39,9 +62,21 @@
3962
</style>
4063
</head>
4164
<body>
42-
<h1><span class="duck">🦆</span> docling-serve</h1>
43-
<p class="sub">HTTP conversion API over <a href="https://github.com/docling-project/docling.rs">docling.rs</a>
44-
convert documents to Markdown, docling JSON, DocLang archives, or chunk records.</p>
65+
<header>
66+
<img src="/logo.svg" alt="docling.rs logo">
67+
<div>
68+
<div class="name">docling-serve</div>
69+
<div class="tag">HTTP conversion API</div>
70+
</div>
71+
<nav>
72+
<a href="/openapi.yaml">OpenAPI</a>
73+
<a href="https://github.com/docling-project/docling.rs">GitHub</a>
74+
</nav>
75+
</header>
76+
<main>
77+
<p class="sub">Convert documents to Markdown, docling JSON, DocLang archives, or chunk records —
78+
over <a href="https://github.com/docling-project/docling.rs">docling.rs</a>, on this server.
79+
The API is described by <a href="/openapi.yaml"><code>/openapi.yaml</code></a> (OpenAPI 3.1).</p>
4580

4681
<h2>Try it</h2>
4782
<div class="form">
@@ -95,6 +130,8 @@ <h2>API</h2>
95130
<tr><td><code>GET</code></td><td><code>/v1/config</code></td><td>Server capabilities — currently <code>{"allow_url_fetch": bool}</code>, which this page adapts to.</td></tr>
96131
<tr><td><code>GET</code></td><td><code>/health</code></td><td>Liveness probe.</td></tr>
97132
<tr><td><code>GET</code></td><td><code>/ready</code></td><td>Readiness probe — 503 until <code>--warmup</code> finishes loading the ML models.</td></tr>
133+
<tr><td><code>GET</code></td><td><code><a href="/openapi.yaml">/openapi.yaml</a></code></td>
134+
<td>OpenAPI 3.1 description of this server — point Swagger UI, Redoc, or a client generator at it.</td></tr>
98135
</table>
99136

100137
<h2>Options</h2>
@@ -117,7 +154,10 @@ <h2>Examples</h2>
117154
curl -F file=@page.html 'localhost:5001/v1/convert?to=chunks' # chunk records
118155
curl -H 'content-type: application/json' \
119156
-d '{"url": "https://example.com/doc.pdf", "to": "md"}' \
120-
localhost:5001/v1/convert # convert a URL</pre>
157+
localhost:5001/v1/convert # convert a URL
158+
159+
curl -O localhost:5001/openapi.yaml # the API description</pre>
160+
</main>
121161

122162
<script>
123163
const $ = (id) => document.getElementById(id);

crates/docling-serve/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//! | GET | `/v1/config` | server capabilities (`{"allow_url_fetch": bool}`) |
1111
//! | GET | `/health` | liveness probe |
1212
//! | GET | `/ready` | readiness probe (200 once models are warm) |
13+
//! | GET | `/openapi.yaml` | OpenAPI 3.1 description of the API |
14+
//! | GET | `/logo.svg` | the playground's logo |
1315
//!
1416
//! `POST /v1/convert` accepts either `multipart/form-data` with a `file` part
1517
//! (the filename's extension selects the input format) or an
@@ -131,6 +133,26 @@ pub fn router(cfg: ServeConfig) -> Router {
131133
"/",
132134
get(|| async { axum::response::Html(include_str!("index.html")) }),
133135
)
136+
// The page's logo and the machine-readable API description. Both are
137+
// baked into the binary, so a server needs no static-file directory.
138+
.route(
139+
"/logo.svg",
140+
get(|| async {
141+
(
142+
[(header::CONTENT_TYPE, "image/svg+xml")],
143+
include_str!("logo.svg"),
144+
)
145+
}),
146+
)
147+
.route(
148+
"/openapi.yaml",
149+
get(|| async {
150+
(
151+
[(header::CONTENT_TYPE, "application/yaml")],
152+
include_str!("openapi.yaml"),
153+
)
154+
}),
155+
)
134156
.route("/health", get(|| async { Json(json!({"status": "ok"})) }))
135157
.route("/ready", get(ready))
136158
.route("/v1/config", get(config))

0 commit comments

Comments
 (0)