ext-apps/examples/pdf-server/mcp-app.html
Olivier Chafik d185099ead
feat(pdf-server): lazy range-based loading + eager background preloading (#480)
* lazy PDF loading w/ PDFDataRangeTransport

* lint

* feat: lazy PDF loading via range transport + eager background preloading

Replace full-file download with PDFDataRangeTransport so PDF.js fetches
byte ranges on demand. First page renders immediately; remaining pages
load in background for search.

**Range transport (server + client)**
- server.ts: add read_pdf_bytes tool returning base64 chunks with offset
- mcp-app.ts: AppRangeTransport feeds PDF.js via fetchRange/fetchChunk
  with parallel sub-requests for large ranges, Promise-based dedup of
  concurrent requests, and a persistent range cache

**Background preloader**
- Sequential page 1→N text extraction, pauses during interactive
  navigation (preloadPaused flag set in goToPage, cleared in renderPage)
- Debounced live search refresh (500ms) as pages load
- Search UI shows '(loading…)' suffix until all pages processed

**Loading indicators**
- SVG pie in toolbar: fills as pages load, fades on completion,
  turns red on errors with tooltip listing failed pages
- Debounced page-loading overlay (150ms) for uncached page navigation

**Cleanup**
- Remove old full-file chunked download and progress bar
- Remove extractAllPageText (replaced by preloader)
- Remove verbose debug logs (Coverage, RangeReq, cache hit)
2026-02-21 16:33:48 -08:00

123 lines
3.8 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PDF Viewer</title>
</head>
<body>
<div class="main">
<!-- Loading State -->
<div id="loading" class="loading">
<div class="spinner"></div>
<p id="loading-text">Loading PDF...</p>
</div>
<!-- Error State -->
<div id="error" class="error" style="display: none">
<div class="error-icon">⚠️</div>
<p id="error-message">An error occurred</p>
</div>
<!-- PDF Viewer -->
<div id="viewer" class="viewer" style="display: none">
<!-- Toolbar -->
<div class="toolbar">
<div class="toolbar-left">
<span id="pdf-title" class="pdf-title">Document</span>
<div id="loading-indicator" class="loading-indicator" style="display: none"
title="Loading pages...">
<svg viewBox="0 0 20 20" width="14" height="14">
<circle class="loading-indicator-bg" cx="10" cy="10" r="8"/>
<circle class="loading-indicator-arc" cx="10" cy="10" r="8"/>
</svg>
</div>
</div>
<div class="toolbar-center">
<button id="prev-btn" class="nav-btn" title="Previous page (←)">
◀
</button>
<div class="page-nav">
<input
id="page-input"
type="number"
class="page-input"
min="1"
value="1"
title="Go to page"
/>
<span id="total-pages" class="total-pages">of 1</span>
</div>
<button id="next-btn" class="nav-btn" title="Next page (→)">
▶
</button>
</div>
<div class="toolbar-right">
<button id="zoom-out-btn" class="zoom-btn" title="Zoom out (-)">
−
</button>
<span id="zoom-level" class="zoom-level">100%</span>
<button id="zoom-in-btn" class="zoom-btn" title="Zoom in (+)">
+
</button>
<button
id="search-btn"
class="search-btn"
title="Search (Ctrl+F)"
></button>
<button
id="fullscreen-btn"
class="fullscreen-btn"
title="Toggle fullscreen"
>
⛶
</button>
</div>
</div>
<!-- Search bar below toolbar, right-aligned -->
<div id="search-bar" class="search-bar" style="display: none">
<input
id="search-input"
class="search-input"
type="text"
placeholder="Search..."
autocomplete="off"
/>
<span id="search-match-count" class="search-match-count"></span>
<button
id="search-prev-btn"
class="search-nav-btn"
title="Previous (Shift+Enter)"
>
&#x25B2;
</button>
<button
id="search-next-btn"
class="search-nav-btn"
title="Next (Enter)"
>
&#x25BC;
</button>
<button
id="search-close-btn"
class="search-nav-btn"
title="Close (Esc)"
>
&#x2715;
</button>
</div>
<!-- Single Page Canvas Container -->
<div class="canvas-container">
<div class="page-wrapper">
<canvas id="pdf-canvas"></canvas>
<div id="highlight-layer" class="highlight-layer"></div>
<div id="text-layer" class="text-layer"></div>
</div>
</div>
</div>
</div>
<script type="module" src="./src/mcp-app.ts"></script>
</body>
</html>