diff --git a/app/actions.tsx b/app/actions.tsx index 31eb8629..eb606ea5 100644 --- a/app/actions.tsx +++ b/app/actions.tsx @@ -27,6 +27,7 @@ import { CopilotDisplay } from '@/components/copilot-display' import RetrieveSection from '@/components/retrieve-section' import { VideoSearchSection } from '@/components/video-search-section' import { MapQueryHandler } from '@/components/map/map-query-handler' // Add this import +import { MapboxElevationDisplay } from '@/components/mapbox-elevation-display' // Define the type for related queries type RelatedQueries = { @@ -653,6 +654,16 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { isCollapsed: false } } + if ( + toolOutput.type === 'ELEVATION_QUERY_RESULT' && + name === 'elevationQueryTool' + ) { + return { + id, + component: , + isCollapsed: false + }; + } const searchResults = createStreamableValue() searchResults.done(JSON.stringify(toolOutput)) diff --git a/components/mapbox-elevation-display.tsx b/components/mapbox-elevation-display.tsx new file mode 100644 index 00000000..af3f8853 --- /dev/null +++ b/components/mapbox-elevation-display.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { useEffect } from 'react'; +import { useMapData } from './map/map-data-context'; +import Image from 'next/image'; + +interface ElevationData { + latitude: number; + longitude: number; + elevation: number; + mapUrl?: string; +} + +interface ElevationToolOutput { + type: string; + originalUserInput: string; + timestamp: string; + elevation_response: ElevationData | null; +} + +interface MapboxElevationDisplayProps { + toolOutput?: ElevationToolOutput | null; +} + +export const MapboxElevationDisplay: React.FC = ({ toolOutput }) => { + const { setMapData } = useMapData(); + + useEffect(() => { + if (toolOutput && toolOutput.elevation_response) { + const { latitude, longitude } = toolOutput.elevation_response; + if (typeof latitude === 'number' && typeof longitude === 'number') { + setMapData(prevData => ({ + ...prevData, + targetPosition: [longitude, latitude], + })); + } + } + }, [toolOutput, setMapData]); + + if (!toolOutput || !toolOutput.elevation_response) { + return null; + } + + const { elevation, mapUrl } = toolOutput.elevation_response; + + return ( +
+

Elevation Information (Mapbox)

+

Elevation: {elevation} meters

