Skip to content
Closed
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6eeae28
Integrate billing popup, usage sidebar, and credit preview toggle
CJWTRUST Jan 20, 2026
160e082
Recover lost commit 488b47c and restore branch head
google-labs-jules[bot] Jan 28, 2026
ae085cf
Merge pull request #456 from QueueLab/jules-16847885310673003402-db9c…
ngoiyaeric Jan 28, 2026
73c385c
Restore branch history to 488b47c and integrate latest fixes
google-labs-jules[bot] Jan 28, 2026
1508c04
Restore branch history to 488b47c and resolve build errors
google-labs-jules[bot] Jan 28, 2026
c8c028b
Restore branch history, fix build errors, and re-enable pricing popup
google-labs-jules[bot] Jan 28, 2026
e2c0615
Finalize recovery with performance optimizations and UI fixes
google-labs-jules[bot] Jan 28, 2026
43179e7
Synchronize with main, restore history, and optimize performance
google-labs-jules[bot] Jan 29, 2026
7d45e02
Finalize recovery with prioritized main branch code and usage UI
google-labs-jules[bot] Jan 29, 2026
5623831
Refine usage UI and Lottie animation visibility
google-labs-jules[bot] Jan 29, 2026
44e86b6
Complete recovery, synchronization with main, and usage UI integration
google-labs-jules[bot] Jan 29, 2026
0a48018
feat: recover branch state, optimize resolution search, and fix UI st…
google-labs-jules[bot] Jan 29, 2026
6acfbe5
fix: resolve build error and ESLint warnings
google-labs-jules[bot] Jan 29, 2026
8ae549a
Update LICENSE
ngoiyaeric Feb 1, 2026
3495da1
Update LICENSE
ngoiyaeric Feb 1, 2026
ba36e58
Merge pull request #469 from QueueLab/ngoiyaeric-patch-4
ngoiyaeric Feb 1, 2026
6431b41
fix: reduce padding in mobile chat input area
google-labs-jules[bot] Feb 1, 2026
8a04d49
Merge pull request #470 from QueueLab/fix/mobile-chat-input-padding-1…
ngoiyaeric Feb 1, 2026
10ab3fe
feat: update Stripe checkout links to new URL
google-labs-jules[bot] Feb 1, 2026
250283e
Merge pull request #471 from QueueLab/update-stripe-links-17354207767…
ngoiyaeric Feb 1, 2026
6554775
fix: ensure long sentences wrap to the next line across UI components
google-labs-jules[bot] Feb 1, 2026
e95a25b
Merge pull request #473 from QueueLab/fix/text-overflow-wrapping-4739…
ngoiyaeric Feb 1, 2026
f80c87f
Resolve merge conflicts and synchronize with main
CJWTRUST Feb 2, 2026
c4278e9
feat: final stability and performance improvements
google-labs-jules[bot] Feb 2, 2026
813d264
Resolve merge conflicts and synchronize with main, preserving branch …
google-labs-jules[bot] Feb 2, 2026
c868dcd
Merge branch 'feature/billing-integration-recovery-648258468266820429…
ngoiyaeric Feb 2, 2026
3b5ed27
Resolve merge conflicts: synchronization with main while preserving b…
google-labs-jules[bot] Feb 2, 2026
3984b9b
Restore missing features from commit c4278e9: tenttree usage UI, popu…
CJWTRUST Feb 2, 2026
f45f687
Merge branch 'main' into jules-8488824498232079115-26d4e4cd
ngoiyaeric Feb 2, 2026
67c26d5
Merge pull request #476 from QueueLab/jules-8488824498232079115-26d4e4cd
ngoiyaeric Feb 2, 2026
a842df1
chore: update stripe payment links
google-labs-jules[bot] Feb 2, 2026
184f678
Merge pull request #477 from QueueLab/update-stripe-links-20260202-15…
ngoiyaeric Feb 2, 2026
895bf37
Update UsageView to yearly refresh model
google-labs-jules[bot] Feb 2, 2026
dc345b9
Update usage-view.tsx
ngoiyaeric Feb 2, 2026
86013ed
Update usage-view.tsx
ngoiyaeric Feb 2, 2026
2ba1f9e
Merge pull request #479 from QueueLab/update-usage-view-to-yearly-ref…
ngoiyaeric Feb 2, 2026
dda7a32
fix: standardize header icon spacing and remove unused portals
google-labs-jules[bot] Feb 2, 2026
23a1d3f
Merge pull request #480 from QueueLab/fix-header-icon-spacing-1628392…
ngoiyaeric Feb 2, 2026
885dbbe
feat: make zoom controls conditional on drawing mode
google-labs-jules[bot] Feb 2, 2026
dd812c1
Merge pull request #482 from QueueLab/conditional-zoom-controls-22256…
ngoiyaeric Feb 2, 2026
00c2e1a
Update usage-view.tsx
ngoiyaeric Feb 2, 2026
54d9d6e
Update usage-view.tsx
ngoiyaeric Feb 2, 2026
c9bd9b2
Merge pull request #484 from QueueLab/ngoiyaeric-patch-5
ngoiyaeric Feb 2, 2026
9ba4dfd
Merge commit f89e3f7: Resolution Search Enhancement
google-labs-jules[bot] Feb 4, 2026
b49f7ca
feat: restore multi-image support and improve drawing context in rese…
google-labs-jules[bot] Feb 4, 2026
8e37c34
feat: restore multi-image support and improve drawing context
google-labs-jules[bot] Feb 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added CC BY-NC 4.0.docx
Binary file not shown.
92 changes: 54 additions & 38 deletions app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,25 @@ 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' // Added getSystemPrompt
import { saveChat, getSystemPrompt } from '@/lib/actions/chat'
import { Chat, AIMessage } from '@/lib/types'
import { UserMessage } from '@/components/user-message'
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 { 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' // Add this import
import { MapQueryHandler } from '@/components/map/map-query-handler'

// 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'

Expand All @@ -43,16 +41,17 @@ 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);
}

Comment on lines 43 to 51
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drawnFeatures is parsed at the top-level of submit() even for actions that never need it. More importantly, the value is computed from client-provided JSON without any size/shape guardrails. A large payload (or maliciously large JSON) can increase server work and bloat logs/state.

Even if you trust the source, it’s cheap to add a minimal cap (e.g., max length of the raw string / max number of features) and only parse when needed (or when present).

Suggestion

Consider lazy-parsing drawnFeatures only when required (e.g., when action === 'resolution_search' or when you’re about to call researcher(...)), and add basic guardrails:

  • Reject/ignore if drawnFeaturesString.length exceeds a reasonable threshold (e.g., 50–200KB)
  • After parsing, clamp drawnFeatures.length (e.g., max 200) and drop oversized measurement strings

Example sketch:

const rawDrawn = (formData?.get('drawnFeatures') as string) ?? ''
const shouldParse = action === 'resolution_search' || rawDrawn
let drawnFeatures: DrawnFeature[] = []
if (shouldParse && rawDrawn && rawDrawn.length < 200_000) {
  try {
    const parsed = JSON.parse(rawDrawn)
    if (Array.isArray(parsed)) drawnFeatures = parsed.slice(0, 200)
  } catch {
    // ignore
  }
}

Reply with "@CharlieHelps yes please" if you’d like me to add a commit with this suggestion.

if (action === 'resolution_search') {
const file = 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);
}

