Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 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
1b56819
feat: implement GeoJSON upload and tool ingestion pipeline
google-labs-jules[bot] Feb 1, 2026
9f79929
feat: implement GeoJSON upload and tool ingestion pipeline
google-labs-jules[bot] Feb 1, 2026
6554775
fix: ensure long sentences wrap to the next line across UI components
google-labs-jules[bot] Feb 1, 2026
bfe62f9
feat: support pasted GeoJSON and fix refresh loop
google-labs-jules[bot] Feb 1, 2026
9450b15
feat: enable GeoJSON uploads, paste support, and fix refresh loop
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
85a85c5
Merge branch 'main' into feature/geojson-upload-pipeline-111249932693…
ngoiyaeric Feb 3, 2026
07f633d
feat: synchronize with main and fix refresh ReferenceError in Chat co…
google-labs-jules[bot] Feb 3, 2026
8fb9809
Merge origin/main and resolve conflicts
ngoiyaeric Feb 5, 2026
6289c7f
chore: update bun.lock after synchronization
ngoiyaeric Feb 8, 2026
209f9be
feat: implement autonomous map navigation for address inputs
ngoiyaeric Feb 15, 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
145 changes: 144 additions & 1 deletion app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ 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 { MapDataUpdater } from '@/components/map/map-data-updater'
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 { geospatialTool } from '@/lib/agents/tools/geospatial'

// Define the type for related queries
type RelatedQueries = {
Expand Down Expand Up @@ -232,6 +234,37 @@ async function submit(formData?: FormData, skip?: boolean) {
: ((formData?.get('related_query') as string) ||
(formData?.get('input') as string))

let isGeoJsonInput = false
if (userInput) {
try {
const trimmedInput = userInput.trim()
if ((trimmedInput.startsWith('{') && trimmedInput.endsWith('}')) || (trimmedInput.startsWith('[') && trimmedInput.endsWith(']'))) {
const geoJson = JSON.parse(trimmedInput)
if (geoJson.type === 'FeatureCollection' || geoJson.type === 'Feature') {
isGeoJsonInput = true
const geoJsonId = nanoid()
aiState.update({
...aiState.get(),
messages: [
...aiState.get().messages,
{
id: geoJsonId,
role: 'assistant',
content: JSON.stringify({ data: geoJson, filename: 'Pasted GeoJSON' }),
type: 'geojson_upload'
}
]
})
Comment on lines +246 to +257
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 | 🟡 Minor

GeoJSON pasted by the user is stored with role: 'assistant' — incorrect attribution.

When a user pastes GeoJSON text, the message is added to AI state with role: 'assistant' (line 252). This misattributes user input to the assistant, which could confuse downstream logic that relies on role for conversation flow.

Consider using role: 'user' with type: 'geojson_upload', or a role: 'data' to keep it neutral.

🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 246 - 257, The pasted GeoJSON message is being
recorded with role: 'assistant' — update the aiState update so the pushed
message object (the one with id: geoJsonId, content: JSON.stringify({ data:
geoJson, filename: 'Pasted GeoJSON' }), type: 'geojson_upload') uses the correct
role (e.g., 'user' or a neutral 'data') instead of 'assistant'; modify the
aiState.update call that appends to messages to set role: 'user' (or 'data') so
downstream logic reads proper attribution.

uiStream.append(
<MapDataUpdater id={geoJsonId} data={geoJson} filename="Pasted GeoJSON" />
)
}
}
} catch (e) {
// Not a valid JSON, ignore
}
}

if (userInput.toLowerCase().trim() === 'what is a planet computer?' || userInput.toLowerCase().trim() === 'what is qcx-terra?') {
const definition = userInput.toLowerCase().trim() === 'what is a planet computer?'
? `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)`
Expand Down Expand Up @@ -322,6 +355,8 @@ async function submit(formData?: FormData, skip?: boolean) {
}[] = []

if (userInput) {
// If it's a GeoJSON input, we still want to keep it in the message history for the AI to see,
// but we might want to truncate it if it's huge. For now, just pass it.
messageParts.push({ type: 'text', text: userInput })
}

Expand All @@ -336,8 +371,39 @@ async function submit(formData?: FormData, skip?: boolean) {
image: dataUrl,
mimeType: file.type
})
} else if (file.type === 'text/plain') {
} else if (file.type === 'text/plain' || file.name.endsWith('.geojson') || file.type === 'application/geo+json') {
const textContent = Buffer.from(buffer).toString('utf-8')
const isGeoJson = file.name.endsWith('.geojson') || file.type === 'application/geo+json'

if (isGeoJson) {
try {
const geoJson = JSON.parse(textContent)
if (geoJson.type === 'FeatureCollection' || geoJson.type === 'Feature') {
const geoJsonId = nanoid()
// Add a special message to track the GeoJSON upload
aiState.update({
...aiState.get(),
messages: [
...aiState.get().messages,
{
id: geoJsonId,
role: 'assistant',
content: JSON.stringify({ data: geoJson, filename: file.name }),
type: 'geojson_upload'
}
]
})
Comment on lines +384 to +395
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Duplicate GeoJSON detection logic between paste (lines 238–266) and file upload (lines 378–405).

The GeoJSON parsing, AI state update, and MapDataUpdater append logic is repeated nearly identically in both code paths. Extract a shared helper to reduce duplication and ensure consistent behavior.

🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 384 - 395, The paste and file-upload branches
duplicate GeoJSON parsing, duplicate detection, aiState.update (building the
messages entry with id = geoJsonId, role 'assistant', content JSON.stringify({
data: geoJson, filename }), type 'geojson_upload') and the MapDataUpdater
append/commit steps; extract a shared helper (e.g. processGeoJsonPayload or
handleGeoJsonUpload) that accepts the parsed geoJson, filename and id and
performs the duplicate check, updates aiState via aiState.update with the same
message shape, and calls the MapDataUpdater append/commit logic, then call that
helper from both the paste handler and the file upload handler to remove the
duplicated code and ensure consistent behavior.


// Immediately append the updater to the UI stream
uiStream.append(
<MapDataUpdater id={geoJsonId} data={geoJson} filename={file.name} />
)
}
} catch (e) {
console.error('Failed to parse GeoJSON:', e)
}
}

const existingTextPart = messageParts.find(p => p.type === 'text')
if (existingTextPart) {
existingTextPart.text = `${textContent}\n\n${existingTextPart.text}`
Expand Down Expand Up @@ -383,6 +449,47 @@ async function submit(formData?: FormData, skip?: boolean) {
const currentSystemPrompt = (await getSystemPrompt(userId)) || ''
const mapProvider = formData?.get('mapProvider') as 'mapbox' | 'google'

// Autonomous Map Navigation: Check if input looks like an address
const isPotentialAddress = (text: string) => {
// Simple heuristic: contains numbers and multiple words, or specific keywords
const addressKeywords = ['palace', 'street', 'st', 'avenue', 'ave', 'road', 'rd', 'boulevard', 'blvd', 'drive', 'dr', 'lane', 'ln', 'court', 'ct', 'square', 'sq', 'parkway', 'pkwy'];
const words = text.toLowerCase().split(/\s+/);
const hasNumber = /\d+/.test(text);
const hasKeyword = words.some(word => addressKeywords.includes(word));
const isCoordinate = /^-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?$/.test(text.trim());

return (hasNumber && words.length >= 2) || hasKeyword || isCoordinate;
};
Comment on lines +452 to +462
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

isPotentialAddress heuristic is too broad — will trigger on many non-address inputs.

The condition hasNumber && words.length >= 2 matches inputs like "buy 3 apples", "top 10 movies", "React 19 hooks". The keyword 'dr' matches "Dr. Smith". This will cause spurious geocoding calls on a significant portion of normal chat messages, wasting API quota and injecting unwanted map UI.

Consider tightening the heuristic — e.g., require both a number and a keyword, or use a more specific address regex pattern, or require a minimum of 3+ words when relying on the number heuristic alone.

Suggested tightening
-   return (hasNumber && words.length >= 2) || hasKeyword || isCoordinate;
+   return (hasNumber && hasKeyword) || isCoordinate;

This still won't be perfect but drastically reduces false positives.

🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 452 - 462, The isPotentialAddress heuristic is
too permissive; update the logic in isPotentialAddress to reduce false positives
by tightening the checks: require either isCoordinate OR (hasKeyword &&
(hasNumber || words.length >= 3)) OR (hasNumber && words.length >= 3) instead of
the current (hasNumber && words.length >= 2) rule, and refine addressKeywords
(e.g., treat short tokens like 'dr' as a keyword only when followed/preceded by
punctuation or longer context) so words.some(word =>
addressKeywords.includes(word)) is less likely to match titles like "Dr. Smith";
adjust the return expression accordingly using the existing symbols
addressKeywords, words, hasNumber, hasKeyword, and isCoordinate.


if (userInput && isPotentialAddress(userInput)) {
console.log('[AutonomousMap] Detected potential address:', userInput);
// Trigger geospatial tool directly for immediate map update
const geoTool = geospatialTool({ uiStream, mapProvider });

// Run geocoding in the background or wait briefly
// We don't await it here to avoid blocking the main AI response,
// but we want it to start immediately.
(async () => {
try {
const isCoordinate = /^-?\d+(\.\d+)?,\s*-?\d+(\.\d+)?$/.test(userInput.trim());
const result = await geoTool.execute(
isCoordinate
? { queryType: 'reverse', coordinates: {
latitude: parseFloat(userInput.split(',')[0]),
longitude: parseFloat(userInput.split(',')[1])
} }
: { queryType: 'geocode', location: userInput }
);

if (result && result.type === 'MAP_QUERY_TRIGGER') {
uiStream.append(<MapQueryHandler toolOutput={result} />);
}
} catch (error) {
console.error('[AutonomousMap] Quick geocode failed:', error);
}
})();
}
Comment on lines +472 to +491
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 | 🔴 Critical

Fire-and-forget async geocoding races with uiStream.done() — will throw on closed stream.

The geocoding IIFE runs concurrently with processEvents() (line 629). When processEvents finishes, it calls uiStream.done() (line 626). If the geocoding promise resolves after that, uiStream.append(...) on line 485 will throw because the stream is already closed.

This is a race condition that will surface as an unhandled runtime error, potentially crashing the request or silently dropping the map result.

Proposed fix: coordinate the geocoding with processEvents

Store the geocoding promise and await it (or handle its completion) before uiStream.done():

+ let autonomousMapPromise: Promise<void> | null = null;
+
  if (userInput && isPotentialAddress(userInput)) {
    console.log('[AutonomousMap] Detected potential address:', userInput);
    const geoTool = geospatialTool({ uiStream, mapProvider });
    
-   (async () => {
+   autonomousMapPromise = (async () => {
      try {
        // ... geocoding logic ...
      } catch (error) {
        console.error('[AutonomousMap] Quick geocode failed:', error);
      }
    })();
  }

Then inside processEvents, before closing the stream:

+ if (autonomousMapPromise) await autonomousMapPromise;
  isGenerating.done(false)
  uiStream.done()
🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 472 - 491, The async IIFE that calls
geoTool.execute(...) and then uiStream.append(MapQueryHandler) can complete
after processEvents() calls uiStream.done(), causing uiStream.append to throw;
capture the geocoding promise (e.g., const geocodePromise =
geoTool.execute(...)) instead of fire-and-forget and ensure processEvents awaits
or coordinates with that promise before calling uiStream.done(); update the code
paths referencing geoTool.execute, the IIFE, uiStream.append and processEvents
so the geocodePromise is awaited (or its resolution handled with safe-checks)
and errors are caught so append is never invoked on a closed stream.


Comment on lines +452 to +492

Choose a reason for hiding this comment

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

Autonomous geocoding is triggered for any input matching the heuristic, including coordinates, but the heuristic is extremely permissive (e.g., any message containing a number and two words; keywords like st will match many non-address sentences). This will cause lots of unintended geocoding calls and UI side effects.

Additionally:

  • It will also trigger when the user pastes GeoJSON (which commonly includes numbers/keywords), unless the heuristic is explicitly gated.
  • The background task uses console.log/error but doesn’t guard against uiStream lifecycle (e.g., user navigates away) and may append late handlers.
  • You compute isCoordinate twice using the same regex; the parsing uses split(',') without trimming, which is brittle for inputs like "12.3, -45.6".

Net: this feature risks noisy calls, rate limiting, and surprising UI updates.

Suggestion

Tighten and gate the autonomous navigation:

  • Skip auto-geocode when the input is detected as GeoJSON (pasted or uploaded).
  • Use a stricter address heuristic (e.g., require ≥3 words and either a street-type keyword or a postal code/city pattern) and add a minimum length.
  • De-duplicate coordinate parsing:
    • Parse once with a single regex capture and .trim() the parts.
  • Consider debouncing/limiting triggers (e.g., only for direct user input, not related_query; and only once per message id).

If you want, I can add a commit that:

  1. adds a shouldAutonav guard (not GeoJSON, not too short, not tool actions),
  2. centralizes coordinate parse,
  3. reduces false positives.

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

async function processEvents() {
let action: any = { object: { next: 'proceed' } }
if (!skip) {
Expand Down Expand Up @@ -644,10 +751,19 @@ export const AI = createAI<AIState, UIState>({
export const getUIStateFromAIState = (aiState: AIState): UIState => {
const chatId = aiState.chatId
const isSharePage = aiState.isSharePage

// Filter messages to only include the last 'data' message if multiple exist
const lastDataMessageIndex = [...aiState.messages].reverse().findIndex(m => m.role === 'data')
const actualLastDataIndex = lastDataMessageIndex === -1 ? -1 : aiState.messages.length - 1 - lastDataMessageIndex

return aiState.messages
.map((message, index) => {
const { role, content, id, type, name } = message

if (role === 'data' && index !== actualLastDataIndex) {
return null
}
Comment on lines 759 to +765
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 | 🟡 Minor

map() callback does not return a value for all code paths (confirmed by static analysis).

When role is 'user' or 'assistant' and type doesn't match any of the handled cases in the inner switch, execution falls through to break without returning. This produces undefined elements in the array, which are then filtered out — but it's fragile and triggers the Biome lint error.

Add explicit return null after each break:

Proposed fix
           }
           break
+        return null
         case 'assistant':
           }
           break
+        return null
         case 'tool':

Or better yet, refactor so each case always returns:

         case 'user':
-          switch (type) {
+          return (() => { switch (type) {
             // ...
-          }
-          break
+          default: return null
+          } })()

Also applies to: 803-804, 864-865

🧰 Tools
🪛 Biome (2.3.14)

[error] 760-760: This callback passed to map() iterable method should always return a value.

Add missing return statements so that this callback returns a value on all execution paths.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 759 - 765, The map callback over
aiState.messages (inside the function that iterates message -> { const { role,
content, id, type, name } = message }) currently has code paths that hit a
switch on type for role 'user'/'assistant' and execute a break without
returning, causing undefined entries; update each switch case and the default
branch so they always return a value (e.g., return null for unhandled types)
rather than just breaking, or refactor the case bodies to return the mapped
JSX/value directly; ensure every branch in the map callback returns (including
after the initial if (role === 'data' && index !== actualLastDataIndex') path)
to satisfy the linter.


if (
!type ||
type === 'end' ||
Expand Down Expand Up @@ -738,6 +854,13 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => {
)
}
}
case 'geojson_upload': {
const { data, filename } = JSON.parse(content as string)
return {
id,
component: <MapDataUpdater id={id} data={data} filename={filename} />
}
}
Comment on lines +857 to +863

Choose a reason for hiding this comment

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

This adds a new geojson_upload case that does JSON.parse(content as string) without any try/catch. A single malformed message content (from older DB rows, partial writes, or tool output changes) will throw and break the entire UI state mapping for the chat.

You already wrap parsing in other branches (e.g. role: 'tool', role: 'data'). This one should be equally defensive.

Suggestion

Wrap the geojson_upload parsing in a try/catch and return { id, component: null } on failure (optionally console.error with message id).

case 'geojson_upload': {
  try {
    const { data, filename } = JSON.parse(content as string)
    return { id, component: <MapDataUpdater id={id} data={data} filename={filename} /> }
  } catch (e) {
    console.error('Error parsing geojson_upload message:', id, e)
    return { id, component: null }
  }
}

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

}
break
case 'tool':
Expand Down Expand Up @@ -813,6 +936,26 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => {
}
}
break
case 'data':
try {
const contextData = JSON.parse(content as string)
if (contextData.uploadedGeoJson && Array.isArray(contextData.uploadedGeoJson)) {
return {
id,
component: (
<>
{contextData.uploadedGeoJson.map((item: any) => (
<MapDataUpdater key={item.id} id={item.id} data={item.data} filename={item.filename} />
))}
</>
)
}
}
return { id, component: null }
} catch (e) {
console.error('Error parsing data message:', e)
return { id, component: null }
}
default:
return {
id,
Expand Down
9 changes: 3 additions & 6 deletions app/search/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,11 @@ export default async function SearchPage({ params }: SearchPageProps) {
const initialMessages: AIMessage[] = dbMessages.map((dbMsg): AIMessage => {
return {
id: dbMsg.id,
role: dbMsg.role as AIMessage['role'], // Cast role, ensure AIMessage['role'] includes all dbMsg.role possibilities
role: dbMsg.role as AIMessage['role'],
content: dbMsg.content,
createdAt: dbMsg.createdAt ? new Date(dbMsg.createdAt) : undefined,
// 'type' and 'name' are not in the basic Drizzle 'messages' schema.
// These would be undefined unless specific logic is added to derive them.
// For instance, if a message with role 'tool' should have a 'name',
// or if some messages have a specific 'type' based on content or other flags.
// This mapping assumes standard user/assistant messages primarily.
type: dbMsg.type as AIMessage['type'],
name: dbMsg.toolName as string,
Comment on lines +54 to +55
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 | 🟡 Minor

Unsafe cast: dbMsg.toolName can be null, but is cast to string.

The toolName column is nullable in the schema. Casting null as string silently bypasses TypeScript, but downstream code checking typeof msg.name === 'string' will get true for what is actually null at runtime… wait, no — typeof null is 'object', so the cast only lies to TS. The real issue is that AIMessage.name is string | undefined, but you're assigning null disguised as string.

Proposed fix
-      type: dbMsg.type as AIMessage['type'],
-      name: dbMsg.toolName as string,
+      type: (dbMsg.type ?? undefined) as AIMessage['type'],
+      name: dbMsg.toolName ?? undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type: dbMsg.type as AIMessage['type'],
name: dbMsg.toolName as string,
type: (dbMsg.type ?? undefined) as AIMessage['type'],
name: dbMsg.toolName ?? undefined,
🤖 Prompt for AI Agents
In `@app/search/`[id]/page.tsx around lines 54 - 55, The assignment unsafely casts
nullable dbMsg.toolName to string; instead, check dbMsg.toolName and only set
the AIMessage.name when it's a real string (e.g. name: dbMsg.toolName !== null ?
dbMsg.toolName : undefined), or omit the name property when null so runtime
typeof checks match TypeScript's types; update the object creation where you set
type: dbMsg.type as AIMessage['type'] and name: dbMsg.toolName to use a
conditional/nullable-safe mapping (and adjust any downstream code expecting
string vs undefined accordingly).

};
});

Expand Down
11 changes: 6 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
ref={fileInputRef}
onChange={handleFileChange}
className="hidden"
accept="text/plain,image/png,image/jpeg,image/webp"
accept="text/plain,image/png,image/jpeg,image/webp,.geojson,application/geo+json"
data-testid="file-upload-input"
Comment on lines +218 to +219
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

GeoJSON files accepted but not validated or previewed distinctly.

The accept attribute now allows .geojson files, and the file will be appended to formData (Line 114-116). However, the content construction in handleSubmit (Line 98-103) only handles image/* types — a GeoJSON file will be silently excluded from the user message content parts. The user sees the filename in the attachment bar, but there's no explicit feedback that it's a GeoJSON upload being processed differently.

Consider adding a content part type for GeoJSON files (or at minimum a text indicator) so the user message reflects that a GeoJSON was attached. Also, the handleFileChange (Line 66-75) only validates size — you may want to add basic validation (e.g., JSON parse check) for .geojson files to fail fast on corrupt uploads.

🤖 Prompt for AI Agents
In `@components/chat-panel.tsx` around lines 218 - 219, handleSubmit currently
only creates content parts for image/* and omits .geojson attachments, and
handleFileChange only checks size; update handleSubmit to detect files with type
'application/geo+json' or filename ending with .geojson and append a content
part (e.g., {type: 'geojson', filename}) to the message payload so the user
message reflects a GeoJSON was attached, and update handleFileChange to perform
a fast validation for .geojson files by reading the file as text and attempting
JSON.parse (rejecting and showing an error/toast if parse fails) before
appending to formData; reference the formData usage, handleSubmit, and
handleFileChange functions and ensure the attachment bar/preview UI is updated
to show a GeoJSON indicator or simple text preview if parse succeeds.

/>
{!isMobile && (
<Button
Expand Down
Loading