From 0dd99609b3b0902d652e6f49a802088aef85df52 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 13:49:55 +0000 Subject: [PATCH] feat: improve PDF report quality and formatting to Fortune 500 standards - Added professional cover page with corporate branding and logo - Enhanced typography, font hierarchy, and color palette - Implemented redundancy filtering for intelligence assessments - Increased PDF generation DPI (scale: 2) and JPEG quality (1.0) - Refined layout with executive visualization and quantitative appendix sections - Improved page-breaking logic in report generator Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com> --- components/report-template.tsx | 309 ++++++++++++++++++++++----------- lib/utils/report-generator.ts | 18 +- server.log | 22 ++- 3 files changed, 229 insertions(+), 120 deletions(-) diff --git a/components/report-template.tsx b/components/report-template.tsx index e6cf8867..a7392fed 100644 --- a/components/report-template.tsx +++ b/components/report-template.tsx @@ -21,13 +21,6 @@ export const ReportTemplate: React.FC = ({ mapSnapshot, chatTitle }) => { - const filteredMessages = messages.filter(m => - m.type === 'input' || - m.type === 'input_related' || - m.type === 'response' || - m.type === 'resolution_search_result' - ) - const renderMessageContent = (content: any): string => { if (typeof content === 'string') { return content @@ -44,119 +37,225 @@ export const ReportTemplate: React.FC = ({ return '' } + // Filter messages to reduce redundancy + const rawFiltered = messages.filter(m => + m.type === 'input' || + m.type === 'input_related' || + m.type === 'response' || + m.type === 'resolution_search_result' + ) + + const filteredMessages = rawFiltered.filter((message, index) => { + // If this is a response followed by a resolution search result, + // and the response is relatively short (likely a transition/redundant summary), filter it out. + if (message.type === 'response' && index + 1 < rawFiltered.length) { + const nextMessage = rawFiltered[index + 1] + if (nextMessage.type === 'resolution_search_result') { + const content = renderMessageContent(message.content) + if (content.length < 300) { + return false + } + } + } + return true + }) + return ( -
-
-

{chatTitle}

-

Generated on: {new Date().toLocaleString()}

-
+
+ {/* Cover Page */} +
+
+
- {mapSnapshot && ( -
-

Live Map View

-
- Map Snapshot +
+
+
+ QCX Logo +
+ QCX TERRA
-
- )} -
-

Conversation History

-
- {filteredMessages.map((message, index) => { - const contentString = renderMessageContent(message.content) +
+
+ Confidential Analysis +
+

+ {chatTitle} +

+
+
+
- if (message.type === 'input' || message.type === 'input_related') { - let content = '' - try { - const json = JSON.parse(contentString) - content = message.type === 'input' ? (json.input || contentString) : (json.related_query || contentString) - } catch (e) { - content = contentString - } - return ( -
-

User Question

-

{content}

-
- ) - } else if (message.type === 'response') { - return ( -
-

AI Response

-
- - {contentString} - +
+
+

Prepared By

+
+

Planet Computer Intelligence

+

Geospatial Intelligence Division

+
+
+
+

Issue Date

+

{new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}

+
+
+
+ + {/* Main Content Container */} +
+ {mapSnapshot && ( +
+
+ 01 +

Executive Visualization

+
+
+ Map Snapshot +
+ ORBITAL PERSPECTIVE REF: {Math.random().toString(16).substring(2, 10).toUpperCase()} +
+
+
+ )} + +
+
+ 02 +

Intelligence Assessment

+
+
+ {filteredMessages.map((message, index) => { + const contentString = renderMessageContent(message.content) + + if (message.type === 'input' || message.type === 'input_related') { + let content = '' + try { + const json = JSON.parse(contentString) + content = message.type === 'input' ? (json.input || contentString) : (json.related_query || contentString) + } catch (e) { + content = contentString + } + return ( +
+
+ +
+

Primary Inquiry

+

+ {content} +

-
- ) - } else if (message.type === 'resolution_search_result') { - try { - const result = JSON.parse(contentString) + ) + } else if (message.type === 'response') { return ( -
-

Analysis Result

- {result.summary && ( -
- {result.summary} +
+
+
+

Strategic Output

+
+
+ + {contentString} + +
+
+ ) + } else if (message.type === 'resolution_search_result') { + try { + const result = JSON.parse(contentString) + return ( +
+
+

Sensor Fusion Analysis

+ LEVEL 4 DATA
- )} -
- {result.mapboxImage && ( -
-

Mapbox View

- Mapbox View -
- )} - {result.googleImage && ( -
-

Google Satellite

- Google Satellite + {result.summary && ( +
+ {result.summary}
)} +
+ {result.mapboxImage && ( +
+
+
+

Spectral RGB

+
+ Mapbox View +
+ )} + {result.googleImage && ( +
+
+
+

High-Res Panchro

+
+ Google Satellite +
+ )} +
-
- ) - } catch (e) { - return null + ) + } catch (e) { + return null + } } - } - return null - })} -
-
- - {drawnFeatures && drawnFeatures.length > 0 && ( -
-

Appendix: Drawn Features & Measurements

-
- - - - - - - - - - {drawnFeatures.map((feature) => ( - - - - - - ))} - -
TypeMeasurementGeometry
{feature.type}{feature.measurement} - {JSON.stringify(feature.geometry.coordinates).substring(0, 100)}... -
+ return null + })}
- )} -
-

© {new Date().getFullYear()} QCX - Planet Computer Analysis Report

+ {drawnFeatures && drawnFeatures.length > 0 && ( +
+
+ 03 +

Quantitative Appendix

+
+
+ + + + + + + + + + {drawnFeatures.map((feature, fIdx) => ( + + + + + + ))} + +
Feature IdentityComputed MetricSpatial Hash
+ + {feature.type} + + + {feature.measurement} + + {JSON.stringify(feature.geometry.coordinates).substring(0, 60)}... +
+
+
+ )} +
+ +
) diff --git a/lib/utils/report-generator.ts b/lib/utils/report-generator.ts index 07ac7ba6..0ee60edf 100644 --- a/lib/utils/report-generator.ts +++ b/lib/utils/report-generator.ts @@ -12,7 +12,7 @@ export const generatePDFReport = async (elementId: string, fileName: string) => ]) const images = Array.from(element.getElementsByTagName('img')) - const imageLoadTimeout = 5000 // 5 seconds timeout + const imageLoadTimeout = 10000 // 10 seconds timeout for high-res images // Wait for images to load, but don't block forever await Promise.race([ @@ -31,12 +31,12 @@ export const generatePDFReport = async (elementId: string, fileName: string) => ]) const canvas = await html2canvas(element, { - scale: 1, // Keep scale low for performance on large reports + scale: 2, // Increase scale for higher DPI/sharpness useCORS: true, logging: false, allowTaint: true, backgroundColor: '#ffffff', - imageTimeout: 10000, + imageTimeout: 15000, onclone: (clonedDoc) => { // Ensure the cloned element is visible for html2canvas const clonedElement = clonedDoc.getElementById(elementId) @@ -44,11 +44,15 @@ export const generatePDFReport = async (elementId: string, fileName: string) => clonedElement.style.position = 'static' clonedElement.style.left = '0' clonedElement.style.visibility = 'visible' + clonedElement.style.width = '800px' // Fix width for consistent scaling } } }) - const imgData = canvas.toDataURL('image/jpeg', 0.6) + const imgData = canvas.toDataURL('image/jpeg', 1.0) // Maximum quality + + // A4 dimensions in px at 72 DPI are roughly 595 x 842 + // But we use the internal pageSize values for flexibility const pdf = new jsPDF({ orientation: 'portrait', unit: 'px', @@ -66,14 +70,14 @@ export const generatePDFReport = async (elementId: string, fileName: string) => let position = 0 // Add first page - pdf.addImage(imgData, 'JPEG', 0, position, pdfWidth, scaledHeight) + pdf.addImage(imgData, 'JPEG', 0, position, pdfWidth, scaledHeight, undefined, 'FAST') heightLeft -= pdfHeight // Add subsequent pages if content overflows - while (heightLeft >= 0) { + while (heightLeft > 0) { position = heightLeft - scaledHeight pdf.addPage() - pdf.addImage(imgData, 'JPEG', 0, position, pdfWidth, scaledHeight) + pdf.addImage(imgData, 'JPEG', 0, position, pdfWidth, scaledHeight, undefined, 'FAST') heightLeft -= pdfHeight } diff --git a/server.log b/server.log index 45044476..41e8e758 100644 --- a/server.log +++ b/server.log @@ -1,11 +1,17 @@ $ next dev --turbo - ⚠ Port 3000 is in use, using available port 3003 instead. - ▲ Next.js 15.3.6 (Turbopack) - - Local: http://localhost:3003 - - Network: http://192.168.0.2:3003 - - Environments: .env.local, .env + ▲ Next.js 15.3.8 (Turbopack) + - Local: http://localhost:3000 + - Network: http://192.168.0.2:3000 + - Environments: .env ✓ Starting... - ○ Compiling middleware ... - ✓ Compiled middleware in 648ms - ✓ Ready in 2.5s + ✓ Compiled middleware in 433ms + ✓ Ready in 1802ms + ○ Compiling / ... + ✓ Compiled / in 27.9s +Chat DB actions loaded. Ensure getCurrentUserId() is correctly implemented for server-side usage if applicable. + GET / 200 in 30481ms + HEAD / 200 in 1010ms + GET / 200 in 986ms + GET / 200 in 1129ms +[?25h