Skip to content

feat(chat): add image attachment support to composer#2676

Open
rsd-darshan wants to merge 9 commits into
tinyhumansai:mainfrom
rsd-darshan:feat/multimodal-attachments
Open

feat(chat): add image attachment support to composer#2676
rsd-darshan wants to merge 9 commits into
tinyhumansai:mainfrom
rsd-darshan:feat/multimodal-attachments

Conversation

@rsd-darshan
Copy link
Copy Markdown

@rsd-darshan rsd-darshan commented May 26, 2026

Closes #2662

Summary

  • Users can attach images (PNG, JPEG, WebP, GIF, BMP) to chat messages via a paperclip button in the text composer
  • Attachments render as thumbnail chips above the textarea with per-item remove buttons
  • Images are encoded as [IMAGE:<data-uri>] markers in the message string — the existing Rust backend (agent/multimodal.rs) already parses these and routes them to the inference provider, so no backend changes were needed
  • Limits match the existing MultimodalConfig defaults: up to 4 images per message, 8 MB each
  • Validation errors (unsupported type, oversized file, count exceeded) surface in the existing composer error banner
  • Send button activates when attachments are present even with empty text

Files changed

File Role
app/src/lib/attachments.ts Validation, FileReader util, [IMAGE:] marker composition, parseMessageImages util
app/src/components/chat/AttachmentPreview.tsx Thumbnail chip strip component
app/src/pages/Conversations.tsx Hidden file input, attachment state, composer toolbar button, send wiring, bubble rendering
app/src/lib/i18n/en.ts + 12 locale chunk files i18n keys for attachment labels and errors
app/src/lib/attachments.test.ts 19 Vitest unit tests covering validation, composition, parsing, and formatting

Test plan

  • Attach a PNG/JPEG — thumbnail preview appears above textarea
  • Remove a chip — it disappears without affecting others
  • Attach more than 4 images — error banner shows the limit message
  • Attach a file > 8 MB — error banner shows the size limit message
  • Attach a PDF — error banner shows unsupported type message
  • Send with attachment + text — both reach the agent
  • Send with attachment only (no text) — send button active, message goes through
  • Attachments clear after send

Notes

Pre-push hook fails on cargo fmt --check due to cargo not being in PATH in this dev environment. This is a pre-existing environment issue unrelated to the changes here (no Rust files were modified). TypeScript typecheck, ESLint, Prettier, and Vitest all pass.

Summary by CodeRabbit

  • New Features

    • Image attachment support in chat with compact thumbnail previews (image, filename, formatted size), per-item remove, and up to 4 images per message.
    • Composer and send behavior updated to allow sending messages with only attachments and show attachment thumbnails in the chat transcript.
  • Tests

    • Added tests covering MIME/type/size validation, data-URI conversion, message building/parsing, and file-size formatting.
  • Documentation

    • Added localized attachment UI strings across 15+ languages (including camera prompts).

Review Change Stack

Users can now attach images (PNG, JPEG, WebP, GIF, BMP) to chat messages
using the paperclip button in the text composer. Attachments are previewed
as thumbnail chips above the textarea and removed individually before send.

On send, images are encoded as `[IMAGE:<data-uri>]` markers appended to the
message text. The Rust agent harness already parses these markers in
`agent/multimodal.rs` and routes them to the inference provider — no backend
changes required.

Limits match the existing backend `MultimodalConfig` defaults: up to 4 images
per message, 8 MB each. Validation errors (unsupported type, oversized file,
count exceeded) surface in the existing composer error banner.

- `app/src/lib/attachments.ts` — validation, FileReader util, marker composer
- `app/src/components/chat/AttachmentPreview.tsx` — thumbnail chip strip
- `app/src/pages/Conversations.tsx` — file input, attachment state, composer wiring
- i18n keys added to `en.ts` and all 12 locale chunk files
- 15 Vitest unit tests covering validation, composition, and formatting
@rsd-darshan rsd-darshan requested a review from a team May 26, 2026 09:29
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds image attachment support: utilities to validate/convert files, AttachmentPreview UI, i18n strings, and composer integration to select, validate, preview, compose, send, and render image attachments with metadata.

Changes

Image Attachments in Chat

Layer / File(s) Summary
Attachment utilities and types
app/src/lib/attachments.ts
Core types and constants (Attachment, AllowedImageMimeType, ATTACHMENT_MAX_IMAGES, ATTACHMENT_MAX_SIZE_BYTES), isAllowedMimeType, fileToDataUri, validateAndReadFile (deterministic error codes), buildMessageWithAttachments, parseMessageImages, and formatFileSize.
Attachment unit tests
app/src/lib/attachments.test.ts
Vitest suite covering MIME allowlist, file→dataURI conversion, validation/read failure codes and success, message composition/parsing, and file-size formatting.
Attachment preview component
app/src/components/chat/AttachmentPreview.tsx
New React component rendering a flex-wrapped list of attachment thumbnails with truncated filenames, formatted sizes, per-item remove buttons, disabled state, and i18n aria-labels; returns null when empty.
Internationalization translations
app/src/lib/i18n/en.ts, app/src/lib/i18n/chunks/{en,de,fr,es,pt,ru,it,ko,zh-CN,hi,bn,ar,id}-2.ts
Adds chat.attachment.* keys across locale chunks for attach/remove labels, limits, validation/read errors, and camera UI strings.
Chat composer attachment integration
app/src/pages/Conversations.tsx
Adds attachments state and hidden multi-file input, handleAttachFiles using validateAndReadFile with attach-specific error reporting, renders AttachmentPreview, allows sends when text is empty if attachments exist, composes message text with buildMessageWithAttachments, embeds attachment metadata into outgoing messages, displays attachment thumbnails in the transcript, and clears attachment state after send.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #2725: i18n chunk placeholders overlap with this PR's chat.attachment.* keys and may need translation updates.

Possibly related PRs

Suggested reviewers

  • graycyrus

Poem

🐇 I found a file, a glossy square,
I read its bits and showed it there,
Tiny markers stitched the view,
Composer cleared — the images flew,
Hop, the chat now shares anew!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding image attachment support to the chat composer, which aligns with the primary feature introduced in the changeset.
Linked Issues check ✅ Passed The PR substantially addresses the core coding requirements from #2662: image upload from disk, attachment previews in composer and history, validation/error handling, and unit test coverage. Camera capture and file upload beyond images are deferred to future work as tracked in #2725.
Out of Scope Changes check ✅ Passed All changes are directly tied to implementing image attachment support: new attachment utilities, validation logic, UI components, message composition/parsing, i18n strings, and integration into the composer. No unrelated refactoring or scope creep detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label May 26, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/pages/Conversations.tsx`:
- Around line 617-649: The batch attach logic in handleAttachFiles uses the
stale attachments.length when calling validateAndReadFile, causing over-limit
selections to be silently dropped; fix by tracking a local counter (e.g., let
currentCount = attachments.length) and pass currentCount to validateAndReadFile
for each file, increment currentCount whenever a file is accepted, and keep
using setAttachments(prev => ...) to append files (while still guarding against
ATTACHMENT_MAX_IMAGES). This ensures validateAndReadFile sees the cumulative
count for the current selection and still enforces the limit and error path
(too_many) correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c2e1e963-88a8-4aa1-99b8-2865db1c6a61

📥 Commits

Reviewing files that changed from the base of the PR and between de2a78a and 54c35be.

📒 Files selected for processing (18)
  • app/src/components/chat/AttachmentPreview.tsx
  • app/src/lib/attachments.test.ts
  • app/src/lib/attachments.ts
  • app/src/lib/i18n/chunks/ar-2.ts
  • app/src/lib/i18n/chunks/bn-2.ts
  • app/src/lib/i18n/chunks/de-2.ts
  • app/src/lib/i18n/chunks/en-2.ts
  • app/src/lib/i18n/chunks/es-2.ts
  • app/src/lib/i18n/chunks/fr-2.ts
  • app/src/lib/i18n/chunks/hi-2.ts
  • app/src/lib/i18n/chunks/id-2.ts
  • app/src/lib/i18n/chunks/it-2.ts
  • app/src/lib/i18n/chunks/ko-2.ts
  • app/src/lib/i18n/chunks/pt-2.ts
  • app/src/lib/i18n/chunks/ru-2.ts
  • app/src/lib/i18n/chunks/zh-CN-2.ts
  • app/src/lib/i18n/en.ts
  • app/src/pages/Conversations.tsx

Comment thread app/src/pages/Conversations.tsx
…flow

- Track accepted count locally in handleAttachFiles loop so the
  too-many error fires correctly when selecting more than 4 at once
- Use separate attachError state so attachment errors are not cleared
  when the user types in the composer
- Store attachment data URIs in extraMetadata so the user message
  bubble can render image thumbnails without embedding markers in content
- Render attachment images above the text in the user bubble using a
  clean image-only layout (no blue background behind images)
- Allow attachment-only sends by bypassing the empty_input block when
  attachments are present
- Add parseMessageImages utility and extend test suite to cover all
  fixed paths (19 tests passing)
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 26, 2026
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

Good work — clean module decomposition, solid test coverage on the utility layer, and the [IMAGE:] marker protocol meshes well with the existing Rust multimodal parser. Two things to address before this can land:

File Change
attachments.ts New — validation, FileReader, marker composition/parsing, formatting
AttachmentPreview.tsx New — thumbnail chip strip component
Conversations.tsx Attachment state, file input, composer button, send wiring, bubble rendering
13 i18n files 8 new attachment-related keys each
attachments.test.ts 19 unit tests

[major] Conversations.tsx bubble rendering — Images render from msg.extraMetadata.attachmentDataUris only. This is a client-side-only field. When messages are rehydrated from the thread API (page reload, different device, history scroll), extraMetadata may not survive the round-trip, and attached images silently vanish.

You already wrote and tested parseMessageImages for exactly this scenario, but it's never called in the rendering path. Wire it up as a fallback:

const dataUris = Array.isArray(msg.extraMetadata?.attachmentDataUris)
  ? (msg.extraMetadata.attachmentDataUris as string[])
  : parseMessageImages(msg.content).dataUris;

This way local sends use the fast path, and persisted messages still render their images from the [IMAGE:] markers in content.


[minor] All 12 non-English locale chunk files (ar-2.ts, bn-2.ts, de-2.ts, etc.) have the new chat.attachment.* keys in English only. Fine as a placeholder but open a tracking issue so they get translated before release.

When messages are rehydrated from the thread API (page reload, history
scroll), extraMetadata.attachmentDataUris may not survive the round-trip.
Use the client-side field as the fast path and parseMessageImages as the
fallback so persisted messages still render their images from the
[IMAGE:] markers embedded in content.
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 27, 2026
@rsd-darshan
Copy link
Copy Markdown
Author

Hey @graycyrus — both points addressed:

[major] Wired parseMessageImages as a fallback in the bubble renderer. Local sends use extraMetadata.attachmentDataUris (fast path); messages rehydrated from the API fall back to parsing [IMAGE:] markers from content, so images survive page reloads and history scrolls.

[minor] Opened #2725 to track translations for the 8 chat.attachment.* keys across all 12 non-English locales.

Let me know if anything else needs attention.

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 27, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 27, 2026
Add unit tests for AttachmentPreview component, attachment validation
utilities (including read_failed path and parseMessageImages), and
Conversations attachment integration to meet the ≥80% diff-cover gate.
@rsd-darshan rsd-darshan requested a review from graycyrus May 27, 2026 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add multimodal attachments to chat

2 participants