Skip to content

Fix Image Pipeline Diagnostics and Type Safety#642

Merged
ngoiyaeric merged 2 commits into
mainfrom
fix-image-pipeline-logging-types-3593452205397511206
Jun 4, 2026
Merged

Fix Image Pipeline Diagnostics and Type Safety#642
ngoiyaeric merged 2 commits into
mainfrom
fix-image-pipeline-logging-types-3593452205397511206

Conversation

@ngoiyaeric

@ngoiyaeric ngoiyaeric commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

This PR addresses issues in the image attachment pipeline by adding diagnostic logging and fixing structural/logic bugs.

Key changes:

  1. Diagnostic Logging: Added logs to trace file extraction, base64 conversion, message part construction, and historical filtering in both the server action and the researcher agent.
  2. Historical Filtering Fix: Adjusted the filter in submit() to allow "IMAGE_PROCESSED" placeholders, preventing images from previous turns from being silently dropped.
  3. Robustness: Added a 10MB file size limit and a try/catch block around arrayBuffer() and base64 conversion to handle processing errors gracefully.
  4. Type Safety: Refactored messageParts to use TextPart and ImagePart types from the ai SDK, ensuring strict type compatibility with CoreMessage['content'] and removing unsafe as any casts.

PR created automatically by Jules for task 3593452205397511206 started by @ngoiyaeric

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved robustness of image processing during message submissions with enhanced filtering logic.
    • Added file size validation to protect against processing excessively large files.
    • Refined message filtering to ensure more accurate conversation context handling.

…and fix historical filtering

- Added comprehensive diagnostic logging to `app/actions.tsx` and `lib/agents/researcher.tsx` to trace image processing and message construction.
- Fixed historical message filter to correctly retain "IMAGE_PROCESSED" placeholders.
- Implemented 10MB server-side file size limit and improved error handling for image processing.
- Refactored message content construction to use SDK-provided `TextPart` and `ImagePart` types, eliminating unsafe type casting.

Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel

vercel Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
qcx Ready Ready Preview, Comment Jun 4, 2026 11:01am

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@ngoiyaeric, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 49 minutes and 2 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 815bd0bf-8c39-4d03-a6d8-ae19ed3b91fd

📥 Commits

Reviewing files that changed from the base of the PR and between 4f17f17 and 9e27c40.

📒 Files selected for processing (1)
  • app/actions.tsx

Walkthrough

This PR refactors image and message handling in the submit action with type-safe message parts, image validation filtering, and file-size guards. It adds diagnostic logging to both the submit flow and researcher agent to improve visibility into the image pipeline processing.

Changes

Image Pipeline and Message Handling

Layer / File(s) Summary
Message and part type imports
app/actions.tsx
Imports CoreMessage, ToolResultPart, TextPart, and ImagePart from the ai library to support type-safe message filtering and request content construction.
Submit action image filtering and message parts
app/actions.tsx
Refactors submit flow to validate image parts (retaining only valid data: URLs or IMAGE_PROCESSED sentinel), construct type-safe TextPart/ImagePart arrays, add 10MB file-size guard with logging, and assemble content using parts directly when images are present or join text parts otherwise.
Researcher agent image pipeline logging
lib/agents/researcher.tsx
Extracts the most recent user message and logs detailed image presence diagnostics including message count with images and last user message content structure (parts and per-part image status).

Sequence Diagram

sequenceDiagram
  participant Client
  participant SubmitAction
  participant MessageFilter
  participant ResearcherAgent
  Client->>SubmitAction: Submit with optional file
  SubmitAction->>SubmitAction: Extract file metadata, log
  SubmitAction->>MessageFilter: Filter messages and image parts
  MessageFilter->>MessageFilter: Retain valid data: URLs or IMAGE_PROCESSED
  SubmitAction->>SubmitAction: Validate file size (≤10MB)
  SubmitAction->>SubmitAction: Assemble messageParts (TextPart/ImagePart)
  SubmitAction->>ResearcherAgent: Send prepared messageParts
  ResearcherAgent->>ResearcherAgent: Extract last user message
  ResearcherAgent->>ResearcherAgent: Log image presence and message structure
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • QueueLab/QCX#303: File-attachment submit-flow refactor and image-part handling in the same file.
  • QueueLab/QCX#492: Related message sanitization and image-part filtering with IMAGE_PROCESSED sentinel handling in the submit construction path.

Suggested Labels

Review effort 4/5, Possible security concern

Poem

🐰 Images now flow through pipes so clean,
With data: checks and validation keen,
Ten megs is plenty, no more, no less,
Type-safe parts put this pipeline to test,
Logging shows what the researcher will see!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately summarizes the main changes: diagnostic logging improvements and type safety fixes to the image pipeline.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-image-pipeline-logging-types-3593452205397511206

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.

@qodo-code-review

Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Fix image pipeline diagnostics, type safety, and filtering

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Added comprehensive diagnostic logging throughout image pipeline
• Fixed historical message filter to retain "IMAGE_PROCESSED" placeholders
• Implemented 10MB server-side file size limit with error handling
• Refactored messageParts to use SDK-provided TextPart and ImagePart types
Diagram
flowchart LR
  A["File Input"] -->|"Extract & Log"| B["File Validation"]
  B -->|"Size Check"| C["Buffer Processing"]
  C -->|"Type Detection"| D["Image or Text"]
  D -->|"Convert to Base64"| E["Create ImagePart"]
  D -->|"Read Content"| F["Create TextPart"]
  E -->|"Type-Safe Parts"| G["messageParts Array"]
  F -->|"Type-Safe Parts"| G
  G -->|"Filter History"| H["CoreMessage Content"]
  H -->|"Retain IMAGE_PROCESSED"| I["Send to Model"]

