-
-
Notifications
You must be signed in to change notification settings - Fork 7
Add Generative Graphs UI and Data Analysis Tool #464
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
base: main
Are you sure you want to change the base?
Changes from all commits
6eeae28
160e082
ae085cf
73c385c
1508c04
c8c028b
e2c0615
43179e7
7d45e02
5623831
44e86b6
0a48018
6acfbe5
8c7120d
38704ca
37d772b
fdc7c5e
8ae549a
3495da1
ba36e58
6431b41
8a04d49
10ab3fe
250283e
6554775
e95a25b
f80c87f
c4278e9
813d264
c868dcd
3b5ed27
3984b9b
f45f687
67c26d5
a842df1
184f678
895bf37
dc345b9
86013ed
2ba1f9e
dda7a32
23a1d3f
885dbbe
dd812c1
00c2e1a
54d9d6e
c9bd9b2
c6a615c
c5aae97
7fa9897
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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 = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const textContent = Buffer.from(buffer).toString('utf-8') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const existingTextPart = messageParts.find(p => p.type === 'text') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (existingTextPart) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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 ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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
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. This constructs a 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. SuggestionAt minimum, consider passing the entire If 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
Contributor
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 defensive checks for geospatial item properties. The code accesses 🛡️ 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isCollapsed: isCollapsed.value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+752
to
+777
Contributor
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. 🧹 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.tsxRepository: 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 -nRepository: QueueLab/QCX Length of output: 5172 Add runtime type validation for The 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.warn( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `Unhandled tool result in getUIStateFromAIState: ${name}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
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
🤖 Prompt for AI Agents