Álvaro Valenzuela Valdes commited on
Commit ·
19e0332
1
Parent(s): 36eebcf
Implement PDF visualization and professional PDF export for annexes
Browse files- frontend/components/AgentAnalysis.tsx +94 -13
- frontend/package.json +2 -1
frontend/components/AgentAnalysis.tsx
CHANGED
|
@@ -41,6 +41,7 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
|
|
| 41 |
const [activeAnimalId, setActiveAnimalId] = useState<string | null>(null);
|
| 42 |
const [generatedAnnexes, setGeneratedAnnexes] = useState<Array<{ name: string, content: string }>>([]);
|
| 43 |
const [isGeneratingAnnexes, setIsGeneratingAnnexes] = useState(false);
|
|
|
|
| 44 |
|
| 45 |
// Removed auto-scroll to keep user at the top during demo recordings
|
| 46 |
|
|
@@ -92,6 +93,49 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
|
|
| 92 |
}, 2000);
|
| 93 |
};
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
| 96 |
if (event.target.files && event.target.files.length > 0) {
|
| 97 |
const filesArray = Array.from(event.target.files);
|
|
@@ -108,6 +152,12 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
|
|
| 108 |
analysis: null,
|
| 109 |
id
|
| 110 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
setCorral(prev => [...prev, newEntry]);
|
| 112 |
setActiveAnimalId(id);
|
| 113 |
}
|
|
@@ -462,6 +512,29 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
|
|
| 462 |
</div>
|
| 463 |
|
| 464 |
{isUploading && <p className="text-[10px] text-purple-400 animate-pulse font-bold">✨ Bringing animal to corral...</p>}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
</div>
|
| 466 |
|
| 467 |
<label className="flex items-center gap-3 p-4 rounded-2xl bg-white/5 cursor-pointer hover:bg-white/10 transition border border-white/5">
|
|
@@ -713,19 +786,27 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
|
|
| 713 |
<pre className="whitespace-pre-wrap">{annex.content}</pre>
|
| 714 |
<div className="absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-black/60 to-transparent" />
|
| 715 |
</div>
|
| 716 |
-
<
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
</div>
|
| 730 |
))}
|
| 731 |
</div>
|
|
|
|
| 41 |
const [activeAnimalId, setActiveAnimalId] = useState<string | null>(null);
|
| 42 |
const [generatedAnnexes, setGeneratedAnnexes] = useState<Array<{ name: string, content: string }>>([]);
|
| 43 |
const [isGeneratingAnnexes, setIsGeneratingAnnexes] = useState(false);
|
| 44 |
+
const [pdfUrls, setPdfUrls] = useState<Record<string, string>>({});
|
| 45 |
|
| 46 |
// Removed auto-scroll to keep user at the top during demo recordings
|
| 47 |
|
|
|
|
| 93 |
}, 2000);
|
| 94 |
};
|
| 95 |
|
| 96 |
+
const downloadAsPDF = async (annex: { name: string, content: string }) => {
|
| 97 |
+
try {
|
| 98 |
+
const { jsPDF } = await import("jspdf");
|
| 99 |
+
const doc = new jsPDF();
|
| 100 |
+
|
| 101 |
+
// Title
|
| 102 |
+
doc.setFontSize(22);
|
| 103 |
+
doc.setTextColor(40, 40, 40);
|
| 104 |
+
doc.text("ANDESOPS AI - COMPLIANCE", 20, 20);
|
| 105 |
+
|
| 106 |
+
doc.setDrawColor(168, 85, 247); // Purple line
|
| 107 |
+
doc.setLineWidth(1);
|
| 108 |
+
doc.line(20, 25, 190, 25);
|
| 109 |
+
|
| 110 |
+
// Content
|
| 111 |
+
doc.setFontSize(16);
|
| 112 |
+
doc.setTextColor(0, 0, 0);
|
| 113 |
+
doc.text(annex.name, 20, 40);
|
| 114 |
+
|
| 115 |
+
doc.setFontSize(10);
|
| 116 |
+
doc.setFont("helvetica", "normal");
|
| 117 |
+
|
| 118 |
+
const splitText = doc.splitTextToSize(annex.content.replace(/# /g, '').replace(/\*\*/g, '').replace(/### /g, ''), 170);
|
| 119 |
+
doc.text(splitText, 20, 55);
|
| 120 |
+
|
| 121 |
+
// Footer
|
| 122 |
+
doc.setFontSize(8);
|
| 123 |
+
doc.setTextColor(150, 150, 150);
|
| 124 |
+
doc.text(`Document generated by AndesOps AI on ${new Date().toLocaleString()}`, 20, 280);
|
| 125 |
+
|
| 126 |
+
doc.save(`${annex.name.replace(/ /g, '_')}.pdf`);
|
| 127 |
+
} catch (err) {
|
| 128 |
+
console.error("PDF Export failed:", err);
|
| 129 |
+
alert("PDF Export failed. Downloading as Markdown instead.");
|
| 130 |
+
const blob = new Blob([annex.content], { type: 'text/markdown' });
|
| 131 |
+
const url = window.URL.createObjectURL(blob);
|
| 132 |
+
const a = document.createElement('a');
|
| 133 |
+
a.href = url;
|
| 134 |
+
a.download = `${annex.name.replace(/ /g, '_')}.md`;
|
| 135 |
+
a.click();
|
| 136 |
+
}
|
| 137 |
+
};
|
| 138 |
+
|
| 139 |
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
| 140 |
if (event.target.files && event.target.files.length > 0) {
|
| 141 |
const filesArray = Array.from(event.target.files);
|
|
|
|
| 152 |
analysis: null,
|
| 153 |
id
|
| 154 |
};
|
| 155 |
+
|
| 156 |
+
if (newFile.type === "application/pdf") {
|
| 157 |
+
const url = URL.createObjectURL(newFile);
|
| 158 |
+
setPdfUrls(prev => ({ ...prev, [id]: url }));
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
setCorral(prev => [...prev, newEntry]);
|
| 162 |
setActiveAnimalId(id);
|
| 163 |
}
|
|
|
|
| 512 |
</div>
|
| 513 |
|
| 514 |
{isUploading && <p className="text-[10px] text-purple-400 animate-pulse font-bold">✨ Bringing animal to corral...</p>}
|
| 515 |
+
|
| 516 |
+
{/* PDF Viewer for Active Selection */}
|
| 517 |
+
{activeAnimalId && pdfUrls[activeAnimalId] && (
|
| 518 |
+
<div className="mt-6 rounded-2xl overflow-hidden border border-white/10 bg-black/40 h-80 relative group">
|
| 519 |
+
<iframe
|
| 520 |
+
src={`${pdfUrls[activeAnimalId]}#toolbar=0&navpanes=0&scrollbar=0`}
|
| 521 |
+
className="w-full h-full border-none opacity-80 group-hover:opacity-100 transition-opacity"
|
| 522 |
+
/>
|
| 523 |
+
<div className="absolute inset-x-0 bottom-0 p-3 bg-gradient-to-t from-black to-transparent flex justify-between items-end">
|
| 524 |
+
<p className="text-[9px] font-mono text-slate-400 truncate max-w-[150px]">
|
| 525 |
+
{corral.find(a => a.id === activeAnimalId)?.file.name}
|
| 526 |
+
</p>
|
| 527 |
+
<a
|
| 528 |
+
href={pdfUrls[activeAnimalId]}
|
| 529 |
+
target="_blank"
|
| 530 |
+
rel="noopener noreferrer"
|
| 531 |
+
className="text-[9px] font-bold text-cyan hover:underline"
|
| 532 |
+
>
|
| 533 |
+
Full View ↗
|
| 534 |
+
</a>
|
| 535 |
+
</div>
|
| 536 |
+
</div>
|
| 537 |
+
)}
|
| 538 |
</div>
|
| 539 |
|
| 540 |
<label className="flex items-center gap-3 p-4 rounded-2xl bg-white/5 cursor-pointer hover:bg-white/10 transition border border-white/5">
|
|
|
|
| 786 |
<pre className="whitespace-pre-wrap">{annex.content}</pre>
|
| 787 |
<div className="absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-black/60 to-transparent" />
|
| 788 |
</div>
|
| 789 |
+
<div className="grid grid-cols-2 gap-2">
|
| 790 |
+
<button
|
| 791 |
+
onClick={() => downloadAsPDF(annex)}
|
| 792 |
+
className="w-full py-2.5 rounded-xl bg-purple-500/10 border border-purple-500/20 text-[9px] font-bold text-purple-400 hover:bg-purple-500 hover:text-white transition uppercase tracking-widest shadow-lg shadow-purple-500/10"
|
| 793 |
+
>
|
| 794 |
+
Download PDF 📥
|
| 795 |
+
</button>
|
| 796 |
+
<button
|
| 797 |
+
onClick={() => {
|
| 798 |
+
const blob = new Blob([annex.content], { type: 'text/markdown' });
|
| 799 |
+
const url = window.URL.createObjectURL(blob);
|
| 800 |
+
const a = document.createElement('a');
|
| 801 |
+
a.href = url;
|
| 802 |
+
a.download = `${annex.name.replace(/ /g, '_')}.md`;
|
| 803 |
+
a.click();
|
| 804 |
+
}}
|
| 805 |
+
className="w-full py-2.5 rounded-xl bg-white/5 border border-white/10 text-[9px] font-bold text-slate-400 hover:text-white hover:bg-white/10 transition uppercase tracking-widest"
|
| 806 |
+
>
|
| 807 |
+
Download .md 📥
|
| 808 |
+
</button>
|
| 809 |
+
</div>
|
| 810 |
</div>
|
| 811 |
))}
|
| 812 |
</div>
|
frontend/package.json
CHANGED
|
@@ -11,7 +11,8 @@
|
|
| 11 |
"dependencies": {
|
| 12 |
"next": "14.2.5",
|
| 13 |
"react": "18.3.1",
|
| 14 |
-
"react-dom": "18.3.1"
|
|
|
|
| 15 |
},
|
| 16 |
"devDependencies": {
|
| 17 |
"@types/node": "20.14.2",
|
|
|
|
| 11 |
"dependencies": {
|
| 12 |
"next": "14.2.5",
|
| 13 |
"react": "18.3.1",
|
| 14 |
+
"react-dom": "18.3.1",
|
| 15 |
+
"jspdf": "^2.5.1"
|
| 16 |
},
|
| 17 |
"devDependencies": {
|
| 18 |
"@types/node": "20.14.2",
|