Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# Bun
.bun/

# dependencies
/node_modules
/.pnp
Expand Down
6 changes: 3 additions & 3 deletions app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type RelatedQueries = {
};

// Removed mcp parameter from submit, as geospatialTool now handles its client.
async function submit(formData?: FormData, skip?: boolean) {
async function submit(formData?: FormData, mapLocation?: object, skip?: boolean) {
'use server';
Comment on lines +35 to 36
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).


// TODO: Update agent function signatures in lib/agents/researcher.tsx and lib/agents/writer.tsx
Expand Down Expand Up @@ -149,8 +149,8 @@ async function submit(formData?: FormData, skip?: boolean) {
uiStream,
streamText,
messages,
// mcp, // mcp instance is no longer passed down
useSpecificAPI
useSpecificAPI,
mapLocation,
);
answer = fullResponse;
toolOutputs = toolResponses;
Expand Down
10 changes: 6 additions & 4 deletions components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect, useState, useRef } from 'react'
import { useRouter } from 'next/navigation'
import type { AI, UIState } from '@/app/actions'
import { useUIState, useActions } from 'ai/rsc'
// Removed import of useGeospatialToolMcp as it's no longer used/available
import { useMCPMapClient } from '@/mapbox_mcp/hooks'
import { cn } from '@/lib/utils'
import { UserMessage } from './user-message'
import { Button } from './ui/button'
Expand All @@ -21,7 +21,7 @@ interface ChatPanelProps {
export function ChatPanel({ messages, input, setInput }: ChatPanelProps) {
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.

const [isButtonPressed, setIsButtonPressed] = useState(false)
const [isMobile, setIsMobile] = useState(false)
const inputRef = useRef<HTMLTextAreaElement>(null)
Expand Down Expand Up @@ -57,9 +57,11 @@ export function ChatPanel({ messages, input, setInput }: ChatPanelProps) {
component: <UserMessage message={input} />
}
])

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])
Comment on lines +61 to 65
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])

}

Expand Down
65 changes: 65 additions & 0 deletions components/map/generated-map.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use client'

import { useEffect, useRef } from 'react'
import mapboxgl from 'mapbox-gl'
import 'mapbox-gl/dist/mapbox-gl.css'

mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN as string

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

Comment on lines +9 to +14
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.

export const GeneratedMap: React.FC<{
position: { latitude: number; longitude: number; zoom?: number }
}> = ({ position }) => {
const mapContainer = useRef<HTMLDivElement>(null)
const map = useRef<mapboxgl.Map | null>(null)
const markerRef = useRef<mapboxgl.Marker | null>(null)

// Initialize map and marker once, and clean up on unmount
useEffect(() => {
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)

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" />
}
18 changes: 14 additions & 4 deletions components/search-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { SearchResults } from './search-results'
import { SearchSkeleton } from './search-skeleton'
import { SearchResultsImageSection } from './search-results-image'
import { GeneratedMap } from './map/generated-map'
import { Section } from './section'
import { ToolBadge } from './tool-badge'
import type { SearchResults as TypeSearchResults } from '@/lib/types'
Expand All @@ -24,10 +25,19 @@ export function SearchSection({ result }: SearchSectionProps) {
</Section>
{searchResults.images && searchResults.images.length > 0 && (
<Section title="Images">
<SearchResultsImageSection
images={searchResults.images}
query={searchResults.query}
/>
<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>
</Section>
)}
<Section title="Sources">
Expand Down
6 changes: 3 additions & 3 deletions lib/agents/researcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export async function researcher(
uiStream: ReturnType<typeof createStreamableUI>,
streamText: ReturnType<typeof createStreamableValue<string>>,
messages: CoreMessage[],
// mcp: any, // Removed mcp parameter
useSpecificModel?: boolean
useSpecificModel?: boolean,
mapLocation?: object
) {
let fullResponse = ''
let hasError = false
Expand Down Expand Up @@ -56,7 +56,7 @@ Match the language of your response to the user's language.`;
tools: getTools({
uiStream,
fullResponse,
// mcp // mcp parameter is no longer passed to getTools
mapLocation
})
})

Expand Down
7 changes: 4 additions & 3 deletions lib/agents/tools/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ import { geospatialTool } from './geospatial' // Removed useGeospatialToolMcp im
export interface ToolProps {
uiStream: ReturnType<typeof createStreamableUI>
fullResponse: string
// mcp?: any; // Removed mcp property as it's no longer passed down for geospatialTool
mapLocation?: object
}

// Removed mcp from parameters
export const getTools = ({ uiStream, fullResponse }: ToolProps) => {
export const getTools = ({ uiStream, fullResponse, mapLocation }: ToolProps) => {
const tools: any = {
search: searchTool({
uiStream,
fullResponse
fullResponse,
mapLocation
}),
retrieve: retrieveTool({
uiStream,
Expand Down
6 changes: 5 additions & 1 deletion lib/agents/tools/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Card } from '@/components/ui/card'
import { SearchSection } from '@/components/search-section'
import { ToolProps } from '.'

export const searchTool = ({ uiStream, fullResponse }: ToolProps) => ({
export const searchTool = ({ uiStream, fullResponse, mapLocation }: ToolProps) => ({
description: 'Search the web for information',
parameters: searchSchema,
execute: async ({
Expand Down Expand Up @@ -47,6 +47,10 @@ export const searchTool = ({ uiStream, fullResponse }: ToolProps) => ({
return searchResult
}

if (mapLocation) {
searchResult.position = mapLocation
}

streamResults.done(JSON.stringify(searchResult))

return searchResult
Expand Down
4 changes: 4 additions & 0 deletions lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ export type SearchResults = {
images: string[]
results: SearchResultItem[]
query: string
position?: {
latitude: number
longitude: number
}
}

export type ExaSearchResults = {
Expand Down
4 changes: 2 additions & 2 deletions mapbox_mcp/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ Focus on extracting and presenting factual data from the tools.`,
const coordMatch = response.text.match(coordPattern);
if (coordMatch) {
mapLocation = {
lat: parseFloat(coordMatch[1]),
lng: parseFloat(coordMatch[2]),
latitude: parseFloat(coordMatch[1]),
longitude: parseFloat(coordMatch[2]),
zoom: 12,
};
shouldShowMap = true;
Expand Down