Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 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
8c7120d
feat: add generative graphs UI and data analysis tool
google-labs-jules[bot] Jan 31, 2026
38704ca
Merge branch 'main' into feature/generative-graphs-ui-data-preview-17…
ngoiyaeric Jan 31, 2026
37d772b
feat: improve dataAnalysis tool triggering and documentation
google-labs-jules[bot] Jan 31, 2026
fdc7c5e
fix: resolve runtime error in GraphSection and improve stability
google-labs-jules[bot] Jan 31, 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
c6a615c
Merge branch 'main' into feature/generative-graphs-ui-data-preview-17…
ngoiyaeric Feb 3, 2026
c5aae97
fix: match graph coloring to platform theme and fix X-axis visibility
ngoiyaeric Feb 3, 2026
7fa9897
fix: resolve missing useEffect dependencies in Mapbox component
ngoiyaeric Feb 3, 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.
71 changes: 44 additions & 27 deletions app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { FeatureCollection } from 'geojson'
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'
import { inquire, researcher, taskManager, querySuggestor, resolutionSearch } 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'
Expand All @@ -27,6 +27,7 @@ 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 { GraphSection } from '@/components/graph-section'

// Define the type for related queries
type RelatedQueries = {
Expand All @@ -46,14 +47,6 @@ async function submit(formData?: FormData, skip?: boolean) {
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 Down Expand Up @@ -95,18 +88,8 @@ async function submit(formData?: FormData, skip?: boolean) {

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

let fullSummary = '';
for await (const partialObject of streamResult.partialObjectStream) {
if (partialObject.summary) {
fullSummary = partialObject.summary;
summaryStream.update(fullSummary);
}
}

const analysisResult = await streamResult.object;
// Call the simplified agent, which now returns data directly.
const analysisResult = await resolutionSearch(messages, timezone) as any;

// Mark the summary stream as done with the result.
summaryStream.done(analysisResult.summary || 'Analysis complete.');
Expand Down Expand Up @@ -315,7 +298,11 @@ 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.type === 'text/csv' ||
file.type === 'application/json'
) {
Comment on lines +301 to +305
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

Add a server-side file size guard for csv/json uploads.

Client-side checks are not sufficient; a malicious request can bypass them and force large memory allocations on the server.

🔧 Proposed fix
-  if (file) {
-    const buffer = await file.arrayBuffer()
+  if (file) {
+    const MAX_FILE_SIZE = 10 * 1024 * 1024
+    if (file.size > MAX_FILE_SIZE) {
+      throw new Error('File size must be less than 10MB')
+    }
+    const buffer = await file.arrayBuffer()
🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 301 - 305, Add a server-side size guard for
uploads matching the file.type checks ('text/plain', 'text/csv',
'application/json') to prevent large-memory malicious uploads: define a
MAX_UPLOAD_BYTES constant and in the same handler where file.type is inspected
(the branch using file.type === 'text/plain' || 'text/csv' ||
'application/json') reject the request if file.size (or a streamed byte counter
if using streams) exceeds that limit, returning/throwing an appropriate error
before attempting to parse or buffer the file; ensure the guard runs
unconditionally on the server side so client-side checks cannot be bypassed.

const textContent = Buffer.from(buffer).toString('utf-8')
const existingTextPart = messageParts.find(p => p.type === 'text')
if (existingTextPart) {
Expand Down Expand Up @@ -669,8 +656,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 +669,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 Down Expand Up @@ -721,7 +710,7 @@ 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 (
Expand All @@ -735,7 +724,9 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => {
}
}

const searchResults = createStreamableValue()
const searchResults = createStreamableValue(
JSON.stringify(toolOutput)
)
searchResults.done(JSON.stringify(toolOutput))
switch (name) {
case 'search':
Expand All @@ -758,6 +749,32 @@ export const getUIStateFromAIState = (aiState: AIState): UIState => {
),
isCollapsed: isCollapsed.value
}
case 'dataAnalysis':
return {
id,
component: (
<>
<GraphSection result={searchResults.value} />
{toolOutput.geospatial && toolOutput.geospatial.length > 0 && (
<MapQueryHandler
toolOutput={{
type: 'MAP_QUERY_TRIGGER',
originalUserInput: JSON.stringify(toolOutput.geospatial[0]),
timestamp: new Date().toISOString(),
mcp_response: {
location: {
latitude: toolOutput.geospatial[0].latitude,
longitude: toolOutput.geospatial[0].longitude,
place_name: toolOutput.geospatial[0].label
}
}
}}
/>
)}
Comment on lines +752 to +773

Choose a reason for hiding this comment

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

This constructs a MAP_QUERY_TRIGGER with originalUserInput: JSON.stringify(toolOutput.geospatial[0]) and uses only the first geospatial point. If multiple points are returned, the map will ignore all but one.

Also, this code is doing a lot of ad-hoc shaping of a map tool payload inside UI-state mapping logic, which makes the contract fragile and hard to evolve.

Suggestion

At minimum, consider passing the entire geospatial array through and letting MapQueryHandler decide what to do, or explicitly document that only the first point is supported.

If MapQueryHandler expects a single location, add a small helper function (in this file) to build the payload so the shape is centralized and testable.

Reply with "@CharlieHelps yes please" if you'd like me to add a commit that factors payload creation into a helper and (optionally) uses the first item explicitly with a comment.

Comment on lines +758 to +773
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

Add defensive checks for geospatial item properties.

The code accesses toolOutput.geospatial[0].latitude, .longitude, and .label without validating these properties exist. If the geospatial array contains a malformed item, undefined values will be passed to MapQueryHandler.

🛡️ Proposed defensive check
-                      {toolOutput.geospatial && toolOutput.geospatial.length > 0 && (
+                      {toolOutput.geospatial &&
+                        toolOutput.geospatial.length > 0 &&
+                        toolOutput.geospatial[0].latitude != null &&
+                        toolOutput.geospatial[0].longitude != null && (
                         <MapQueryHandler
                           toolOutput={{
                             type: 'MAP_QUERY_TRIGGER',
                             originalUserInput: JSON.stringify(toolOutput.geospatial[0]),
                             timestamp: new Date().toISOString(),
                             mcp_response: {
                               location: {
                                 latitude: toolOutput.geospatial[0].latitude,
                                 longitude: toolOutput.geospatial[0].longitude,
-                                place_name: toolOutput.geospatial[0].label
+                                place_name: toolOutput.geospatial[0].label ?? ''
                               }
                             }
                           }}
                         />
                       )}
📝 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
{toolOutput.geospatial && toolOutput.geospatial.length > 0 && (
<MapQueryHandler
toolOutput={{
type: 'MAP_QUERY_TRIGGER',
originalUserInput: JSON.stringify(toolOutput.geospatial[0]),
timestamp: new Date().toISOString(),
mcp_response: {
location: {
latitude: toolOutput.geospatial[0].latitude,
longitude: toolOutput.geospatial[0].longitude,
place_name: toolOutput.geospatial[0].label
}
}
}}
/>
)}
{toolOutput.geospatial &&
toolOutput.geospatial.length > 0 &&
toolOutput.geospatial[0].latitude != null &&
toolOutput.geospatial[0].longitude != null && (
<MapQueryHandler
toolOutput={{
type: 'MAP_QUERY_TRIGGER',
originalUserInput: JSON.stringify(toolOutput.geospatial[0]),
timestamp: new Date().toISOString(),
mcp_response: {
location: {
latitude: toolOutput.geospatial[0].latitude,
longitude: toolOutput.geospatial[0].longitude,
place_name: toolOutput.geospatial[0].label ?? ''
}
}
}}
/>
)}
🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 772 - 787, The code passes potentially
undefined latitude/longitude/label from toolOutput.geospatial[0] into
MapQueryHandler; add defensive checks before constructing the MAP_QUERY_TRIGGER
payload: verify toolOutput.geospatial exists and is a non-empty array, and
validate that toolOutput.geospatial[0] has numeric latitude and longitude and a
non-empty label (or provide safe defaults or skip rendering). Update the
conditional around MapQueryHandler (referencing toolOutput.geospatial and
MapQueryHandler) to only render when these validated properties are present, or
transform the values to safe fallbacks before passing them into the
mcp_response.location fields.

</>
),
isCollapsed: isCollapsed.value
}
Comment on lines +752 to +777
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the DataAnalysisResult type definition
echo "=== Searching for DataAnalysisResult type ==="
rg -n -A 20 'type DataAnalysisResult|DataAnalysisResult = ' --type=ts -g '!node_modules' | head -100

echo -e "\n=== Searching for schema definitions ==="
rg -n -B 2 -A 15 'dataAnalysisSchema' --type=ts -g '!node_modules' | head -150

echo -e "\n=== Checking app/actions.tsx context (around line 766) ==="
wc -l app/actions.tsx

Repository: QueueLab/QCX

Length of output: 4460


🏁 Script executed:

#!/bin/bash
echo "=== Reading app/actions.tsx around line 766 ==="
sed -n '740,810p' app/actions.tsx | cat -n

echo -e "\n=== Checking broader context (line 700-750) ==="
sed -n '700,750p' app/actions.tsx | cat -n

Repository: QueueLab/QCX

Length of output: 5172


Add runtime type validation for toolOutput in the dataAnalysis case.

The toolOutput is parsed from JSON without schema validation. While a DataAnalysisResult type and dataAnalysisSchema already exist (in lib/types and lib/schema/data-analysis.tsx), they are only used in the tool definition. Here, the code accesses nested properties like toolOutput.geospatial[0].latitude without confirming the structure at runtime. Consider reusing dataAnalysisSchema with parse() to validate before access, or add explicit type guards for the geospatial property structure.

🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 766 - 791, The dataAnalysis branch reads
toolOutput and accesses nested fields without runtime validation; use the
existing dataAnalysisSchema (from lib/schema/data-analysis) to validate/parse
toolOutput (e.g., dataAnalysisSchema.parse or safeParse) into a typed
DataAnalysisResult before rendering, then conditionally render MapQueryHandler
only when the parsed result has a non-empty geospatial array and use
parsed.geospatial[0].latitude/longitude/label for the mcp_response;
alternatively add explicit type guards for toolOutput.geospatial and its
elements to avoid direct indexing of potentially invalid data.

default:
console.warn(
`Unhandled tool result in getUIStateFromAIState: ${name}`
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
Loading