Skip to content

Conversation

@sfc-gh-nbellante
Copy link
Contributor

@sfc-gh-nbellante sfc-gh-nbellante commented Jan 8, 2026

Describe your changes

Added image preview thumbnails for image files in chat input file chips. When users upload image files to the chat input, a thumbnail preview of the image is now displayed in the file chip instead of a generic file icon. This provides better visual feedback and makes it easier to identify uploaded images.

Screenshot or video (only for visual changes)

The PR updates the snapshot tests for file chips in both light and dark themes across all browsers to reflect the new image preview functionality.

Testing Plan

  • Unit Tests: Added the useImagePreview hook to create and manage blob URLs for image previews
  • E2E Tests: Updated the file chip theming test to use a real PNG image instead of fake data so the image preview renders correctly in snapshots
  • Added a minimal valid 8x8 smiley face PNG constant for testing image previews

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

@snyk-io
Copy link
Contributor

snyk-io bot commented Jan 8, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link
Contributor Author

sfc-gh-nbellante commented Jan 8, 2026

@github-actions
Copy link
Contributor

github-actions bot commented Jan 8, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13547/streamlit-1.52.2-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13547.streamlit.app (☁️ Deploy here if not accessible)

@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/file-chip-image-preview branch 2 times, most recently from 95af98d to d4253be Compare January 8, 2026 21:19
@sfc-gh-nbellante sfc-gh-nbellante marked this pull request as draft January 8, 2026 21:34
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/file-chip-image-preview branch from d4253be to d3cd7a1 Compare January 9, 2026 03:10
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/chat-input-dynamic-stacked-layout branch 2 times, most recently from b42d84f to c644a77 Compare January 9, 2026 03:16
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/file-chip-image-preview branch from d3cd7a1 to 3f4716f Compare January 9, 2026 03:16
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/chat-input-dynamic-stacked-layout branch from c644a77 to 79baf6a Compare January 9, 2026 03:17
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/file-chip-image-preview branch from 3f4716f to 1855aac Compare January 9, 2026 03:17
@sfc-gh-nbellante sfc-gh-nbellante changed the base branch from feat/chat-input-dynamic-stacked-layout to graphite-base/13547 January 9, 2026 03:21
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/file-chip-image-preview branch from 1855aac to d9ed691 Compare January 9, 2026 03:21
@sfc-gh-nbellante sfc-gh-nbellante changed the base branch from graphite-base/13547 to develop January 9, 2026 03:21
@sfc-gh-nbellante sfc-gh-nbellante added security-assessment-completed Security assessment has been completed for PR change:chore PR contains maintenance or housekeeping change impact:users PR changes affect end users labels Jan 9, 2026
@sfc-gh-nbellante sfc-gh-nbellante changed the title feat: show image thumbnail preview in file chip icon [feat] Add image thumbnails to st.chat_input file uploads Jan 10, 2026
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/file-chip-image-preview branch 2 times, most recently from b584b83 to ca6ffd9 Compare January 11, 2026 19:25
@sfc-gh-nbellante sfc-gh-nbellante changed the base branch from develop to graphite-base/13547 January 11, 2026 20:57
@sfc-gh-nbellante sfc-gh-nbellante changed the base branch from graphite-base/13547 to fixit/file-chip-min-max-width-v2 January 11, 2026 22:51
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/file-chip-image-preview branch from 908aff1 to ea9f235 Compare January 11, 2026 23:18
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the fixit/file-chip-min-max-width-v2 branch from 49b1769 to 1c4a42f Compare January 11, 2026 23:18
@sfc-gh-nbellante sfc-gh-nbellante changed the base branch from fixit/file-chip-min-max-width-v2 to graphite-base/13547 January 11, 2026 23:59
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/file-chip-image-preview branch from f9309c2 to 49dfc04 Compare January 12, 2026 00:10
@sfc-gh-nbellante sfc-gh-nbellante changed the base branch from graphite-base/13547 to fixit/file-chip-min-max-width-v2 January 12, 2026 00:10
@sfc-gh-nbellante sfc-gh-nbellante added the ai-review If applied to PR or issue will run AI review workflow label Jan 12, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Jan 12, 2026
@github-actions
Copy link
Contributor

Summary

This PR adds image preview thumbnails for image files uploaded via st.chat_input. When users upload image files, a thumbnail preview is displayed in the file chip instead of a generic file icon, providing better visual feedback.

Changes include:

  • New useImagePreview hook to create and manage blob URLs for image previews
  • New isImageFile utility function to determine if a file is an image based on extension
  • New StyledChatUploadedFileImagePreview styled component for the thumbnail
  • Updates to ChatUploadedFileIcon component to render image previews
  • Updated E2E test to use a real PNG instead of fake data for accurate snapshot testing

Code Quality

The code quality is good overall and follows Streamlit's established patterns:

Strengths:

  • The useImagePreview hook correctly follows React best practices:
    • Uses useMemo for synchronous URL creation during render (lines 33-38 in useImagePreview.ts)
    • Uses useEffect only for cleanup to revoke blob URLs (lines 41-47)
    • This aligns with the "You Might Not Need an Effect" guidelines
  • Proper memory management with URL.revokeObjectURL in cleanup
  • The styled component follows naming conventions (Styled prefix)
  • The image preview has proper alt attribute for accessibility (alt={fileInfo.name})
  • The icon container correctly has overflow: "hidden" to contain image previews within bounds

Minor observations:

  • The SMILEY_PNG constant in st_chat_input_test.py (lines 1663-1670) is appropriately documented and minimal in size for testing

Test Coverage

E2E Tests:

  • The existing test_file_chip_theming test was updated to use a real PNG (SMILEY_PNG) instead of fake data, ensuring the image preview renders correctly in snapshots
  • Snapshot tests cover both light and dark themes across all browsers (chromium, firefox, webkit)
  • This follows the E2E best practices of using real data for visual testing

Unit Tests: ⚠️ Missing Coverage

The following unit tests should be added:

  1. useImagePreview hook tests (new file needed: useImagePreview.test.ts):

    • Should create blob URL for image files (e.g., .png, .jpg)
    • Should return null for non-image files (e.g., .pdf, .txt)
    • Should return null when file is undefined
    • Should revoke blob URL on unmount (cleanup verification)
    • Should revoke old blob URL when file changes
  2. isImageFile function tests (add to getFileTypeIcon.test.ts):

    • Should return true for image extensions (jpg, jpeg, png, gif, webp, svg, bmp)
    • Should return false for non-image extensions
    • Should handle case-insensitive extensions
    • Should return false for files without extension
  3. ChatUploadedFile component tests (add to ChatUploadedFile.test.tsx):

    • Should render image preview when file is an image
    • Should render file type icon when file is not an image
    • Should have correct data-testid="stChatInputFileImagePreview" for image previews

Backwards Compatibility

Fully backwards compatible

  • The changes are additive - image files now show previews, non-image files continue to show icons
  • No API changes to user-facing Streamlit functions
  • No changes to protobuf definitions
  • Existing behavior for non-image files is unchanged

Security & Risk

No security concerns identified

  • Blob URLs are created from user-uploaded File objects, which is the standard browser pattern
  • Blob URLs are properly revoked in the cleanup function to prevent memory leaks
  • Image files are validated by extension using the existing IMAGE_EXTENSIONS set
  • The image preview only renders for successfully uploaded files (status.type === "uploaded")

Minor consideration:

  • Large images will be loaded in full into memory via createObjectURL. However, this is the standard browser behavior and the files are already loaded for upload purposes, so no additional memory impact.

Recommendations

  1. Add unit tests for useImagePreview hook - This is the primary recommendation. The hook manages browser resources (blob URLs) and should have tests verifying:

    // Example test structure for useImagePreview.test.ts
    describe("useImagePreview", () => {
      it("creates blob URL for image files")
      it("returns null for non-image files")
      it("returns null when file is undefined")
      it("revokes blob URL on cleanup")
    })
  2. Add unit tests for isImageFile function - Add to getFileTypeIcon.test.ts:

    describe("isImageFile", () => {
      it.each(["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"])(
        "returns true for .%s files",
        extension => expect(isImageFile(`file.${extension}`)).toBe(true)
      )
      it("returns false for non-image extensions")
    })
  3. Add component test for image preview rendering - Add to ChatUploadedFile.test.tsx:

    it("renders image preview for image files", () => {
      // Create fileInfo with image file
      // Verify stChatInputFileImagePreview is rendered
    })

Verdict

CHANGES REQUESTED: The implementation is solid and follows best practices, but the PR is missing unit tests for the new useImagePreview hook and isImageFile function. Unit tests are important for verifying blob URL memory management and ensuring the image detection logic works correctly across edge cases.


This is an automated AI review. Please verify the feedback and use your judgment.

@github-actions github-actions bot added the do-not-merge PR is blocked from merging label Jan 12, 2026
@sfc-gh-nbellante sfc-gh-nbellante added ai-review If applied to PR or issue will run AI review workflow and removed do-not-merge PR is blocked from merging labels Jan 12, 2026
@sfc-gh-nbellante sfc-gh-nbellante marked this pull request as ready for review January 12, 2026 02:47
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Jan 12, 2026
@github-actions
Copy link
Contributor

Summary

This PR adds image thumbnail previews to file chips in st.chat_input. When users upload image files (jpg, jpeg, png, gif, webp, svg, bmp), the component now displays a thumbnail preview of the actual image instead of a generic file icon. The implementation uses browser blob URLs for efficient preview generation without server requests.

Key changes:

  • New useImagePreview hook for creating and managing blob URLs
  • Updated ChatUploadedFile component to conditionally render image previews
  • Exported isImageFile utility function for extension-based image detection
  • Comprehensive unit tests for all new functionality
  • Updated E2E test to use a real PNG image for accurate snapshot testing

Code Quality

Strengths

  1. Excellent React patterns in useImagePreview.ts: The hook correctly uses useMemo for synchronous blob URL creation and useEffect only for cleanup. This follows the "You Might Not Need an Effect" best practices - the URL derivation happens during render (synchronous), while cleanup is appropriately handled in an effect.
  // Derive the preview URL during render - createObjectURL is synchronous
  const previewUrl = useMemo(() => {
    if (!file || !isImageFile(filename)) {
      return null
    }
    return URL.createObjectURL(file)
  }, [file, filename])

  // Effect only for cleanup - revoke the blob URL when it changes or unmounts
  useEffect(() => {
    return () => {
      if (previewUrl) {
        URL.revokeObjectURL(previewUrl)
      }
    }
  }, [previewUrl])
  1. DRY principle: The isImageFile function reuses the existing IMAGE_EXTENSIONS constant from getFileTypeIcon.ts rather than duplicating the extension list.

  2. Good component integration: The ChatUploadedFileIcon component cleanly handles the image preview case alongside existing status cases (uploading, error, uploaded).

  3. Proper accessibility: The image preview includes an alt attribute with the filename for screen readers.

  4. Styled component follows conventions: StyledChatUploadedFileImagePreview follows the naming pattern and uses object notation styling as per the TypeScript guidelines.

  5. Memory leak prevention: Blob URLs are properly revoked on unmount and when the file changes, preventing memory leaks.

Minor Observations

  1. E2E test constant placement: In st_chat_input_test.py, SMILEY_PNG is defined after the test function that uses it. While this works in Python, placing it before the test function (or at the module top with other constants) would improve readability.

Test Coverage

Unit Tests - Excellent Coverage

useImagePreview.test.ts (125 lines):

  • ✅ Returns blob URL for image files
  • ✅ Returns null for non-image files
  • ✅ Returns null when file is undefined
  • ✅ Revokes blob URL on unmount (memory leak prevention)
  • ✅ Revokes old blob URL when file changes
  • ✅ Memoizes URL for same file reference (performance)

ChatUploadedFile.test.tsx (new image preview section):

  • ✅ Renders image preview with correct src and alt
  • ✅ Does not render preview for non-image files (negative assertion)
  • ✅ Does not render preview when File object is missing
  • ✅ Revokes blob URL on unmount

getFileTypeIcon.test.ts (new sections):

  • ✅ Tests getFileExtension function thoroughly
  • ✅ Tests isImageFile for all supported image extensions
  • ✅ Tests negative cases (non-image files, files without extension)

E2E Tests

The existing test_file_chip_theming test was updated to use a real PNG image (SMILEY_PNG) instead of fake data, ensuring the image preview thumbnail renders correctly in snapshot tests. Snapshots were updated for all browsers (chromium, firefox, webkit) in both light and dark themes.

The test follows the E2E best practices:

  • Uses existing themed_app fixture
  • Screenshot tests specific elements (file chip), not the whole page
  • Uses descriptive snapshot naming (st_chat_input-file_chip_themed)

Backwards Compatibility

No breaking changes. This is a purely additive feature:

  1. Files without the File object (e.g., from previous sessions or external sources) gracefully fall back to the standard file type icon
  2. Non-image files continue to display their appropriate file type icons
  3. No changes to the public API or data structures
  4. The file property on UploadFileInfo already existed (for retry functionality)

Security & Risk

Low risk:

  1. Blob URLs are secure: They are local to the browser, cannot be shared externally, and don't make network requests
  2. Proper cleanup: Blob URLs are revoked on unmount and file changes, preventing memory accumulation
  3. No new external dependencies: Uses only native browser APIs (URL.createObjectURL, URL.revokeObjectURL)
  4. Extension-based detection: Uses filename extension rather than MIME type sniffing, which is consistent with existing getFileTypeIcon behavior

Recommendations

No blocking issues. Minor suggestions for future consideration:

  1. Consider adding loading state for large images: Currently, large images may briefly show as blank while loading. A loading indicator could improve UX (though this is arguably over-engineering for thumbnails).

  2. E2E test organization: Consider moving SMILEY_PNG to the top of the file with other test constants for better readability.

Verdict

APPROVED: This is a well-implemented feature with excellent test coverage, proper memory management, and follows React/TypeScript best practices. The code is clean, maintainable, and introduces no breaking changes or security concerns.


This is an automated AI review. Please verify the feedback and use your judgment.

Copy link
Collaborator

@kmcgrady kmcgrady left a comment

Choose a reason for hiding this comment

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

Small question but the code looks good.

export const StyledChatUploadedFileImagePreview = styled.img({
width: "100%",
height: "100%",
objectFit: "cover",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just double checking this shouldn't be contain? I only ask cause I figure the user would want to see the full icon picture.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good q, let me clarify with Jessi.

Copy link
Contributor Author

sfc-gh-nbellante commented Jan 12, 2026

Merge activity

  • Jan 12, 4:27 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jan 12, 4:46 PM UTC: Graphite rebased this pull request as part of a merge.
  • Jan 12, 5:02 PM UTC: @sfc-gh-nbellante merged this pull request with Graphite.

@sfc-gh-nbellante sfc-gh-nbellante changed the base branch from fixit/file-chip-min-max-width-v2 to graphite-base/13547 January 12, 2026 16:28
@sfc-gh-nbellante sfc-gh-nbellante changed the base branch from graphite-base/13547 to develop January 12, 2026 16:44
- Add useImagePreview.test.ts for hook behavior and blob URL memory management
- Add isImageFile and getFileExtension tests to getFileTypeIcon.test.ts
- Add image preview rendering tests to ChatUploadedFile.test.tsx
@sfc-gh-nbellante sfc-gh-nbellante force-pushed the feat/file-chip-image-preview branch from 4a11a35 to ff186eb Compare January 12, 2026 16:45
@github-actions
Copy link
Contributor

📉 Frontend coverage change detected

The frontend unit test (vitest) coverage has decreased by 0.0300%

  • Current PR: 86.5600% (12840 lines, 1725 missed)
  • Latest develop: 86.5900% (12826 lines, 1719 missed)

💡 Consider adding more unit tests to maintain or improve coverage.

📊 View detailed coverage comparison

@sfc-gh-nbellante sfc-gh-nbellante merged commit 377c595 into develop Jan 12, 2026
44 checks passed
@sfc-gh-nbellante sfc-gh-nbellante deleted the feat/file-chip-image-preview branch January 12, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:chore PR contains maintenance or housekeeping change impact:users PR changes affect end users security-assessment-completed Security assessment has been completed for PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants