-
-
Notifications
You must be signed in to change notification settings - Fork 7
Fix/google maps gemini grounding #406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ import { Button } from './ui/button' | |
| import { ArrowRight, Plus, Paperclip, X } from 'lucide-react' | ||
| import Textarea from 'react-textarea-autosize' | ||
| import { nanoid } from 'nanoid' | ||
| import { useSettingsStore } from '@/lib/store/settings' | ||
|
|
||
|
Comment on lines
+13
to
14
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a Zustand selector for Proposed change- const { mapProvider } = useSettingsStore()
+ const mapProvider = useSettingsStore(s => s.mapProvider)Also ensure the server action/tooling treats Also applies to: 29-30, 181-181 🤖 Prompt for AI Agents |
||
| interface ChatPanelProps { | ||
| messages: UIState | ||
|
|
@@ -25,6 +26,7 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i | |
| const [, setMessages] = useUIState<typeof AI>() | ||
| const { submit, clearChat } = useActions() | ||
| // Removed mcp instance as it's no longer passed to submit | ||
| const { mapProvider } = useSettingsStore() | ||
| const [isMobile, setIsMobile] = useState(false) | ||
| const [selectedFile, setSelectedFile] = useState<File | null>(null) | ||
| const inputRef = useRef<HTMLTextAreaElement>(null) | ||
|
|
@@ -176,6 +178,7 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i | |
| isMobile && 'mobile-chat-input' // Apply mobile chat input styling | ||
| )} | ||
| > | ||
| <input type="hidden" name="mapProvider" value={mapProvider} /> | ||
| <input | ||
| type="file" | ||
| ref={fileInputRef} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,12 +5,14 @@ import { useEffect } from 'react' | |
| import { useToast } from '@/components/ui/hooks/use-toast' | ||
| import { useMapData } from './map-data-context' | ||
| import { useSettingsStore } from '@/lib/store/settings' | ||
| import { useMapLoading } from '../map-loading-context'; | ||
| import { Map3D } from './map-3d' | ||
|
|
||
| export function GoogleMapComponent() { | ||
| const { toast } = useToast() | ||
| const { mapData } = useMapData() | ||
| const { setMapProvider } = useSettingsStore() | ||
| const { setIsMapLoaded } = useMapLoading(); | ||
|
|
||
| const apiKey = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY | ||
|
|
||
|
|
@@ -25,18 +27,26 @@ export function GoogleMapComponent() { | |
| } | ||
| }, [apiKey, setMapProvider, toast]) | ||
|
|
||
| useEffect(() => { | ||
| setIsMapLoaded(true); | ||
| return () => { | ||
| setIsMapLoaded(false); | ||
| }; | ||
| }, [setIsMapLoaded]); | ||
|
|
||
| if (!apiKey) { | ||
| return null | ||
| } | ||
|
|
||
| const cameraOptions = mapData.targetPosition | ||
| ? { center: mapData.targetPosition, range: 1000, tilt: 60, heading: 0 } | ||
| : { center: { lat: 37.7749, lng: -122.4194 }, range: 1000, tilt: 60, heading: 0 }; | ||
|
|
||
| return ( | ||
| <APIProvider apiKey={apiKey} version="alpha"> | ||
| <Map3D | ||
| style={{ width: '100%', height: '100%' }} | ||
| center={{ lat: 37.7749, lng: -122.4194, altitude: 0 }} | ||
| heading={0} | ||
| tilt={60} | ||
| range={1000} | ||
| cameraOptions={cameraOptions} | ||
|
Comment on lines
+41
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider memoizing The ♻️ Suggested refactor+'use client'
+
+import { APIProvider } from '@vis.gl/react-google-maps'
+import { useEffect, useMemo } from 'react'
+import { useToast } from '@/components/ui/hooks/use-toast'
+import { useMapData } from './map-data-context'
+import { useSettingsStore } from '@/lib/store/settings'
+import { Map3D } from './map-3d'
+
+export function GoogleMapComponent() {
+ const { toast } = useToast()
+ const { mapData } = useMapData()
+ const { setMapProvider } = useSettingsStore()
+
+ const apiKey = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY
+
+ useEffect(() => {
+ if (!apiKey) {
+ toast({
+ title: 'Google Maps API Key Missing',
+ description: 'The Google Maps API key is not configured. Falling back to Mapbox.',
+ variant: 'destructive',
+ })
+ setMapProvider('mapbox')
+ }
+ }, [apiKey, setMapProvider, toast])
+
+ if (!apiKey) {
+ return null
+ }
+
+ const cameraOptions = useMemo(() => ({
+ center: mapData.targetPosition ?? { lat: 37.7749, lng: -122.4194 },
+ range: 1000,
+ tilt: 60,
+ heading: 0
+ }), [mapData.targetPosition])
+
+ return (
+ <APIProvider apiKey={apiKey} version="alpha">
+ <Map3D
+ style={{ width: '100%', height: '100%' }}
+ cameraOptions={cameraOptions}
+ mode="SATELLITE"
+ />
+ </APIProvider>
+ )
+}🤖 Prompt for AI Agents |
||
| mode="SATELLITE" | ||
| /> | ||
| </APIProvider> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,12 @@ declare global { | |
| export interface Map3DProps extends google.maps.maps3d.Map3DElementOptions { | ||
| style?: CSSProperties; | ||
| onCameraChange?: (e: Map3DCameraChangeEvent) => void; | ||
| cameraOptions?: { | ||
| center?: { lat: number; lng: number }; | ||
| heading?: number; | ||
| tilt?: number; | ||
| range?: number; | ||
| }; | ||
| } | ||
|
Comment on lines
38
to
47
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Align Right now Proposed change export interface Map3DProps extends google.maps.maps3d.Map3DElementOptions {
style?: CSSProperties;
onCameraChange?: (e: Map3DCameraChangeEvent) => void;
cameraOptions?: {
- center?: { lat: number; lng: number };
+ center?: google.maps.LatLngLiteral;
heading?: number;
tilt?: number;
range?: number;
+ roll?: number;
};
}🤖 Prompt for AI Agents |
||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,14 +29,7 @@ export const Map3D = forwardRef( | |
| props.onCameraChange(p); | ||
| }); | ||
|
|
||
| const [customElementsReady, setCustomElementsReady] = useState(false); | ||
| useEffect(() => { | ||
| customElements.whenDefined('gmp-map-3d').then(() => { | ||
| setCustomElementsReady(true); | ||
| }); | ||
| }, []); | ||
|
|
||
| const {center, heading, tilt, range, roll, ...map3dOptions} = props; | ||
| const {center, heading, tilt, range, roll, cameraOptions, ...map3dOptions} = props; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Clarify the interaction between individual camera props and Both individual props ( Consider either:
🤖 Prompt for AI Agents |
||
|
|
||
| useDeepCompareEffect(() => { | ||
| if (!map3DElement) return; | ||
|
|
@@ -45,13 +38,30 @@ export const Map3D = forwardRef( | |
| Object.assign(map3DElement, map3dOptions); | ||
| }, [map3DElement, map3dOptions]); | ||
|
|
||
| useDeepCompareEffect(() => { | ||
| if (!map3DElement || !cameraOptions) return; | ||
|
|
||
| const { center, heading, tilt, range } = cameraOptions; | ||
|
|
||
| if (center) { | ||
| map3DElement.center = { ...center, altitude: 0 }; | ||
| } | ||
| if (heading !== undefined) { | ||
| map3DElement.heading = heading; | ||
| } | ||
| if (tilt !== undefined) { | ||
| map3DElement.tilt = tilt; | ||
| } | ||
| if (range !== undefined) { | ||
| map3DElement.range = range; | ||
| } | ||
| }, [map3DElement, cameraOptions]); | ||
|
|
||
| useImperativeHandle< | ||
| google.maps.maps3d.Map3DElement | null, | ||
| google.maps.maps3d.Map3DElement | null | ||
| >(forwardedRef, () => map3DElement, [map3DElement]); | ||
|
|
||
| if (!customElementsReady) return null; | ||
|
|
||
| return ( | ||
| <div style={props.style}> | ||
| <gmp-map-3d | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,9 @@ | ||
| 'use client'; | ||
|
|
||
| import React, { createContext, useContext, useState, ReactNode } from 'react'; | ||
| import { LngLatLike } from 'mapbox-gl'; // Import LngLatLike | ||
|
|
||
| // Define the shape of the map data you want to share | ||
| export interface MapData { | ||
| targetPosition?: LngLatLike | null; // For flying to a location | ||
| targetPosition?: { lat: number; lng: number } | null; // For flying to a location | ||
| // TODO: Add other relevant map data types later (e.g., routeGeoJSON, poiList) | ||
| mapFeature?: any | null; // Generic feature from MCP hook's processLocationQuery | ||
|
Comment on lines
5
to
8
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Prefer If consumers don’t need to distinguish “unset” vs “cleared”, make it non-optional and initialize to Proposed change export interface MapData {
- targetPosition?: { lat: number; lng: number } | null; // For flying to a location
+ targetPosition: { lat: number; lng: number } | null; // For flying to a location
// TODO: Add other relevant map data types later (e.g., routeGeoJSON, poiList)
mapFeature?: any | null; // Generic feature from MCP hook's processLocationQuery
drawnFeatures?: Array<{
id: string;
type: 'Polygon' | 'LineString';
measurement: string;
geometry: any;
}>;
markers?: Array<{
latitude: number;
longitude: number;
title?: string;
}>;
}
export const MapDataProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
- const [mapData, setMapData] = useState<MapData>({ drawnFeatures: [], markers: [] });
+ const [mapData, setMapData] = useState<MapData>({ targetPosition: null, drawnFeatures: [], markers: [] });Also applies to: 29-31 |
||
| drawnFeatures?: Array<{ // Added to store drawn features and their measurements | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| $ next dev --turbo | ||
| ⚠ Port 3000 is in use, using available port 3001 instead. | ||
| ▲ Next.js 15.3.6 (Turbopack) | ||
| - Local: http://localhost:3001 | ||
| - Network: http://192.168.0.2:3001 | ||
| - Environments: .env | ||
|
|
||
| ✓ Starting... | ||
| ○ Compiling middleware ... | ||
| ✓ Compiled middleware in 528ms | ||
| ✓ Ready in 2.7s |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,9 @@ import { Client as MCPClientClass } from '@modelcontextprotocol/sdk/client/index | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Smithery SDK removed - using direct URL construction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { z } from 'zod'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { GoogleGenerativeAI } from '@google/generative-ai'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getSelectedModel } from '@/lib/actions/users'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { MapProvider } from '@/lib/store/settings'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Types | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type McpClient = MCPClientClass; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -152,7 +155,13 @@ async function closeClient(client: McpClient | null) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Main geospatial tool executor. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const geospatialTool = ({ uiStream }: { uiStream: ReturnType<typeof createStreamableUI> }) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const geospatialTool = ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiStream, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mapProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiStream: ReturnType<typeof createStreamableUI> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mapProvider?: MapProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: `Use this tool for location-based queries including: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| There a plethora of tools inside this tool accessible on the mapbox mcp server where switch case into the tool of choice for that use case | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| If the Query is supposed to use multiple tools in a sequence you must access all the tools in the sequence and then provide a final answer based on the results of all the tools used. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -224,11 +233,67 @@ Uses the Mapbox Search Box Text Search API endpoint to power searching for and g | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parameters: geospatialQuerySchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| execute: async (params: z.infer<typeof geospatialQuerySchema>) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { queryType, includeMap = true } = params; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log('[GeospatialTool] Execute called with:', params); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log('[GeospatialTool] Execute called with:', params, 'and map provider:', mapProvider); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const uiFeedbackStream = createStreamableValue<string>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiStream.append(<BotMessage content={uiFeedbackStream.value} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const selectedModel = await getSelectedModel(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (selectedModel?.includes('gemini') && mapProvider === 'google') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let feedbackMessage = `Processing geospatial query with Gemini...`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiFeedbackStream.update(feedbackMessage); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const genAI = new GoogleGenerativeAI(process.env.GEMINI_3_PRO_API_KEY!); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const model = genAI.getGenerativeModel({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model: 'gemini-1.5-pro-latest', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+248
to
+251
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing environment variable check for Gemini API key. The non-null assertion ( 🐛 Suggested fix+ const geminiApiKey = process.env.GEMINI_3_PRO_API_KEY;
+ if (!geminiApiKey) {
+ throw new Error('GEMINI_3_PRO_API_KEY environment variable is not configured');
+ }
+
- const genAI = new GoogleGenerativeAI(process.env.GEMINI_3_PRO_API_KEY!);
+ const genAI = new GoogleGenerativeAI(geminiApiKey);
const model = genAI.getGenerativeModel({
model: 'gemini-1.5-pro-latest',
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const searchText = (params as any).location || (params as any).query; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const prompt = `Find the location for: ${searchText}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const tools: any = [{ googleSearch: {} }]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await model.generateContent({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contents: [{ role: 'user', parts: [{ text: prompt }] }], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tools, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+253
to
+259
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find the geospatialQuerySchema definition
ast-grep --pattern 'export const geospatialQuerySchema = $_'Repository: QueueLab/QCX Length of output: 6682 🏁 Script executed: #!/bin/bash
# Also search for geospatialQuerySchema in case it's not exported or has different syntax
rg 'geospatialQuerySchema' -A 10Repository: QueueLab/QCX Length of output: 3467 🏁 Script executed: #!/bin/bash
# Check the function containing lines 253-259 to understand params type
sed -n '240,270p' lib/agents/tools/geospatial.tsxRepository: QueueLab/QCX Length of output: 1379 Remove unsafe The 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await result.response; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const functionCalls = (response as any).functionCalls(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (functionCalls && functionCalls.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const gsr = functionCalls[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This is a placeholder for the actual response structure, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // as I don't have a way to inspect it at the moment. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const place = (gsr as any).results[0].place; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (place) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { latitude, longitude } = place.coordinates; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const place_name = place.displayName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mcpData = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| latitude, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| longitude, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| place_name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| feedbackMessage = `Found location: ${place_name}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiFeedbackStream.update(feedbackMessage); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiFeedbackStream.done(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiStream.update(<BotMessage content={uiFeedbackStream.value} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { type: 'MAP_QUERY_TRIGGER', originalUserInput: JSON.stringify(params), queryType, timestamp: new Date().toISOString(), mcp_response: mcpData, error: null }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+261
to
+285
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsafe access to Gemini response structure without null checks. The code accesses nested properties ( 🐛 Suggested fix with defensive checks const functionCalls = (response as any).functionCalls();
if (functionCalls && functionCalls.length > 0) {
const gsr = functionCalls[0];
- // This is a placeholder for the actual response structure,
- // as I don't have a way to inspect it at the moment.
- const place = (gsr as any).results[0].place;
- if (place) {
- const { latitude, longitude } = place.coordinates;
+ const results = (gsr as any)?.results;
+ const place = results?.[0]?.place;
+ const coordinates = place?.coordinates;
+
+ if (coordinates?.latitude != null && coordinates?.longitude != null) {
+ const { latitude, longitude } = coordinates;
const place_name = place.displayName;
const mcpData = {📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('No location found by Gemini.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const toolError = `Gemini grounding error: ${error.message}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiFeedbackStream.update(toolError); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('[GeospatialTool] Gemini execution failed:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiFeedbackStream.done(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiStream.update(<BotMessage content={uiFeedbackStream.value} />); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { type: 'MAP_QUERY_TRIGGER', originalUserInput: JSON.stringify(params), queryType, timestamp: new Date().toISOString(), mcp_response: null, error: toolError }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+241
to
+295
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new Gemini grounding path is built around multiple Additionally, SuggestionHarden the Gemini branch by (1) checking the API key, (2) parsing the response defensively with optional chaining and validation, and (3) falling back to the MCP path when Gemini doesn’t return a usable location. const apiKey = process.env.GEMINI_3_PRO_API_KEY
if (!apiKey) {
console.warn('[GeospatialTool] GEMINI_3_PRO_API_KEY not set; falling back to MCP')
} else if (selectedModel?.includes('gemini') && mapProvider === 'google') {
try {
// ... call Gemini ...
const calls = (response as any)?.functionCalls?.() ?? []
const place = calls?.[0]?.results?.[0]?.place
const latitude = place?.coordinates?.latitude
const longitude = place?.coordinates?.longitude
const place_name = place?.displayName
if (typeof latitude === 'number' && typeof longitude === 'number' && place_name) {
return { /* success */ }
}
console.warn('[GeospatialTool] Gemini returned no usable location; falling back to MCP')
} catch (e) {
console.warn('[GeospatialTool] Gemini failed; falling back to MCP', e)
}
}
// continue to MCP flowReply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let feedbackMessage = `Processing geospatial query (type: ${queryType})... Connecting to mapping service...`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uiFeedbackStream.update(feedbackMessage); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate
mapProvidervalue before use.The type assertion
as 'mapbox' | 'google'doesn't validate that the form data actually contains one of these values. IfformData.get('mapProvider')returnsnullor an unexpected string, this could cause issues downstream.🛡️ Suggested validation
📝 Committable suggestion
🤖 Prompt for AI Agents