How Pi-Web File Preview Supports Images, PDFs, and Audio: Implementation Guide
Pi-Web routes image, audio, and document files to specialized viewer components by detecting file extensions in components/FileViewer.tsx, streaming content via /api/files endpoints with live synchronization through EventSource watchers.
Pi-Web is an open-source file management interface that provides real-time previews for multimedia and document formats. The preview system in the agegr/pi-web repository intelligently selects rendering strategies based on file extensions, utilizing a modular architecture that separates detection logic from presentation components.
File Type Detection and Component Routing
The entry point for all file previews is the FileViewer function in components/FileViewer.tsx. This dispatcher delegates rendering to specialized components by evaluating boolean predicates imported from lib/file-types.ts.
Three primary detection functions determine the rendering path:
isImagePath– Returnstruefor supported image extensions (png, jpg, svg)isAudioPath– Matches audio extensions (mp3, wav, ogg)isDocumentPreviewPath– Identifies.pdfand.docxfiles
When a user selects a file, FileViewer evaluates these conditions in sequence and returns the appropriate viewer component, falling back to TextFileViewer for unsupported formats.
export function FileViewer({ filePath, cwd, sourceSessionId }: Props) {
if (isImagePath(filePath)) {
return <ImageViewer filePath={filePath} cwd={cwd} sourceSessionId={sourceSessionId} />;
}
if (isAudioPath(filePath)) {
return <AudioViewer filePath={filePath} cwd={cwd} sourceSessionId={sourceSessionId} />;
}
if (isDocumentPreviewPath(filePath)) {
return <DocumentViewer filePath={filePath} cwd={cwd} sourceSessionId={sourceSessionId} />;
}
return <TextFileViewer {...} />;
}
Image Preview with Live Sync
When isImagePath returns true, FileViewer renders the ImageViewer component. This viewer streams the binary file through the /api/files/... endpoint using the read operation type.
The implementation constructs a cache-busting URL via getFileApiUrl from lib/file-paths.ts, appending a version parameter (v) when file changes occur. The image loads in a standard <img> tag with objectFit: "contain" styling, and the component captures natural dimensions through the onLoad event to report image width and height.
Live synchronization works via EventSource connection to /api/files/<encoded>?type=watch, listening for change events that trigger URL updates and force browser reloads.
function ImageViewer({ filePath, cwd, sourceSessionId }: Props) {
const src = getFileApiUrl(filePath, "read", sourceSessionId, bust ? { v: bust } : undefined);
return (
<img
src={src}
alt={filePath}
onLoad={e => setNaturalSize({ w: e.currentTarget.naturalWidth, h: e.currentTarget.naturalHeight })}
onError={() => setError("Failed to load image")}
style={{ maxWidth: "100%", maxHeight: "100%", objectFit: "contain" }}
/>
);
}
Audio Preview and Metadata Extraction
Audio files trigger the AudioViewer component when isAudioPath identifies supported formats. Like images, audio streams through the read endpoint, but renders using the HTML5 <audio> element with native controls enabled.
The component sets preload="metadata" to optimize loading performance and listens for onLoadedMetadata to extract duration information. File size displays alongside the player, and the same EventSource infrastructure watches for file modifications, applying cache-busting parameters to the src attribute when changes are detected.
function AudioViewer({ filePath, cwd, sourceSessionId }: Props) {
const src = getFileApiUrl(filePath, "read", sourceSessionId, bust ? { v: bust } : undefined);
return (
<audio
controls
preload="metadata"
src={src}
onLoadedMetadata={e => setDuration(e.currentTarget.duration)}
onError={() => setError("Failed to load audio")}
/>
);
}
PDF and DOCX Document Preview
Document previews route to DocumentViewer, which handles both formats through <iframe> embedding but with distinct data strategies.
PDF Rendering Strategy
PDF files render using the browser's native viewer by streaming raw bytes (type="read") into an unsandboxed <iframe>. This allows full access to the PDF's interactive features while displaying file size metadata.
DOCX Conversion and Sandboxing
DOCX files require server-side conversion. The viewer requests type="preview" from the API, which returns HTML representation of the document content. For security, this HTML loads inside a sandboxed <iframe> (sandbox attribute set to empty string), preventing script execution while preserving formatting.
Both document types use EventSource watchers to refresh the iframe source when underlying files change.
function DocumentViewer({ filePath, cwd, sourceSessionId }: Props) {
const isPdf = getFileExt(filePath) === "pdf";
const previewUrl = isPdf
? getFileApiUrl(filePath, "read", sourceSessionId, bust ? { v: bust } : undefined)
: getFileApiUrl(filePath, "preview", sourceSessionId, bust ? { v: bust } : undefined);
return <iframe src={previewUrl} sandbox={isPdf ? undefined : ""} title={`Preview ${getFileName(filePath)}`} />;
}
Shared Live Synchronization Infrastructure
All three viewer types implement a common watching mechanism defined in components/FileViewer.tsx. Each component opens an EventSource connection to /api/files/<encoded>?type=watch immediately upon mounting.
The EventSource emits connected, change, and error events. When a change event fires, the component increments a cache-busting counter and appends it as the v query parameter to the resource URL. This forces the browser to bypass cached versions and reload the updated asset without requiring a full page refresh.
Summary
- Pi-Web detects file types using
isImagePath,isAudioPath, andisDocumentPreviewPathinlib/file-types.ts - Images render via
<img>tags streaming from/api/fileswith natural dimension reporting andobjectFit: "contain"styling - Audio uses HTML5
<audio>controls with metadata extraction for duration and file size information - PDFs embed raw bytes in unsandboxed iframes; DOCX files receive server-converted HTML in sandboxed iframes via the
previewendpoint - All viewers support real-time synchronization through EventSource watchers and cache-busting URL parameters that force reloads on file changes
Frequently Asked Questions
How does Pi-Web determine which preview component to use?
The FileViewer component in components/FileViewer.tsx evaluates file extensions using helper functions from lib/file-types.ts. It checks for images first, then audio, then documents, returning the appropriate specialized viewer or falling back to text view for unsupported formats.
Why are PDFs displayed differently from DOCX files?
PDFs render using the browser's native viewer by streaming raw bytes (type="read") into an unsandboxed iframe, allowing full PDF functionality. DOCX files require server-side conversion to HTML (type="preview") and load in sandboxed iframes to prevent potential security issues from converted markup.
How does live file synchronization work in Pi-Web previews?
Each viewer opens an EventSource connection to /api/files/<encoded>?type=watch that monitors file system changes. When the server detects modifications, it sends a change event that triggers a cache-busting parameter update, forcing the browser to reload the asset without refreshing the entire page.
Can Pi-Web preview other document formats besides PDF and DOCX?
Currently, isDocumentPreviewPath only matches .pdf and .docx extensions. Other formats fall back to TextFileViewer or binary download options, though the modular architecture in components/FileViewer.tsx allows for easy extension to support additional document types by adding new detection predicates and viewer components.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →