-
-
Notifications
You must be signed in to change notification settings - Fork 7
Implement intent-driven object detection and iterative zoom #596
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 all commits
58df5f7
48ff9d2
b806fa4
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 |
|---|---|---|
|
|
@@ -57,7 +57,14 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| const timezone = (formData?.get('timezone') as string) || 'UTC'; | ||
| 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 boundsString = formData?.get('bounds') as string; | ||
| let bounds = undefined; | ||
| try { | ||
| bounds = boundsString ? JSON.parse(boundsString) : undefined; | ||
| } catch (e) { | ||
| console.error('Failed to parse bounds:', e); | ||
| } | ||
| const location = (lat !== undefined && lng !== undefined) ? { lat, lng, bounds } : undefined; | ||
|
|
||
| if (!file) { | ||
| throw new Error('No file provided for resolution search.'); | ||
|
|
@@ -81,7 +88,7 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| message.type !== 'resolution_search_result' | ||
| ); | ||
|
|
||
| const userInput = 'Analyze this map view.'; | ||
| const userInput = (formData?.get('input') as string) || 'Analyze this map view.'; | ||
| const content: CoreMessage['content'] = [ | ||
| { type: 'text', text: userInput }, | ||
| { type: 'image', image: dataUrl, mimeType: file.type } | ||
|
|
@@ -96,32 +103,43 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| }); | ||
| messages.push({ role: 'user', content }); | ||
|
|
||
| const summaryStream = createStreamableValue<string>('Analyzing map view...'); | ||
| const summaryStream = createStreamableValue<string>(`Analyzing: ${userInput}`); | ||
| const groupeId = nanoid(); | ||
|
|
||
| async function processResolutionSearch() { | ||
| try { | ||
| const streamResult = await resolutionSearch(messages, timezone, drawnFeatures, location); | ||
|
|
||
| uiStream.append( | ||
| <GeoJsonLayer | ||
| id={groupeId} | ||
| data={{ type: 'FeatureCollection', features: [] } as FeatureCollection} | ||
| /> | ||
| ); | ||
|
|
||
| let fullSummary = ''; | ||
| for await (const partialObject of streamResult.partialObjectStream) { | ||
| if (partialObject.summary) { | ||
| fullSummary = partialObject.summary; | ||
| summaryStream.update(fullSummary); | ||
| if (fullSummary.includes('ZOOM PASSContext') || fullSummary.includes('zoomed-in CROP')) { | ||
| summaryStream.update(`${fullSummary}\n\n[Zoom Pass in progress...]`); | ||
| } | ||
| } | ||
| if (partialObject.geoJson) { | ||
| uiStream.update( | ||
| <GeoJsonLayer | ||
| id={groupeId} | ||
| data={partialObject.geoJson as FeatureCollection} | ||
| /> | ||
| ); | ||
|
Comment on lines
+129
to
+135
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. 2. Geojson update blanks response During streaming, processResolutionSearch() calls uiStream.update(<GeoJsonLayer .../>), which replaces the already-rendered response section with a component that returns null, making the chat response disappear. Agent Prompt
|
||
| } | ||
| } | ||
|
|
||
| const analysisResult = await streamResult.object; | ||
| summaryStream.done(analysisResult.summary || 'Analysis complete.'); | ||
|
|
||
| if (analysisResult.geoJson) { | ||
| uiStream.append( | ||
| <GeoJsonLayer | ||
| id={groupeId} | ||
| data={analysisResult.geoJson as FeatureCollection} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
|
|
||
| messages.push({ role: 'assistant', content: analysisResult.summary || 'Analysis complete.' }); | ||
|
|
||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,7 @@ interface HeaderActions { | |
| submit: (formData: FormData) => Promise<any>; | ||
| } | ||
|
|
||
| export function HeaderSearchButton() { | ||
| export function HeaderSearchButton({ input, setInput }: { input?: string, setInput?: (value: string) => void }) { | ||
| const { map } = useMap() | ||
| const { mapProvider } = useSettingsStore() | ||
| const { mapData } = useMapData() | ||
|
|
@@ -72,7 +72,7 @@ export function HeaderSearchButton() { | |
| ...currentMessages, | ||
| { | ||
| id: nanoid(), | ||
| component: <UserMessage content={[{ type: 'text', text: 'Analyze this map view.' }]} /> | ||
| component: <UserMessage content={input || 'Analyze this map view.'} /> | ||
| } | ||
| ]) | ||
|
|
||
|
|
@@ -146,6 +146,7 @@ export function HeaderSearchButton() { | |
| // Keep 'file' for backward compatibility if needed, or just use the first available | ||
| formData.append('file', (mapboxBlob || googleBlob)!, 'map_capture.png') | ||
|
|
||
| if (input) formData.append('input', input) | ||
| formData.append('action', 'resolution_search') | ||
| formData.append('timezone', mapData.currentTimezone || 'UTC') | ||
| formData.append('drawnFeatures', JSON.stringify(mapData.drawnFeatures || [])) | ||
|
|
@@ -154,10 +155,18 @@ export function HeaderSearchButton() { | |
| if (center) { | ||
| formData.append('latitude', center.lat.toString()) | ||
| formData.append('longitude', center.lng.toString()) | ||
| const bounds = mapProvider === 'mapbox' && map ? map.getBounds() : null; | ||
| if (bounds) { | ||
| formData.append('bounds', JSON.stringify({ | ||
| sw: { lat: bounds.getSouthWest().lat, lng: bounds.getSouthWest().lng }, | ||
| ne: { lat: bounds.getNorthEast().lat, lng: bounds.getNorthEast().lng } | ||
| })) | ||
| } | ||
| } | ||
|
Comment on lines
+158
to
165
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. Bounds are not captured for the Google Maps provider. For 🛠️ Suggested fix — capture bounds for Google Maps provider const center = mapProvider === 'mapbox' && map ? map.getCenter() : mapData.cameraState?.center;
if (center) {
formData.append('latitude', center.lat.toString())
formData.append('longitude', center.lng.toString())
const bounds = mapProvider === 'mapbox' && map ? map.getBounds() : null;
if (bounds) {
formData.append('bounds', JSON.stringify({
sw: { lat: bounds.getSouthWest().lat, lng: bounds.getSouthWest().lng },
ne: { lat: bounds.getNorthEast().lat, lng: bounds.getNorthEast().lng }
}))
+ } else if (mapData.cameraState?.bounds) {
+ formData.append('bounds', JSON.stringify(mapData.cameraState.bounds))
}
}🤖 Prompt for AI Agents |
||
|
|
||
| const responseMessage = await actions.submit(formData) | ||
| setMessages((currentMessages: any[]) => [...currentMessages, responseMessage as any]) | ||
| if (setInput) setInput('') | ||
| } catch (error) { | ||
| console.error('Failed to perform resolution search:', error) | ||
| toast.error('An error occurred while analyzing the map.') | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import sys | ||
|
|
||
| with open('lib/agents/resolution-search.tsx', 'r') as f: | ||
| content = f.read() | ||
|
|
||
| old_vars = """ let finalSummary = ''; | ||
| let finalCogInfo = undefined; | ||
| let finalNewsContext = undefined; | ||
| let finalExtractedCoordinates = undefined;""" | ||
|
|
||
| new_vars = """ let finalSummary = ''; | ||
| let finalCogInfo: z.infer<typeof resolutionSearchSchema>['cogInfo'] = undefined; | ||
| let finalNewsContext: z.infer<typeof resolutionSearchSchema>['newsContext'] = undefined; | ||
| let finalExtractedCoordinates: z.infer<typeof resolutionSearchSchema>['extractedCoordinates'] = undefined;""" | ||
|
|
||
| content = content.replace(old_vars, new_vars) | ||
|
|
||
| with open('lib/agents/resolution-search.tsx', 'w') as f: | ||
| f.write(content) |
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.
'ZOOM PASSContext'is missing a space — this branch can never be entered.The string
'ZOOM PASSContext'has no space betweenPASSandContext. The system prompt uses**ZOOM PASS Context:**(with a space), but even so, the AI'ssummaryoutput is unlikely to reproduce that system-prompt phrase verbatim. The[Zoom Pass in progress...]indicator therefore never appears in the streamed summary.🐛 Proposed fix
A more reliable alternative is to track zoom-pass state in a flag set when
extractRegionis called by the agent, rather than checking for keywords in the AI-generated text.📝 Committable suggestion
🤖 Prompt for AI Agents