How pi-web Handles File Previews for Images, Audio, PDF, and DOCX

pi-web delegates file preview rendering to type-specific viewer components within a central FileViewer orchestrator, enforcing 10 MiB size limits for images and DOCX files while streaming content through dedicated API endpoints.

The agegr/pi-web repository provides a React-based file manager where the pi-web file preview system distinguishes between binary assets and documents to optimize rendering performance. The implementation centers on the FileViewer component in components/FileViewer.tsx, which coordinates with utility functions in lib/file-types.ts to classify extensions and select the appropriate rendering strategy.

File Type Classification

Before rendering, the system categorizes files using three helper predicates exported from lib/file-types.ts:

  • isImagePath(filePath) – Validates extensions against the IMAGE_EXT_TO_MIME mapping.
  • isAudioPath(filePath) – Checks extensions against AUDIO_EXT_TO_MIME.
  • isDocumentPreviewPath(filePath) – Determines if the file is a PDF or DOCX via documentPreviewKind.

The FileViewer component applies these checks sequentially to instantiate the correct sub-component:

// components/FileViewer.tsx (lines 78-89)
if (isImagePath(filePath))   return <ImageViewer … />;
if (isAudioPath(filePath))   return <AudioViewer … />;
if (isDocumentPreviewPath(filePath)) return <DocumentViewer … />;
return <TextFileViewer … />;

Image Preview with Live Reload

The ImageViewer (starting at line 109 in components/FileViewer.tsx) renders bitmaps by fetching a signed URL from getFileApiUrl(filePath, "read"). To prevent caching stale data, the component maintains a bust state variable that appends a cache-buster query parameter to the image source.

A critical feature of the pi-web file preview system is real-time synchronization. Each viewer opens an EventSource connection to the watch endpoint. When the server emits a change event, the component increments bust, forcing the browser to reload the image. The implementation wraps the native <img> element with CSS constraints (max-width: 100%, object-fit: contain) and displays metadata such as dimensions and file size.

The server enforces a hard limit: images exceeding IMAGE_PREVIEW_MAX_BYTES (10 MiB) are rejected by the API endpoint defined in app/api/files/[...path]/route.ts.

Audio Streaming and Metadata

For audio assets, the AudioViewer (lines 42-56) utilizes the same read endpoint but renders a native <audio> element with controls and preload="metadata". When the browser fires the loadedmetadata event, the component extracts the duration and surfaces it in the toolbar.

Like images, audio previews support live synchronization via EventSource. The system does not impose a specific size cap for audio streams beyond the generic file-size checks performed by the meta endpoint.

PDF and DOCX Document Rendering

The DocumentViewer (lines 58-71) handles compound documents through two distinct paths:

PDF files stream directly through the read endpoint and render inside an <iframe> pointing to the raw file URL. No sandbox restrictions are applied, allowing the browser's native PDF viewer to execute.

DOCX files require server-side conversion. The viewer requests type="preview" from getFileApiUrl, which triggers a conversion to HTML on the backend. Before fetching, the component checks the file metadata; if the size exceeds DOCX_PREVIEW_MAX_BYTES (10 MiB), it displays an error instead of attempting conversion. The resulting HTML loads into an <iframe> with an empty sandbox attribute to isolate the converted content.

Usage Examples

To open an image with live-update capabilities:

import { FileViewer } from "@/components/FileViewer";

<FileViewer
  filePath="/home/user/assets/photo.png"
  cwd="/home/user"
  sourceSessionId={sessionId}
/>

For DOCX files, the component automatically detects the extension and applies the size limit:

<FileViewer
  filePath="reports/annual-report.docx"
  cwd="/home/user/project"
  sourceSessionId={sessionId}
/>

PDF files load directly without conversion overhead:

<FileViewer
  filePath="static/brochure.pdf"
  cwd="/home/user/project"
  sourceSessionId={sessionId}
/>

Summary

  • Centralized routing: The FileViewer component in components/FileViewer.tsx delegates to specialized viewers based on lib/file-types.ts predicates.
  • Size enforcement: Images and DOCX files are capped at 10 MiB (IMAGE_PREVIEW_MAX_BYTES and DOCX_PREVIEW_MAX_BYTES).
  • Live sync: All viewers utilize EventSource connections to the watch endpoint for real-time content updates.
  • Endpoint differentiation: Images and PDFs use type="read", while DOCX files require type="preview" for server-side HTML conversion.
  • Secure rendering: Documents render in <iframe> elements, with DOCX previews sandboxed separately from native PDF viewer contexts.

Frequently Asked Questions

What is the maximum file size for file previews in pi-web?

Images and DOCX documents are limited to 10 MiB by the IMAGE_PREVIEW_MAX_BYTES and DOCX_PREVIEW_MAX_BYTES constants. Exceeding these limits triggers an error state in the ImageViewer or DocumentViewer components. Audio files and PDFs do not have explicit preview size limits beyond standard API constraints.

How does pi-web synchronize preview content when a file changes?

The system establishes an EventSource connection to the watch endpoint upon mounting a viewer component. When the server broadcasts a change event, the viewer updates a bust state variable, appending a cache-busting query parameter to force a fresh fetch of the file content.

Why do PDF and DOCX files use different API endpoints for previews?

PDFs render natively in the browser, so the DocumentViewer requests type="read" to stream the raw binary. DOCX files require server-side conversion to HTML, necessitating the type="preview" endpoint. This distinction allows pi-web to handle complex Office documents without client-side parsing libraries.

What happens if a file type is not supported for preview?

When a file fails the isImagePath, isAudioPath, and isDocumentPreviewPath checks, the FileViewer falls back to TextFileViewer, which attempts to display the content as plain text or code. Binary files that cannot be interpreted as text will typically show encoding errors or empty states depending on the browser context.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →