🗂️ feat: Preview Large PowerPoint and Word Outputs from File Storage - #16518
Open
TomasPalsson wants to merge 32 commits into
Open
TomasPalsson wants to merge 32 commits into
TomasPalsson wants to merge 32 commits into
Conversation
…version Two cards for the same file across different messages now both render instead of one message winning the only visible chip. Registration into the artifact panel now compares update timestamps (falling back to a mount-order tie-break) so an older card mounting after a newer one, or remounting after the newer card unmounts, can never clobber the newer content. Search and shared conversation views now scope every rendered part to its own message so the same fix applies there.
Extends the JSDOM pptx bootstrap harness with an options object (native aspect ratio, renderer install/behavior, a capturable 8s safety-net timer, and a mutable render-slot width) so it can drive every panel width, a 30-slide deck with no inner scroll box, a resize-triggered refit, both 16:9 and 4:3 decks, and each fallback trigger (renderer missing, renderer throws, empty slide list, empty slide wrappers, render timeout). Clears the pptx-preview library's own inline width/background once slides are wrapped, since JSDOM's getComputedStyle doesn't apply the existing stylesheet's !important override the way a real browser does, and restores the 16px spacing between stacked slide blocks now that they live inside the library's own wrapper box instead of directly under #lc-render.
…sage A message that ran a tool twice on the same output file (e.g. rewriting data.zip) showed a card for every run. mapAttachments now collapses attachments that share a file identity to their last occurrence before grouping by tool call, so a repeated file surfaces once, under its newest run. Non-file attachments (no file_id or filepath) are untouched.
A code-execution diagram rewritten in a later turn now shows a card on every message holding that file, but opening an older message's card still displayed the stale content. Each ToolMermaidArtifact now offers its version to a shared per-file "newest seen" record on mount, and hands the newest entry to Mermaid for registration while keeping its own inline render unchanged. Corrects two comments claiming shared conversation views mount with no message context, which Share/Message already provides.
… copy over a later unlinked duplicate, and key id-less files by filepath instead of filename
… of the message pipeline Two id-less attachments that share a display name but live at different filepaths no longer collapse into one chip in the folded attachment group.
…med files each keep their own card
TomasPalsson
marked this pull request as ready for review
September 29, 2026 18:37
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
Summary
When code execution produces a PowerPoint or Word file larger than 350 KB, the side panel never shows the real document. The stored preview lives in the file record's
textfield, which is capped at 512 KB, and the document's own bytes ride inline inside that preview. So files between 350 KB and 2 MB fall back to a plain slide or text outline, and anything over 2 MB is refused before it is opened and labelledparser-error, which shows "Preview unavailable" for a file that downloads fine.This PR stores a small preview page (a "shell") for
.pptxand.docxoutputs above 350 KB, with an empty data slot and the existing text fallback. When the panel opens, the browser fetches the file's bytes through the existing authenticated download route (or the share route for a shared link) and fills the slot, so decks and documents up to 25 MB preview as the real document. Files above the limit fail with a new, honestpreviewError: 'too-large'instead ofparser-error. Files of 350 KB or less keep today's inline path unchanged.The behavior is controlled by a new
fileConfig.officePreviewsetting inlibrechat.yaml(enabled, defaulttrue;fileSizeLimitin MB, default25, minimum0).Related to #16496
Documentation: LibreChat-AI/docs#790
How it works
The contract (
OFFICE_FILE_SHELL_MARKER,OFFICE_DOC_DATA_SLOT,isOfficeFileShell,fillOfficeFileShell) and the setting live inpackages/data-provider/src/file-config.ts, so server and client share one definition. The byte limit isenabled ? max(2 MB, fileSizeLimit) : 2 MBfor pptx/docx and 2 MB for everything else. LibreOffice, when enabled, still runs first and is untouched. The shell is never base64-encoded on the server, so a 25 MB deck is not copied into a ~33 MB string.Mixed versions: an older client that receives a shell sees an empty data slot, and both bootstraps now show the text fallback immediately (
no-data) instead of waiting for the renderer timeout. A newer client reading an older inline row does not fetch anything and renders it as before. If a shell's fallback would push it past 512 KB, the fallback is dropped and the notice says the document is too large for the simplified preview and can be downloaded, rather than pointing at content that is not there.Type of change
Testing
Tested environments/configuration:
.docx(40 headings, 160 paragraphs, 10 PNGs), run through the realextractCodeArtifactTextwith default settings: both produce shells (17,420 B and 7,672 B).fillOfficeFileShelland rendered: deck shows all 3 slides, document renders with all 10 images, fallback hidden, no console errors.<img>at natural size), so small decks do not drop pictures today. The 4 MB deck's slide pictures are painted by the renderer without<img>elements; picture fidelity inpptx-previewis out of scope here.Automated tests:
packages/data-provider:npx jest && npx tsc --noEmit— 2208 passed (1 pre-existing skip). New: setting defaults, MB→bytes merge, negative value rejected naming the field, slot filled once and literally.packages/api:npx jest src/files && npx tsc --noEmit— 1168 passed. New: shell building for pptx/docx, inline kept at ≤ 350 KB, trimmed fallback notice, no base64 on the shell path, shell<head>(CDN scriptsrc/integrity/crossorigin, CSP) identical to inline and unchanged by filling, a ≥ 4 MB deck's shell ≤ 16 KB without its fallback, every size boundary (350 KB, 350 KB + 1, limit, limit + 1), a yaml limit and a disabled setting reaching routing and the label, bootstraps showing the fallback at once for an empty slot (JSDOM).api:npx jest server/services/Files— 455 passed. New: the merged setting is passed to the extractor and the failure label.client:npx jest src/hooks/Artifacts src/components/Artifacts src/data-provider/Files && npm run typecheck— 213 passed. New: fill with the file bytes, share route, fetch failure keeps the shell, legacy inline untouched, preparing state, a second artifact with an identical shell reports loading instead of the first one's document, and the real hook inside the panel (editor gets the stored shell, preview the filled one).--max-warnings=0) and Prettier clean on every changed file.Screenshots / recordings
No "before" capture: at
dev, the same 4 MB deck produces no preview and its card shows "Preview unavailable" (labelledparser-error); a 1.1 MB document shows the plain text outline. The "after" images below are the stored shells rendered in headless Chrome; the running app was checked by hand.Screenshots (4 MB deck and 1.1 MB document, each filled and shell-only) are being added through the web editor.
Risk / compatibility
fileConfig.officePreviewdefaults to enabled at 25 MB, so large decks change from "Preview unavailable" to a real preview without any config.enabled: falserestores today's routing (inline ≤ 350 KB, text outline up to 2 MB) and labels larger filestoo-large..potxshares the presentation bucket and takes the shell path, since it is the same OOXML deck format. A large file that hits the render timeout is still labelledtimeout, nottoo-large:too-largeis reserved for files over the limit. While bytes load, the panel shows the existing "Preparing preview…" state in place of both tabs.Checklist