Loading

Grey Divider

File Changes

1. app/actions.tsx Bug fix, enhancement, error handling +100/-43

Add diagnostics, type safety, and filtering fixes

• Added imports for TextPart and ImagePart types from ai SDK
• Added diagnostic logging for file extraction, buffer loading, and image processing
• Fixed historical message filter to allow "IMAGE_PROCESSED" placeholders alongside data URLs
• Implemented 10MB file size limit with error handling and try-catch block
• Refactored messageParts to use strict TextPart and ImagePart types instead of generic objects
• Added detailed logging for messageParts structure and final content validation

app/actions.tsx


2. lib/agents/researcher.tsx Enhancement, diagnostics +22/-0

Add image pipeline diagnostic logging

• Added comprehensive diagnostic logging to trace image pipeline execution
• Logs include message count, image presence detection, and last user message content structure
• Provides visibility into message content types and image part detection

lib/agents/researcher.tsx


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0)

Grey Divider


Action required

1. File-only upload dropped 🐞 Bug ≡ Correctness
Description
In submit(), if the user submits only a file (no text) and the file is >10MB or processing throws,
the code only logs and continues, leaving messageParts empty; this makes content an empty string and
the if (content) guard skips appending the user message. The pipeline then runs
taskManager/researcher using only historical messages, so the response ignores the user’s upload
while still consuming tokens.
Code

