# How pi-web's FileViewer Previews Images, Audio, PDFs, and Source Code

> Discover how pi-web's FileViewer previews images, audio, PDFs, and source code. Learn about its specialized rendering engines and live synchronization using SSE.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-07-31

---

**pi-web's FileViewer component selects specialized rendering engines based on file extensions, using Server-Sent Events (SSE) for live synchronization and dedicated sub-components for images, audio, documents, and source code.**

The `agegr/pi-web` repository provides a browser-based file management interface with a sophisticated preview system. At the heart of this system lies the **FileViewer** component, which automatically detects file types and renders appropriate previews for images, audio files, PDFs, and source code. Located in [`components/FileViewer.tsx`](https://github.com/agegr/pi-web/blob/main/components/FileViewer.tsx), this component orchestrates multiple specialized viewers that handle real-time updates and format-specific rendering.

## File Type Detection and Routing Logic

The entry point [`components/FileViewer.tsx`](https://github.com/agegr/pi-web/blob/main/components/FileViewer.tsx) determines which viewer to render by evaluating the file path against type-checking utilities defined in [`lib/file-types.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-types.ts). The selection logic uses a cascading conditional structure at the bottom of the component:

```tsx
if (isImagePath(filePath)) { … }
if (isAudioPath(filePath)) { … }
if (isDocumentPreviewPath(filePath)) { … }

```

These helper functions—`isImagePath`, `isAudioPath`, and `isDocumentPreviewPath`—map file extensions to MIME types and preview categories. When no specific media type matches, the component defaults to `TextFileViewer` for source code and plain text files.

## Image Preview with Live Synchronization

When `isImagePath` returns `true`, the `ImageViewer` sub-component renders the file. This implementation opens an `EventSource` connection to `/api/files/...` with `type=watch` to receive Server-Sent Events (SSE) for live file synchronization.

The image source URL is constructed using `getFileApiUrl(..., "read")` and includes a cache-busting query parameter `v` that increments on every change event to prevent stale renders:

```tsx
const src = getFileApiUrl(filePath, "read", sourceSessionId, bust ? { v: bust } : undefined);
return <img src={src} alt={filePath} style={{ maxWidth: "100%", maxHeight: "100%" }} />;

```

The viewer displays a toolbar containing a live-sync indicator, file size information, and a download button. The image itself renders via a standard `<img>` tag with `objectFit: "contain"` and lazy loading enabled.

## Audio Preview with Metadata Support

For audio files identified by `isAudioPath`, the `AudioViewer` component handles playback. Like the image viewer, it maintains an SSE connection to watch for file changes and updates the audio source accordingly.

The component calculates duration by listening to the `onLoadedMetadata` event on the audio element:

```tsx
const src = getFileApiUrl(filePath, "read", sourceSessionId, bust ? { v: bust } : undefined);
return <audio controls src={src} onLoadedMetadata={(e) => setDuration(e.currentTarget.duration)} />;

```

The toolbar displays the file extension, size, computed duration, live-sync status, and download functionality.

## Document Preview for PDF and DOCX Files

The `DocumentViewer` component activates when `isDocumentPreviewPath` identifies a document file. It distinguishes between PDF and DOCX formats using `documentPreviewKind` logic.

**PDF rendering** uses the standard read endpoint via an iframe:

```tsx
const previewUrl = isPdf
  ? getFileApiUrl(filePath, "read", sourceSessionId, bust ? { v: bust } : undefined)
  : getFileApiUrl(filePath, "preview", sourceSessionId, bust ? { v: bust } : undefined);
return <iframe src={previewUrl} title="PDF preview" style={{ width: "100%", height: "100%" }} />;

```

**DOCX handling** routes through the API's `preview` endpoint, which returns rendered HTML. The component checks file size against `DOCX_PREVIEW_MAX_BYTES` (10 MiB) via the `meta` endpoint and refuses preview if the limit is exceeded. Both formats support live synchronization through SSE.

## Source Code Rendering and Markdown Preview

All other files route to `TextFileViewer`, which provides syntax highlighting and text analysis features. The component fetches content via the `read` endpoint and determines the programming language from the file extension.

Source code renders using `react-syntax-highlighter` with theme-aware styling:

```tsx
<SyntaxHighlighter
  language={language === "text" ? "plaintext" : language}
  style={isDark ? vscDarkPlus : vs}
  showLineNumbers
  wrapLongLines={wrapLines}
  renderer={(props) => <SourceCodeRenderer {...props} wrapLines={wrapLines} />}
>
  {content}
</SyntaxHighlighter>

```

For Markdown files, a toggle switches between raw source and rendered preview using `react-markdown`, supporting embedded images, links, and Mermaid diagrams. Additional features include line number annotations, wrap-line toggling, and a "mention selected lines" shortcut for collaboration.

## Summary

- **Type Detection**: [`lib/file-types.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-types.ts) provides `isImagePath`, `isAudioPath`, and `isDocumentPreviewPath` to classify files by extension.
- **Live Sync**: All viewers implement Server-Sent Events via `EventSource` to watch files for changes and update content in real-time.
- **Media Handling**: `ImageViewer` and `AudioViewer` use the `read` API endpoint with cache-busting query parameters.
- **Document Support**: `DocumentViewer` renders PDFs directly in iframes and converts DOCX files via the `preview` endpoint with a 10 MiB size limit.
- **Code Display**: `TextFileViewer` leverages `react-syntax-highlighter` for language-specific formatting and supports Markdown rendering with diagrams.

## Frequently Asked Questions

### How does pi-web FileViewer handle file changes while previewing?

The component establishes an `EventSource` connection to the `/api/files/...` endpoint with the `type=watch` parameter. When the underlying file changes on disk, the server pushes events to the client, triggering a cache-bust parameter update that refreshes the preview without manual reloading.

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

According to the source code in [`components/FileViewer.tsx`](https://github.com/agegr/pi-web/blob/main/components/FileViewer.tsx), the `DocumentViewer` checks file size against `DOCX_PREVIEW_MAX_BYTES`, which is set to **10 MiB** (10,485,760 bytes). Files exceeding this limit display a size error rather than rendering the preview.

### Which syntax highlighting themes does pi-web FileViewer support?

The `TextFileViewer` component imports `vscDarkPlus` and `vs` themes from `react-syntax-highlighter`. It dynamically selects between these based on the application's current dark mode state, providing either a dark or light code viewing experience.

### Can pi-web FileViewer display Mermaid diagrams in Markdown files?

Yes. When previewing Markdown files, the `TextFileViewer` uses `react-markdown` with plugins that support embedded Mermaid diagrams, alongside standard Markdown features like images and links.