+ {mapUrl && ( +
+ Map preview +
+ )} +
+ ); +}; diff --git a/dev.log b/dev.log new file mode 100644 index 00000000..59e3b892 --- /dev/null +++ b/dev.log @@ -0,0 +1 @@ +$ next dev --turbo diff --git a/jules-scratch/verification/verify_elevation.py b/jules-scratch/verification/verify_elevation.py new file mode 100644 index 00000000..f85be4ae --- /dev/null +++ b/jules-scratch/verification/verify_elevation.py @@ -0,0 +1,29 @@ +import re +from playwright.sync_api import Playwright, sync_playwright, expect + +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=True) + context = browser.new_context() + page = context.new_page() + page.goto("http://localhost:3000/") + + # Wait for the loading overlay to disappear + loading_overlay = page.locator('div[class*="z-[9999]"]') + expect(loading_overlay).to_be_hidden(timeout=60000) + + # Use the correct placeholder text: "Explore" + input_field = page.get_by_placeholder("Explore") + expect(input_field).to_be_visible(timeout=30000) + expect(input_field).to_be_enabled(timeout=30000) + + input_field.click() + input_field.fill("what is the elevation of mount everest at latitude 27.9881 and longitude 86.9250?") + page.get_by_role("button", name="Send message").click() + expect(page.get_by_text("Elevation Information (Mapbox)")).to_be_visible(timeout=90000) + page.screenshot(path="jules-scratch/verification/elevation-display.png") + + context.close() + browser.close() + +with sync_playwright() as playwright: + run(playwright) diff --git a/lib/agents/tools/elevation.tsx b/lib/agents/tools/elevation.tsx new file mode 100644 index 00000000..a25d92e5 --- /dev/null +++ b/lib/agents/tools/elevation.tsx @@ -0,0 +1,94 @@ +/** + * Elevation tool to fetch elevation data for a given location using Mapbox Tilequery API. + */ +import { createStreamableUI, createStreamableValue } from 'ai/rsc'; +import { BotMessage } from '@/components/message'; +import { z } from 'zod'; + +// Define the schema for the elevation tool's parameters +export const elevationQuerySchema = z.object({ + latitude: z.number().describe('The latitude of the location.'), + longitude: z.number().describe('The longitude of the location.'), + includeMap: z.boolean().optional().default(true).describe('Whether to include a map preview.'), +}); + +// Main elevation tool executor +export const elevationTool = ({ uiStream }: { uiStream: ReturnType }) => ({ + description: 'Use this tool to get the elevation for a specific location (latitude and longitude) using Mapbox.', + parameters: elevationQuerySchema, + execute: async (params: z.infer) => { + const { latitude, longitude, includeMap } = params; + const mapboxAccessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN; + + const uiFeedbackStream = createStreamableValue(); + uiStream.append(); + + if (!mapboxAccessToken) { + const errorMessage = 'Mapbox access token is not configured. This tool is unavailable.'; + uiFeedbackStream.done(errorMessage); + return { + type: 'ELEVATION_QUERY_RESULT', + error: errorMessage, + }; + } + + let feedbackMessage = `Processing elevation query for coordinates: ${latitude}, ${longitude}...`; + uiFeedbackStream.update(feedbackMessage); + + let elevationData: { + latitude: number; + longitude: number; + elevation: number; + mapUrl?: string + } | null = null; + let toolError: string | null = null; + + try { + const apiUrl = `https://api.mapbox.com/v4/mapbox.mapbox-terrain-v2/tilequery/${longitude},${latitude}.json?access_token=${mapboxAccessToken}`; + const response = await fetch(apiUrl); + + if (!response.ok) { + throw new Error(`Failed to fetch elevation data from Mapbox. Status: ${response.status}`); + } + + const data = await response.json(); + + if (data.features && data.features.length > 0) { + // Find the highest elevation value from the contour features + const maxElevation = data.features + .filter((feature: any) => feature.properties && typeof feature.properties.ele !== 'undefined') + .reduce((max: number, feature: any) => Math.max(max, feature.properties.ele), -Infinity); + + if (maxElevation === -Infinity) { + throw new Error('No elevation data found in the response features.'); + } + + elevationData = { latitude, longitude, elevation: maxElevation }; + + if (includeMap) { + const mapUrl = `https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/pin-s-marker+285A98(${longitude},${latitude})/${longitude},${latitude},12,0/600x400?access_token=${mapboxAccessToken}`; + elevationData.mapUrl = mapUrl; + } + + feedbackMessage = `Elevation at ${latitude}, ${longitude}: ${maxElevation} meters.`; + uiFeedbackStream.update(feedbackMessage); + } else { + throw new Error('No features returned from Mapbox for the given coordinates.'); + } + } catch (error: any) { + toolError = `Error fetching elevation data from Mapbox: ${error.message}`; + uiFeedbackStream.update(toolError); + console.error('[ElevationTool] Tool execution failed:', error); + } finally { + uiFeedbackStream.done(); + } + + return { + type: 'ELEVATION_QUERY_RESULT', + originalUserInput: JSON.stringify(params), + timestamp: new Date().toISOString(), + elevation_response: elevationData, + error: toolError, + }; + }, +}); diff --git a/lib/agents/tools/index.tsx b/lib/agents/tools/index.tsx index 4c08f373..6d6da019 100644 --- a/lib/agents/tools/index.tsx +++ b/lib/agents/tools/index.tsx @@ -3,6 +3,7 @@ import { retrieveTool } from './retrieve' import { searchTool } from './search' import { videoSearchTool } from './video-search' import { geospatialTool } from './geospatial' // Removed useGeospatialToolMcp import +import { elevationTool } from './elevation' export interface ToolProps { uiStream: ReturnType @@ -25,6 +26,9 @@ export const getTools = ({ uiStream, fullResponse }: ToolProps) => { geospatialQueryTool: geospatialTool({ uiStream // mcp: mcp || null // Removed mcp argument + }), + elevationQueryTool: elevationTool({ + uiStream }) }