app/actions.tsx[R385-451]

  if (userInput) {
    messageParts.push({ type: 'text', text: userInput })
  }

  if (file) {
-    const buffer = await file.arrayBuffer()
-    if (file.type.startsWith('image/')) {
-      const dataUrl = `data:${file.type};base64,${Buffer.from(
-        buffer
-      ).toString('base64')}`
-      messageParts.push({
-        type: 'image',
-        image: dataUrl,
-        mimeType: file.type
-      })
-    } else if (file.type === 'text/plain') {
-      const textContent = Buffer.from(buffer).toString('utf-8')
-      const existingTextPart = messageParts.find(p => p.type === 'text')
-      if (existingTextPart) {
-        existingTextPart.text = `${textContent}\n\n${existingTextPart.text}`
-      } else {
-        messageParts.push({ type: 'text', text: textContent })
+    const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
+    if (file.size > MAX_FILE_SIZE) {
+      console.error('File size exceeds 10MB limit:', file.size)
+    } else {
+      try {
+        const buffer = await file.arrayBuffer()
+        console.log('File buffer loaded:', { size: buffer.byteLength })
+        if (file.type.startsWith('image/')) {
+          const dataUrl = `data:${file.type};base64,${Buffer.from(
+            buffer
+          ).toString('base64')}`
+          console.log('Image processed:', {
+            dataUrlPrefix: dataUrl.substring(0, 50),
+            totalLength: dataUrl.length
+          })
+          const imagePart: ImagePart = {
+            type: 'image',
+            image: dataUrl,
+            mimeType: file.type
+          }
+          console.log('Pushing image part (debug shape):', {
+            ...imagePart,
+            image: imagePart.image.substring(0, 50) + '...'
+          })
+          messageParts.push(imagePart)
+        } else if (file.type === 'text/plain') {
+          const textContent = Buffer.from(buffer).toString('utf-8')
+          const existingTextPart = messageParts.find(
+            (p): p is TextPart => p.type === 'text'
+          )
+          if (existingTextPart) {
+            existingTextPart.text = `${textContent}\n\n${existingTextPart.text}`
+          } else {
+            messageParts.push({ type: 'text', text: textContent })
+          }
+        }
+      } catch (error) {
+        console.error('Error processing file:', error)
      }
    }
  }

  const hasImage = messageParts.some(part => part.type === 'image')
+  console.log('messageParts structure:', {
+    parts: messageParts.map(p => ({
+      type: p.type,
+      length: p.type === 'text' ? p.text.length : undefined
+    })),
+    hasImage
+  })
  const content: CoreMessage['content'] = hasImage
-    ? messageParts as CoreMessage['content']
-    : messageParts.map(part => part.text).join('\n')
+    ? messageParts
+    : messageParts.map(part => (part.type === 'text' ? part.text : '')).join('\n')
+  console.log('Final content structure:', {
+    hasImage,
+    contentType: typeof content,
+    isArray: Array.isArray(content),
+    partsCount: Array.isArray(content) ? content.length : 'N/A'
+  })

  const type = skip
    ? undefined
Evidence
The function permits file-only submissions, but on oversize/error it only logs and does not add any
messageParts; content becomes an empty string and is not appended due to a truthy guard, yet
processEvents() still runs using messages (history only).

app/actions.tsx[327-335]
app/actions.tsx[385-430]
app/actions.tsx[440-475]
app/actions.tsx[481-529]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`submit()` allows file-only requests, but when the file is rejected (size > 10MB) or throws during processing, the request continues without creating a user message. This results in the system running `taskManager()`/`researcher()` against stale `messages`, producing an unrelated response and silently ignoring the user’s upload.

## Issue Context
- The code returns early only when *both* `userInput` and `file` are missing.
- If the file is too large or processing fails, no message part is added and `content` becomes `''`.
- Because message append is guarded by `if (content)`, empty string prevents the user message from being appended.

## Fix Focus Areas
- app/actions.tsx[327-335]
- app/actions.tsx[385-475]
- app/actions.tsx[481-529]

## What to change
- When `file.size > MAX_FILE_SIZE`, stop the pipeline (early return) and surface a user-visible error (e.g., append an assistant error message to `uiStream` and `aiState.done(...)`, and set `isGenerating.done(false)` / `uiStream.done()`).
- In the `catch` for file processing, similarly fail fast with a user-visible error.
- Additionally, guard against `messageParts.length === 0` before building/using `content` (so file-only requests can’t fall through into `processEvents()` with no new user message).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Base64 image logged 🐞 Bug ⛨ Security
Description
submit() logs a prefix of the base64 data URL for uploaded images (and also logs file metadata),
which can leak user-provided image content into server logs. This also adds noisy, per-request
logging on a hot path.
Code

app/actions.tsx[R396-413]

+        console.log('File buffer loaded:', { size: buffer.byteLength })
+        if (file.type.startsWith('image/')) {
+          const dataUrl = `data:${file.type};base64,${Buffer.from(
+            buffer
+          ).toString('base64')}`
+          console.log('Image processed:', {
+            dataUrlPrefix: dataUrl.substring(0, 50),
+            totalLength: dataUrl.length
+          })
+          const imagePart: ImagePart = {
+            type: 'image',
+            image: dataUrl,
+            mimeType: file.type
+          }
+          console.log('Pushing image part (debug shape):', {
+            ...imagePart,
+            image: imagePart.image.substring(0, 50) + '...'
+          })
Evidence
The added logs explicitly output substrings of the generated data:${file.type};base64,... URL and
file metadata, which are derived from user uploads and will be written to server logs.

app/actions.tsx[244-250]
app/actions.tsx[395-414]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New diagnostic logs in `submit()` print portions of user-uploaded image data URLs (base64) and file metadata. This can leak sensitive user content into server logs and unnecessarily increases log volume.

## Issue Context
The logs currently print:
- `dataUrl.substring(0, 50)` (includes MIME prefix and start of base64)
- `imagePart.image.substring(0, 50)`
- file name/type/size

## Fix Focus Areas
- app/actions.tsx[244-250]
- app/actions.tsx[395-414]

## What to change
- Remove logging of any `data:` URLs or base64 substrings.
- If diagnostics are still needed, gate them behind an explicit server-side debug flag (e.g., `IMAGE_PIPELINE_DEBUG === 'true'`) and only log non-sensitive metadata (mimeType, byteLength, boolean flags).
- Prefer a structured logger with redaction if the project has one; otherwise keep logs minimal and never include user content-derived payloads.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@ngoiyaeric

Copy link
Copy Markdown
Collaborator Author

@coderabbitai 13:48:30.070 Running build in Washington, D.C., USA (East) – iad1
13:48:30.071 Build machine configuration: 2 cores, 8 GB
13:48:30.206 Cloning github.com/QueueLab/QCX (Branch: fix-image-pipeline-logging-types-3593452205397511206, Commit: 4f17f17)
13:48:30.917 Cloning completed: 711.000ms
13:48:33.142 Restored build cache from previous deployment (4aGjFmNSrfPeLLexY9aTDcMXxwvZ)
13:48:33.342 Running "vercel build"
13:48:33.377 Vercel CLI 54.9.0
13:48:33.660 Running "install" command: bun install...
13:48:33.720 [0.44ms] ".env"
13:48:33.726 bun install v1.3.12 (700fc117)
13:48:34.088 Saved lockfile
13:48:34.089
13:48:34.089 Checked 1294 installs across 1318 packages (no changes) [381.00ms]
13:48:34.092 Detected Next.js version: 15.3.8
13:48:34.093 Running "bun run build"
13:48:34.098 $ next build
13:48:34.878 ▲ Next.js 15.3.8
13:48:34.879 - Environments: .env
13:48:34.879
13:48:34.933 Creating an optimized production build ...
13:49:21.208 ✓ Compiled successfully in 42s
13:49:21.214 Linting and checking validity of types ...
13:49:29.267
13:49:29.268 ./components/chat.tsx
13:49:29.269 89:6 Warning: React Hook useEffect has a missing dependency: 'aiState.messages'. Either include it or remove the dependency array. react-hooks/exhaustive-deps
13:49:29.269
13:49:29.269 ./components/compare-slider.tsx
13:49:29.270 49:7 Warning: Using <img> could result in slower LCP and higher bandwidth. Consider using <Image /> from next/image to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
13:49:29.270 61:11 Warning: Using <img> could result in slower LCP and higher bandwidth. Consider using <Image /> from next/image to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
13:49:29.270
13:49:29.270 ./components/copilot-optimized.tsx
13:49:29.270 70:6 Warning: React Hook useEffect has missing dependencies: 'checkedOptions' and 'query'. Either include them or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setIsButtonDisabled' needs the current value of 'query'. react-hooks/exhaustive-deps
13:49:29.271
13:49:29.271 ./components/copilot.tsx
13:49:29.272 70:6 Warning: React Hook useEffect has missing dependencies: 'checkedOptions' and 'query'. Either include them or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setIsButtonDisabled' needs the current value of 'query'. react-hooks/exhaustive-deps
13:49:29.272
13:49:29.272 ./components/map/mapbox-map.tsx
13:49:29.273 470:6 Warning: React Hook useEffect has missing dependencies: 'mapData.cameraState', 'position?.latitude', and 'position?.longitude'. Either include them or remove the dependency array. react-hooks/exhaustive-deps
13:49:29.273
13:49:29.273 info - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/app/api-reference/config/eslint#disabling-rules
13:49:40.576 Failed to compile.
13:49:40.577
13:49:40.578 ./app/actions.tsx:412:36
13:49:40.578 Type error: Property 'substring' does not exist on type 'DataContent | URL'.
13:49:40.578 Property 'substring' does not exist on type 'ArrayBuffer'.
13:49:40.578
13:49:40.579 410 | console.log('Pushing image part (debug shape):', {
13:49:40.579 411 | ...imagePart,
13:49:40.579 > 412 | image: imagePart.image.substring(0, 50) + '...'
13:49:40.579 | ^
13:49:40.579 413 | })
13:49:40.579 414 | messageParts.push(imagePart)
13:49:40.579 415 | } else if (file.type === 'text/plain') {
13:49:40.633 Next.js build worker exited with code: 1 and signal: null
13:49:40.928 error: script "build" exited with code 1
13:49:40.946 Error: Command "bun run build" exited with 1

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '395,430p' app/actions.tsx

Length of output: 1419



@ngoiyaeric The build failure is a TypeScript type error on the diagnostic log line. imagePart.image is typed as DataContent | URL (per the ai SDK's ImagePart type), which can be string | Uint8Array | ArrayBuffer | Buffer | URL, so .substring() doesn't compile.

The simplest fix is to use the dataUrl variable directly in the log — it's already a string and is still in scope at that point:

// app/actions.tsx  (around line 410)
console.log('Pushing image part (debug shape):', {
  ...imagePart,
- image: imagePart.image.substring(0, 50) + '...'
+ image: dataUrl.substring(0, 50) + '...'
})

This keeps the log output identical at runtime while satisfying TypeScript's type checker without any unsafe cast.


🧠 Learnings used
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-04-25T08:10:37.673Z
Learning: In the QCX repository (feat/ai-sdk-ui-migration branch and going forward), there is no token generation/counting tracking and no multimodal LLM features. Base64 image data sent via `append()` body fields in components like `header-search-button.tsx` and `resolution-carousel.tsx` is for server-side `resolution_search` processing only — it is NOT passed as multimodal message content parts to a vision model. Avoid flagging token tracking or multimodal LLM handling gaps as review concerns in this codebase.

Comment thread app/actions.tsx
Comment on lines 385 to 451
if (userInput) {
messageParts.push({ type: 'text', text: userInput })
}

if (file) {
const buffer = await file.arrayBuffer()
if (file.type.startsWith('image/')) {
const dataUrl = `data:${file.type};base64,${Buffer.from(
buffer
).toString('base64')}`
messageParts.push({
type: 'image',
image: dataUrl,
mimeType: file.type
})
} else if (file.type === 'text/plain') {
const textContent = Buffer.from(buffer).toString('utf-8')
const existingTextPart = messageParts.find(p => p.type === 'text')
if (existingTextPart) {
existingTextPart.text = `${textContent}\n\n${existingTextPart.text}`
} else {
messageParts.push({ type: 'text', text: textContent })
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
if (file.size > MAX_FILE_SIZE) {
console.error('File size exceeds 10MB limit:', file.size)
} else {
try {
const buffer = await file.arrayBuffer()
console.log('File buffer loaded:', { size: buffer.byteLength })
if (file.type.startsWith('image/')) {
const dataUrl = `data:${file.type};base64,${Buffer.from(
buffer
).toString('base64')}`
console.log('Image processed:', {
dataUrlPrefix: dataUrl.substring(0, 50),
totalLength: dataUrl.length
})
const imagePart: ImagePart = {
type: 'image',
image: dataUrl,
mimeType: file.type
}
console.log('Pushing image part (debug shape):', {
...imagePart,
image: imagePart.image.substring(0, 50) + '...'
})
messageParts.push(imagePart)
} else if (file.type === 'text/plain') {
const textContent = Buffer.from(buffer).toString('utf-8')
const existingTextPart = messageParts.find(
(p): p is TextPart => p.type === 'text'
)
if (existingTextPart) {
existingTextPart.text = `${textContent}\n\n${existingTextPart.text}`
} else {
messageParts.push({ type: 'text', text: textContent })
}
}
} catch (error) {
console.error('Error processing file:', error)
}
}
}

const hasImage = messageParts.some(part => part.type === 'image')
console.log('messageParts structure:', {
parts: messageParts.map(p => ({
type: p.type,
length: p.type === 'text' ? p.text.length : undefined
})),
hasImage
})
const content: CoreMessage['content'] = hasImage
? messageParts as CoreMessage['content']
: messageParts.map(part => part.text).join('\n')
? messageParts
: messageParts.map(part => (part.type === 'text' ? part.text : '')).join('\n')
console.log('Final content structure:', {
hasImage,
contentType: typeof content,
isArray: Array.isArray(content),
partsCount: Array.isArray(content) ? content.length : 'N/A'
})

const type = skip
? undefined

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.

Action required

1. File-only upload dropped 🐞 Bug ≡ Correctness

In submit(), if the user submits only a file (no text) and the file is >10MB or processing throws,
the code only logs and continues, leaving messageParts empty; this makes content an empty string and
the if (content) guard skips appending the user message. The pipeline then runs
taskManager/researcher using only historical messages, so the response ignores the user’s upload
while still consuming tokens.
Agent Prompt
## Issue description
`submit()` allows file-only requests, but when the file is rejected (size > 10MB) or throws during processing, the request continues without creating a user message. This results in the system running `taskManager()`/`researcher()` against stale `messages`, producing an unrelated response and silently ignoring the user’s upload.

## Issue Context
- The code returns early only when *both* `userInput` and `file` are missing.
- If the file is too large or processing fails, no message part is added and `content` becomes `''`.
- Because message append is guarded by `if (content)`, empty string prevents the user message from being appended.

## Fix Focus Areas
- app/actions.tsx[327-335]
- app/actions.tsx[385-475]
- app/actions.tsx[481-529]

## What to change
- When `file.size > MAX_FILE_SIZE`, stop the pipeline (early return) and surface a user-visible error (e.g., append an assistant error message to `uiStream` and `aiState.done(...)`, and set `isGenerating.done(false)` / `uiStream.done()`).
- In the `catch` for file processing, similarly fail fast with a user-visible error.
- Additionally, guard against `messageParts.length === 0` before building/using `content` (so file-only requests can’t fall through into `processEvents()` with no new user message).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🤖 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/actions.tsx`:
- Around line 389-428: The file-processing block that builds messageParts (the
branch handling file, MAX_FILE_SIZE, image/text conversion, and the catch)
currently only logs errors and can leave messageParts unchanged; update this
logic to fail fast by detecting when no usable part was produced and then either
return/throw immediately from the enclosing action or append a user-facing
TextPart error before continuing so processEvents() never runs on stale history;
specifically, after the image/text handling and inside the
catch/oversize/unsupported-MIME branches, set a boolean flag (e.g., fileHandled)
or push a TextPart like {type: 'text', text: 'Upload failed: <brief reason>'}
and short-circuit the function (or return that error to the caller) so
processEvents() is not invoked with no new message part. Ensure changes touch
the same symbols: the file handling block that uses file.arrayBuffer(),
messageParts, ImagePart/TextPart, and the later processEvents() caller.
- Around line 401-413: The logs currently leak base64 image bytes via
dataUrl.substring(...) and imagePart.image.substring(...) — remove those
substring logs and replace them with non-sensitive metadata: log image MIME type
(file.type), byte/character length (dataUrl.length), and a short content
fingerprint (e.g., SHA-256 hex or truncated hash computed from dataUrl) instead;
update the console messages around dataUrl and imagePart (the places where
dataUrl, imagePart, and ImagePart are constructed) to output only mimeType,
totalLength, and the computed hash/fingerprint.
- Around line 405-414: The code currently pushes raw base64 data URLs into
aiState.messages via the imagePart object (type: 'image', image: dataUrl) which
later flows through onSetAIState into saveChat(), causing large binary blobs to
be persisted; update the logic that constructs and pushes imagePart (and the
similar block referenced at the other location) to avoid storing the full data
URL—replace the image field with either a sentinel string like 'IMAGE_PROCESSED'
or an asset reference/id after uploading the file to your asset store, and only
keep the real base64/data URL transiently for immediate use (e.g.,
preview/upload) before substituting the persistent imagePart prior to calling
onSetAIState/saveChat.
- Around line 348-368: The historical message filter currently preserves image
parts with ImagePart.image === 'IMAGE_PROCESSED'; update the filter logic (the
map over messages that builds filteredContent) to exclude or replace the
'IMAGE_PROCESSED' sentinel when constructing messages that will be sent to the
model (CoreMessage[]), not just when persisting/displaying history. Ensure the
UI/persistence code can still store the sentinel but the code path that prepares
messages for the model (the place where messages are forwarded into
nonexperimental_streamText in researcher logic) replaces 'IMAGE_PROCESSED' with
a real image payload or removes the image part so the model never receives the
sentinel; reference the ImagePart.image checks and the
nonexperimental_streamText/CoreMessage[] construction to locate the changes.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 13f4e768-09cb-4958-a83c-f61ce329e38c

📥 Commits

Reviewing files that changed from the base of the PR and between b110b93 and 4f17f17.

📒 Files selected for processing (2)
  • app/actions.tsx
  • lib/agents/researcher.tsx
📜 Review details
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-04-25T08:10:37.673Z
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-04-25T08:10:37.673Z
Learning: In the QCX repository (feat/ai-sdk-ui-migration branch and going forward), there is no token generation/counting tracking and no multimodal LLM features. Base64 image data sent via `append()` body fields in components like `header-search-button.tsx` and `resolution-carousel.tsx` is for server-side `resolution_search` processing only — it is NOT passed as multimodal message content parts to a vision model. Avoid flagging token tracking or multimodal LLM handling gaps as review concerns in this codebase.

Applied to files:

  • lib/agents/researcher.tsx
  • app/actions.tsx
📚 Learning: 2026-02-08T16:50:33.791Z
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-02-08T16:50:33.791Z
Learning: The PR branch implements RAG (Retrieval-Augmented Generation) via `lib/actions/rag.ts` which uses Supabase RPC functions `generate_embedding()` and `hybrid_search()` to retrieve relevant context from past conversations. However, there's a bug on line 433 of `app/actions.tsx` where `currentSystemPrompt` is passed to the researcher instead of `augmentedSystemPrompt`, preventing the retrieved context from being used.

Applied to files:

  • lib/agents/researcher.tsx
📚 Learning: 2026-01-13T13:26:30.086Z
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-13T13:26:30.086Z
Learning: Workers that rely on ai.generateObject (geojsonParser, map-command-generator, feedback-analyzer) must not use the X.AI 'grok-4-fast-non-reasoning' model because X.AI’s chat/completions rejects JSON Schema structured-output arguments; prefer OpenAI gpt-4o for structured outputs.

Applied to files:

  • app/actions.tsx
🔇 Additional comments (1)
app/actions.tsx (1)

389-428: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

resolution_search still bypasses the new upload safeguards.

The new 10MB guard and arrayBuffer() error handling only run in the generic submit path. action === 'resolution_search' returns at Line 242, so the main map-analysis flow from components/header-search-button.tsx:142-160 still reaches the unguarded file_mapbox.arrayBuffer() / file_google.arrayBuffer() / file.arrayBuffer() calls at Lines 66-73. A large or unreadable screenshot can still fail the server action on the primary image path.

⛔ Skipped due to learnings
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-04-25T08:10:37.673Z
Learning: In the QCX repository (feat/ai-sdk-ui-migration branch and going forward), there is no token generation/counting tracking and no multimodal LLM features. Base64 image data sent via `append()` body fields in components like `header-search-button.tsx` and `resolution-carousel.tsx` is for server-side `resolution_search` processing only — it is NOT passed as multimodal message content parts to a vision model. Avoid flagging token tracking or multimodal LLM handling gaps as review concerns in this codebase.

Comment thread app/actions.tsx
Comment on lines +348 to +368
.map((m: any) => {
if (Array.isArray(m.content)) {
const filteredContent = m.content.filter((part: any) => {
if (part.type === 'image') {
const isValid =
typeof part.image === 'string' &&
(part.image.startsWith('data:') ||
part.image === 'IMAGE_PROCESSED')
if (isValid) {
retainedImagesCount++
} else {
filteredImagesCount++
}
return isValid
}
return true
})
return {
...m,
content: filteredContent
} as any

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For AI SDK version 4.3.19, what values are valid for ImagePart.imageinsideCoreMessage['content']? Is an arbitrary sentinel string like IMAGE_PROCESSED supported in provider requests, or must it be an actual data URL/URL/binary payload?

💡 Result:

For AI SDK version 4.3.19, the ImagePart.image property is designed to hold actual image data or a reference to it, and it does not support arbitrary sentinel strings like IMAGE_PROCESSED [1][2]. The valid types for ImagePart.image are restricted to the following [1][3][4]: - Data payloads: base64-encoded strings, Uint8Array, ArrayBuffer, or Buffer [1][3][4]. - URLs: a URL object or string pointing to an image (e.g., http(s) URLs or base64 data URLs) [1][3][4]. Providing an arbitrary string that does not conform to these formats (i.e., is not a valid data representation or a valid URL) will generally cause the SDK or the underlying model provider to fail, as they expect valid image content to process [3][4]. Note that in recent versions of the AI SDK, ImagePart has been deprecated in favor of FilePart (using { type: 'file', mediaType: 'image', data: ... }) [5][6]. If you need to include metadata or indicate that an image has already been processed, this should be handled through other fields, such as providerOptions, or by managing the file state outside of the ImagePart content array [7][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact image filtering block in app/actions.tsx
echo "=== app/actions.tsx (filtering around retainedImagesCount) ==="
sed -n '320,460p' app/actions.tsx | nl -ba | sed -n '1,200p'

# Show the submit flow where the CoreMessage[] is built / aiState is appended
echo
echo "=== app/actions.tsx (building CoreMessage / appending user messages) ==="
sed -n '460,560p' app/actions.tsx | nl -ba | sed -n '1,220p'

# Show where onSetAIState persists messages
echo
echo "=== app/actions.tsx (onSetAIState persistence) ==="
sed -n '650,780p' app/actions.tsx | nl -ba | sed -n '1,200p'

# Show lib/agents/researcher.tsx where messages are forwarded to nonexperimental_streamText
echo
echo "=== lib/agents/researcher.tsx (forwarding to nonexperimental_streamText) ==="
sed -n '1,220p' lib/agents/researcher.tsx | nl -ba | sed -n '1,220p'

Repository: QueueLab/QCX

Length of output: 180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== app/actions.tsx (filtering around retainedImagesCount) ==="
sed -n '320,460p' app/actions.tsx | cat -n

echo
echo "=== app/actions.tsx (building CoreMessage / appending user messages) ==="
sed -n '460,560p' app/actions.tsx | cat -n

echo
echo "=== app/actions.tsx (onSetAIState persistence) ==="
sed -n '650,780p' app/actions.tsx | cat -n

echo
echo "=== lib/agents/researcher.tsx (forwarding to nonexperimental_streamText) ==="
sed -n '1,220p' lib/agents/researcher.tsx | cat -n

Repository: QueueLab/QCX

Length of output: 21615


Stop preserving IMAGE_PROCESSED as an ImagePart.image sent to the SDK
app/actions.tsx’s historical message filter keeps { type: 'image', image: 'IMAGE_PROCESSED' }, and lib/agents/researcher.tsx forwards messages unchanged into nonexperimental_streamText (and enables the “has image” path whenever any type: 'image' part exists). ImagePart.image must be real image data/URL (or binary payload), not an arbitrary sentinel—strip/replace this placeholder when building the CoreMessage[] passed to the model, while allowing the sentinel only for persisted/UI history.

🤖 Prompt for 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.

In `@app/actions.tsx` around lines 348 - 368, The historical message filter
currently preserves image parts with ImagePart.image === 'IMAGE_PROCESSED';
update the filter logic (the map over messages that builds filteredContent) to
exclude or replace the 'IMAGE_PROCESSED' sentinel when constructing messages
that will be sent to the model (CoreMessage[]), not just when
persisting/displaying history. Ensure the UI/persistence code can still store
the sentinel but the code path that prepares messages for the model (the place
where messages are forwarded into nonexperimental_streamText in researcher
logic) replaces 'IMAGE_PROCESSED' with a real image payload or removes the image
part so the model never receives the sentinel; reference the ImagePart.image
checks and the nonexperimental_streamText/CoreMessage[] construction to locate
the changes.

Comment thread app/actions.tsx
Comment on lines 389 to 428
if (file) {
const buffer = await file.arrayBuffer()
if (file.type.startsWith('image/')) {
const dataUrl = `data:${file.type};base64,${Buffer.from(
buffer
).toString('base64')}`
messageParts.push({
type: 'image',
image: dataUrl,
mimeType: file.type
})
} else if (file.type === 'text/plain') {
const textContent = Buffer.from(buffer).toString('utf-8')
const existingTextPart = messageParts.find(p => p.type === 'text')
if (existingTextPart) {
existingTextPart.text = `${textContent}\n\n${existingTextPart.text}`
} else {
messageParts.push({ type: 'text', text: textContent })
const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
if (file.size > MAX_FILE_SIZE) {
console.error('File size exceeds 10MB limit:', file.size)
} else {
try {
const buffer = await file.arrayBuffer()
console.log('File buffer loaded:', { size: buffer.byteLength })
if (file.type.startsWith('image/')) {
const dataUrl = `data:${file.type};base64,${Buffer.from(
buffer
).toString('base64')}`
console.log('Image processed:', {
dataUrlPrefix: dataUrl.substring(0, 50),
totalLength: dataUrl.length
})
const imagePart: ImagePart = {
type: 'image',
image: dataUrl,
mimeType: file.type
}
console.log('Pushing image part (debug shape):', {
...imagePart,
image: imagePart.image.substring(0, 50) + '...'
})
messageParts.push(imagePart)
} else if (file.type === 'text/plain') {
const textContent = Buffer.from(buffer).toString('utf-8')
const existingTextPart = messageParts.find(
(p): p is TextPart => p.type === 'text'
)
if (existingTextPart) {
existingTextPart.text = `${textContent}\n\n${existingTextPart.text}`
} else {
messageParts.push({ type: 'text', text: textContent })
}
}
} catch (error) {
console.error('Error processing file:', error)
}

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast when attachment processing yields no usable part.

Oversize files, unsupported MIME types, and arrayBuffer() failures only log and then fall through. If the request is file-only, Line 327 does not short-circuit because file exists, but no new user message is appended at Lines 458-475, so processEvents() runs against stale history instead of returning a user-facing error for the bad upload.

Suggested direction
   if (file) {
     const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
     if (file.size > MAX_FILE_SIZE) {
-      console.error('File size exceeds 10MB limit:', file.size)
+      throw new Error('File size must be less than 10MB')
     } else {
       try {
         const buffer = await file.arrayBuffer()
         ...
-        }
+        } else {
+          throw new Error(`Unsupported file type: ${file.type || 'unknown'}`)
+        }
       } catch (error) {
-        console.error('Error processing file:', error)
+        throw error
       }
     }
   }

Also applies to: 458-475

🤖 Prompt for 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.

In `@app/actions.tsx` around lines 389 - 428, The file-processing block that
builds messageParts (the branch handling file, MAX_FILE_SIZE, image/text
conversion, and the catch) currently only logs errors and can leave messageParts
unchanged; update this logic to fail fast by detecting when no usable part was
produced and then either return/throw immediately from the enclosing action or
append a user-facing TextPart error before continuing so processEvents() never
runs on stale history; specifically, after the image/text handling and inside
the catch/oversize/unsupported-MIME branches, set a boolean flag (e.g.,
fileHandled) or push a TextPart like {type: 'text', text: 'Upload failed: <brief
reason>'} and short-circuit the function (or return that error to the caller) so
processEvents() is not invoked with no new message part. Ensure changes touch
the same symbols: the file handling block that uses file.arrayBuffer(),
messageParts, ImagePart/TextPart, and the later processEvents() caller.

Comment thread app/actions.tsx
Comment on lines +401 to +413
console.log('Image processed:', {
dataUrlPrefix: dataUrl.substring(0, 50),
totalLength: dataUrl.length
})
const imagePart: ImagePart = {
type: 'image',
image: dataUrl,
mimeType: file.type
}
console.log('Pushing image part (debug shape):', {
...imagePart,
image: imagePart.image.substring(0, 50) + '...'
})

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t log raw base64 image bytes.

The dataUrl.substring(0, 50) and imagePart.image.substring(0, 50) logs still emit user-uploaded content into server logs. These attachments can contain sensitive data, and logs usually outlive request bodies. Log MIME type, byte length, or a hash instead.

Safer logging
           console.log('Image processed:', {
-            dataUrlPrefix: dataUrl.substring(0, 50),
-            totalLength: dataUrl.length
+            mimeType: file.type,
+            payloadBytes: buffer.byteLength,
+            totalLength: dataUrl.length
           })
           const imagePart: ImagePart = {
             type: 'image',
             image: dataUrl,
             mimeType: file.type
           }
           console.log('Pushing image part (debug shape):', {
-            ...imagePart,
-            image: imagePart.image.substring(0, 50) + '...'
+            type: imagePart.type,
+            mimeType: imagePart.mimeType,
+            totalLength: imagePart.image.length
           })
🤖 Prompt for 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.

In `@app/actions.tsx` around lines 401 - 413, The logs currently leak base64 image
bytes via dataUrl.substring(...) and imagePart.image.substring(...) — remove
those substring logs and replace them with non-sensitive metadata: log image
MIME type (file.type), byte/character length (dataUrl.length), and a short
content fingerprint (e.g., SHA-256 hex or truncated hash computed from dataUrl)
instead; update the console messages around dataUrl and imagePart (the places
where dataUrl, imagePart, and ImagePart are constructed) to output only
mimeType, totalLength, and the computed hash/fingerprint.

Comment thread app/actions.tsx
Comment on lines +405 to +414
const imagePart: ImagePart = {
type: 'image',
image: dataUrl,
mimeType: file.type
}
console.log('Pushing image part (debug shape):', {
...imagePart,
image: imagePart.image.substring(0, 50) + '...'
})
messageParts.push(imagePart)

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t persist raw data URLs in chat history.

When an image is attached, the full base64 data URL is pushed into aiState.messages here. onSetAIState later forwards state.messages straight to saveChat() at Lines 707-735, so each upload is retained in chat storage. A 10MB image becomes ~13MB once base64-encoded, and it also stores the raw user attachment indefinitely. Replace stored images with IMAGE_PROCESSED or an asset reference before persistence.

Also applies to: 458-475

🤖 Prompt for 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.

In `@app/actions.tsx` around lines 405 - 414, The code currently pushes raw base64
data URLs into aiState.messages via the imagePart object (type: 'image', image:
dataUrl) which later flows through onSetAIState into saveChat(), causing large
binary blobs to be persisted; update the logic that constructs and pushes
imagePart (and the similar block referenced at the other location) to avoid
storing the full data URL—replace the image field with either a sentinel string
like 'IMAGE_PROCESSED' or an asset reference/id after uploading the file to your
asset store, and only keep the real base64/data URL transiently for immediate
use (e.g., preview/upload) before substituting the persistent imagePart prior to
calling onSetAIState/saveChat.

@google-labs-jules

Copy link
Copy Markdown
Contributor

@coderabbitai 13:48:30.070 Running build in Washington, D.C., USA (East) – iad1
13:48:30.071 Build machine configuration: 2 cores, 8 GB
13:48:30.206 Cloning github.com/QueueLab/QCX (Branch: fix-image-pipeline-logging-types-3593452205397511206, Commit: 4f17f17)
13:48:30.917 Cloning completed: 711.000ms
13:48:33.142 Restored build cache from previous deployment (4aGjFmNSrfPeLLexY9aTDcMXxwvZ)
13:48:33.342 Running "vercel build"
13:48:33.377 Vercel CLI 54.9.0
13:48:33.660 Running "install" command: bun install...
13:48:33.720 [0.44ms] ".env"
13:48:33.726 bun install v1.3.12 (700fc117)
13:48:34.088 Saved lockfile
13:48:34.089
13:48:34.089 Checked 1294 installs across 1318 packages (no changes) [381.00ms]
13:48:34.092 Detected Next.js version: 15.3.8
13:48:34.093 Running "bun run build"
13:48:34.098 $ next build
13:48:34.878 ▲ Next.js 15.3.8
13:48:34.879 - Environments: .env
13:48:34.879
13:48:34.933 Creating an optimized production build ...
13:49:21.208 ✓ Compiled successfully in 42s
13:49:21.214 Linting and checking validity of types ...
13:49:29.267
13:49:29.268 ./components/chat.tsx
13:49:29.269 89:6 Warning: React Hook useEffect has a missing dependency: 'aiState.messages'. Either include it or remove the dependency array. react-hooks/exhaustive-deps
13:49:29.269
13:49:29.269 ./components/compare-slider.tsx
13:49:29.270 49:7 Warning: Using <img> could result in slower LCP and higher bandwidth. Consider using <Image /> from next/image to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
13:49:29.270 61:11 Warning: Using <img> could result in slower LCP and higher bandwidth. Consider using <Image /> from next/image to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
13:49:29.270
13:49:29.270 ./components/copilot-optimized.tsx
13:49:29.270 70:6 Warning: React Hook useEffect has missing dependencies: 'checkedOptions' and 'query'. Either include them or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setIsButtonDisabled' needs the current value of 'query'. react-hooks/exhaustive-deps
13:49:29.271
13:49:29.271 ./components/copilot.tsx
13:49:29.272 70:6 Warning: React Hook useEffect has missing dependencies: 'checkedOptions' and 'query'. Either include them or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setIsButtonDisabled' needs the current value of 'query'. react-hooks/exhaustive-deps
13:49:29.272
13:49:29.272 ./components/map/mapbox-map.tsx
13:49:29.273 470:6 Warning: React Hook useEffect has missing dependencies: 'mapData.cameraState', 'position?.latitude', and 'position?.longitude'. Either include them or remove the dependency array. react-hooks/exhaustive-deps
13:49:29.273
13:49:29.273 info - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/app/api-reference/config/eslint#disabling-rules
13:49:40.576 Failed to compile.
13:49:40.577
13:49:40.578 ./app/actions.tsx:412:36
13:49:40.578 Type error: Property 'substring' does not exist on type 'DataContent | URL'.
13:49:40.578 Property 'substring' does not exist on type 'ArrayBuffer'.
13:49:40.578
13:49:40.579 410 | console.log('Pushing image part (debug shape):', {
13:49:40.579 411 | ...imagePart,
13:49:40.579 > 412 | image: imagePart.image.substring(0, 50) + '...'
13:49:40.579 | ^
13:49:40.579 413 | })
13:49:40.579 414 | messageParts.push(imagePart)
13:49:40.579 415 | } else if (file.type === 'text/plain') {
13:49:40.633 Next.js build worker exited with code: 1 and signal: null
13:49:40.928 error: script "build" exited with code 1
13:49:40.946 Error: Command "bun run build" exited with 1

Acknowledged. I have fixed the TypeScript error by using the local dataUrl string variable instead of imagePart.image (which is a union type) in the debug log. I verified the fix by running bun run build and it now compiles successfully.

- Fixed TypeScript error in `app/actions.tsx` by using local `dataUrl` string for debug logging.
- Verified fix with `bun run build`.
- Retained all previous improvements: diagnostic logging, historical filter fix, 10MB limit, and type safety with SDK parts.

Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
@ngoiyaeric ngoiyaeric merged commit c00774f into main Jun 4, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants