Á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 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
- <button
717
- onClick={() => {
718
- const blob = new Blob([annex.content], { type: 'text/markdown' });
719
- const url = window.URL.createObjectURL(blob);
720
- const a = document.createElement('a');
721
- a.href = url;
722
- a.download = `${annex.name.replace(/ /g, '_')}.md`;
723
- a.click();
724
- }}
725
- 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"
726
- >
727
- Download .md 📥
728
- </button>
 
 
 
 
 
 
 
 
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",