@@ -21,6 +21,7 @@ import { BotMessage } from '@/components/message'
2121import { SearchSection } from '@/components/search-section'
2222import SearchRelated from '@/components/search-related'
2323import { GeoJsonLayer } from '@/components/map/geojson-layer'
24+ import { ResolutionCarousel } from '@/components/resolution-carousel'
2425import { ResolutionImage } from '@/components/resolution-image'
2526import { CopilotDisplay } from '@/components/copilot-display'
2627import RetrieveSection from '@/components/retrieve-section'
@@ -50,18 +51,29 @@ async function submit(formData?: FormData, skip?: boolean) {
5051 }
5152
5253 if ( action === 'resolution_search' ) {
53- const file = formData ?. get ( 'file' ) as File ;
54+ const file_mapbox = formData ?. get ( 'file_mapbox' ) as File ;
55+ const file_google = formData ?. get ( 'file_google' ) as File ;
56+ const file = ( formData ?. get ( 'file' ) as File ) || file_mapbox || file_google ;
5457 const timezone = ( formData ?. get ( 'timezone' ) as string ) || 'UTC' ;
58+ const lat = formData ?. get ( 'latitude' ) ? parseFloat ( formData . get ( 'latitude' ) as string ) : undefined ;
59+ const lng = formData ?. get ( 'longitude' ) ? parseFloat ( formData . get ( 'longitude' ) as string ) : undefined ;
60+ const location = ( lat !== undefined && lng !== undefined ) ? { lat, lng } : undefined ;
5561
5662 if ( ! file ) {
5763 throw new Error ( 'No file provided for resolution search.' ) ;
5864 }
5965
66+ const mapboxBuffer = file_mapbox ? await file_mapbox . arrayBuffer ( ) : null ;
67+ const mapboxDataUrl = mapboxBuffer ? `data:${ file_mapbox . type } ;base64,${ Buffer . from ( mapboxBuffer ) . toString ( 'base64' ) } ` : null ;
68+
69+ const googleBuffer = file_google ? await file_google . arrayBuffer ( ) : null ;
70+ const googleDataUrl = googleBuffer ? `data:${ file_google . type } ;base64,${ Buffer . from ( googleBuffer ) . toString ( 'base64' ) } ` : null ;
71+
6072 const buffer = await file . arrayBuffer ( ) ;
6173 const dataUrl = `data:${ file . type } ;base64,${ Buffer . from ( buffer ) . toString ( 'base64' ) } ` ;
6274
6375 const messages : CoreMessage [ ] = [ ...( aiState . get ( ) . messages as any [ ] ) ] . filter (
64- message =>
76+ ( message : any ) =>
6577 message . role !== 'tool' &&
6678 message . type !== 'followup' &&
6779 message . type !== 'related' &&
@@ -89,7 +101,7 @@ async function submit(formData?: FormData, skip?: boolean) {
89101
90102 async function processResolutionSearch ( ) {
91103 try {
92- const streamResult = await resolutionSearch ( messages , timezone , drawnFeatures ) ;
104+ const streamResult = await resolutionSearch ( messages , timezone , drawnFeatures , location ) ;
93105
94106 let fullSummary = '' ;
95107 for await ( const partialObject of streamResult . partialObjectStream ) {
@@ -113,7 +125,7 @@ async function submit(formData?: FormData, skip?: boolean) {
113125
114126 messages . push ( { role : 'assistant' , content : analysisResult . summary || 'Analysis complete.' } ) ;
115127
116- const sanitizedMessages : CoreMessage [ ] = messages . map ( m => {
128+ const sanitizedMessages : CoreMessage [ ] = messages . map ( ( m : any ) => {
117129 if ( Array . isArray ( m . content ) ) {
118130 return {
119131 ...m ,
@@ -124,7 +136,7 @@ async function submit(formData?: FormData, skip?: boolean) {
124136 } )
125137
126138 const currentMessages = aiState . get ( ) . messages ;
127- const sanitizedHistory = currentMessages . map ( m => {
139+ const sanitizedHistory = currentMessages . map ( ( m : any ) => {
128140 if ( m . role === "user" && Array . isArray ( m . content ) ) {
129141 return {
130142 ...m ,
@@ -159,7 +171,9 @@ async function submit(formData?: FormData, skip?: boolean) {
159171 role : 'assistant' ,
160172 content : JSON . stringify ( {
161173 ...analysisResult ,
162- image : dataUrl
174+ image : dataUrl ,
175+ mapboxImage : mapboxDataUrl ,
176+ googleImage : googleDataUrl
163177 } ) ,
164178 type : 'resolution_search_result'
165179 } ,
@@ -190,7 +204,11 @@ async function submit(formData?: FormData, skip?: boolean) {
190204
191205 uiStream . update (
192206 < Section title = "response" >
193- < ResolutionImage src = { dataUrl } />
207+ < ResolutionCarousel
208+ mapboxImage = { mapboxDataUrl || undefined }
209+ googleImage = { googleDataUrl || undefined }
210+ initialImage = { dataUrl }
211+ />
194212 < BotMessage content = { summaryStream . value } />
195213 </ Section >
196214 ) ;
@@ -203,43 +221,20 @@ async function submit(formData?: FormData, skip?: boolean) {
203221 } ;
204222 }
205223
206- const messages : CoreMessage [ ] = [ ...( aiState . get ( ) . messages as any [ ] ) ] . filter (
207- message =>
208- message . role !== 'tool' &&
209- message . type !== 'followup' &&
210- message . type !== 'related' &&
211- message . type !== 'end' &&
212- message . type !== 'resolution_search_result'
213- ) . map ( m => {
214- if ( Array . isArray ( m . content ) ) {
215- return {
216- ...m ,
217- content : m . content . filter ( ( part : any ) =>
218- part . type !== "image" || ( typeof part . image === "string" && part . image . startsWith ( "data:" ) )
219- )
220- } as any
221- }
222- return m
223- } )
224-
225- const groupeId = nanoid ( )
226- const useSpecificAPI = process . env . USE_SPECIFIC_API_FOR_WRITER === 'true'
227- const maxMessages = useSpecificAPI ? 5 : 10
228- messages . splice ( 0 , Math . max ( messages . length - maxMessages , 0 ) )
229-
224+ const file = ! skip ? ( formData ?. get ( 'file' ) as File ) : undefined
230225 const userInput = skip
231226 ? `{"action": "skip"}`
232227 : ( ( formData ?. get ( 'related_query' ) as string ) ||
233228 ( formData ?. get ( 'input' ) as string ) )
234229
235- if ( userInput . toLowerCase ( ) . trim ( ) === 'what is a planet computer?' || userInput . toLowerCase ( ) . trim ( ) === 'what is qcx-terra?' ) {
230+ if ( userInput && ( userInput . toLowerCase ( ) . trim ( ) === 'what is a planet computer?' || userInput . toLowerCase ( ) . trim ( ) === 'what is qcx-terra?' ) ) {
236231 const definition = userInput . toLowerCase ( ) . trim ( ) === 'what is a planet computer?'
237232 ? `A planet computer is a proprietary environment aware system that interoperates weather forecasting, mapping and scheduling using cutting edge multi-agents to streamline automation and exploration on a planet. Available for our Pro and Enterprise customers. [QCX Pricing](https://www.queue.cx/#pricing)`
238-
239233 : `QCX-Terra is a model garden of pixel level precision geospatial foundational models for efficient land feature predictions from satellite imagery. Available for our Pro and Enterprise customers. [QCX Pricing] (https://www.queue.cx/#pricing)` ;
240234
241235 const content = JSON . stringify ( Object . fromEntries ( formData ! ) ) ;
242236 const type = 'input' ;
237+ const groupeId = nanoid ( ) ;
243238
244239 aiState . update ( {
245240 ...aiState . get ( ) ,
@@ -299,10 +294,9 @@ async function submit(formData?: FormData, skip?: boolean) {
299294 id : nanoid ( ) ,
300295 isGenerating : isGenerating . value ,
301296 component : uiStream . value ,
302- isCollapsed : isCollapsed . value ,
297+ isCollapsed : isCollapsed . value
303298 } ;
304299 }
305- const file = ! skip ? ( formData ?. get ( 'file' ) as File ) : undefined
306300
307301 if ( ! userInput && ! file ) {
308302 isGenerating . done ( false )
@@ -314,6 +308,30 @@ async function submit(formData?: FormData, skip?: boolean) {
314308 }
315309 }
316310
311+ const messages : CoreMessage [ ] = [ ...( aiState . get ( ) . messages as any [ ] ) ] . filter (
312+ ( message : any ) =>
313+ message . role !== 'tool' &&
314+ message . type !== 'followup' &&
315+ message . type !== 'related' &&
316+ message . type !== 'end' &&
317+ message . type !== 'resolution_search_result'
318+ ) . map ( ( m : any ) => {
319+ if ( Array . isArray ( m . content ) ) {
320+ return {
321+ ...m ,
322+ content : m . content . filter ( ( part : any ) =>
323+ part . type !== "image" || ( typeof part . image === "string" && part . image . startsWith ( "data:" ) )
324+ )
325+ } as any
326+ }
327+ return m
328+ } )
329+
330+ const groupeId = nanoid ( )
331+ const useSpecificAPI = process . env . USE_SPECIFIC_API_FOR_WRITER === 'true'
332+ const maxMessages = useSpecificAPI ? 5 : 10
333+ messages . splice ( 0 , Math . max ( messages . length - maxMessages , 0 ) )
334+
317335 const messageParts : {
318336 type : 'text' | 'image'
319337 text ?: string
@@ -725,12 +743,18 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => {
725743 const analysisResult = JSON . parse ( content as string ) ;
726744 const geoJson = analysisResult . geoJson as FeatureCollection ;
727745 const image = analysisResult . image as string ;
746+ const mapboxImage = analysisResult . mapboxImage as string ;
747+ const googleImage = analysisResult . googleImage as string ;
728748
729749 return {
730750 id,
731751 component : (
732752 < >
733- { image && < ResolutionImage src = { image } /> }
753+ < ResolutionCarousel
754+ mapboxImage = { mapboxImage }
755+ googleImage = { googleImage }
756+ initialImage = { image }
757+ />
734758 { geoJson && (
735759 < GeoJsonLayer id = { id } data = { geoJson } />
736760 ) }
0 commit comments