-
-
Couldn't load subscription status.
- Fork 6
Refactor Elevation Tool to Use Mapbox API #326
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
Open
google-labs-jules
wants to merge
1
commit into
main
Choose a base branch
from
feature/mapbox-elevation-tool
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<MapboxElevationDisplayProps> = ({ 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 ( | ||
| <div className="bg-zinc-900 border-zinc-700 border rounded-lg p-4 my-4"> | ||
| <h3 className="text-lg font-semibold text-white">Elevation Information (Mapbox)</h3> | ||
| <p className="text-zinc-300">Elevation: {elevation} meters</p> | ||
| {mapUrl && ( | ||
| <div className="mt-4"> | ||
| <Image | ||
| src={mapUrl} | ||
| alt="Map preview" | ||
| width={600} | ||
| height={400} | ||
| className="rounded-lg" | ||
| /> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| $ next dev --turbo |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<typeof createStreamableUI> }) => ({ | ||
| description: 'Use this tool to get the elevation for a specific location (latitude and longitude) using Mapbox.', | ||
| parameters: elevationQuerySchema, | ||
| execute: async (params: z.infer<typeof elevationQuerySchema>) => { | ||
| const { latitude, longitude, includeMap } = params; | ||
| const mapboxAccessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN; | ||
|
|
||
| const uiFeedbackStream = createStreamableValue<string>(); | ||
| uiStream.append(<BotMessage content={uiFeedbackStream.value} />); | ||
|
|
||
| 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, | ||
| }; | ||
| }, | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Consider conditionally registering
elevationQueryToolbased on the presence of a Mapbox access token, similar to howvideoSearchis gated bySERPER_API_KEY. Without gating, environments lacking the token may still expose this tool in the UI, leading to avoidable runtime failures when invoked. Aligning with the existing conditional registration pattern improves robustness and avoids presenting unusable tools.Suggestion
To match the existing pattern used for
videoSearch, remove the inlineelevationQueryToolproperty from the object initializer and register it conditionally after the object is created:Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change.