-
-
Notifications
You must be signed in to change notification settings - Fork 6
Revert "Reset branch to 76ace98 and synchronize with main" #489
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -13,8 +13,10 @@ import { Spinner } from '@/components/ui/spinner' | |
| import { Section } from '@/components/section' | ||
| import { FollowupPanel } from '@/components/followup-panel' | ||
| import { inquire, researcher, taskManager, querySuggestor, resolutionSearch, type DrawnFeature } from '@/lib/agents' | ||
| // Removed import of useGeospatialToolMcp as it no longer exists and was incorrectly used here. | ||
| // The geospatialTool (if used by agents like researcher) now manages its own MCP client. | ||
| import { writer } from '@/lib/agents/writer' | ||
| import { saveChat, getSystemPrompt } from '@/lib/actions/chat' | ||
| import { saveChat, getSystemPrompt } from '@/lib/actions/chat' // Added getSystemPrompt | ||
| import { Chat, AIMessage } from '@/lib/types' | ||
| import { UserMessage } from '@/components/user-message' | ||
| import { BotMessage } from '@/components/message' | ||
|
|
@@ -25,13 +27,14 @@ import { ResolutionImage } from '@/components/resolution-image' | |
| import { CopilotDisplay } from '@/components/copilot-display' | ||
| import RetrieveSection from '@/components/retrieve-section' | ||
| import { VideoSearchSection } from '@/components/video-search-section' | ||
| import { MapQueryHandler } from '@/components/map/map-query-handler' | ||
| import { MapQueryHandler } from '@/components/map/map-query-handler' // Add this import | ||
|
|
||
| // Define the type for related queries | ||
| type RelatedQueries = { | ||
| items: { query: string }[] | ||
| } | ||
|
|
||
| // Removed mcp parameter from submit, as geospatialTool now handles its client. | ||
| async function submit(formData?: FormData, skip?: boolean) { | ||
| 'use server' | ||
|
|
||
|
|
@@ -41,25 +44,42 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| const isCollapsed = createStreamableValue(false) | ||
|
|
||
| const action = formData?.get('action') as string; | ||
| const drawnFeaturesString = formData?.get('drawnFeatures') as string; | ||
| let drawnFeatures: DrawnFeature[] = []; | ||
| try { | ||
| drawnFeatures = drawnFeaturesString ? JSON.parse(drawnFeaturesString) : []; | ||
| } catch (e) { | ||
| console.error('Failed to parse drawnFeatures:', e); | ||
| } | ||
|
|
||
| if (action === 'resolution_search') { | ||
| const file = formData?.get('file') as File; | ||
| const mapboxFile = formData?.get('mapboxFile') as File; | ||
| const googleFile = formData?.get('googleFile') as File; | ||
| const legacyFile = formData?.get('file') as File; | ||
| const timezone = (formData?.get('timezone') as string) || 'UTC'; | ||
| const drawnFeaturesString = formData?.get('drawnFeatures') as string; | ||
| let drawnFeatures: DrawnFeature[] = []; | ||
| try { | ||
| drawnFeatures = drawnFeaturesString ? JSON.parse(drawnFeaturesString) : []; | ||
| } catch (e) { | ||
| console.error('Failed to parse drawnFeatures:', e); | ||
| } | ||
|
|
||
| let mapboxDataUrl = ''; | ||
| let googleDataUrl = ''; | ||
|
|
||
| if (!file) { | ||
| throw new Error('No file provided for resolution search.'); | ||
| if (mapboxFile) { | ||
| const buffer = await mapboxFile.arrayBuffer(); | ||
| mapboxDataUrl = `data:${mapboxFile.type};base64,${Buffer.from(buffer).toString('base64')}`; | ||
| } | ||
| if (googleFile) { | ||
| const buffer = await googleFile.arrayBuffer(); | ||
| googleDataUrl = `data:${googleFile.type};base64,${Buffer.from(buffer).toString('base64')}`; | ||
| } | ||
|
|
||
| const buffer = await file.arrayBuffer(); | ||
| const dataUrl = `data:${file.type};base64,${Buffer.from(buffer).toString('base64')}`; | ||
| // Fallback if only 'file' was provided (backward compatibility) | ||
| if (!mapboxDataUrl && !googleDataUrl && legacyFile) { | ||
| const buffer = await legacyFile.arrayBuffer(); | ||
| mapboxDataUrl = `data:${legacyFile.type};base64,${Buffer.from(buffer).toString('base64')}`; | ||
| } | ||
|
|
||
| if (!mapboxDataUrl && !googleDataUrl) { | ||
| throw new Error('No files provided for resolution search.'); | ||
| } | ||
|
|
||
| // Get the current messages, excluding tool-related ones. | ||
| const messages: CoreMessage[] = [...(aiState.get().messages as any[])].filter( | ||
| message => | ||
| message.role !== 'tool' && | ||
|
|
@@ -69,12 +89,32 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| message.type !== 'resolution_search_result' | ||
| ); | ||
|
|
||
| // The user's prompt for this action is static. | ||
| const userInput = 'Analyze this map view.'; | ||
| const content: CoreMessage['content'] = [ | ||
| { type: 'text', text: userInput }, | ||
| { type: 'image', image: dataUrl, mimeType: file.type } | ||
| ]; | ||
|
|
||
| // Construct the multimodal content for the user message. | ||
| const contentParts: any[] = [ | ||
| { type: 'text', text: userInput } | ||
| ] | ||
|
|
||
| if (mapboxDataUrl) { | ||
| contentParts.push({ | ||
| type: 'image', | ||
| image: mapboxDataUrl, | ||
| mimeType: 'image/png' | ||
| }) | ||
| } | ||
| if (googleDataUrl) { | ||
| contentParts.push({ | ||
| type: 'image', | ||
| image: googleDataUrl, | ||
| mimeType: 'image/png' | ||
| }) | ||
| } | ||
|
|
||
| const content = contentParts as any | ||
|
|
||
|
Comment on lines
+95
to
+116
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.
|
||
| // Add the new user message to the AI state. | ||
| aiState.update({ | ||
| ...aiState.get(), | ||
| messages: [ | ||
|
|
@@ -84,11 +124,13 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| }); | ||
| messages.push({ role: 'user', content }); | ||
|
|
||
| // Create a streamable value for the summary. | ||
| const summaryStream = createStreamableValue<string>(''); | ||
| const groupeId = nanoid(); | ||
|
|
||
| async function processResolutionSearch() { | ||
| try { | ||
| // Call the simplified agent, which now returns a stream. | ||
| const streamResult = await resolutionSearch(messages, timezone, drawnFeatures); | ||
|
|
||
| let fullSummary = ''; | ||
|
|
@@ -100,6 +142,8 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| } | ||
|
|
||
| const analysisResult = await streamResult.object; | ||
|
|
||
| // Mark the summary stream as done with the result. | ||
| summaryStream.done(analysisResult.summary || 'Analysis complete.'); | ||
|
|
||
| if (analysisResult.geoJson) { | ||
|
|
@@ -147,7 +191,7 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| role: 'assistant', | ||
| content: JSON.stringify({ | ||
| ...analysisResult, | ||
| image: dataUrl | ||
| image: JSON.stringify({ mapbox: mapboxDataUrl, google: googleDataUrl }) | ||
| }), | ||
| type: 'resolution_search_result' | ||
| }, | ||
|
Comment on lines
191
to
197
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. Resolution search stores
|
||
|
|
@@ -174,11 +218,13 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| } | ||
| } | ||
|
|
||
| // Start the background process without awaiting it. | ||
| processResolutionSearch(); | ||
|
|
||
| // Immediately update the UI stream with the BotMessage component. | ||
| uiStream.update( | ||
| <Section title="response"> | ||
| <ResolutionImage src={dataUrl} /> | ||
| <ResolutionImage mapboxSrc={mapboxDataUrl} googleSrc={googleDataUrl} /> | ||
| <BotMessage content={summaryStream.value} /> | ||
| </Section> | ||
| ); | ||
|
|
@@ -243,6 +289,7 @@ async function submit(formData?: FormData, skip?: boolean) { | |
|
|
||
| uiStream.append(answerSection); | ||
|
|
||
| const groupeId = nanoid(); | ||
| const relatedQueries = { items: [] }; | ||
|
|
||
| aiState.done({ | ||
|
|
@@ -326,6 +373,7 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| } | ||
|
|
||
| const hasImage = messageParts.some(part => part.type === 'image') | ||
| // Properly type the content based on whether it contains images | ||
| const content: CoreMessage['content'] = hasImage | ||
| ? messageParts as CoreMessage['content'] | ||
| : messageParts.map(part => part.text).join('\n') | ||
|
|
@@ -359,6 +407,7 @@ async function submit(formData?: FormData, skip?: boolean) { | |
|
|
||
| const userId = 'anonymous' | ||
| const currentSystemPrompt = (await getSystemPrompt(userId)) || '' | ||
|
|
||
| const mapProvider = formData?.get('mapProvider') as 'mapbox' | 'google' | ||
|
|
||
| async function processEvents() { | ||
|
|
@@ -407,8 +456,7 @@ async function submit(formData?: FormData, skip?: boolean) { | |
| streamText, | ||
| messages, | ||
| mapProvider, | ||
| useSpecificAPI, | ||
| drawnFeatures | ||
| useSpecificAPI | ||
| ) | ||
| answer = fullResponse | ||
| toolOutputs = toolResponses | ||
|
|
@@ -641,10 +689,12 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { | |
| case 'input_related': | ||
| let messageContent: string | any[] | ||
| try { | ||
| // For backward compatibility with old messages that stored a JSON string | ||
| const json = JSON.parse(content as string) | ||
| messageContent = | ||
| type === 'input' ? json.input : json.related_query | ||
| } catch (e) { | ||
| // New messages will store the content array or string directly | ||
| messageContent = content | ||
| } | ||
| return { | ||
|
|
@@ -665,8 +715,8 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { | |
| } | ||
| break | ||
| case 'assistant': | ||
| const answer = createStreamableValue(content as string) | ||
| answer.done(content as string) | ||
| const answer = createStreamableValue() | ||
| answer.done(content) | ||
| switch (type) { | ||
| case 'response': | ||
| return { | ||
|
|
@@ -678,9 +728,7 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { | |
| ) | ||
| } | ||
| case 'related': | ||
| const relatedQueries = createStreamableValue<RelatedQueries>({ | ||
| items: [] | ||
| }) | ||
| const relatedQueries = createStreamableValue<RelatedQueries>() | ||
| relatedQueries.done(JSON.parse(content as string)) | ||
| return { | ||
| id, | ||
|
|
@@ -702,13 +750,28 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { | |
| case 'resolution_search_result': { | ||
| const analysisResult = JSON.parse(content as string); | ||
| const geoJson = analysisResult.geoJson as FeatureCollection; | ||
| const image = analysisResult.image as string; | ||
| const imageData = analysisResult.image as string; | ||
| let mapboxSrc = ''; | ||
| let googleSrc = ''; | ||
|
|
||
| if (imageData) { | ||
| try { | ||
| const parsed = JSON.parse(imageData); | ||
| mapboxSrc = parsed.mapbox || ''; | ||
| googleSrc = parsed.google || ''; | ||
| } catch (e) { | ||
| // Fallback for older image format which was just a single string | ||
| mapboxSrc = imageData; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| id, | ||
| component: ( | ||
| <> | ||
| {image && <ResolutionImage src={image} />} | ||
| {(mapboxSrc || googleSrc) && ( | ||
| <ResolutionImage mapboxSrc={mapboxSrc} googleSrc={googleSrc} /> | ||
| )} | ||
| {geoJson && ( | ||
| <GeoJsonLayer id={id} data={geoJson} /> | ||
| )} | ||
|
|
@@ -721,7 +784,7 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { | |
| case 'tool': | ||
| try { | ||
| const toolOutput = JSON.parse(content as string) | ||
| const isCollapsed = createStreamableValue(true) | ||
| const isCollapsed = createStreamableValue() | ||
| isCollapsed.done(true) | ||
|
|
||
| if ( | ||
|
|
@@ -749,9 +812,7 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => { | |
| } | ||
| } | ||
|
|
||
| const searchResults = createStreamableValue( | ||
| JSON.stringify(toolOutput) | ||
| ) | ||
| const searchResults = createStreamableValue() | ||
| searchResults.done(JSON.stringify(toolOutput)) | ||
| switch (name) { | ||
| case 'search': | ||
|
|
||
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.
Hardcoded
mimeType: 'image/png'ignores the actual file typeThe Data URL is created using
mapboxFile.type/googleFile.type, but the message parts always sendmimeType: 'image/png'. If the browser provides e.g.image/jpegorimage/webp, downstream model/tooling could mis-handle or reject the payload.This is a runtime correctness issue, not a typing issue.
Suggestion
Preserve the original MIME type alongside each data URL. For example, store
mapboxMimeType/googleMimeTypewhen building the data URLs and pass them through into the content parts.Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.