Skip to content

Add image message content support#19

Merged
CoolSpring8 merged 7 commits into
mainfrom
codex/add-image-input-support
Nov 26, 2025
Merged

Add image message content support#19
CoolSpring8 merged 7 commits into
mainfrom
codex/add-image-input-support

Conversation

@CoolSpring8
Copy link
Copy Markdown
Owner

@CoolSpring8 CoolSpring8 commented Nov 24, 2025

Summary

  • allow chat messages to include image parts and propagate through tree storage
  • render image attachments in chat, clipboard handling, and diagram view labels
  • update message sending to accept mixed content for AI SDK compatibility

Testing

  • bunx tsc --noEmit

Codex Task

Summary by CodeRabbit

  • New Features

    • Image attachments in chat (drag-and-drop and file picker), preview bar, thumbnails, and 10MB limit; text files can be appended.
    • Rich message composition & editing: messages can mix text and images and edits preserve mixed content.
    • Improved copy behavior: non-text parts clearly noted when copying.
  • Documentation

    • Clarified CSS/Tailwind guidance: when preflight is disabled, include explicit border style and color.
  • Chores

    • Conversation snapshot/storage upgraded to support richer content.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 24, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The PR converts message payloads from plain strings to a structured MessageContent (text and image parts), updates types and public APIs across tree, hooks, AI, and UI, adds image attachment support and rendering in ChatView and MessageItem, and bumps conversation snapshot version to v2.

Changes

Cohort / File(s) Summary
Documentation
AGENTS.md
Adds Tailwind/CSS guidance: when preflight is disabled, use border-solid and an explicit color with border utilities.
Core Types
src/types.ts
Adds MessageContentPart (`{type: "text"
AI / sendMessage
src/ai/sendMessage.ts
Imports MessageContent; sendMessage now accepts promptContent: MessageContent; adds hasMessageContent helper and updates SendMessageContext.createUserAfter to accept MessageContent.
Conversation Tree Types
src/tree/types.ts
Replaces TreeNode.text: string with content: MessageContent; renames snapshot to v2 and updates exported ConversationSnapshot to v2.
Conversation Tree Logic
src/tree/useConversationTree.ts
Replaces text-based APIs with content-based ones (create/clone/append/replace use content); adds appendTextToContent; updates snapshot export/import to version 2; maps toMessage from node.content.
Chat UI
src/components/ChatView.tsx
Adds attachment state, file input/drag-and-drop, attachment preview/removal; introduces buildMessageContent/splitContent; changes onSend/onEditSubmit to accept MessageContent.
Message Rendering
src/components/MessageItem.tsx
Renders MessageContentPart[] (text parts as Markdown blocks, image parts as images); copy-to-clipboard joins text parts and marks images as “[image attachment]”.
Diagram Rendering
src/components/DiagramView.tsx
Derives contentText from node.content (string or parts) and uses it for node labels instead of node.text.
Conversation Controller Hook
src/hooks/useConversationController.ts
Imports MessageContent; adds areContentsEqual and updates areMessagesEqual; send and submitEdit now accept MessageContent.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ChatView
    participant Controller
    participant Tree
    participant AI
    rect rgb(230,245,255)
    User->>ChatView: Type text + attach image(s)
    ChatView->>ChatView: buildMessageContent() → MessageContent
    end
    User->>ChatView: Send
    ChatView->>Controller: onSend(MessageContent)
    Controller->>Tree: createUserAfter(parentId, content)
    Tree-->>Controller: TreeNode with content
    Controller->>AI: sendMessage(promptContent)
    AI->>AI: hasMessageContent(promptContent) → decide attach user message
    AI->>AI: Build ModelMessage[] from MessageContent
    par model request
        AI->>AI: call model API
        AI-->>Controller: model response
    and update UI
        Controller-->>ChatView: new assistant node
        ChatView->>User: render MessageItem with parts (text/images)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Heterogeneous changes across types, persistence (snapshot v2), tree logic, AI path, hooks, and UI.
  • Focus review on:
    • Snapshot export/import and migration correctness (src/tree/*).
    • Consistent replacement of textcontent across codepaths.
    • Serialization and modeling of MessageContent in the AI path (src/ai/sendMessage.ts).
    • Attachment lifecycle, edit-mode initialization/clearing, and file handling (src/components/ChatView.tsx).
    • Content equality logic (areContentsEqual) and its use in message diffing (src/hooks/useConversationController.ts).

Possibly related PRs

Poem

🐰 I found a crumb of text and a shiny pic,

I stitched them both neat in a single quick flick.
Hops through the tree, I paste with delight,
Messages now sparkle in day and in night.
Hooray — content and images, all set just right!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add image message content support' is clear, specific, and directly summarizes the main change—adding image support to message content throughout the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-image-input-support

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.

- Added support for displaying image attachments in the ChatView component.
- Improved the layout and styling of the message input area, ensuring better user experience.
- Updated the attachment removal functionality for better interaction and visual feedback.
- Adjusted CSS classes to align with Tailwind's utility-first approach.
@CoolSpring8 CoolSpring8 marked this pull request as ready for review November 26, 2025 03:49
Copy link
Copy Markdown

@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

🧹 Nitpick comments (2)
src/tree/types.ts (1)

5-13: Consider using a top-level import for consistency.

The inline import("../types").MessageContent syntax works but is less common than a top-level import. A regular import would be more conventional and consistent with other files.

+import type { MessageContent } from "../types";
+
 export type NodeID = string;
 export type EdgeID = string;
 export type EdgeKind = "sequence";

 export interface TreeNode {
 	id: NodeID;
 	role: "system" | "user" | "assistant" | "tool";
-	content: import("../types").MessageContent;
+	content: MessageContent;
 	reasoningContent?: string;
 	createdAt: number;
 	status?: "draft" | "streaming" | "final" | "error";
 	parentId: NodeID | null;
 }
src/components/ChatView.tsx (1)

127-144: Consider adding file size limits for large files.

Reading large images as data URLs or appending large text files could impact performance and memory. Consider adding a size check before processing.

 	const handleFileInput = useCallback(
 		async (file: File) => {
+			const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
+			if (file.size > MAX_FILE_SIZE) {
+				// Could use toast to notify user
+				return;
+			}
 			if (file.type.startsWith("image/")) {
 				const reader = new FileReader();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f695f0 and 2dd77ab.

📒 Files selected for processing (9)
  • AGENTS.md (1 hunks)
  • src/ai/sendMessage.ts (3 hunks)
  • src/components/ChatView.tsx (6 hunks)
  • src/components/DiagramView.tsx (1 hunks)
  • src/components/MessageItem.tsx (3 hunks)
  • src/hooks/useConversationController.ts (4 hunks)
  • src/tree/types.ts (1 hunks)
  • src/tree/useConversationTree.ts (16 hunks)
  • src/types.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to src/tree/{useConversationTree,types}.ts : Conversation state and snapshots must live in the tree store (`src/tree/useConversationTree.ts`, `src/tree/types.ts`)

Applied to files:

  • src/tree/types.ts
  • src/tree/useConversationTree.ts
  • src/hooks/useConversationController.ts
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to src/ai/**/*.ts : OpenAI-compatible provider supports chat and text completion views

Applied to files:

  • src/ai/sendMessage.ts
  • src/hooks/useConversationController.ts
  • src/components/ChatView.tsx
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to src/index.css : Prefer Tailwind utilities for CSS; add global styles in `src/index.css` only when necessary

Applied to files:

  • AGENTS.md
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to **/*.{ts,tsx} : Use tsc for type-checking; run `bunx tsc --noEmit` to check for type errors

Applied to files:

  • AGENTS.md
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to src/components/**/*.{ts,tsx} : Components under `src/components/` must include their own UI behavior (hover/edit states, popovers, menus) rather than being purely presentational

Applied to files:

  • AGENTS.md
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to src/**/*.{ts,tsx} : Use Tailwind for styling

Applied to files:

  • AGENTS.md
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Keep UI logic close to the component (e.g., hover/edit toggles in `MessageItem`, menu interactions in `DiagramView`); shared conversation/tree logic belongs in the store

Applied to files:

  • AGENTS.md
  • src/hooks/useConversationController.ts
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to **/*.{ts,tsx,js,jsx,json} : Use Biome for formatting and import organization; run `bunx biome check --write .`

Applied to files:

  • AGENTS.md
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript and React 18

Applied to files:

  • AGENTS.md
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to src/components/**/*.{ts,tsx} : Use Mantine for UI components

Applied to files:

  • AGENTS.md
📚 Learning: 2025-11-24T14:54:54.455Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.455Z
Learning: Applies to src/{index,App}.tsx : Source code lives in `src/` directory (`index.tsx`, `App.tsx`, `index.css`); build output goes to `dist/`

Applied to files:

  • AGENTS.md
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to src/index.tsx : Providers (e.g., `MantineProvider`) must only be in `index.tsx`

Applied to files:

  • AGENTS.md
🧬 Code graph analysis (5)
src/ai/sendMessage.ts (1)
src/types.ts (2)
  • ChatProviderReady (43-52)
  • MessageContent (13-13)
src/tree/useConversationTree.ts (2)
src/tree/types.ts (2)
  • NodeID (1-1)
  • TreeNode (5-13)
src/types.ts (2)
  • MessageContent (13-13)
  • MessageContentPart (9-11)
src/hooks/useConversationController.ts (2)
src/types.ts (2)
  • MessageContent (13-13)
  • Message (15-20)
src/ai/sendMessage.ts (1)
  • sendMessage (36-108)
src/components/MessageItem.tsx (1)
src/types.ts (1)
  • MessageContentPart (9-11)
src/components/ChatView.tsx (1)
src/types.ts (3)
  • Message (15-20)
  • MessageContent (13-13)
  • MessageContentPart (9-11)
🔇 Additional comments (33)
AGENTS.md (1)

24-24: Helpful documentation addition.

This guidance prevents common styling issues when Tailwind preflight is disabled, as browsers apply default border styles that may cause unexpected rendering.

src/types.ts (2)

9-13: Well-designed type structure for mixed content.

The discriminated union with type field enables type-safe content handling. The design maintains backward compatibility by accepting plain strings alongside structured parts.


15-20: LGTM.

The Message interface now properly uses MessageContent, aligning with the broader refactor.

src/components/DiagramView.tsx (1)

260-274: Clean content extraction for diagram labels.

The logic correctly handles both string and structured content formats, appropriately filtering to text-only parts for the diagram view. Excluding images from node labels is a reasonable UX choice for the diagram visualization.

src/components/MessageItem.tsx (3)

31-36: LGTM on content normalization.

The logic correctly handles both string and array content formats, including the edge case of empty strings.


38-52: Good UX for copy-to-clipboard.

Replacing images with [image attachment] placeholder text provides clear feedback to users about what was copied.


135-158: Content parts rendering looks good.

The iteration handles both text and image parts correctly. The streaming indicator (▪️) is properly appended only to the last text part during generation.

src/hooks/useConversationController.ts (3)

103-126: LGTM on message equality check.

The areMessagesEqual function correctly delegates content comparison to areContentsEqual and checks all relevant message fields.


162-204: LGTM on handleSend signature update.

The function now correctly accepts MessageContent and passes it to sendMessage.


265-284: LGTM on handleFinishEdit signature update.

The function now accepts MessageContent and properly passes it to replaceNodeWithEditedClone.

src/ai/sendMessage.ts (2)

27-34: Well-implemented content validation helper.

The hasMessageContent function correctly handles both string and structured content, checking for meaningful content in both text and image parts.


70-75: Verify AI SDK compatibility with MessageContent type - Confirmed.

The Vercel AI SDK's streamText function accepts message content in two formats: simple strings or arrays of content parts (text, image, file, tool-call/tool-result). The message.content being passed as MessageContent type to ModelMessage is compatible with this design and requires no changes.

src/components/ChatView.tsx (5)

45-56: LGTM on content splitting utility.

The splitContent function correctly extracts text and images from either string or structured content.


100-116: Smart optimization for single text content.

Returning a plain string when there's only one text part maintains backward compatibility and simplifies downstream handling.


167-178: Sequential file processing in drop handler.

Using await in the loop processes files sequentially, which is appropriate here to avoid race conditions with state updates.


195-259: Clean attachment preview UI implementation.

The attachment bar with removable image previews provides good UX. The styling follows Tailwind conventions and includes border-solid as recommended in AGENTS.md.


284-298: LGTM on file input handling.

The hidden file input correctly resets its value after selection to allow re-selecting the same file.

src/tree/useConversationTree.ts (16)

3-3: LGTM: Import additions support content model.

The new imports enable structured content handling with text and image parts.


25-28: LGTM: API signature updated for content model.

The parameter change from text: string to content: MessageContent aligns with the structured content model and is consistent with the PR objectives.


45-45: LGTM: Signature updated to accept MessageContent.

Correctly reflects the migration from text to content-based updates.


149-154: LGTM: Message conversion updated for content field.

The toMessage function correctly maps the new content field structure.


156-178: LGTM: Content append logic handles all cases correctly.

The appendTextToContent helper properly handles:

  • String content concatenation
  • Appending to the last text part in an array
  • Creating new text parts when needed
  • Immutable updates with proper array spreading

244-251: LGTM: Type casting after filter is safe.

The as TreeNode cast is safe here because filter(Boolean) removes undefined entries. This is a standard TypeScript pattern when the type system doesn't narrow automatically after boolean filtering.


252-259: LGTM: Consistent type narrowing pattern.

Same safe casting pattern as compilePathTo, ensuring type safety after filtering.


270-286: LGTM: System message creation updated for content field.

Correctly stores the text parameter in the content field while maintaining the string-only API for system messages.


295-312: LGTM: User node creation uses content parameter correctly.

The implementation properly stores the content parameter in the new node structure.


313-334: LGTM: Assistant node initialized with empty content.

Starting with an empty string for content is appropriate for draft assistant messages that will be populated during streaming.


335-358: LGTM: Node append logic properly uses helper function.

The appendToNode implementation correctly delegates to appendTextToContent for merging text deltas into existing content.


359-373: LGTM: Text setter updated to use content field.

The implementation correctly updates the content field. Note that the function name setNodeText now sets the content field, which is fine since content can be a plain string.


417-439: LGTM: Node cloning preserves content correctly.

The content field is properly copied from the source node during cloning.


440-473: LGTM: Edited clone properly handles content updates.

The fallback logic updates.content ?? target.content correctly preserves existing content when no update is provided.


207-230: LGTM: Linear sync updated for content field.

The syncLinearTail function correctly maps message.content to node content, maintaining consistency with the new message format.


526-555: Original review comment concern is not applicable.

After verifying the codebase:

  1. Only one snapshot version exists: ConversationSnapshotV1 (version 1), with no v2 or migration logic found
  2. No "text" field ever existed: The TreeNode interface has always used the content field, and no references to node.text appear anywhere in the codebase
  3. Snapshot schema is strongly typed: The snapshot format is defined explicitly in src/tree/types.ts and validates version on import, rejecting incompatible versions
  4. Fallback is defensive: The content: node.content ?? "" fallback is reasonable defensive programming for handling missing content gracefully

The import logic is correct and requires no changes.

Comment thread src/hooks/useConversationController.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/tree/useConversationTree.ts
- BREAKING CHANGE: Changed the version of the ConversationSnapshot interface from V1 to V2.
- Updated the useConversationTree hook to reflect the new snapshot version and ensure compatibility with version checks.
…ponents with Biome

- Adjusted the formatting of classNames in the ChatView component for improved readability.
- Simplified the div structure in the MessageItem component by removing unnecessary line breaks.
Copy link
Copy Markdown

@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: 0

🧹 Nitpick comments (1)
src/components/ChatView.tsx (1)

127-144: Add error handling for file reading operations.

The FileReader lacks an onerror handler, which could lead to silent failures when file reading fails (e.g., due to permissions or corrupted files).

Apply this diff to add error handling:

 		async (file: File) => {
 			if (file.type.startsWith("image/")) {
 				const reader = new FileReader();
+				reader.onerror = () => {
+					console.error("Failed to read image file:", file.name);
+				};
 				reader.onload = () => {
 					const result = typeof reader.result === "string" ? reader.result : "";
 					if (result) {
 						addImageAttachment(result, file.type);
 					}
 				};
 				reader.readAsDataURL(file);
 				return;
 			}
-			const content = await file.text();
-			setPrompt((draft) => draft + content);
+			try {
+				const content = await file.text();
+				setPrompt((draft) => draft + content);
+			} catch (error) {
+				console.error("Failed to read text file:", file.name);
+			}
 		},

Note: The file input restricts selection to images only (line 289: accept="image/*"), but this handler also processes non-image files as text. This behavior is available via drag-and-drop. Consider whether this is intentional or if non-image file handling should be removed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2dd77ab and b2b71fb.

📒 Files selected for processing (5)
  • src/components/ChatView.tsx (6 hunks)
  • src/components/MessageItem.tsx (3 hunks)
  • src/hooks/useConversationController.ts (4 hunks)
  • src/tree/types.ts (2 hunks)
  • src/tree/useConversationTree.ts (18 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/tree/types.ts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to src/tree/{useConversationTree,types}.ts : Conversation state and snapshots must live in the tree store (`src/tree/useConversationTree.ts`, `src/tree/types.ts`)

Applied to files:

  • src/hooks/useConversationController.ts
  • src/tree/useConversationTree.ts
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to src/ai/**/*.ts : OpenAI-compatible provider supports chat and text completion views

Applied to files:

  • src/hooks/useConversationController.ts
  • src/components/ChatView.tsx
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Keep UI logic close to the component (e.g., hover/edit toggles in `MessageItem`, menu interactions in `DiagramView`); shared conversation/tree logic belongs in the store

Applied to files:

  • src/hooks/useConversationController.ts
🧬 Code graph analysis (4)
src/hooks/useConversationController.ts (2)
src/types.ts (2)
  • MessageContent (13-13)
  • Message (15-20)
src/ai/sendMessage.ts (1)
  • sendMessage (36-108)
src/components/MessageItem.tsx (1)
src/types.ts (1)
  • MessageContentPart (9-11)
src/components/ChatView.tsx (1)
src/types.ts (3)
  • Message (15-20)
  • MessageContent (13-13)
  • MessageContentPart (9-11)
src/tree/useConversationTree.ts (2)
src/tree/types.ts (2)
  • NodeID (1-1)
  • TreeNode (5-13)
src/types.ts (2)
  • MessageContent (13-13)
  • MessageContentPart (9-11)
🔇 Additional comments (20)
src/components/MessageItem.tsx (3)

31-36: LGTM! Content parts derivation is correct.

The logic properly handles both string and array content types, converting strings to single text parts when non-empty.


38-52: LGTM! Clipboard handling is user-friendly.

The copy logic appropriately substitutes images with a readable placeholder while preserving text content.


135-156: LGTM! Content rendering handles mixed text and images correctly.

The rendering logic properly maps both text and image parts, with appropriate keys and the generation indicator only on the final text part.

src/hooks/useConversationController.ts (4)

66-102: LGTM! Content equality logic is now correct.

The continue statement at line 96 has been added, fixing the issue flagged in the previous review. Both text and image comparison branches now properly advance the loop, ensuring accurate equality results.


104-127: LGTM! Message equality comparison is thorough.

The function correctly compares all message fields including using the deep areContentsEqual check for content.


163-205: LGTM! Message sending updated to support rich content.

The function signature correctly accepts MessageContent and propagates it through the send flow.


266-285: LGTM! Edit submission updated to support rich content.

The function signature correctly accepts MessageContent for edited messages.

src/components/ChatView.tsx (6)

45-56: LGTM! Content splitting logic is correct.

The function properly decomposes MessageContent into text and image parts for editing workflows.


100-116: LGTM! Content building logic normalizes appropriately.

The function correctly constructs MessageContent, returning a string for simple text-only messages and an array for mixed content.


146-161: LGTM! Submit handler correctly processes mixed content.

The function properly builds MessageContent from text and attachments, handles both send and edit flows, and cleans up state appropriately.


167-178: LGTM! Drag-and-drop implementation is correct.

The handler properly processes dropped files, supporting both images and text files.


196-229: LGTM! Attachment preview UI is functional.

The preview chips properly display image thumbnails with remove controls. The key generation using ${attachment.image}-${index} works but could have collisions if the same image is added multiple times at different positions, though this is unlikely to cause issues in practice.


286-300: LGTM! File input element is properly configured.

The hidden file input correctly restricts selection to images and clears the value after processing to allow re-selection of the same file.

src/tree/useConversationTree.ts (7)

156-178: LGTM! Text appending logic is efficient and correct.

The appendTextToContent helper properly handles both string and array content, efficiently appending to the last text part when possible to avoid unnecessary part fragmentation during streaming.


287-293: LGTM! Tail resolution safely handles empty paths.

The function correctly guards against undefined when the compiled path is empty.


295-311: LGTM! User node creation updated for rich content.

The function correctly accepts and stores MessageContent instead of plain text.


335-357: LGTM! Node appending uses the efficient content helper.

The function properly leverages appendTextToContent to update node content during streaming.


440-472: LGTM! Node replacement correctly updates content.

The function properly applies content updates during edit operations.


509-524: LGTM! Snapshot export reflects the new version.

The version bump to 2 correctly indicates the schema change from text to content.


526-555: Verify whether v1 snapshot migration is needed.

The import logic rejects v1 snapshots (line 527) rather than migrating them. While this is safer than silent data loss, users with previously saved conversations will be unable to import them.

As noted in a previous review, v1 snapshots stored message text in a text field, whereas v2 uses content. Consider whether backward compatibility is required for your users.

If migration is needed, apply this diff to support v1 snapshots:

 		importSnapshot: (snapshot) => {
-			if (snapshot.version !== 2) {
+			if (snapshot.version !== 1 && snapshot.version !== 2) {
 				throw new Error(`Unsupported snapshot version: ${snapshot.version}`);
 			}
 			const nodesRaw = snapshot.tree?.nodes ?? {};
 			const nodes: NodeMap = {};
 			for (const [id, node] of Object.entries(nodesRaw)) {
 				const createdAt =
 					typeof node.createdAt === "number" ? node.createdAt : Date.now();
 				const parentId =
 					node.parentId && nodesRaw[node.parentId] ? node.parentId : null;
+				// Migrate v1 text field to v2 content field
+				const content = node.content ?? (node as any).text ?? "";
 				nodes[id] = {
 					id,
 					role: coerceRole(node.role),
-					content: node.content ?? "",
+					content,
 					reasoningContent: node.reasoningContent,
 					createdAt,
 					status: node.status,
 					parentId,
 				} satisfies TreeNode;
 			}

Alternatively, if breaking backward compatibility is acceptable, consider documenting this in the release notes so users can export their v1 data before upgrading.

- Added file size validation to restrict uploads to a maximum of 10MB, displaying an error toast for oversized files.
- Implemented error handling for file reading operations, providing user feedback via toast notifications for failed reads.
- Updated type imports in the TreeNode interface for improved clarity and consistency.
Copy link
Copy Markdown

@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

🧹 Nitpick comments (2)
src/components/ChatView.tsx (2)

89-101: Consider optimizing the dependency array.

The effect depends on editingMessage?.content, which may trigger more frequently than needed if the messages array reference changes often (even when the editing message itself hasn't changed). This won't cause incorrect behavior but could lead to minor unnecessary re-renders.

If this becomes a performance concern, consider restructuring to depend on a more stable reference or extracting content directly within the effect:

 useEffect(() => {
-  if (editingMessageId && editingMessage) {
-    const { text, images } = splitContent(editingMessage.content);
+  const message = messages.find(m => m._metadata.uuid === editingMessageId);
+  if (editingMessageId && message) {
+    const { text, images } = splitContent(message.content);
     setPrompt(text);
     setAttachments(images);
   }
 }, [
-  editingMessage?.content,
   editingMessageId,
+  messages,
   setAttachments,
   setPrompt,
   splitContent,
 ]);

212-245: Consider adding aria-label for better accessibility.

The attachment preview UI is well-implemented. For improved accessibility, consider adding an aria-label to the remove button that includes which image is being removed.

Apply this diff to enhance accessibility:

 <UnstyledButton
   className="ml-1 flex h-5 w-5 items-center justify-center rounded-full text-slate-600 hover:bg-slate-200"
   onClick={() => {
     setAttachments((draft) => {
       draft.splice(index, 1);
     });
   }}
   title="Remove attachment"
+  aria-label={`Remove image ${index + 1}`}
 >
   <div className="i-lucide-x w-4 h-4" />
 </UnstyledButton>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2b71fb and 50e5f6c.

📒 Files selected for processing (2)
  • src/components/ChatView.tsx (6 hunks)
  • src/tree/types.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/tree/types.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-24T14:54:54.456Z
Learnt from: CR
Repo: CoolSpring8/iaslate PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T14:54:54.456Z
Learning: Applies to src/ai/**/*.ts : OpenAI-compatible provider supports chat and text completion views

Applied to files:

  • src/components/ChatView.tsx
🧬 Code graph analysis (1)
src/components/ChatView.tsx (1)
src/types.ts (3)
  • Message (15-20)
  • MessageContent (13-13)
  • MessageContentPart (9-11)
🔇 Additional comments (11)
src/components/ChatView.tsx (11)

9-9: LGTM!

The 10MB file size limit is reasonable for image attachments and aligns with typical API constraints.


15-24: LGTM! Breaking API change is consistent with PR objectives.

The updated signatures for onSend and onEditSubmit correctly accept MessageContent instead of plain strings, enabling support for mixed text and image content.


44-45: LGTM!

State management for attachments using useImmer is appropriate for managing the array of image parts, and the file input ref follows standard React patterns.


48-59: LGTM!

The splitContent function correctly handles both string and array forms of MessageContent, properly separating text and image parts for editing workflows.


103-119: LGTM!

The buildMessageContent function correctly constructs MessageContent, optimizing for the plain string case when there's only text and properly handling mixed content scenarios.


162-177: LGTM!

The submit handler correctly builds message content, dispatches to the appropriate callback, and cleans up local state for both new messages and edits.


183-194: LGTM!

The drag-and-drop handler correctly prevents default behavior and processes each dropped file through handleFileInput.


246-276: LGTM!

The textarea implementation correctly handles IME composition states and keyboard shortcuts, maintaining good UX for international input methods.


278-286: LGTM!

The attach image button correctly triggers the hidden file input. Consider adding an aria-label for better screen reader support, though the title attribute already provides helpful tooltip text.


302-316: LGTM!

The hidden file input is properly configured with ref handling, and correctly resets its value after processing to allow re-uploading the same file. The use of void to explicitly ignore the promise return is good practice.


317-333: LGTM!

The editing banner provides clear visual feedback and appropriately handles both text and image-only content with a helpful fallback message.

Comment thread src/components/ChatView.tsx
@CoolSpring8 CoolSpring8 merged commit 4062100 into main Nov 26, 2025
1 check passed
@CoolSpring8 CoolSpring8 deleted the codex/add-image-input-support branch November 26, 2025 05:57
@coderabbitai coderabbitai Bot mentioned this pull request Nov 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant