-
-
Notifications
You must be signed in to change notification settings - Fork 6
GeoJSON Upload and Tool Ingestion Pipeline #472
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
base: main
Are you sure you want to change the base?
Changes from 8 commits
6eeae28
160e082
ae085cf
73c385c
1508c04
c8c028b
e2c0615
43179e7
7d45e02
5623831
44e86b6
0a48018
6acfbe5
8ae549a
3495da1
ba36e58
6431b41
8a04d49
10ab3fe
250283e
1b56819
9f79929
6554775
bfe62f9
9450b15
e95a25b
f80c87f
c4278e9
813d264
c868dcd
3b5ed27
3984b9b
f45f687
67c26d5
a842df1
184f678
895bf37
dc345b9
86013ed
2ba1f9e
dda7a32
23a1d3f
885dbbe
dd812c1
00c2e1a
54d9d6e
c9bd9b2
85a85c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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( | ||||||||||||||||||||||||||||||||||||||||
| <MapDataUpdater id={geoJsonId} data={geoJson} filename={file.name} /> | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+360
to
+379
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. Server action appends a React client component into the UI stream
This is fragile for a few reasons:
Given you already insert a SuggestionRemove the immediate If you need instant feedback before the assistant message is produced, consider appending a lightweight server-renderable placeholder (e.g., “Uploaded X”) and let the client handle map updates based on AI state. Reply with "@CharlieHelps yes please" if you'd like me to add a commit making this change. |
||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||
| console.error('Failed to parse GeoJSON:', e) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+352
to
+383
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. Silent error handling may confuse users when GeoJSON parsing fails. When 🛡️ Proposed improvement } catch (e) {
console.error('Failed to parse GeoJSON:', e)
+ // Optionally notify the user
+ uiStream.append(
+ <BotMessage content={createStreamableValue(`⚠️ Failed to parse ${file.name} as valid GeoJSON. The file will be processed as plain text.`).value} />
+ )
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| 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: <MapDataUpdater id={id} data={data} filename={filename} /> | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+795
to
+801
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. Unvalidated
|
||||||||||||||||||||||||||||||||||||||||
| case 'geojson_upload': { | |
| const { data, filename } = JSON.parse(content as string) | |
| return { | |
| id, | |
| component: <MapDataUpdater id={id} data={data} filename={filename} /> | |
| } | |
| } | |
| case 'geojson_upload': { | |
| try { | |
| const { data, filename } = JSON.parse(content as string) | |
| return { | |
| id, | |
| component: <MapDataUpdater id={id} data={data} filename={filename} /> | |
| } | |
| } catch (e) { | |
| console.error('Error parsing geojson_upload content:', e) | |
| return { id, component: null } | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 780 - 786, The JSON.parse in the
'geojson_upload' case can throw for malformed content; wrap the parse of content
in a try-catch (the case handling that returns component: <MapDataUpdater ...
/>) similar to the other role branches: on success return the existing object
with id and component: <MapDataUpdater id={id} data={data} filename={filename}
/>, on parse failure catch the error, log or report it, and return a safe
fallback entry (e.g., an object with id and a lightweight error/display
component or null component) so UI state reconstruction doesn't crash; reference
the 'geojson_upload' case, JSON.parse(content as string), and MapDataUpdater
when applying the fix.
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.
data message rendering uses any and replays side effects without guarding
In the data case you iterate uploadedGeoJson.map((item: any) => <MapDataUpdater ... />). Two concerns:
- Using
anyhere makes it easy to introduce unsafe shapes that still compile. This is UI-state hydration code; it should be strict. - Rendering many
MapDataUpdaters will attemptfitBoundsfor each layer; on chat reload this can cause multiple sequential camera jumps. The currentMapDataUpdateronly de-dupes byidfor inserting into context, but it does not de-dupe thefitBoundsside effect. So reload may result in the map fitting to the last item every time, even if the user previously set a different view.
Suggestion
-
Replace
anywith a minimal structural type (e.g.,{ id: string; data: unknown; filename: string }) and validate required fields before rendering. -
Add a flag to
MapDataUpdaterlikeshouldFlyTo?: booleanand set it tofalsewhen replaying persisteduploadedGeoJsonfrom adatamessage, or add internal logic to onlyfitBoundson first-ever insert (e.g., only when it was newly added to context).
Reply with "@CharlieHelps yes please" if you'd like me to add a commit implementing shouldFlyTo and tightening the hydration type.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -89,16 +89,17 @@ export function Chat({ id }: ChatProps) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [isSubmitting]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // useEffect to call the server action when drawnFeatures changes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // useEffect to call the server action when drawnFeatures or uploadedGeoJson changes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (id && mapData.drawnFeatures && mapData.cameraState) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log('Chat.tsx: drawnFeatures changed, calling updateDrawingContext', mapData.drawnFeatures); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (id && (mapData.drawnFeatures || mapData.uploadedGeoJson) && mapData.cameraState) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log('Chat.tsx: map data changed, calling updateDrawingContext'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateDrawingContext(id, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| drawnFeatures: mapData.drawnFeatures, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| drawnFeatures: mapData.drawnFeatures || [], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cameraState: mapData.cameraState, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploadedGeoJson: mapData.uploadedGeoJson || [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [id, mapData.drawnFeatures, mapData.cameraState]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [id, mapData.drawnFeatures, mapData.cameraState, mapData.uploadedGeoJson]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
99
to
118
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. Condition may trigger excessive database writes. The condition This means 🐛 Suggested fix to only save when there's actual data // useEffect to call the server action when drawnFeatures or uploadedGeoJson changes
useEffect(() => {
- if (id && (mapData.drawnFeatures || mapData.uploadedGeoJson) && mapData.cameraState) {
+ const hasDrawnFeatures = mapData.drawnFeatures && mapData.drawnFeatures.length > 0;
+ const hasUploadedGeoJson = mapData.uploadedGeoJson && mapData.uploadedGeoJson.length > 0;
+
+ if (id && (hasDrawnFeatures || hasUploadedGeoJson) && mapData.cameraState) {
console.log('Chat.tsx: map data changed, calling updateDrawingContext');
updateDrawingContext(id, {
drawnFeatures: mapData.drawnFeatures || [],
cameraState: mapData.cameraState,
uploadedGeoJson: mapData.uploadedGeoJson || []
});
}
}, [id, mapData.drawnFeatures, mapData.cameraState, mapData.uploadedGeoJson]);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Comment on lines
99
to
118
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. This effect logs every map-data change and triggers a server action whenever Also, sending full GeoJSON on every change is likely heavy. If SuggestionReduce redundant writes and payload size:
Reply with "@CharlieHelps yes please" if you’d like me to add a commit implementing a debounce + change detection. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Mobile layout | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isMobile) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -81,6 +81,9 @@ export function GoogleMapComponent() { | |||||||||||||||||
| mode="SATELLITE" | ||||||||||||||||||
| /> | ||||||||||||||||||
| <GoogleGeoJsonLayer data={featureCollection} /> | ||||||||||||||||||
| {mapData.uploadedGeoJson?.map(item => ( | ||||||||||||||||||
| item.visible && <GoogleGeoJsonLayer key={item.id} data={item.data} /> | ||||||||||||||||||
| ))} | ||||||||||||||||||
|
Comment on lines
+84
to
+86
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 Conditional rendering inside When ♻️ Suggested refactor- {mapData.uploadedGeoJson?.map(item => (
- item.visible && <GoogleGeoJsonLayer key={item.id} data={item.data} />
- ))}
+ {mapData.uploadedGeoJson
+ ?.filter(item => item.visible)
+ .map(item => (
+ <GoogleGeoJsonLayer key={item.id} data={item.data} />
+ ))}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| </APIProvider> | ||||||||||||||||||
| ) | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -29,6 +29,12 @@ export interface MapData { | |||||||||||||||||||||||||
| longitude: number; | ||||||||||||||||||||||||||
| title?: string; | ||||||||||||||||||||||||||
| }>; | ||||||||||||||||||||||||||
| uploadedGeoJson?: Array<{ | ||||||||||||||||||||||||||
| id: string; | ||||||||||||||||||||||||||
| filename: string; | ||||||||||||||||||||||||||
| data: any; // FeatureCollection | ||||||||||||||||||||||||||
| visible: boolean; | ||||||||||||||||||||||||||
| }>; | ||||||||||||||||||||||||||
|
Comment on lines
+32
to
+37
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 typing The ♻️ Suggested type improvement+import type { FeatureCollection } from 'geojson';
+
// ... in MapData interface
uploadedGeoJson?: Array<{
id: string;
filename: string;
- data: any; // FeatureCollection
+ data: FeatureCollection;
visible: boolean;
}>;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Comment on lines
+32
to
+37
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.
Even a lightweight validation (checking SuggestionReplace Example types: import type { Feature, FeatureCollection } from 'geojson'
data: FeatureCollection | FeatureReply with "@CharlieHelps yes please" if you’d like me to add a commit that tightens types and adds a shared |
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| interface MapDataContextType { | ||||||||||||||||||||||||||
|
|
@@ -39,7 +45,11 @@ interface MapDataContextType { | |||||||||||||||||||||||||
| const MapDataContext = createContext<MapDataContextType | undefined>(undefined); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| export const MapDataProvider: React.FC<{ children: ReactNode }> = ({ children }) => { | ||||||||||||||||||||||||||
| const [mapData, setMapData] = useState<MapData>({ drawnFeatures: [], markers: [] }); | ||||||||||||||||||||||||||
| const [mapData, setMapData] = useState<MapData>({ | ||||||||||||||||||||||||||
| drawnFeatures: [], | ||||||||||||||||||||||||||
| markers: [], | ||||||||||||||||||||||||||
| uploadedGeoJson: [] | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||
| <MapDataContext.Provider value={{ mapData, setMapData }}> | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,63 @@ | ||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import { useEffect } from 'react'; | ||||||||||||||||||||||||||||||
| import { useMapData } from './map-data-context'; | ||||||||||||||||||||||||||||||
| import { useMap } from './map-context'; | ||||||||||||||||||||||||||||||
| import type { FeatureCollection } from 'geojson'; | ||||||||||||||||||||||||||||||
| import * as turf from '@turf/turf'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| interface MapDataUpdaterProps { | ||||||||||||||||||||||||||||||
| id: string; | ||||||||||||||||||||||||||||||
| data: any; // FeatureCollection or Feature | ||||||||||||||||||||||||||||||
| filename: string; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+9
to
+13
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 stronger typing for the Using +import type { Feature } from 'geojson';
+
interface MapDataUpdaterProps {
id: string;
- data: any; // FeatureCollection or Feature
+ data: FeatureCollection | Feature | null;
filename: string;
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export function MapDataUpdater({ id, data, filename }: MapDataUpdaterProps) { | ||||||||||||||||||||||||||||||
| const { setMapData } = useMapData(); | ||||||||||||||||||||||||||||||
| const { map } = useMap(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||
| if (!data) return; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Ensure it's a FeatureCollection for consistency | ||||||||||||||||||||||||||||||
| const featureCollection: FeatureCollection = data.type === 'FeatureCollection' | ||||||||||||||||||||||||||||||
| ? data | ||||||||||||||||||||||||||||||
| : { type: 'FeatureCollection', features: [data] }; | ||||||||||||||||||||||||||||||
|
Comment on lines
+23
to
+26
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. Potential silent failure with invalid GeoJSON data. The normalization logic assumes Consider adding validation: 🛡️ Proposed validation+ // Validate that data is a Feature or FeatureCollection
+ if (data.type !== 'FeatureCollection' && data.type !== 'Feature') {
+ console.warn('MapDataUpdater: Invalid GeoJSON type, expected Feature or FeatureCollection:', data.type);
+ return;
+ }
+
// Ensure it's a FeatureCollection for consistency
const featureCollection: FeatureCollection = data.type === 'FeatureCollection'
? data
: { type: 'FeatureCollection', features: [data] };📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Update MapData context | ||||||||||||||||||||||||||||||
| setMapData(prev => { | ||||||||||||||||||||||||||||||
| // Avoid duplicate entries | ||||||||||||||||||||||||||||||
| const alreadyExists = prev.uploadedGeoJson?.some(item => item.id === id); | ||||||||||||||||||||||||||||||
| if (alreadyExists) return prev; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| ...prev, | ||||||||||||||||||||||||||||||
| uploadedGeoJson: [ | ||||||||||||||||||||||||||||||
| ...(prev.uploadedGeoJson || []), | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||
| filename, | ||||||||||||||||||||||||||||||
| data: featureCollection, | ||||||||||||||||||||||||||||||
| visible: true | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Fly to the extent of the GeoJSON | ||||||||||||||||||||||||||||||
| if (map && featureCollection.features.length > 0) { | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| const bbox = turf.bbox(featureCollection); | ||||||||||||||||||||||||||||||
| map.fitBounds(bbox as [number, number, number, number], { | ||||||||||||||||||||||||||||||
| padding: 50, | ||||||||||||||||||||||||||||||
| maxZoom: 15, | ||||||||||||||||||||||||||||||
| duration: 2000 | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
Comment on lines
48
to
57
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.
Right now you’re relying on a cast to force it through. SuggestionNormalize the bounds shape and isolate provider differences:
Example: const [minX, minY, maxX, maxY] = turf.bbox(featureCollection)
const bounds: [[number, number],[number, number]] = [[minX, minY], [maxX, maxY]]
map.fitBounds(bounds, { padding: 50, maxZoom: 15, duration: 2000 })Reply with "@CharlieHelps yes please" if you’d like me to add a commit with this change (and optionally a small adapter in |
||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||
| console.error('Failed to fit bounds for GeoJSON:', e); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
48
to
61
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: # Check how useMap is implemented and what map instance it returns
ast-grep --pattern 'export const useMap = $_'Repository: QueueLab/QCX Length of output: 489 🏁 Script executed: # Check for fitBounds usage and context
rg -n 'fitBounds' --type=ts --type=tsx -C3Repository: QueueLab/QCX Length of output: 83 🏁 Script executed: # Look for Google Maps imports or setup
rg -n 'google\.maps|@react-google-maps|GoogleMap' --type=ts --type=tsxRepository: QueueLab/QCX Length of output: 83 🏁 Script executed: # Check the context around useMap hook to understand the provider
rg -n 'useMap' --type=ts --type=tsx -B2 -A2 | head -50Repository: QueueLab/QCX Length of output: 83 🏁 Script executed: # Find MapContextType definition
rg -n 'MapContextType' -A5 | head -30Repository: QueueLab/QCX Length of output: 1138 🏁 Script executed: # Find MapProvider implementation
rg -n 'MapProvider|export.*Provider' components/map/ -A10 | head -50Repository: QueueLab/QCX Length of output: 2955 🏁 Script executed: # Search for fitBounds without type filter
rg -n 'fitBounds' -C3Repository: QueueLab/QCX Length of output: 584 🏁 Script executed: # Search for Google Maps references
rg -n 'google.maps|@react-google-maps|GoogleMap'Repository: QueueLab/QCX Length of output: 3033 🏁 Script executed: # Check package.json to see what mapping libraries are used
cat package.json | grep -A5 -B5 'mapbox\|google\|map'Repository: QueueLab/QCX Length of output: 1895 🏁 Script executed: # Check what map-data-updater.tsx imports and which useMap it uses
head -20 components/map/map-data-updater.tsxRepository: QueueLab/QCX Length of output: 584 🏁 Script executed: # Find the Mapbox component to see if it sets map in context
rg -n 'export.*Mapbox|function Mapbox' components/map/ -A15Repository: QueueLab/QCX Length of output: 1615 🏁 Script executed: # Check GoogleMapComponent implementation
rg -n 'export function GoogleMapComponent' components/map/ -A30Repository: QueueLab/QCX Length of output: 1955 🏁 Script executed: # Check if there's any conditional handling for different map types
rg -n 'MapboxMap|google.maps.Map' components/map/map-data-updater.tsxRepository: QueueLab/QCX Length of output: 38 🏁 Script executed: # Get more of GoogleMapComponent implementation
cat components/map/google-map.tsx | head -80Repository: QueueLab/QCX Length of output: 2406 🏁 Script executed: # Check if google-geojson-layer uses a different approach
cat components/map/google-geojson-layer.tsxRepository: QueueLab/QCX Length of output: 1414 🏁 Script executed: # Verify what providers are actually selectable in settings
rg -n 'mapProvider.*google|mapProvider.*mapbox' components/ -iRepository: QueueLab/QCX Length of output: 434 🏁 Script executed: # Check where MapDataUpdater is imported/used
rg -n 'MapDataUpdater' components/Repository: QueueLab/QCX Length of output: 398 🏁 Script executed: # Check if there are any other places attempting to call fitBounds or similar
rg -n 'fitBounds|flyTo|panTo' components/ -B2 -A2Repository: QueueLab/QCX Length of output: 750 🏁 Script executed: # Verify the actual provider switching logic
cat components/map/map-provider.tsxRepository: QueueLab/QCX Length of output: 843 Implement bounds-fitting for Google Maps or restrict MapDataUpdater to Mapbox. When using the Google Maps provider, the Either:
The current code works without errors only because the bounds-fitting is silently skipped for Google Maps. |
||||||||||||||||||||||||||||||
| }, [id, data, filename, setMapData, map]); | ||||||||||||||||||||||||||||||
|
Comment on lines
20
to
62
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.
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return null; // Headless component | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| import { useEffect } from 'react'; | ||
| // Removed useMCPMapClient as we'll use data passed via props | ||
| import { useMapData } from './map-data-context'; | ||
| import { MapDataUpdater } from './map-data-updater'; | ||
|
|
||
| // Define the expected structure of the mcp_response from geospatialTool | ||
| interface McpResponseData { | ||
|
|
@@ -13,6 +14,7 @@ interface McpResponseData { | |
| address?: string; | ||
| }; | ||
| mapUrl?: string; | ||
| geoJson?: any; | ||
| } | ||
|
|
||
| interface GeospatialToolOutput { | ||
|
|
@@ -76,7 +78,17 @@ export const MapQueryHandler: React.FC<MapQueryHandlerProps> = ({ toolOutput }) | |
| // Its purpose is to trigger map data updates based on AI tool results. | ||
| // If it were to use the old useMCPMapClient, mcpLoading and mcpError would be relevant. | ||
| // It could return a small status indicator or debug info if needed for development. | ||
| return null; | ||
| return ( | ||
| <> | ||
| {toolOutput?.mcp_response?.geoJson && ( | ||
| <MapDataUpdater | ||
| id={toolOutput.timestamp} | ||
| data={toolOutput.mcp_response.geoJson} | ||
| filename={toolOutput.mcp_response.location?.place_name || 'Tool Result'} | ||
| /> | ||
| )} | ||
| </> | ||
| ); | ||
|
Comment on lines
+81
to
+91
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. Tool-derived GeoJSON
|
||
| // Example for debugging with previous client: | ||
| // return <div data-map-query-processed={originalUserInput} data-mcp-loading={mcpLoading} data-mcp-error={mcpError} style={{display: 'none'}} />; | ||
| }; | ||
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.
🧹 Nitpick | 🔵 Trivial
Consider validating GeoJSON structure more thoroughly.
The current validation only checks
geoJson.type. Invalid GeoJSON (e.g., a FeatureCollection with malformed features) could still pass through and cause rendering issues downstream. Consider using a lightweight validation:🛡️ Optional: Add feature validation
if (geoJson.type === 'FeatureCollection' || geoJson.type === 'Feature') { + // Basic validation for FeatureCollection + if (geoJson.type === 'FeatureCollection' && !Array.isArray(geoJson.features)) { + console.warn('Invalid FeatureCollection: missing features array') + return + } const geoJsonId = nanoid()📝 Committable suggestion
🤖 Prompt for AI Agents