Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- 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?
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
6eeae28160e082ae085cf73c385c1508c04c8c028be2c061543179e77d45e02562383144e86b60a480186acfbe58c7120d38704ca37d772bfdc7c5e8ae549a3495da1ba36e586431b418a04d4910ab3fe250283e6554775e95a25bf80c87fc4278e9813d264c868dcd3b5ed273984b9bf45f68767c26d5a842df1184f678895bf37dc345b986013ed2ba1f9edda7a3223a1d3f885dbbedd812c100c2e1a54d9d6ec9bd9b2c6a615cc5aae977fa9897File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
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 definitionecho"=== 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/bashecho"=== 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}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
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