Skip to content

fix: set inline disposition for renderable blob elements - #2950

Open
axelray-dev wants to merge 3 commits into
Chainlit:mainfrom
axelray-dev:fix-2946-inline-blob-disposition
Open

fix: set inline disposition for renderable blob elements#2950
axelray-dev wants to merge 3 commits into
Chainlit:mainfrom
axelray-dev:fix-2946-inline-blob-disposition

Conversation

@axelray-dev

@axelray-dev axelray-dev commented Jun 7, 2026

Copy link
Copy Markdown

Fixes #2946

Summary

SQLAlchemyDataLayer.create_element never passed content_disposition to storage_provider.upload_file. Azure Blob Storage defaults to Content-Disposition: attachment in this case, which causes browser-renderable elements (PDF, images, audio, video) to render blank on chat resume because iframes and media tags refuse to display content served with an attachment disposition.

This fix sets content_disposition='inline' for browser-renderable MIME types while preserving the existing behavior for non-renderable uploads.

Scope

Only browser-renderable MIME types are affected:

  • application/pdf
  • image/*
  • audio/*
  • video/*

All other element types (generic files, text/plain, application/octet-stream, etc.) keep content_disposition=None, preserving the existing download behavior.

Changes

  • backend/chainlit/data/sql_alchemy.py: determine content_disposition before upload_file call based on element.mime
  • backend/tests/data/test_sql_alchemy.py: 3 new tests verifying disposition behavior for PDF, image, and text/plain elements

Testing

Backend tests run locally:

  • tests/data/test_sql_alchemy.py: 18/18 passed (including 3 new tests)
  • tests/data/: 71/71 passed, 1 pre-existing error (test_get_data_layer fails without built frontend)
  • ruff check: all passed
  • ruff format --check: all passed
  • git diff --check: clean

Note: pre-commit hooks could not run locally (frontend build OOMs on VPS). CI will verify lint and format.

Backward Compatibility

This change is fully backward-compatible per AGENTS.md requirements:

  • Non-renderable element types are unaffected
  • The content_disposition parameter was already accepted by all storage clients but never used by SQLAlchemyDataLayer
  • The fix follows the same pattern used in chainlit_data_layer.py

Summary by cubic

Serve browser-renderable blobs with inline Content-Disposition so PDFs, images, audio, and video display in iframes/media on chat resume. The policy is now centralized on Element and applied across data layers; non-renderable files keep download behavior.

  • Bug Fixes
    • Centralized disposition in Element.get_content_disposition() and used by SQLAlchemyDataLayer, ChainlitDataLayer, and DynamoDB.
    • Set inline for pdf, image, audio, video; keep default for others. ChainlitDataLayer preserves attachment; filename=... for generic files (non-GCS). Tests added for inline/default cases and Element behavior.

Written for commit 3d7346e. Summary will update on new commits.

Review in cubic

SQLAlchemyDataLayer.create_element never passed content_disposition to
storage_provider.upload_file, so Azure Blob Storage defaulted to
Content-Disposition: attachment. This caused PDF elements (and other
browser-renderable types) to render blank on chat resume because the
browser refused to display them in an iframe.

Set content_disposition='inline' for browser-renderable MIME types
(application/pdf, image/*, audio/*, video/*) while preserving the
existing behavior (None) for non-renderable uploads like generic files.

Fixes Chainlit#2946

Co-Authored-By: OpenAI Codex <noreply@openai.com>
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. backend Pertains to the Python backend. bug Something isn't working unit-tests Has unit tests. labels Jun 7, 2026

@cubic-dev-ai cubic-dev-ai Bot 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.

No issues found across 2 files

Re-trigger cubic

@dokterbob

Copy link
Copy Markdown
Collaborator

@codex review

@dokterbob dokterbob left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please see my comment.

Comment thread backend/chainlit/data/sql_alchemy.py Outdated
if element.mime:
if element.mime in _RENDERABLE_MIME_TYPES or element.mime.startswith(
_RENDERABLE_MIME_PREFIXES
):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a more structural/consistent way to define renderable mime types?

I don't think this definition belongs inside a storage engine. I also don't think it should be in a single storage engine (how are others doing it?).

Sanity would dictate that some piece of code somewhere decides whether some attachment is rendered inline. Probably closer to the frontend than the storage backend. If this code already determines it, we should get the info from where it's determined, and there should be a well-defined interface for all data backends to look this up in the same way.

Move renderable disposition policy out of SQLAlchemy into
Element.get_content_disposition based on element.type, and use it in
SQLAlchemy, DynamoDB, and ChainlitDataLayer so browser-rendered media
gets inline disposition consistently.
@axelray-dev

Copy link
Copy Markdown
Author

Moved the renderable disposition policy out of SQLAlchemy into Element.get_content_disposition().

It is based on element.type (image/pdf/audio/video), not MIME, because that is what the frontend uses for rendering. Element.display is still only UI placement.

SQLAlchemy, DynamoDB, and ChainlitDataLayer now all use the same helper. ChainlitDataLayer still keeps its previous attachment/GCS fallback for non-renderable files.

Also fixed the backend typecheck issues in the SQLAlchemy tests.

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Pertains to the Python backend. bug Something isn't working size:M This PR changes 30-99 lines, ignoring generated files. unit-tests Has unit tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cl.Pdf element blank on chat resume with Azure Blob storage — Content-Disposition: attachment

2 participants