diff --git a/app/actions.tsx b/app/actions.tsx index d7798fe6..48ed4cdb 100644 --- a/app/actions.tsx +++ b/app/actions.tsx @@ -58,6 +58,9 @@ async function submit(formData?: FormData, skip?: boolean) { const lat = formData?.get('latitude') ? parseFloat(formData.get('latitude') as string) : undefined; const lng = formData?.get('longitude') ? parseFloat(formData.get('longitude') as string) : undefined; const location = (lat !== undefined && lng !== undefined) ? { lat, lng } : undefined; + const cursorLat = formData.get("cursorLat") ? parseFloat(formData.get("cursorLat") as string) : undefined; + const cursorLng = formData.get("cursorLng") ? parseFloat(formData.get("cursorLng") as string) : undefined; + const cursorLocation = (cursorLat !== undefined && cursorLng !== undefined) ? { lat: cursorLat, lng: cursorLng } : undefined; if (!file) { throw new Error('No file provided for resolution search.'); @@ -101,7 +104,7 @@ async function submit(formData?: FormData, skip?: boolean) { async function processResolutionSearch() { try { - const streamResult = await resolutionSearch(messages, timezone, drawnFeatures, location); + const streamResult = await resolutionSearch(messages, timezone, drawnFeatures, location, cursorLocation); let fullSummary = ''; for await (const partialObject of streamResult.partialObjectStream) { @@ -822,6 +825,9 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { const image = analysisResult.image as string; const mapboxImage = analysisResult.mapboxImage as string; const googleImage = analysisResult.googleImage as string; + const mapboxImageLabel = analysisResult.mapboxImageLabel as string; + const googleImageLabel = analysisResult.googleImageLabel as string; + const analysisFocus = analysisResult.analysisFocus as string; return { id, @@ -831,6 +837,9 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { mapboxImage={mapboxImage} googleImage={googleImage} initialImage={image} + mapboxImageLabel={mapboxImageLabel} + googleImageLabel={googleImageLabel} + analysisFocus={analysisFocus} /> {geoJson && ( diff --git a/components/header-search-button.tsx b/components/header-search-button.tsx index 7090a52c..57fffcfe 100644 --- a/components/header-search-button.tsx +++ b/components/header-search-button.tsx @@ -150,6 +150,11 @@ export function HeaderSearchButton() { formData.append('timezone', mapData.currentTimezone || 'UTC') formData.append('drawnFeatures', JSON.stringify(mapData.drawnFeatures || [])) + if (mapData.cursorLocation) { + formData.append('cursorLat', mapData.cursorLocation.lat.toString()) + formData.append('cursorLng', mapData.cursorLocation.lng.toString()) + } + const center = mapProvider === 'mapbox' && map ? map.getCenter() : mapData.cameraState?.center; if (center) { formData.append('latitude', center.lat.toString()) diff --git a/components/map/map-3d.tsx b/components/map/map-3d.tsx index 29f6763f..4505b9eb 100644 --- a/components/map/map-3d.tsx +++ b/components/map/map-3d.tsx @@ -26,6 +26,20 @@ export const Map3D = forwardRef( const [map3DElement, map3dRef] = useCallbackRef(); + useEffect(() => { + if (!map3DElement) return; + const listener = (e: any) => { + if (e.position) { + setMapData(prev => ({ + ...prev, + cursorLocation: { lat: e.position.lat, lng: e.position.lng } + })); + } + }; + map3DElement.addEventListener("gmp-click", listener); + return () => map3DElement.removeEventListener("gmp-click", listener); + }, [map3DElement, setMapData]); + useMap3DCameraEvents(map3DElement, p => { const { center, range, heading, tilt } = p.detail; const lat = center.lat(); diff --git a/components/map/map-data-context.tsx b/components/map/map-data-context.tsx index 9b102547..399a8fd3 100644 --- a/components/map/map-data-context.tsx +++ b/components/map/map-data-context.tsx @@ -29,6 +29,7 @@ export interface MapData { longitude: number; title?: string; }>; + cursorLocation?: { lat: number; lng: number } | null; // Added to track user click/pointer } interface MapDataContextType { diff --git a/components/resolution-carousel.tsx b/components/resolution-carousel.tsx index 1b28cbf7..a9dd7643 100644 --- a/components/resolution-carousel.tsx +++ b/components/resolution-carousel.tsx @@ -22,9 +22,19 @@ interface ResolutionCarouselProps { mapboxImage?: string | null googleImage?: string | null initialImage?: string | null + mapboxImageLabel?: string + googleImageLabel?: string + analysisFocus?: string } -export function ResolutionCarousel({ mapboxImage, googleImage, initialImage }: ResolutionCarouselProps) { +export function ResolutionCarousel({ + mapboxImage, + googleImage, + initialImage, + mapboxImageLabel, + googleImageLabel, + analysisFocus +}: ResolutionCarouselProps) { const actions = useActions() as any const [, setMessages] = useUIState() const [isAnalyzing, setIsAnalyzing] = React.useState(false) @@ -75,12 +85,27 @@ export function ResolutionCarousel({ mapboxImage, googleImage, initialImage }: R } // Individual slides - if (mapboxImage) slides.push({ type: 'image', src: mapboxImage, showAnalysis: false, label: 'MAPBOX' }) - if (googleImage) slides.push({ type: 'image', src: googleImage, showAnalysis: true, label: 'GOOGLE SATELLITE' }) + if (mapboxImage) slides.push({ + type: 'image', + src: mapboxImage, + showAnalysis: false, + label: mapboxImageLabel || 'MAPBOX' + }) + if (googleImage) slides.push({ + type: 'image', + src: googleImage, + showAnalysis: true, + label: googleImageLabel || 'GOOGLE SATELLITE' + }) // Fallback if (slides.length === 0 && initialImage) { - slides.push({ type: 'image', src: initialImage, showAnalysis: false, label: 'MAP CAPTURE' }) + slides.push({ + type: 'image', + src: initialImage, + showAnalysis: false, + label: analysisFocus || 'MAP CAPTURE' + }) } if (slides.length === 0) return null @@ -102,6 +127,9 @@ export function ResolutionCarousel({ mapboxImage, googleImage, initialImage }: R {isAnalyzing ? 'ANALYZING...' : 'QCX-TERRA ANALYSIS'} )} +
+ {item.label} +
) } @@ -130,7 +158,7 @@ export function ResolutionCarousel({ mapboxImage, googleImage, initialImage }: R {isAnalyzing ? 'ANALYZING...' : 'QCX-TERRA ANALYSIS'} )} -
+
{slide.label}
diff --git a/lib/agents/resolution-search.tsx b/lib/agents/resolution-search.tsx index 9985712d..ce23066a 100644 --- a/lib/agents/resolution-search.tsx +++ b/lib/agents/resolution-search.tsx @@ -62,7 +62,13 @@ async function getReverseGeocode(lat: number, lng: number): Promise { } } -export async function resolutionSearch(messages: CoreMessage[], timezone: string = 'UTC', drawnFeatures?: DrawnFeature[], location?: { lat: number, lng: number }) { +export async function resolutionSearch( + messages: CoreMessage[], + timezone: string = 'UTC', + drawnFeatures?: DrawnFeature[], + location?: { lat: number, lng: number }, + cursorLocation?: { lat: number, lng: number } +) { const now = new Date(); // OPTIMIZATION: Format local time with timezone context @@ -81,9 +87,12 @@ export async function resolutionSearch(messages: CoreMessage[], timezone: string let locationName = 'this location'; let newsContext = ''; - if (location?.lat && location?.lng) { + const searchLat = cursorLocation?.lat || location?.lat; + const searchLng = cursorLocation?.lng || location?.lng; + + if (searchLat && searchLng) { try { - locationName = await getReverseGeocode(location.lat, location.lng); + locationName = await getReverseGeocode(searchLat, searchLng); // OPTIMIZATION: Fetch news in parallel with AI analysis const newsData = await fetchLocationNews(locationName, timezone); @@ -101,6 +110,12 @@ export async function resolutionSearch(messages: CoreMessage[], timezone: string const systemPrompt = ` As a geospatial analyst, your task is to analyze the provided satellite image of a geographic location. +**CRITICAL GUIDELINES:** +1. **ACCURACY & CONFIDENCE:** Your analysis MUST be grounded strictly in the visual evidence of the image. Do NOT generate GeoJSON features (points, polygons) for landmarks or features that you are not 100% confident in. If you are unsure, omit the GeoJSON feature entirely. +2. **STRICT CONTEXTUAL ALIGNMENT:** Prioritize the user's intent and any user-drawn features as the primary subjects of analysis. Do NOT provide "general" points of interest unless they directly relate to the analysis focus or the user's query. +3. **NO RANDOM OVERLAYS:** Ensure that any boxes or markers you generate are accurately placed within the spatial context of the image and clearly correspond to real objects visible in the satellite view. Avoid "random" or disconnected labeling. +4. **SUPPRESSION OF NOISE:** It is better to return fewer, accurate results than many low-confidence or out-of-context features. + **Temporal Context:** The current local time at this location is ${localTime} (timezone: ${timezone}). This temporal information is important for understanding the current state and any time-sensitive features visible in the image. @@ -109,6 +124,10 @@ ${location ? `**Geographic Coordinates:** The coordinates provided for this image are: Latitude ${location.lat}, Longitude ${location.lng}. Location: ${locationName}` : ''} +${cursorLocation ? `**Pointer Focus:** +The user is specifically pointing at/clicking on the following coordinate: Latitude ${cursorLocation.lat}, Longitude ${cursorLocation.lng}. +Prioritize analysis of the features exactly at or immediately surrounding this point.` : ''} + ${newsContext ? `**Recent Context:** ${newsContext} @@ -117,17 +136,22 @@ Please incorporate this recent news context into your analysis where relevant.` ${drawnFeatures && drawnFeatures.length > 0 ? `**User-Drawn Features:** The user has drawn the following features on the map for your reference: ${drawnFeatures.map(f => `- ${f.type} (${f.measurement}): ${JSON.stringify(f.geometry)}`).join('\n')} -Use these user-drawn areas/lines as primary areas of interest for your analysis.` : ''} +Use these user-drawn areas/lines as primary areas of interest for your analysis. +IMPORTANT: In your summary, explicitly state what features you are analyzing and why. Any GeoJSON you generate should strictly align with or augment these drawn areas.` : ''} **Analysis Requirements:** 1. **Land Feature Classification:** Identify and describe the different types of land cover visible in the image (e.g., urban areas, forests, water bodies, agricultural fields). -2. **Points of Interest (POI):** Detect and name any significant landmarks, infrastructure (e.g., bridges, major roads), or notable buildings. +2. **Points of Interest (POI):** Detect and name any significant landmarks, infrastructure (e.g., bridges, major roads), or notable buildings ONLY if they are clearly visible. 3. **Temporal Analysis:** Consider how the time of day and season might affect what's visible in the image. 4. **Coordinate Extraction:** If possible, confirm or refine the geocoordinates (latitude/longitude) of the center of the image. 5. **COG Applicability:** Determine if this location would benefit from Cloud Optimized GeoTIFF (COG) analysis for high-precision temporal or spectral data. 6. **News Integration:** Reference any recent news or events that may be relevant to the current state of the location. 7. **Structured Output:** Return your findings in a structured JSON format including summary, geoJson (if any), news context, and any extracted coordinates or COG information. Use the provided schema. +8. **Contextual Labeling:** Generate highly specific, descriptive labels for the map images that reflect the primary analysis focus. Avoid generic labels like "Mapbox" or "Google". + - If user-drawn features are present, the labels MUST reference them (e.g., "Analysis of drawn ${drawnFeatures?.[0]?.type || 'area'}: [feature type]"). + - If a specific location is known, include it (e.g., "[${locationName}] satellite view - [Focus Area]"). + - The 'analysisFocus' field should capture the specific theme of this analysis (e.g., "Coastal erosion assessment", "Industrial zone monitoring"). Your analysis should be based on the visual information in the image, the temporal context provided, and your general knowledge. Do not attempt to access external websites or perform web searches beyond what has been provided. diff --git a/lib/schema/resolution-search.ts b/lib/schema/resolution-search.ts index e7908abd..9ce44e41 100644 --- a/lib/schema/resolution-search.ts +++ b/lib/schema/resolution-search.ts @@ -21,7 +21,7 @@ export const resolutionSearchSchema = z.object({ name: z.string().describe('Name of the feature or point of interest'), description: z.string().optional().describe('Description of the feature') })) - }).optional().describe('A collection of points of interest and classified land features to be overlaid on the map.'), + }).optional().describe('A collection of HIGH-CONFIDENCE points of interest and classified land features to be overlaid on the map. ONLY include features that are clearly identifiable and highly relevant to the analysis focus or user-drawn areas.'), // Flattened top-level fields for better xAI compatibility extractedLatitude: z.number().optional().describe('The extracted latitude of the center of the image.'), @@ -35,7 +35,12 @@ export const resolutionSearchSchema = z.object({ title: z.string(), summary: z.string(), relevance: z.string() - })).optional().describe('List of recent news items relevant to the location.') + })).optional().describe('List of recent news items relevant to the location.'), + + // New fields for contextual labels + mapboxImageLabel: z.string().optional().describe('A contextual label describing what the Mapbox image shows, based on the analysis focus.'), + googleImageLabel: z.string().optional().describe('A contextual label describing what the Google Satellite image shows, based on the analysis focus.'), + analysisFocus: z.string().optional().describe('A brief phrase describing the primary focus of the analysis (e.g., "Urban infrastructure analysis", "Forest coverage assessment").') }) export type ResolutionSearch = z.infer; diff --git a/server.log b/server.log index 41e8e758..87880682 100644 --- a/server.log +++ b/server.log @@ -5,13 +5,72 @@ $ next dev --turbo - Environments: .env ✓ Starting... - ✓ Compiled middleware in 433ms - ✓ Ready in 1802ms +Attention: Next.js now collects completely anonymous telemetry regarding usage. +This information is used to shape Next.js' roadmap and prioritize features. +You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: +https://nextjs.org/telemetry + + ✓ Compiled middleware in 363ms + ✓ Ready in 1710ms ○ Compiling / ... - ✓ Compiled / in 27.9s + ✓ Compiled / in 29.3s Chat DB actions loaded. Ensure getCurrentUserId() is correctly implemented for server-side usage if applicable. - GET / 200 in 30481ms - HEAD / 200 in 1010ms - GET / 200 in 986ms - GET / 200 in 1129ms -[?25h + GET / 200 in 34985ms + GET / 200 in 35005ms + GET / 200 in 1472ms + GET / 200 in 2734ms + GET / 200 in 1145ms + GET / 200 in 1843ms + GET / 200 in 1137ms + GET / 200 in 1370ms + GET / 200 in 1107ms + GET / 200 in 1355ms + HEAD / 200 in 995ms + GET / 200 in 1896ms + GET / 200 in 1894ms + GET / 200 in 2017ms + GET / 200 in 2021ms + GET / 200 in 2087ms + GET / 200 in 2246ms + GET / 200 in 2268ms + GET / 200 in 2245ms +[Auth] Supabase URL or Anon Key is not set for server-side auth. + POST / 200 in 1605ms + GET / 200 in 1178ms + GET / 200 in 2469ms + GET / 200 in 2179ms + GET / 200 in 2328ms + GET / 200 in 1137ms +[Auth] Supabase URL or Anon Key is not set for server-side auth. + POST / 200 in 2000ms +[Auth] Supabase URL or Anon Key is not set for server-side auth. + POST / 200 in 1989ms + GET / 200 in 1181ms +[Auth] Supabase URL or Anon Key is not set for server-side auth. + POST / 200 in 1073ms + GET / 200 in 1048ms + GET / 200 in 1104ms +[Auth] Supabase URL or Anon Key is not set for server-side auth. + POST / 200 in 1055ms + ✓ Compiled middleware in 2ms + ○ Compiling /test-carousel ... + ✓ Compiled /test-carousel in 6.1s + ⨯ Error: `useUIState` must be used inside an provider. + at ResolutionCarousel (components/resolution-carousel.tsx:39:35) + 37 | }: ResolutionCarouselProps) { + 38 | const actions = useActions() as any +> 39 | const [, setMessages] = useUIState() + | ^ + 40 | const [isAnalyzing, setIsAnalyzing] = React.useState(false) + 41 | + 42 | const handleQCXAnalysis = async () => { { + digest: '3019542783' +} + GET /test-carousel 500 in 7348ms + ✓ Compiled middleware in 38ms + ✓ Compiled in 1094ms + ✓ Compiled in 1332ms + ✓ Compiled in 215ms + ✓ Compiled in 601ms + ✓ Compiled in 267ms + ✓ Compiled in 86ms