Skip to content

✨ feat: Preview PDF Code-Interpreter Attachments Instead of Downloading - #16282

Open
TomasPalsson wants to merge 18 commits into
LibreChat-AI:devfrom
TomasPalsson:feat/pdf-preview-code-attachments-upstream
Open

TomasPalsson wants to merge 18 commits into
LibreChat-AI:devfrom
TomasPalsson:feat/pdf-preview-code-attachments-upstream

Conversation

@TomasPalsson

@TomasPalsson TomasPalsson commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Clicking a PDF (or text) attachment produced by code execution force-downloads it instead of previewing it. FilePreviewDialog already fetches an authed blob and renders it in an <iframe> for persisted PDF and text files, but the attachment chip in Attachment.tsx always called handleDownload on click and never opened the dialog. The dialog also only fetched persisted TFile records through the owner/share file-ACL path, not code-interpreter outputs served from /api/files/code/download/:session_id/:fileId, so a download-fallback code-output attachment (no persisted file record) would have opened on a dead, empty dialog even if a click had routed to it.

FileAttachment now opens FilePreviewDialog on click whenever getPreviewKind(...) says the file is previewable and its bytes are actually fetchable — either a persisted file (file_id plus a locally-stored source, checked directly) or a code-interpreter download-fallback attachment (a session-scoped filePath, no file_id/source, matched by the new isCodeOutputAttachment helper in LogLink.tsx). The dialog picks its preview/download hook pair off that same check: useCodeOutputPreviewBlob/useCodeOutputDownload for a code-output attachment, or the existing owner/share hooks otherwise. Non-previewable files (zip, …) and attachments backed by an absolute http(s) URL still download directly on click, and the dialog's own download button keeps working either way.

How it works

FileAttachment (Parts/Attachment.tsx) click
  previewKind = getPreviewKind(filename, type, source)
  canFetchPreview = (file_id && isLocallyStoredSource(source)) || isCodeOutputAttachment(filepath, file_id, source)
  canPreview = previewKind && canFetchPreview
  → canPreview: setPreviewOpen(true), renders <FilePreviewDialog filePath={filepath} .../>
  → !canPreview: handleDownload (unchanged)

FilePreviewDialog (FilePreviewDialog.tsx)
  isCodeOutput = isCodeOutputAttachment(filePath, fileId, fileSource)
  → isCodeOutput: preview/download via useCodeOutputPreviewBlob(filePath) / useCodeOutputDownload(filePath)
  → !isCodeOutput: preview/download via useFilePreviewBlob(fileId) / useFileDownload(fileId)   # existing owner/share path

No server changes: the code-output route still sends Content-Disposition: attachment, and fetching it as a blob sidesteps that the same way the existing owner/share preview path already does. The preview renders through an <iframe>, not <object>/<embed>, so it stays compatible with the CSP's object-src 'none'.

Type of change

  • Feature

Testing

Tested environments/configuration:

Verified with the automated tests below; no manual browser run is recorded here yet.

Automated tests:

  • New Parts/__tests__/Attachment.test.tsx: a persisted PDF and a download-fallback PDF (no file_id/source) each open the preview dialog instead of downloading, and a non-previewable download-fallback file (zip) still downloads.
  • Parts/__tests__/ArtifactRouting.test.tsx and Parts/__tests__/TextAttachment.test.tsx: mock updates only, keeping LogLink's pure helpers (isLocallyStoredSource, isCodeOutputAttachment) real instead of mocking them away.
  • __tests__/FilePreviewDialog.lifecycle.test.tsx: added a case for a download-fallback code-output attachment (no fileId/fileSource/fileType) previewing and downloading through filePath.
  • npx jest src/components/Chat/Messages/Content/ src/data-provider/Files → 80 suites, 1198 tests pass.
  • eslint clean on the changed files; npx tsc --noEmit (client) passes.

Screenshots / recordings

Screenshots not attached yet.

Risk / compatibility

Client-only change, no server, schema, or config changes. This changes click behavior for any previewable persisted attachment (PDF, text), not just code-interpreter output: clicking now opens the preview dialog instead of downloading directly, though the dialog's own download button still downloads the same bytes. Non-previewable files and http(s)-sourced attachments keep the original direct-download click.

Checklist

  • I reviewed my own changes
  • Relevant tests have been added or updated
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors
  • User-facing or complex behavior is documented where necessary
  • Required dependency changes have been merged/published
  • Required documentation PR: N/A

Clicking a code-execution PDF attachment force-downloaded it. Route
previewable attachments (PDF, text) through the existing file preview
dialog instead, fetching code-interpreter output bytes through the same
session-scoped download path the chip's own download button already uses.
Copilot AI lite review requested due to automatic review settings September 24, 2026 09:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions
github-actions Bot changed the base branch from main to dev September 24, 2026 09:44
@github-actions

Copy link
Copy Markdown
Contributor

👋 Thanks for the contribution! LibreChat merges all changes into dev first — main only moves at release time — so this pull request's base branch was switched from main to dev automatically.

Nothing is needed from you; your commits, reviews and discussion are unchanged. If the diff now shows files you did not touch, rebase onto dev:

git remote add upstream https://github.com/LibreChat-AI/LibreChat.git
git fetch upstream dev
git rebase upstream/dev
git push --force-with-lease

Maintainers: apply the target: main label and restore the base branch if this one genuinely belongs on main.

@TomasPalsson

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

The prior commit's code-output check (`fileSource === 'execute_code'`)
never fires: persisted code outputs carry a real storage source + file_id
(already handled by the owner path) and download-fallback outputs (no
storage strategy, or oversized) carry neither, only a session-scoped
filePath. The dialog's fileId-gated effect/download guards then left the
fallback case opening to a dead, empty dialog.

Add `isCodeOutputAttachment`, mirroring LogLink's own download-routing
discriminator, and use it both to pick the dialog's fetch path and to
decide up front whether an attachment is fetchable at all before routing
its click to the dialog instead of a direct download.
@TomasPalsson
TomasPalsson marked this pull request as ready for review September 24, 2026 10:17

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants