Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Aug 20, 2025

User description

This commit integrates the GeneratedMap component with the mapbox_mcp workflow.

The useMCPMapClient hook is updated to extract location information from the user's query. This location data is then passed through the ChatPanel and app/actions.tsx to the searchTool.

The searchTool now includes the location in the search results, which allows the SearchSection to render the GeneratedMap component with the correct position.

This change enables the display of a map related to the user's query in parallel with the existing map render.


PR Type

Enhancement


Description

  • Integrate GeneratedMap component with Mapbox MCP workflow

  • Extract location data from user queries for map rendering

  • Add position coordinates to search results structure

  • Display maps alongside search images when location available


Diagram Walkthrough

flowchart LR
  A["User Query"] --> B["useMCPMapClient Hook"]
  B --> C["Extract Location Data"]
  C --> D["ChatPanel"]
  D --> E["submit() with mapLocation"]
  E --> F["researcher() Agent"]
  F --> G["searchTool"]
  G --> H["SearchResults with position"]
  H --> I["SearchSection"]
  I --> J["GeneratedMap Component"]
Loading

File Walkthrough

Relevant files
Enhancement
9 files
index.ts
Add position coordinates to SearchResults type                     
+4/-0     
hooks.ts
Update coordinate property names to latitude/longitude     
+2/-2     
actions.tsx
Pass mapLocation through submit and researcher functions 
+8/-4     
chat-panel.tsx
Process location queries using MCP client                               
+6/-4     
generated-map.tsx
Create new Mapbox map component                                                   
+33/-0   
search-section.tsx
Render GeneratedMap alongside search images                           
+14/-4   
researcher.tsx
Accept mapLocation parameter in researcher function           
+3/-3     
index.tsx
Update ToolProps interface with mapLocation                           
+4/-3     
search.tsx
Include position data in search results                                   
+5/-1     

Summary by CodeRabbit

  • New Features
    • Location-aware search: queries can include places and attach detected positions to results.
    • Map preview: search results show a map with a marker when a position is found (new map component).
    • Chat location integration: chat submissions include detected map locations to improve results.
    • Dynamic system prompt: responses can be tailored via a dynamic system prompt.
    • AI messages now include optional timestamps and a new "drawing_context" type.

This commit integrates the `GeneratedMap` component with the `mapbox_mcp` workflow.

The `useMCPMapClient` hook is updated to extract location information from the user's query. This location data is then passed through the `ChatPanel` and `app/actions.tsx` to the `searchTool`.

The `searchTool` now includes the location in the search results, which allows the `SearchSection` to render the `GeneratedMap` component with the correct position.

This change enables the display of a map related to the user's query in parallel with the existing map render.
@CLAassistant
Copy link

CLAassistant commented Aug 20, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ ngoiyaeric
❌ google-labs-jules[bot]
You have signed the CLA already but the status is still pending? Let us recheck it.

@vercel
Copy link
Contributor

vercel bot commented Aug 20, 2025

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

Project Deployment Preview Comments Updated (UTC)
qcx Error Error Aug 24, 2025 11:18am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 20, 2025

Walkthrough

Submit flow and agent tooling now accept and propagate a mapLocation object; ChatPanel derives mapLocation via Mapbox MCP hook; search results can include a position and render a GeneratedMap; researcher/getTools/searchTool signatures updated to accept mapLocation; types and mapbox hook shape adjusted accordingly.

Changes

Cohort / File(s) Summary
Submission & actions
app/actions.tsx
submit signature adds mapLocation (now submit(formData?: FormData, mapLocation?: object, skip?: boolean)) and forwards dynamicSystemPrompt and mapLocation to researcher; UI streaming and response handling preserved.
Chat input / MCP integration
components/chat-panel.tsx
Imports useMCPMapClientprocessLocationQuery; handleSubmit awaits processLocationQuery(input) to get mapLocation and calls submit(formData, mapLocation).
Generated Map UI
components/map/generated-map.tsx
Added GeneratedMap React component rendering Mapbox centered on {latitude, longitude} with a marker; token read from NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN.
Search UI update
components/search-section.tsx
Image results layout split into two columns; conditionally renders GeneratedMap in right column when searchResults.position exists.
Agent plumbing
lib/agents/researcher.tsx, lib/agents/tools/index.tsx, lib/agents/tools/search.tsx
researcher gains dynamicSystemPrompt as first param, removes mcp, and accepts optional mapLocation; ToolProps and getTools add/forward mapLocation; searchTool accepts mapLocation and attaches it to SearchResults.position when present; geospatial tool calls updated to remove mcp.
Types
lib/types/index.ts
SearchResults adds position?: { latitude: number; longitude: number }; AIMessage adds createdAt?: Date and 'drawing_context' to its type union.
Mapbox MCP hook
mapbox_mcp/hooks.ts
processLocationQuery now returns mapLocation with shape { latitude, longitude, zoom } (replacing { lat, lng, zoom }).
Repo ignore
.gitignore
Adds .bun/ ignore entry under a "# Bun" header.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant CP as ChatPanel
  participant MQ as Mapbox MCP Hook
  participant S as submit (app/actions)
  participant R as researcher
  participant T as getTools / tools
  participant UI as UI Stream
  participant SS as SearchSection
  participant GM as GeneratedMap

  U->>CP: Enter message
  CP->>MQ: processLocationQuery(input)
  MQ-->>CP: { mapLocation? }
  CP->>S: submit(formData, mapLocation)
  activate S
  S->>S: getSystemPrompt(userId)
  S->>R: researcher(dynamicSystemPrompt, uiStream, streamText, messages, useSpecificModel?, mapLocation)
  activate R
  R->>T: getTools({ uiStream, fullResponse, mapLocation })
  T-->>R: tools (search, geospatial, ...)
  R->>T: invoke tools as needed
  T-->>UI: tool outputs (search results [+position], ...)
  UI-->>S: streamed UI updates
  S->>SS: render search results
  alt position present
    SS->>GM: render GeneratedMap(position)
  end
  deactivate R
  S-->>CP: completion
  deactivate S
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

A hop, a map, a pinpoint bright,
I sniff the paths from day to night.
Latitude tucked in furry notes,
Longitude packed in little throats.
I chart the world, one carrot byte! 🥕🗺️

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/connect-mapbox-mcp

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@qodo-code-review
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

Accessing searchResults.value after calling done() may yield undefined or stale data; parsing JSON from a closed streamable value can fail. Prefer using the already available toolOutput object directly to build the SearchSection props.

  };
}

// Existing tool handling
const searchResults = createStreamableValue();
searchResults.done(JSON.stringify(toolOutput));
switch (name) {
  case 'search':
    const searchResult = JSON.parse(searchResults.value);
    if (toolOutput.position) {
      searchResult.position = toolOutput.position;
    }
    return {
      id,
      component: <SearchSection result={JSON.stringify(searchResult)} />,
      isCollapsed: isCollapsed.value,
    };
Missing Cleanup

Mapbox map instance is never removed on unmount; add a cleanup function to effect to call map.current.remove() and avoid memory leaks when the component unmounts or position changes.

  useEffect(() => {
    if (map.current) return // initialize map only once
    if (mapContainer.current) {
      map.current = new mapboxgl.Map({
        container: mapContainer.current,
        style: 'mapbox://styles/mapbox/streets-v12',
        center: [position.longitude, position.latitude],
        zoom: 12
      })

      // Add a marker to the map
      new mapboxgl.Marker()
        .setLngLat([position.longitude, position.latitude])
        .addTo(map.current)
    }
  }, [position])

  return <div ref={mapContainer} className="h-full w-full" />
}
Coordinate Parsing Robustness

The coordinate regex only matches decimal degrees with a dot; it won't catch integers or degrees without decimals and may misparse text with additional context. Consider more robust parsing or validation and bounds checking for latitude/longitude.

let mapLocation = null;
let shouldShowMap = false;
const coordPattern = /(-?\d+\.\d+),\s*(-?\d+\.\d+)/;
const coordMatch = response.text.match(coordPattern);
if (coordMatch) {
  mapLocation = {
    latitude: parseFloat(coordMatch[1]),
    longitude: parseFloat(coordMatch[2]),
    zoom: 12,
  };
  shouldShowMap = true;
}

@codiumai-pr-agent-free
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Map Cleanup

The map instance is created but never cleaned up when the component unmounts. This could lead to memory leaks, especially if the component is mounted/unmounted frequently.

useEffect(() => {
  if (map.current) return // initialize map only once
  if (mapContainer.current) {
    map.current = new mapboxgl.Map({
      container: mapContainer.current,
      style: 'mapbox://styles/mapbox/streets-v12',
      center: [position.longitude, position.latitude],
      zoom: 12
    })

    // Add a marker to the map
    new mapboxgl.Marker()
      .setLngLat([position.longitude, position.latitude])
      .addTo(map.current)
  }
}, [position])
Conditional Layout

The layout structure changes significantly when a map is present, but there's no fallback for maintaining consistent UI when position data is missing. This could cause layout shifts.

<div className="flex space-x-4">
  <div className="w-1/2">
    <SearchResultsImageSection
      images={searchResults.images}
      query={searchResults.query}
    />
  </div>
  {searchResults.position && (
    <div className="w-1/2 h-64">
      <GeneratedMap position={searchResults.position} />
    </div>
  )}
</div>
Naming Inconsistency

Property names were changed from lat/lng to latitude/longitude, but the zoom property remains. This creates inconsistent naming patterns across the location object structure.

mapLocation = {
  latitude: parseFloat(coordMatch[1]),
  longitude: parseFloat(coordMatch[2]),
  zoom: 12,
};

@codiumai-pr-agent-free
Copy link
Contributor

codiumai-pr-agent-free bot commented Aug 20, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent map recreation issues
Suggestion Impact:The commit refactored to initialize the map once with an empty dependency effect, added a cleanup function to remove the map and marker, and introduced a second effect to update center/zoom and marker when position changes.

code diff:

+  // Initialize map and marker once, and clean up on unmount
   useEffect(() => {
-    if (map.current) return // initialize map only once
-    if (mapContainer.current) {
-      map.current = new mapboxgl.Map({
-        container: mapContainer.current,
-        style: 'mapbox://styles/mapbox/streets-v12',
-        center: [position.longitude, position.latitude],
-        zoom: 12
-      })
+    if (!mapContainer.current || map.current) return
+    if (!mapboxgl.accessToken) {
+      console.warn('GeneratedMap: NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN is not set.')
+      return
+    }
+    map.current = new mapboxgl.Map({
+      container: mapContainer.current,
+      style: 'mapbox://styles/mapbox/streets-v12',
+      center: [position.longitude, position.latitude],
+      zoom: position.zoom ?? 12
+    })
+    markerRef.current = new mapboxgl.Marker()
+      .setLngLat([position.longitude, position.latitude])
+      .addTo(map.current)
 
-      // Add a marker to the map
-      new mapboxgl.Marker()
-        .setLngLat([position.longitude, position.latitude])
+    return () => {
+      markerRef.current?.remove()
+      markerRef.current = null
+      map.current?.remove()
+      map.current = null
+    }
+  }, [])
+
+  // Update map center/zoom and marker when position changes
+  useEffect(() => {
+    if (!map.current) return
+    const lngLat: [number, number] = [position.longitude, position.latitude]
+    map.current.flyTo({
+      center: lngLat,
+      zoom: position.zoom ?? map.current.getZoom(),
+      essential: true
+    })
+    if (markerRef.current) {
+      markerRef.current.setLngLat(lngLat)
+    } else {
+      markerRef.current = new mapboxgl.Marker()
+        .setLngLat(lngLat)
         .addTo(map.current)
     }
   }, [position])
-
   return <div ref={mapContainer} className="h-full w-full" />

The map is reinitialized whenever the position prop changes, but the effect's
dependency array includes position which causes the map to be recreated
unnecessarily. Add a cleanup function to properly dispose of the map instance
when the component unmounts.

components/map/generated-map.tsx [15-30]

 useEffect(() => {
-  if (map.current) return // initialize map only once
-  if (mapContainer.current) {
-    map.current = new mapboxgl.Map({
-      container: mapContainer.current,
-      style: 'mapbox://styles/mapbox/streets-v12',
-      center: [position.longitude, position.latitude],
-      zoom: 12
-    })
+  if (!mapContainer.current) return;
+  
+  map.current = new mapboxgl.Map({
+    container: mapContainer.current,
+    style: 'mapbox://styles/mapbox/streets-v12',
+    center: [position.longitude, position.latitude],
+    zoom: 12
+  });
 
-    // Add a marker to the map
-    new mapboxgl.Marker()
-      .setLngLat([position.longitude, position.latitude])
-      .addTo(map.current)
+  // Add a marker to the map
+  new mapboxgl.Marker()
+    .setLngLat([position.longitude, position.latitude])
+    .addTo(map.current);
+    
+  // Return cleanup function
+  return () => {
+    map.current?.remove();
+    map.current = null;
+  };
+}, []); // Empty dependency array
+
+// Update map when position changes
+useEffect(() => {
+  if (!map.current) return;
+  map.current.setCenter([position.longitude, position.latitude]);
+  // Update marker position
+  const markers = map.current.getContainer().querySelectorAll('.mapboxgl-marker');
+  if (markers.length > 0) {
+    markers.forEach(marker => marker.remove());
   }
-}, [position])
+  new mapboxgl.Marker()
+    .setLngLat([position.longitude, position.latitude])
+    .addTo(map.current);
+}, [position]);

[Suggestion processed]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug where the map fails to update when the position prop changes due to a flawed useEffect dependency and logic. The proposed fix is excellent, correctly separating map initialization from updates and adding a crucial cleanup function.

High
  • Update

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Guard missing access token

Add a runtime guard for a missing Mapbox access token to avoid initializing the
map with an undefined token, which causes hard-to-debug failures at runtime.
Provide a no-op render or console error in that case.

components/map/generated-map.tsx [7]

-mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN as string
+const accessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN
+mapboxgl.accessToken = accessToken || ''
 
+export const GeneratedMap: React.FC<{
+  position: { latitude: number; longitude: number }
+}> = ({ position }) => {
+  const mapContainer = useRef<HTMLDivElement>(null)
+  const map = useRef<mapboxgl.Map | null>(null)
+
+  useEffect(() => {
+    if (!accessToken) {
+      console.error('Missing NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN')
+      return
+    }
+    if (map.current) return
+    if (mapContainer.current) {
+      map.current = new mapboxgl.Map({
+        container: mapContainer.current,
+        style: 'mapbox://styles/mapbox/streets-v12',
+        center: [position.longitude, position.latitude],
+        zoom: 12
+      })
+
+      new mapboxgl.Marker().setLngLat([position.longitude, position.latitude]).addTo(map.current)
+    }
+  }, [position])
+
+  return <div ref={mapContainer} className="h-full w-full" />
+}
+
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that a missing Mapbox access token would cause a runtime error and proposes adding a crucial check, which significantly improves the component's robustness.

Medium
Possible issue
Validate and guard map location

Guard against errors from processLocationQuery and ensure mapLocation has the
expected shape before passing it on. This prevents runtime crashes if the hook
throws or returns unexpected data.

components/chat-panel.tsx [61-64]

-const { mapLocation } = await processLocationQuery(input)
+let mapLocation: { latitude: number; longitude: number } | undefined
+try {
+  const res = await processLocationQuery(input)
+  if (res && typeof res.mapLocation?.latitude === 'number' && typeof res.mapLocation?.longitude === 'number') {
+    mapLocation = { latitude: res.mapLocation.latitude, longitude: res.mapLocation.longitude }
+  }
+} catch (e) {
+  // swallow location errors; continue without map context
+  mapLocation = undefined
+}
 
 const formData = new FormData(e.currentTarget)
 const responseMessage = await submit(formData, mapLocation)
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential runtime error if processLocationQuery fails and adds robust error handling and type validation, improving the code's resilience.

Medium
  • More

Copy link
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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/search-section.tsx (1)

18-18: Fix TypeScript type error for searchResults initialization.

const searchResults: TypeSearchResults = data ? JSON.parse(data) : undefined assigns undefined to a non-optional type. This will fail type-checking.

One simple fix is to compute inside the guarded block:

-  const searchResults: TypeSearchResults = data ? JSON.parse(data) : undefined
+  // Defer JSON parsing to the guarded render block below to keep types sound.

And just before first use inside the truthy branch:

const searchResults = JSON.parse(data) as TypeSearchResults

Alternatively, keep it here but type it as union:

-  const searchResults: TypeSearchResults = data ? JSON.parse(data) : undefined
+  const searchResults: TypeSearchResults | undefined = data ? (JSON.parse(data) as TypeSearchResults) : undefined

Then narrow with a guard before usage.

🧹 Nitpick comments (7)
lib/types/index.ts (1)

5-8: Align position shape with upstream mapLocation (add optional zoom or standardize via a shared type).

searchTool sets searchResult.position = mapLocation, and mapLocation currently includes { latitude, longitude, zoom }. Your SearchResults.position omits zoom, creating a silent type drift. Either:

  • Add zoom?: number here, or
  • Ensure tools only pass { latitude, longitude }.

Given future UI needs, adding zoom?: number is pragmatic.

Apply this diff:

   position?: {
     latitude: number
     longitude: number
+    zoom?: number
   }
mapbox_mcp/hooks.ts (1)

126-129: Good: standardized to latitude/longitude keys and consistent with downstream types.

This aligns with SearchResults.position and generated-map prop shape.

Two minor improvements to consider:

  • Make the coordinate regex accept integers too (some APIs may return whole degrees).
  • Decide whether zoom should be propagated through types (see lib/types comment).

You can broaden the regex as follows (outside this hunk):

const coordPattern = /(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)/;
components/search-section.tsx (1)

28-41: Make the images/map layout responsive and ensure map remounts when position changes.

  • Current w-1/2 splits on all breakpoints; on narrow screens, this will squash content. Prefer stacking on mobile and split on md+.
  • GeneratedMap initializes once; if position changes across searches, the map may not recenter. Force re-mount by keying on coordinates.

Apply this diff:

-              <div className="flex space-x-4">
-                <div className="w-1/2">
+              <div className="flex flex-col md:flex-row md:space-x-4 space-y-4 md:space-y-0">
+                <div className="w-full md:w-1/2">
                   <SearchResultsImageSection
                     images={searchResults.images}
                     query={searchResults.query}
                   />
                 </div>
                 {searchResults.position && (
-                  <div className="w-1/2 h-64">
-                    <GeneratedMap position={searchResults.position} />
+                  <div className="w-full md:w-1/2 h-64">
+                    <GeneratedMap
+                      key={`${searchResults.position.latitude},${searchResults.position.longitude}`}
+                      position={searchResults.position}
+                    />
                   </div>
                 )}
               </div>
components/map/generated-map.tsx (1)

7-7: Guard against missing Mapbox access token

If NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN is undefined, map initialization will fail at runtime. Add a defensive check (as shown in the previous diff) to fail gracefully and log a clear warning.

lib/agents/tools/index.tsx (1)

7-11: Strongly type mapLocation instead of using object

Using object loses type safety downstream. Define an explicit shape to ensure latitude/longitude correctness and optional zoom propagation.

 export interface ToolProps {
   uiStream: ReturnType<typeof createStreamableUI>
   fullResponse: string
-  mapLocation?: object
+  mapLocation?: { latitude: number; longitude: number; zoom?: number }
 }
lib/agents/researcher.tsx (2)

19-21: Strongly type mapLocation param for safety

Mirror the ToolProps shape to maintain consistent typing and avoid accidental misuse.

   messages: CoreMessage[],
   useSpecificModel?: boolean,
-  mapLocation?: object
+  mapLocation?: { latitude: number; longitude: number; zoom?: number }
 )

56-60: Note: fullResponse passed into getTools is not effectively mutated

Tools (e.g., searchTool) mutate their captured fullResponse string, but since strings are immutable and you don’t read it back from tools, those mutations won’t propagate. Consider removing fullResponse from ToolProps in a follow-up to reduce confusion.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 96c0b81 and 0b2748b.

📒 Files selected for processing (9)
  • app/actions.tsx (3 hunks)
  • components/chat-panel.tsx (3 hunks)
  • components/map/generated-map.tsx (1 hunks)
  • components/search-section.tsx (2 hunks)
  • lib/agents/researcher.tsx (2 hunks)
  • lib/agents/tools/index.tsx (1 hunks)
  • lib/agents/tools/search.tsx (2 hunks)
  • lib/types/index.ts (1 hunks)
  • mapbox_mcp/hooks.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
components/map/generated-map.tsx (2)
components/map/mapbox-map.tsx (6)
  • mapContainer (17-614)
  • map (501-505)
  • mapContainer (391-488)
  • map (419-455)
  • map (62-163)
  • map (316-323)
components/map/map-query-handler.tsx (1)
  • useMapData (30-83)
components/search-section.tsx (2)
components/search-results-image.tsx (2)
  • SearchResultsImageSection (29-139)
  • SearchResultsImageSectionProps (24-27)
components/map/generated-map.tsx (1)
  • GeneratedMap (9-33)
lib/agents/tools/search.tsx (2)
lib/agents/tools/index.tsx (1)
  • ToolProps (7-11)
lib/agents/tools/video-search.tsx (1)
  • ToolProps (8-50)
components/chat-panel.tsx (2)
mapbox_mcp/hooks.ts (1)
  • useMCPMapClient (48-225)
components/map/map-query-handler.tsx (3)
  • toolOutput (33-74)
  • useMapData (30-83)
  • prevData (39-49)
mapbox_mcp/hooks.ts (2)
components/map/map-query-handler.tsx (3)
  • toolOutput (33-74)
  • prevData (39-49)
  • McpResponseData (8-16)
lib/agents/tools/geospatial.tsx (1)
  • McpResponse (22-25)
app/actions.tsx (2)
components/search-section.tsx (1)
  • SearchSection (16-54)
lib/agents/tools/geospatial.tsx (2)
  • uiStream (144-269)
  • params (153-268)
lib/agents/tools/index.tsx (2)
lib/agents/tools/search.tsx (1)
  • searchTool (8-58)
lib/agents/tools/geospatial.tsx (6)
  • uiStream (144-269)
  • McpResponse (22-25)
  • params (153-268)
  • mcpClient (180-192)
  • tools (181-181)
  • Location (15-20)
lib/agents/researcher.tsx (2)
lib/agents/tools/geospatial.tsx (2)
  • uiStream (144-269)
  • params (153-268)
components/map/map-query-handler.tsx (1)
  • prevData (39-49)
🪛 Biome (2.1.2)
app/actions.tsx

[error] 438-438: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (5)
lib/types/index.ts (1)

66-78: LGTM: AIMessage metadata extensions look good.

Optional createdAt and the new 'drawing_context' discriminator are reasonable evolutions and should be non-breaking.

components/search-section.tsx (1)

6-6: LGTM: Imported GeneratedMap for colocated render.

Import path and usage align with the new position prop shape.

components/chat-panel.tsx (1)

7-7: LGTM: Introducing MCP hook is the right integration point.

Proceeding to connect and guard for readiness will make it robust (see below).

lib/agents/tools/search.tsx (1)

8-8: LGTM: Tool now accepts mapLocation to enrich results.

Signature expansion is minimal and keeps existing behavior intact.

lib/agents/tools/index.tsx (1)

14-20: LGTM: mapLocation plumbed into searchTool

Passing mapLocation through getTools to searchTool aligns with the new UI flow where SearchSection can render a GeneratedMap using result.position.

Comment on lines +35 to 36
async function submit(formData?: FormData, mapLocation?: object, skip?: boolean) {
'use server';
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm all submit(...) call sites updated for the new signature

submit now accepts (formData?: FormData, mapLocation?: object, skip?: boolean). Ensure all callers (e.g., chat-panel) pass mapLocation as the second arg. If any call site still passes skip as second arg, behavior will be off.

Run this to find submit invocations and quickly spot incorrect arity/ordering:


🏁 Script executed:

#!/bin/bash
# Search for submit( usages and show 3 lines of context
rg -nC3 --type=ts --type=tsx '\bsubmit\s*\('

Length of output: 74


Let’s retry searching without relying on --type. This will scan all .ts and .tsx files:


🏁 Script executed:

#!/bin/bash
# Search for all submit( calls in TS/TSX files
rg -nC3 '\bsubmit\s*\(' --glob '*.ts' --glob '*.tsx'

Length of output: 2179


Ensure submit call in components/copilot.tsx passes mapLocation before skip

The only incorrect invocation is in components/copilot.tsx (line 83), which currently does:

const response = await submit(formData, skip)

This assigns your boolean skip to the mapLocation parameter, leaving skip undefined. Update it to explicitly pass a (possibly undefined) mapLocation as the second argument:

components/copilot.tsx:83

  • const response = await submit(formData, skip)
  • const response = await submit(formData, undefined, skip)

All other call sites either only pass formData or correctly pass (formData, mapLocation).

🤖 Prompt for AI Agents
components/copilot.tsx around line 83; app/actions.tsx lines 35-36 — the submit
signature is submit(formData?: FormData, mapLocation?: object, skip?: boolean)
but the call at components/copilot.tsx:83 passes submit(formData, skip) which
assigns the boolean to mapLocation and leaves skip undefined; update that call
to pass an explicit second argument (e.g., undefined) and the boolean as the
third argument so it becomes submit(formData, undefined, skip).

Comment on lines 437 to 445
case 'search':
const searchResult = JSON.parse(searchResults.value);
if (toolOutput.position) {
searchResult.position = toolOutput.position;
}
return {
id,
component: <SearchSection result={searchResults.value} />,
component: <SearchSection result={JSON.stringify(searchResult)} />,
isCollapsed: isCollapsed.value,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix: SearchSection integration and linter error in switch case

Two issues here:

  • noSwitchDeclarations: const declared directly in a case without a block can leak across clauses.
  • SearchSection expects a StreamableValue, but you’re passing a plain string and attempting to JSON.parse searchResults.value (which is a StreamableValue, not a string).

Wrap the case in a block and pass a proper streamable with the augmented position.

-            const searchResults = createStreamableValue();
-            searchResults.done(JSON.stringify(toolOutput));
+            const searchResults = createStreamableValue<string>();
+            searchResults.done(JSON.stringify(toolOutput));
             switch (name) {
               case 'search':
-                const searchResult = JSON.parse(searchResults.value);
-                if (toolOutput.position) {
-                  searchResult.position = toolOutput.position;
-                }
-                return {
-                  id,
-                  component: <SearchSection result={JSON.stringify(searchResult)} />,
-                  isCollapsed: isCollapsed.value,
-                };
+                {
+                  const augmented = typeof toolOutput === 'string'
+                    ? JSON.parse(toolOutput)
+                    : { ...toolOutput };
+                  if (toolOutput.position) {
+                    augmented.position = toolOutput.position;
+                  }
+                  const augmentedResults = createStreamableValue<string>();
+                  augmentedResults.done(JSON.stringify(augmented));
+                  return {
+                    id,
+                    component: <SearchSection result={augmentedResults.value} />,
+                    isCollapsed: isCollapsed.value,
+                  };
+                }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 438-438: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
In app/actions.tsx around lines 437 to 445, the switch 'search' case declares
consts directly and passes a plain string to SearchSection; wrap the case body
in a block ({ ... }) to fix noSwitchDeclarations, parse the streamable's current
value into an object, augment position if present, then stringify it and pass a
StreamableValue<string> (do not pass a raw string) — e.g., produce a new
streamable from the existing searchResults (using the streamable's map/creation
helper) containing the updated JSON string and use that as the result prop for
<SearchSection />.

const [, setMessages] = useUIState<typeof AI>()
const { submit } = useActions()
// Removed mcp instance as it's no longer passed to submit
const { processLocationQuery } = useMCPMapClient()
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Establish MCP connection lifecycle to avoid "MCP client not connected" errors.

processLocationQuery requires the MCP client to be ready and toolsRef to be populated via connect(), but connect() is never called here.

Apply this diff to pull in connect and then connect on mount:

-  const { processLocationQuery } = useMCPMapClient()
+  const { processLocationQuery, connect } = useMCPMapClient()

Add this effect (outside the changed hunk):

useEffect(() => {
  let mounted = true
  ;(async () => {
    try {
      await connect()
    } catch (e) {
      console.warn('MCP connect failed, continuing without map location:', e)
    }
  })()
  return () => {
    mounted = false
  }
}, [connect])
🤖 Prompt for AI Agents
In components/chat-panel.tsx around line 24, the MCP client connection lifecycle
is missing: you only destructured processLocationQuery from useMCPMapClient but
never call connect(), causing "MCP client not connected" errors; update the hook
destructuring to also pull connect, and add a useEffect that runs on mount which
calls connect() asynchronously, awaits it, catches and logs any error
(console.warn with context), and returns a cleanup that marks unmounted (or
otherwise ignores late results); include connect in the effect dependency array.

Comment on lines +61 to 65
const { mapLocation } = await processLocationQuery(input)

const formData = new FormData(e.currentTarget)
// Removed mcp argument from submit call
const responseMessage = await submit(formData)
const responseMessage = await submit(formData, mapLocation)
setMessages(currentMessages => [...currentMessages, responseMessage as any])
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard MCP failures and gracefully fall back to submit without mapLocation.

If MCP isn’t ready or processing fails, current code will throw and block the chat flow.

Apply this diff:

-    const { mapLocation } = await processLocationQuery(input)
+    let mapLocation: any = undefined
+    try {
+      const res = await processLocationQuery(input)
+      mapLocation = res?.mapLocation
+    } catch (e) {
+      console.warn('processLocationQuery failed, submitting without mapLocation:', e)
+    }
 
     const formData = new FormData(e.currentTarget)
     const responseMessage = await submit(formData, mapLocation)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { mapLocation } = await processLocationQuery(input)
const formData = new FormData(e.currentTarget)
// Removed mcp argument from submit call
const responseMessage = await submit(formData)
const responseMessage = await submit(formData, mapLocation)
setMessages(currentMessages => [...currentMessages, responseMessage as any])
let mapLocation: any = undefined
try {
const res = await processLocationQuery(input)
mapLocation = res?.mapLocation
} catch (e) {
console.warn('processLocationQuery failed, submitting without mapLocation:', e)
}
const formData = new FormData(e.currentTarget)
const responseMessage = await submit(formData, mapLocation)
setMessages(currentMessages => [...currentMessages, responseMessage as any])

This commit fixes a build error caused by incorrectly handling the search results in the `getUIStateFromAIState` function. The previous implementation attempted to parse a `StreamableValue` object as a string, which resulted in a type error.

The logic for adding the map position to the search results was also redundant, as the position is already added in the `searchTool`.

This commit reverts the incorrect changes in `app/actions.tsx`, which resolves the build error.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
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

🧹 Nitpick comments (4)
components/map/generated-map.tsx (4)

47-63: Avoid redundant flyTo calls and honor prefers-reduced-motion.

Small UX/perf wins: skip re-centering when the new position is effectively the same, and use jumpTo if the user prefers reduced motion.

Apply this diff:

   // Update map center/zoom and marker when position changes
   useEffect(() => {
     if (!map.current) return
     const lngLat: [number, number] = [position.longitude, position.latitude]
-    map.current.flyTo({
-      center: lngLat,
-      zoom: position.zoom ?? map.current.getZoom(),
-      essential: true
-    })
+    const currentCenter = map.current.getCenter()
+    const currentZoom = map.current.getZoom()
+    const targetZoom = position.zoom ?? currentZoom
+    // Skip if delta is negligible to avoid jitter
+    const delta =
+      Math.abs(currentCenter.lng - lngLat[0]) +
+      Math.abs(currentCenter.lat - lngLat[1]) +
+      Math.abs(currentZoom - targetZoom)
+    if (delta < 1e-6) return
+
+    const prefersReducedMotion =
+      typeof window !== 'undefined' &&
+      window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
+
+    if (prefersReducedMotion) {
+      map.current.jumpTo({ center: lngLat, zoom: targetZoom })
+    } else {
+      map.current.flyTo({ center: lngLat, zoom: targetZoom, essential: true })
+    }

5-5: Move Mapbox global CSS import to a single global entry point.

Importing global CSS from a component can break Next.js builds depending on your setup and can also lead to duplicate style injection. Prefer adding it once in app/layout.tsx (App Router) or _app.tsx (Pages Router). Remove it here if it’s already globally included.

Apply this diff to this file:

-import 'mapbox-gl/dist/mapbox-gl.css'

Then, ensure it’s imported once globally, e.g., in app/layout.tsx:

// app/layout.tsx
import 'mapbox-gl/dist/mapbox-gl.css'

15-17: Centralize the position shape for reuse and consistency.

Inline prop shapes are easy to drift. Introduce a small MapPosition type and reuse it here and anywhere else that passes the same object.

Apply this diff to the signature:

-export const GeneratedMap: React.FC<{
-  position: { latitude: number; longitude: number; zoom?: number }
-}> = ({ position }) => {
+export const GeneratedMap: React.FC<{ position: MapPosition }> = ({ position }) => {

Add this type near the top of the file (above the component):

type MapPosition = { latitude: number; longitude: number; zoom?: number }

25-28: Access token handling: consider failing fast in dev.

Right now we only warn and return. To surface misconfiguration early, consider throwing in development and logging an error in production.

Apply this diff:

-    if (!mapboxgl.accessToken) {
-      console.warn('GeneratedMap: NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN is not set.')
-      return
-    }
+    if (!mapboxgl.accessToken) {
+      if (process.env.NODE_ENV !== 'production') {
+        throw new Error('GeneratedMap: NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN is not set.')
+      }
+      console.error('GeneratedMap: NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN is not set.')
+      return
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 08731db and 995138e.

📒 Files selected for processing (1)
  • components/map/generated-map.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
components/map/generated-map.tsx (1)
components/map/mapbox-map.tsx (5)
  • mapContainer (17-614)
  • map (501-505)
  • mapContainer (391-488)
  • map (419-455)
  • map (62-163)
🪛 Biome (2.1.2)
components/map/generated-map.tsx

[error] 14-65: Illegal use of an export declaration not at the top level

move this declaration to the top level

(parse)


[error] 65-65: expected } but instead the file ends

the file ends here

(parse)

🔇 Additional comments (1)
components/map/generated-map.tsx (1)

22-45: Initialization, marker ref, and cleanup look solid.

Map is initialized once, marker is retained in a ref, and both map and marker are removed on unmount. This addresses stale marker/positioning and cleanup concerns from earlier feedback.

Comment on lines +9 to +14
export const GeneratedMap: React.FC<{
position: { latitude: number; longitude: number }
}> = ({ position }) => {
const mapContainer = useRef<HTMLDivElement>(null)
const map = useRef<mapboxgl.Map | null>(null)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix duplicate/unfinished component declaration (parsing/build will fail).

There’s a half-defined GeneratedMap export preceding the real one. It never closes, which nests the second export and causes Biome/TS parser errors (“export not at top level”, “expected } before EOF”). Remove the first stub entirely.

Apply this diff:

-export const GeneratedMap: React.FC<{
-  position: { latitude: number; longitude: number }
-}> = ({ position }) => {
-  const mapContainer = useRef<HTMLDivElement>(null)
-  const map = useRef<mapboxgl.Map | null>(null)
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const GeneratedMap: React.FC<{
position: { latitude: number; longitude: number }
}> = ({ position }) => {
const mapContainer = useRef<HTMLDivElement>(null)
const map = useRef<mapboxgl.Map | null>(null)
🤖 Prompt for AI Agents
In components/map/generated-map.tsx around lines 9 to 14, there is a
duplicate/unfinished export of GeneratedMap (a stub that opens but never closes)
which causes parsing/TS errors; remove that entire stub export (the first export
const GeneratedMap ... block and its open braces/ref declarations) so only the
real, complete GeneratedMap component remains at top level, ensuring no nested
or duplicate export declarations are left.

@ngoiyaeric ngoiyaeric deleted the feature/connect-mapbox-mcp branch September 10, 2025 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants