Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
309 changes: 204 additions & 105 deletions components/report-template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@ export const ReportTemplate: React.FC<ReportTemplateProps> = ({
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
Expand All @@ -44,119 +37,225 @@ export const ReportTemplate: React.FC<ReportTemplateProps> = ({
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 (
<div id="report-template" className="p-8 bg-white text-black font-sans max-w-4xl mx-auto border border-gray-200">
<header className="mb-8 border-b-2 border-[#1a1a1a] pb-4">
<h1 className="text-3xl font-bold mb-2">{chatTitle}</h1>
<p className="text-gray-600">Generated on: {new Date().toLocaleString()}</p>
</header>
<div id="report-template" className="bg-white text-[#1a1a1a] font-sans max-w-[800px] mx-auto">
{/* Cover Page */}
<section className="h-[1120px] flex flex-col justify-between p-20 border-b-[16px] border-[#003366] bg-slate-50 relative overflow-hidden">
<div className="absolute top-0 right-0 w-96 h-96 bg-[#003366] opacity-[0.03] -mr-48 -mt-48 rounded-full"></div>
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-[#003366] opacity-[0.02] rounded-full"></div>

{mapSnapshot && (
<section className="mb-10">
<h2 className="text-xl font-semibold mb-4 border-l-4 border-blue-600 pl-2">Live Map View</h2>
<div className="border rounded-lg overflow-hidden shadow-sm bg-gray-100 min-h-[200px] flex items-center justify-center">
<img src={mapSnapshot} alt="Map Snapshot" className="w-full h-auto block" crossOrigin="anonymous" />
<div>
<div className="flex items-center gap-4 mb-16">
<div className="bg-[#003366] p-2 rounded-lg">
<img src="/images/logo.svg" alt="QCX Logo" className="w-10 h-10 brightness-0 invert" crossOrigin="anonymous" />
</div>
<span className="text-3xl font-black tracking-tighter text-[#003366]">QCX TERRA</span>
</div>
</section>
)}

<section className="mb-10">
<h2 className="text-xl font-semibold mb-6 border-l-4 border-blue-600 pl-2">Conversation History</h2>
<div className="space-y-8">
{filteredMessages.map((message, index) => {
const contentString = renderMessageContent(message.content)
<div className="space-y-6 mt-32">
<div className="inline-block px-3 py-1 bg-[#003366] text-white text-[10px] font-bold tracking-[0.2em] uppercase rounded-sm">
Confidential Analysis
</div>
<h1 className="text-6xl font-black text-[#1a1a1a] leading-[1.1] tracking-tight">
{chatTitle}
</h1>
<div className="w-24 h-2 bg-[#003366]"></div>
</div>
</div>

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 (
<div key={index} className="bg-gray-50 p-4 rounded-lg border-l-4 border-blue-400">
<p className="text-sm font-bold text-blue-600 mb-1">User Question</p>
<p className="text-gray-800 italic">{content}</p>
</div>
)
} else if (message.type === 'response') {
return (
<div key={index} className="prose prose-sm max-w-none border-b border-gray-100 pb-4">
<p className="text-sm font-bold text-green-600 mb-1">AI Response</p>
<div className="text-gray-800">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{contentString}
</ReactMarkdown>
<div className="mt-auto pt-12 flex justify-between items-end border-t border-slate-200">
<div className="space-y-3">
<p className="text-[10px] text-slate-400 uppercase font-black tracking-[0.15em]">Prepared By</p>
<div>
<p className="text-xl font-bold text-[#003366]">Planet Computer Intelligence</p>
<p className="text-sm text-slate-500 font-medium italic">Geospatial Intelligence Division</p>
</div>
</div>
<div className="text-right space-y-3">
<p className="text-[10px] text-slate-400 uppercase font-black tracking-[0.15em]">Issue Date</p>
<p className="text-xl font-bold text-[#1a1a1a]">{new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</p>
</div>
</div>
</section>

{/* Main Content Container */}
<div className="p-16 space-y-20">
{mapSnapshot && (
<section className="break-inside-avoid">
<div className="flex items-baseline gap-4 mb-8 border-b border-slate-100 pb-4">
<span className="text-4xl font-black text-slate-200">01</span>
<h2 className="text-2xl font-black text-[#003366] uppercase tracking-tight">Executive Visualization</h2>
</div>
<div className="border-[12px] border-slate-50 rounded-2xl overflow-hidden shadow-2xl">
<img src={mapSnapshot} alt="Map Snapshot" className="w-full h-auto block" crossOrigin="anonymous" />
<div className="bg-slate-50 px-6 py-4 text-xs text-slate-500 text-center font-bold tracking-wide border-t border-slate-100">
ORBITAL PERSPECTIVE REF: {Math.random().toString(16).substring(2, 10).toUpperCase()}
</div>
</div>
</section>
)}

<section>
<div className="flex items-baseline gap-4 mb-12 border-b border-slate-100 pb-4">
<span className="text-4xl font-black text-slate-200">02</span>
<h2 className="text-2xl font-black text-[#003366] uppercase tracking-tight">Intelligence Assessment</h2>
</div>
<div className="space-y-16">
{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 (
<div key={index} className="bg-[#003366] p-10 rounded-2xl shadow-xl break-inside-avoid relative overflow-hidden group">
<div className="absolute top-0 right-0 p-4">
<svg className="w-8 h-8 text-white opacity-10" fill="currentColor" viewBox="0 0 24 24"><path d="M14.017 21L14.017 18C14.017 16.8954 14.9124 16 16.017 16H19.017C19.5693 16 20.017 15.5523 20.017 15V9C20.017 8.44772 19.5693 8 19.017 8H16.017C14.9124 8 14.017 7.10457 14.017 6V3L14.017 3C14.017 2.44772 14.4647 2 15.017 2H21.017C21.5693 2 22.017 2.44772 22.017 3V15C22.017 18.3137 19.3307 21 16.017 21H14.017ZM3.017 21L3.017 18C3.017 16.8954 3.91243 16 5.017 16H8.017C8.56928 16 9.017 15.5523 9.017 15V9C9.017 8.44772 8.56928 8 8.017 8H5.017C3.91243 8 3.017 7.10457 3.017 6V3L3.017 3C3.017 2.44772 3.46472 2 4.017 2H10.017C10.5693 2 11.017 2.44772 11.017 3V15C11.017 18.3137 8.33072 21 5.017 21H3.017Z" /></svg>
</div>
<p className="text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] mb-4">Primary Inquiry</p>
<p className="text-2xl text-white font-bold leading-tight tracking-tight italic">
{content}
</p>
</div>
</div>
)
} else if (message.type === 'resolution_search_result') {
try {
const result = JSON.parse(contentString)
)
} else if (message.type === 'response') {
return (
<div key={index} className="space-y-4 bg-purple-50 p-4 rounded-lg">
<p className="text-sm font-bold text-purple-600 mb-1">Analysis Result</p>
{result.summary && (
<div className="text-gray-800 mb-4">
{result.summary}
<div key={index} className="prose prose-slate prose-lg max-w-none break-inside-avoid">
<div className="flex items-center gap-3 mb-6">
<div className="w-1.5 h-6 bg-emerald-500"></div>
<p className="text-xs font-black text-emerald-700 uppercase tracking-[0.2em] m-0">Strategic Output</p>
</div>
<div className="text-slate-700 leading-relaxed font-medium">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{contentString}
</ReactMarkdown>
</div>
</div>
)
} else if (message.type === 'resolution_search_result') {
try {
const result = JSON.parse(contentString)
return (
<div key={index} className="space-y-8 bg-slate-50 p-12 rounded-3xl border border-slate-100 break-inside-avoid">
<div className="flex items-center justify-between">
<p className="text-xs font-black text-indigo-700 uppercase tracking-[0.2em]">Sensor Fusion Analysis</p>
<span className="px-2 py-0.5 bg-indigo-100 text-indigo-700 text-[9px] font-bold rounded">LEVEL 4 DATA</span>
</div>
)}
<div className="grid grid-cols-2 gap-4">
{result.mapboxImage && (
<div className="space-y-1">
<p className="text-[10px] text-gray-500 font-semibold uppercase">Mapbox View</p>
<img src={result.mapboxImage} alt="Mapbox View" className="rounded border border-purple-200 w-full block" crossOrigin="anonymous" />
</div>
)}
{result.googleImage && (
<div className="space-y-1">
<p className="text-[10px] text-gray-500 font-semibold uppercase">Google Satellite</p>
<img src={result.googleImage} alt="Google Satellite" className="rounded border border-purple-200 w-full block" crossOrigin="anonymous" />
{result.summary && (
<div className="text-slate-800 text-lg font-bold leading-snug bg-white p-8 rounded-2xl border border-slate-100 shadow-sm">
{result.summary}
</div>
)}
<div className="grid grid-cols-2 gap-10">
{result.mapboxImage && (
<div className="space-y-4">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-indigo-500"></div>
<p className="text-[10px] text-slate-400 font-black uppercase tracking-widest">Spectral RGB</p>
</div>
<img src={result.mapboxImage} alt="Mapbox View" className="rounded-xl border-4 border-white shadow-xl w-full block" crossOrigin="anonymous" />
</div>
)}
{result.googleImage && (
<div className="space-y-4">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-orange-500"></div>
<p className="text-[10px] text-slate-400 font-black uppercase tracking-widest">High-Res Panchro</p>
</div>
<img src={result.googleImage} alt="Google Satellite" className="rounded-xl border-4 border-white shadow-xl w-full block" crossOrigin="anonymous" />
</div>
)}
</div>
</div>
</div>
)
} catch (e) {
return null
)
} catch (e) {
return null
}
}
}
return null
})}
</div>
</section>

{drawnFeatures && drawnFeatures.length > 0 && (
<section className="mt-10 border-t-2 border-gray-100 pt-6">
<h2 className="text-xl font-semibold mb-4 border-l-4 border-orange-500 pl-2">Appendix: Drawn Features & Measurements</h2>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 text-sm">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left font-medium text-gray-500 uppercase tracking-wider">Type</th>
<th className="px-4 py-2 text-left font-medium text-gray-500 uppercase tracking-wider">Measurement</th>
<th className="px-4 py-2 text-left font-medium text-gray-500 uppercase tracking-wider">Geometry</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{drawnFeatures.map((feature) => (
<tr key={feature.id}>
<td className="px-4 py-2 whitespace-nowrap text-gray-900">{feature.type}</td>
<td className="px-4 py-2 whitespace-nowrap text-gray-900">{feature.measurement}</td>
<td className="px-4 py-2 text-gray-500 break-all font-mono text-[10px]">
{JSON.stringify(feature.geometry.coordinates).substring(0, 100)}...
</td>
</tr>
))}
</tbody>
</table>
return null
})}
</div>
</section>
)}

<footer className="mt-12 text-center text-gray-400 text-xs border-t pt-4">
<p>© {new Date().getFullYear()} QCX - Planet Computer Analysis Report</p>
{drawnFeatures && drawnFeatures.length > 0 && (
<section className="break-inside-avoid pt-12">
<div className="flex items-baseline gap-4 mb-8 border-b border-slate-100 pb-4">
<span className="text-4xl font-black text-slate-200">03</span>
<h2 className="text-2xl font-black text-[#003366] uppercase tracking-tight">Quantitative Appendix</h2>
</div>
<div className="overflow-hidden rounded-2xl border border-slate-200 shadow-xl">
<table className="min-w-full divide-y divide-slate-200">
<thead className="bg-[#003366]">
<tr>
<th className="px-8 py-5 text-left font-black text-white uppercase tracking-[0.1em] text-[10px]">Feature Identity</th>
<th className="px-8 py-5 text-left font-black text-white uppercase tracking-[0.1em] text-[10px]">Computed Metric</th>
<th className="px-8 py-5 text-left font-black text-white uppercase tracking-[0.1em] text-[10px]">Spatial Hash</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-slate-100">
{drawnFeatures.map((feature, fIdx) => (
<tr key={feature.id} className={fIdx % 2 === 0 ? 'bg-white' : 'bg-slate-50/50'}>
<td className="px-8 py-6 whitespace-nowrap">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-bold bg-slate-100 text-slate-800 border border-slate-200">
{feature.type}
</span>
</td>
<td className="px-8 py-6 whitespace-nowrap text-indigo-700 font-black text-sm">
{feature.measurement}
</td>
<td className="px-8 py-6 text-slate-400 font-mono text-[9px] leading-tight">
{JSON.stringify(feature.geometry.coordinates).substring(0, 60)}...
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
)}
</div>

<footer className="mt-24 px-16 py-12 text-center border-t border-slate-100 bg-slate-50">
<div className="flex justify-center gap-8 mb-6">
<div className="text-[10px] font-black text-slate-300 tracking-[0.3em] uppercase">QCX TERRA</div>
<div className="text-[10px] font-black text-slate-300 tracking-[0.3em] uppercase">PLANET COMPUTER</div>
<div className="text-[10px] font-black text-slate-300 tracking-[0.3em] uppercase">CLASSIFIED</div>
</div>
<p className="text-[10px] font-bold text-slate-400 tracking-wider uppercase mb-2">
© {new Date().getFullYear()} ALL RIGHTS RESERVED • SUBJECT TO GOVERNMENT DATA POLICIES
</p>
<p className="max-w-md mx-auto text-[9px] text-slate-400 font-medium leading-relaxed italic">
This intelligence report was autonomously generated by the Planet Computer system.
Information contained herein is derived from orbital sensors and AI synthesis.
</p>
</footer>
</div>
)
Expand Down
Loading