if (!file) {
throw new Error('No file provided for resolution search.');
Expand All @@ -61,7 +60,6 @@ async function submit(formData?: FormData, skip?: boolean) {
const buffer = await file.arrayBuffer();
const dataUrl = `data:${file.type};base64,${Buffer.from(buffer).toString('base64')}`;

// Get the current messages, excluding tool-related ones.
const messages: CoreMessage[] = [...(aiState.get().messages as any[])].filter(
message =>
message.role !== 'tool' &&
Expand All @@ -71,16 +69,12 @@ 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.';

// Construct the multimodal content for the user message.
const content: CoreMessage['content'] = [
{ type: 'text', text: userInput },
{ type: 'image', image: dataUrl, mimeType: file.type }
];

// Add the new user message to the AI state.
aiState.update({
...aiState.get(),
messages: [
Expand All @@ -90,12 +84,11 @@ 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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider renaming groupeId to groupId for clarity.

The variable name groupeId appears to be a typo or non-standard spelling. Using groupId would align with common English naming conventions and improve readability.

✏️ Suggested rename
-    const groupeId = nanoid();
+    const groupId = nanoid();

Apply the same change at line 203 and update all references.

🤖 Prompt for AI Agents
In `@app/actions.tsx` at line 88, Rename the misspelled variable groupeId to
groupId throughout the file: change the declaration const groupeId = nanoid() to
const groupId = nanoid() and update every usage/reference (including the other
occurrence noted and any functions or JSX that consume it) to the new name to
avoid runtime undefined errors; ensure related imports/exports, state hooks, and
props that referenced groupeId are updated to groupId as well and run type
checks/compile to confirm no remaining references.


async function processResolutionSearch() {
try {
// Call the simplified agent, which now returns a stream.
const streamResult = await resolutionSearch(messages, timezone, drawnFeatures);

let fullSummary = '';
Expand All @@ -107,10 +100,17 @@ 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) {
uiStream.append(
<GeoJsonLayer
id={groupeId}
data={analysisResult.geoJson as FeatureCollection}
/>
);
}

messages.push({ role: 'assistant', content: analysisResult.summary || 'Analysis complete.' });

const sanitizedMessages: CoreMessage[] = messages.map(m => {
Expand All @@ -132,8 +132,6 @@ async function submit(formData?: FormData, skip?: boolean) {

await new Promise(resolve => setTimeout(resolve, 500));

const groupeId = nanoid();

aiState.done({
...aiState.get(),
messages: [
Expand All @@ -147,7 +145,10 @@ async function submit(formData?: FormData, skip?: boolean) {
{
id: groupeId,
role: 'assistant',
content: JSON.stringify(analysisResult),
content: JSON.stringify({
...analysisResult,
image: dataUrl
}),
Comment on lines 188 to 194
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re storing the full dataUrl (base64 image) into AI state (resolution_search_result). That can massively inflate persisted chat state, slow down UI state hydration, and potentially exceed storage limits depending on your saveChat implementation.

This is particularly risky because static map screenshots can be large, and every resolution search will permanently embed the image bytes into chat history.

Suggestion

Avoid persisting base64 data URLs in chat history. Prefer storing:

  • a short-lived object storage URL (S3/GCS/R2) for the captured image, or
  • a server-generated cache key referencing stored bytes, or
  • omit the image from persisted state and rely on the already-rendered UI stream for the immediate session.

Minimal improvement if you can’t add storage yet: store only the original capture parameters (provider, center/zoom/bounds) and re-fetch on render.

Reply with "@CharlieHelps yes please" if you’d like me to add a commit with this suggestion.

Comment on lines +191 to +194
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Double JSON.stringify creates nested string encoding.

The image data is stringified twice: once at line 173 and again as part of the outer JSON.stringify at line 171. While the read-side (lines 732-739) handles this with a try/catch fallback, consider storing the image object directly to simplify parsing:

♻️ Alternative approach
             content: JSON.stringify({
               ...analysisResult,
-              image: JSON.stringify({ mapbox: mapboxDataUrl, google: googleDataUrl })
+              image: { mapbox: mapboxDataUrl, google: googleDataUrl }
             }),

Then update the read side:

-              const imageData = analysisResult.image as string;
+              const imageData = analysisResult.image;
               let mapboxSrc = '';
               let googleSrc = '';

               if (imageData) {
-                try {
-                  const parsed = JSON.parse(imageData);
-                  mapboxSrc = parsed.mapbox || '';
-                  googleSrc = parsed.google || '';
-                } catch (e) {
-                  mapboxSrc = imageData;
-                }
+                if (typeof imageData === 'string') {
+                  // Legacy format fallback
+                  mapboxSrc = imageData;
+                } else {
+                  mapboxSrc = imageData.mapbox || '';
+                  googleSrc = imageData.google || '';
+                }
               }
🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 171 - 174, The image field is being
JSON.stringified twice causing nested string encoding; update the payload
construction in app/actions.tsx where you build content (the object that spreads
analysisResult) so that image is stored as an object (image: { mapbox:
mapboxDataUrl, google: googleDataUrl }) instead of JSON.stringify(...), and then
update the read-side parsing logic that inspects analysisResult.image (the code
around the current try/catch at lines ~732-739) to expect and handle an object
directly (remove the extra JSON.parse attempt or fall back only if a string is
encountered).

type: 'resolution_search_result'
},
{
Expand All @@ -173,12 +174,11 @@ async function submit(formData?: FormData, skip?: boolean) {
}
Comment on lines 211 to 216
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing isGenerating.done(false) and uiStream.done() on success path.

In the processResolutionSearch function, the catch block calls isGenerating.done(false) and uiStream.done(), but the success path (after aiState.done()) does not. This could leave streams in an incomplete state.

🐛 Proposed fix
             }
           ]
         })
+        isGenerating.done(false)
+        uiStream.done()
       } catch (error) {
         console.error('Failed to process resolution search:', error);
         summaryStream.done('An error occurred during analysis.');
         isGenerating.done(false);
         uiStream.done();
       }
🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 191 - 196, The success path in
processResolutionSearch currently calls aiState.done() but doesn't call
isGenerating.done(false) and uiStream.done(), leaving streams incomplete; update
the success branch (after aiState.done()) to also call isGenerating.done(false)
and uiStream.done() and ensure summaryStream.done(...) is invoked consistently
(mirror the catch block cleanup) so all three streams (isGenerating, uiStream,
summaryStream) are completed on both success and error.

}

// 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} />
<BotMessage content={summaryStream.value} />
</Section>
);
Expand Down Expand Up @@ -243,7 +243,6 @@ async function submit(formData?: FormData, skip?: boolean) {

uiStream.append(answerSection);

const groupeId = nanoid();
const relatedQueries = { items: [] };

aiState.done({
Expand Down Expand Up @@ -327,7 +326,6 @@ 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')
Expand Down Expand Up @@ -361,7 +359,6 @@ 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() {
Expand Down Expand Up @@ -410,7 +407,8 @@ async function submit(formData?: FormData, skip?: boolean) {
streamText,
messages,
mapProvider,
useSpecificAPI
useSpecificAPI,
drawnFeatures
)
answer = fullResponse
toolOutputs = toolResponses
Expand Down Expand Up @@ -643,12 +641,10 @@ 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 {
Expand All @@ -669,8 +665,8 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => {
}
break
case 'assistant':
const answer = createStreamableValue()
answer.done(content)
const answer = createStreamableValue(content as string)
answer.done(content as string)
switch (type) {
case 'response':
return {
Expand All @@ -682,7 +678,9 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => {
)
}
case 'related':
const relatedQueries = createStreamableValue<RelatedQueries>()
const relatedQueries = createStreamableValue<RelatedQueries>({
items: []
})
relatedQueries.done(JSON.parse(content as string))
return {
id,
Expand All @@ -704,11 +702,13 @@ 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;

return {
id,
component: (
Comment on lines 771 to 774
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getUIStateFromAIState directly JSON.parses resolution_search_result without a try/catch. If older messages exist (or partial/corrupt content), this will throw during render and can break the entire chat UI.

You already handle backward compatibility in the user message case; this should get similar protection.

Suggestion

Wrap parsing with a guard and fail soft:

case 'resolution_search_result': {
  let analysisResult: any
  try {
    analysisResult = JSON.parse(content as string)
  } catch {
    return { id, component: null }
  }
  const geoJson = analysisResult.geoJson as FeatureCollection
  const image = analysisResult.image as string
  // ...
}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

<>
{image && <ResolutionImage src={image} />}
{geoJson && (
<GeoJsonLayer id={id} data={geoJson} />
)}
Expand All @@ -721,21 +721,37 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => {
case 'tool':
try {
const toolOutput = JSON.parse(content as string)
const isCollapsed = createStreamableValue()
const isCollapsed = createStreamableValue(true)
isCollapsed.done(true)

if (
toolOutput.type === 'MAP_QUERY_TRIGGER' &&
name === 'geospatialQueryTool'
) {
const mapUrl = toolOutput.mcp_response?.mapUrl;
const placeName = toolOutput.mcp_response?.location?.place_name;

return {
id,
component: <MapQueryHandler toolOutput={toolOutput} />,
component: (
<>
{mapUrl && (
<ResolutionImage
src={mapUrl}
className="mb-0"
alt={placeName ? `Map of ${placeName}` : 'Map Preview'}
/>
)}
<MapQueryHandler toolOutput={toolOutput} />
</>
),
isCollapsed: false
}
}

const searchResults = createStreamableValue()
const searchResults = createStreamableValue(
JSON.stringify(toolOutput)
)
searchResults.done(JSON.stringify(toolOutput))
switch (name) {
case 'search':
Expand Down
52 changes: 30 additions & 22 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import { SpeedInsights } from "@vercel/speed-insights/next"
import { Toaster } from '@/components/ui/sonner'
import { MapToggleProvider } from '@/components/map-toggle-context'
import { ProfileToggleProvider } from '@/components/profile-toggle-context'
import { UsageToggleProvider } from '@/components/usage-toggle-context'
import { CalendarToggleProvider } from '@/components/calendar-toggle-context'
import { HistoryToggleProvider } from '@/components/history-toggle-context'
import { HistorySidebar } from '@/components/history-sidebar'
import { MapLoadingProvider } from '@/components/map-loading-context';
import ConditionalLottie from '@/components/conditional-lottie';
import { MapProvider as MapContextProvider } from '@/components/map/map-context'
Expand Down Expand Up @@ -70,28 +73,33 @@ export default function RootLayout({
)}
>
<CalendarToggleProvider>
<MapToggleProvider>
<ProfileToggleProvider>
<ThemeProvider
attribute="class"
defaultTheme="earth"
enableSystem
disableTransitionOnChange
themes={['light', 'dark', 'earth']}
>
<MapContextProvider>
<MapLoadingProvider>
<Header />
<ConditionalLottie />
{children}
<Sidebar />
<Footer />
<Toaster />
</MapLoadingProvider>
</MapContextProvider>
</ThemeProvider>
</ProfileToggleProvider>
</MapToggleProvider>
<HistoryToggleProvider>
<MapToggleProvider>
<ProfileToggleProvider>
<UsageToggleProvider>
<ThemeProvider
attribute="class"
defaultTheme="earth"
enableSystem
disableTransitionOnChange
themes={['light', 'dark', 'earth']}
>
<MapContextProvider>
<MapLoadingProvider>
<Header />
<ConditionalLottie />
{children}
<Sidebar />
<HistorySidebar />
<Footer />
<Toaster />
</MapLoadingProvider>
</MapContextProvider>
</ThemeProvider>
</UsageToggleProvider>
</ProfileToggleProvider>
</MapToggleProvider>
</HistoryToggleProvider>
</CalendarToggleProvider>
<Analytics />
<SpeedInsights />
Expand Down
8 changes: 4 additions & 4 deletions components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { useEffect, useState, useRef, ChangeEvent, forwardRef, useImperativeHandle, useCallback } from 'react'
import type { AI, UIState } from '@/app/actions'
import { useUIState, useActions, readStreamableValue } from 'ai/rsc'
// Removed import of useGeospatialToolMcp as it's no longer used/available
import { cn } from '@/lib/utils'
import { UserMessage } from './user-message'
import { Button } from './ui/button'
Expand Down Expand Up @@ -31,7 +30,6 @@ export interface ChatPanelRef {
export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, input, setInput, onSuggestionsChange }, ref) => {
const [, setMessages] = useUIState<typeof AI>()
const { submit, clearChat } = useActions()
// Removed mcp instance as it's no longer passed to submit
const { mapProvider } = useSettingsStore()
const [isMobile, setIsMobile] = useState(false)
const [selectedFile, setSelectedFile] = useState<File | null>(null)
Expand Down Expand Up @@ -117,6 +115,9 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
formData.append('file', selectedFile)
}

// Include drawn features in the form data
formData.append('drawnFeatures', JSON.stringify(mapData.drawnFeatures || []))

setInput('')
clearAttachment()

Expand Down Expand Up @@ -153,7 +154,7 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
}
}, 500) // 500ms debounce delay
},
[mapData]
[mapData, setSuggestions]
)

useEffect(() => {
Expand Down Expand Up @@ -288,7 +289,6 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
>
<ArrowRight size={isMobile ? 18 : 20} />
</Button>
{/* Suggestions are now handled by the parent component (chat.tsx) as an overlay */}
</div>
</form>
{selectedFile && (
Expand Down
Loading