From 1b56819c0af02b42f8c591b7dda98c698ba094fa Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:57:35 +0000 Subject: [PATCH 1/4] feat: implement GeoJSON upload and tool ingestion pipeline - Extend MapData context to support uploaded GeoJSON layers - Update ChatPanel to support GeoJSON file selection - Implement MapDataUpdater component for automatic context sync and map framing - Update Mapbox and Google Maps components to render uploaded GeoJSON - Enable AI tools to ingest GeoJSON into the map pipeline via MapQueryHandler - Ensure persistence of GeoJSON data across chat sessions via database sync - Add test IDs to key components for improved observability Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com> --- app/actions.tsx | 61 ++++++++++++++++++++++++++- components/chat-panel.tsx | 3 +- components/chat.tsx | 11 ++--- components/map/google-map.tsx | 3 ++ components/map/map-data-context.tsx | 12 +++++- components/map/map-data-updater.tsx | 63 ++++++++++++++++++++++++++++ components/map/map-query-handler.tsx | 14 ++++++- components/map/mapbox-map.tsx | 4 ++ components/message.tsx | 2 +- components/user-message.tsx | 2 +- lib/actions/chat.ts | 2 +- lib/agents/tools/geospatial.tsx | 31 ++++++++++++-- 12 files changed, 193 insertions(+), 15 deletions(-) create mode 100644 components/map/map-data-updater.tsx diff --git a/app/actions.tsx b/app/actions.tsx index 9e0ee20a..a1ab3b1f 100644 --- a/app/actions.tsx +++ b/app/actions.tsx @@ -23,6 +23,7 @@ import { BotMessage } from '@/components/message' import { SearchSection } from '@/components/search-section' import SearchRelated from '@/components/search-related' import { GeoJsonLayer } from '@/components/map/geojson-layer' +import { MapDataUpdater } from '@/components/map/map-data-updater' import { CopilotDisplay } from '@/components/copilot-display' import RetrieveSection from '@/components/retrieve-section' import { VideoSearchSection } from '@/components/video-search-section' @@ -315,8 +316,39 @@ async function submit(formData?: FormData, skip?: boolean) { image: dataUrl, mimeType: file.type }) - } else if (file.type === 'text/plain') { + } else if (file.type === 'text/plain' || file.name.endsWith('.geojson') || file.type === 'application/geo+json') { const textContent = Buffer.from(buffer).toString('utf-8') + const isGeoJson = file.name.endsWith('.geojson') || file.type === 'application/geo+json' + + if (isGeoJson) { + try { + const geoJson = JSON.parse(textContent) + if (geoJson.type === 'FeatureCollection' || geoJson.type === 'Feature') { + const geoJsonId = nanoid() + // Add a special message to track the GeoJSON upload + aiState.update({ + ...aiState.get(), + messages: [ + ...aiState.get().messages, + { + id: geoJsonId, + role: 'assistant', + content: JSON.stringify({ data: geoJson, filename: file.name }), + type: 'geojson_upload' + } + ] + }) + + // Immediately append the updater to the UI stream + uiStream.append( + + ) + } + } catch (e) { + console.error('Failed to parse GeoJSON:', e) + } + } + const existingTextPart = messageParts.find(p => p.type === 'text') if (existingTextPart) { existingTextPart.text = `${textContent}\n\n${existingTextPart.text}` @@ -716,6 +748,13 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { ) } } + case 'geojson_upload': { + const { data, filename } = JSON.parse(content as string) + return { + id, + component: + } + } } break case 'tool': @@ -775,6 +814,26 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { } } break + case 'data': + try { + const contextData = JSON.parse(content as string) + if (contextData.uploadedGeoJson && Array.isArray(contextData.uploadedGeoJson)) { + return { + id, + component: ( + <> + {contextData.uploadedGeoJson.map((item: any) => ( + + ))} + + ) + } + } + return { id, component: null } + } catch (e) { + console.error('Error parsing data message:', e) + return { id, component: null } + } default: return { id, diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index 2a8c559e..3a00cde2 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -213,7 +213,8 @@ export const ChatPanel = forwardRef(({ messages, i ref={fileInputRef} onChange={handleFileChange} className="hidden" - accept="text/plain,image/png,image/jpeg,image/webp" + accept="text/plain,image/png,image/jpeg,image/webp,.geojson,application/geo+json" + data-testid="file-upload-input" /> {!isMobile && (