-
-
Notifications
You must be signed in to change notification settings - Fork 8
Implement PDF Report Generation #602
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
Changes from all commits
1ca140d
5517ab6
e1b6ff0
e0cda69
9e686ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| 'use client' | ||
|
|
||
| import React, { useState } from 'react' | ||
| import { Button } from '@/components/ui/button' | ||
| import { FileDown, Loader2 } from 'lucide-react' | ||
| import { useAIState } from 'ai/rsc' | ||
| import { useMapData } from '@/components/map/map-data-context' | ||
| import { useMap } from '@/components/map/map-context' | ||
| import { generateReport } from '@/lib/utils/report-generator' | ||
| import { toast } from 'sonner' | ||
|
|
||
| interface ReportButtonProps { | ||
| inline?: boolean | ||
| } | ||
|
|
||
| export const ReportButton = ({ inline = false }: ReportButtonProps) => { | ||
| const [aiState] = useAIState() | ||
| const { mapData } = useMapData() | ||
| const { map } = useMap() | ||
| const [isGenerating, setIsGenerating] = useState(false) | ||
|
|
||
| const handleDownloadReport = async () => { | ||
| if (isGenerating) return | ||
|
|
||
| setIsGenerating(true) | ||
| try { | ||
| const mapSnapshot = map ? map.getCanvas().toDataURL('image/png') : '' | ||
|
|
||
| const chatTitle = aiState.chatId ? `Chat-${aiState.chatId.substring(0, 8)}` : 'QCX-Analysis' | ||
|
|
||
| await generateReport({ | ||
| messages: aiState.messages, | ||
| drawnFeatures: mapData.drawnFeatures || [], | ||
| mapSnapshot, | ||
| chatTitle | ||
| }) | ||
|
|
||
| // Removed success toast as per user request | ||
| } catch (error) { | ||
| console.error('Failed to generate report:', error) | ||
| toast.error('Failed to generate report') | ||
| } finally { | ||
| setIsGenerating(false) | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
| <Button | ||
| variant={inline ? "default" : "ghost"} | ||
| size={inline ? "default" : "icon"} | ||
| onClick={handleDownloadReport} | ||
| title="Download PDF Report" | ||
| disabled={isGenerating} | ||
| className={inline ? "w-full" : ""} | ||
| > | ||
| {isGenerating ? ( | ||
| <Loader2 className="h-[1.2rem] w-[1.2rem] animate-spin" /> | ||
| ) : ( | ||
| <FileDown className="h-[1.2rem] w-[1.2rem]" /> | ||
| )} | ||
| {inline && <span className="ml-2">Generate Report</span>} | ||
| </Button> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -346,12 +346,27 @@ Uses the Mapbox Search Box Text Search API endpoint to power searching for and g | |
| // Build arguments | ||
| const toolArgs = (() => { | ||
| switch (queryType) { | ||
| case 'directions': return { waypoints: [params.origin, params.destination], includeMapPreview: includeMap, profile: params.mode }; | ||
| case 'distance': return { places: [params.origin, params.destination], includeMapPreview: includeMap, mode: params.mode || 'driving' }; | ||
| case 'reverse': return { searchText: `${params.coordinates.latitude},${params.coordinates.longitude}`, includeMapPreview: includeMap, maxResults: params.maxResults || 5 }; | ||
| case 'search': return { searchText: params.query, includeMapPreview: includeMap, maxResults: params.maxResults || 5, ...(params.coordinates && { proximity: `${params.coordinates.latitude},${params.coordinates.longitude}` }), ...(params.radius && { radius: params.radius }) }; | ||
| case 'geocode': | ||
| case 'map': return { searchText: params.location, includeMapPreview: includeMap, maxResults: queryType === 'geocode' ? params.maxResults || 5 : undefined }; | ||
| case 'directions': { | ||
| if (!params.origin || !params.destination) throw new Error("'directions' query requires origin and destination"); | ||
| return { waypoints: [params.origin, params.destination], includeMapPreview: includeMap, profile: params.mode }; | ||
| } | ||
| case 'distance': { | ||
| if (!params.origin || !params.destination) throw new Error("'distance' query requires origin and destination"); | ||
| return { places: [params.origin, params.destination], includeMapPreview: includeMap, mode: params.mode || 'driving' }; | ||
| } | ||
| case 'reverse': { | ||
| if (!params.coordinates) throw new Error("'reverse' query requires coordinates"); | ||
| return { searchText: `${params.coordinates.latitude},${params.coordinates.longitude}`, includeMapPreview: includeMap, maxResults: params.maxResults || 5 }; | ||
| } | ||
| case 'search': { | ||
| if (!params.query) throw new Error("'search' query requires query"); | ||
| return { searchText: params.query, includeMapPreview: includeMap, maxResults: params.maxResults || 5, ...(params.coordinates && { proximity: `${params.coordinates.latitude},${params.coordinates.longitude}` }), ...(params.radius && { radius: params.radius }) }; | ||
| } | ||
| case 'geocode': | ||
| case 'map': { | ||
| if (!params.location) throw new Error(`'${queryType}' query requires location`); | ||
| return { searchText: params.location, includeMapPreview: includeMap, maxResults: queryType === 'geocode' ? params.maxResults || 5 : undefined }; | ||
|
Comment on lines
347
to
+368
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. Validate These guards run after Suggested direction- const mcpClient = await getConnectedMcpClient();
+ const toolArgs = buildToolArgs(params, includeMap);
+ const mcpClient = await getConnectedMcpClient();
if (!mcpClient) {
feedbackMessage = 'Geospatial functionality is unavailable. Please check configuration.';
uiFeedbackStream.update(feedbackMessage);
uiFeedbackStream.done();
uiStream.update(<BotMessage content={uiFeedbackStream.value} />);
return { type: 'MAP_QUERY_TRIGGER', originalUserInput: JSON.stringify(params), timestamp: new Date().toISOString(), mcp_response: null, error: 'MCP client initialization failed' };
}
@@
- const toolArgs = (() => {
- switch (queryType) {
- ...
- }
- })();🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| })(); | ||
|
|
||
|
|
||
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.
Use the exact schema field names in the prompt.
Line 130 asks for “news context”, but the schema only accepts
hasRecentNewsandnewsItems. That mismatch makes the model more likely to emit the wrong key or bury the data insummary, which then gets lost at validation time. Please name the concrete fields the schema expects.🤖 Prompt for AI Agents