Álvaro Valenzuela Valdes commited on
Commit
b25238b
·
1 Parent(s): 863be56

feat: enhance tender details UI, fix scroll-to-top issues, and integrate Groq chat

Browse files
frontend/app/page.tsx CHANGED
@@ -58,10 +58,19 @@ export default function HomePage() {
58
 
59
  // Scroll to top when tab changes
60
  useEffect(() => {
61
- window.scrollTo(0, 0);
 
62
  if (contentRef.current) {
63
- contentRef.current.scrollTo(0, 0);
64
  }
 
 
 
 
 
 
 
 
65
  }, [activeTab]);
66
 
67
  useEffect(() => {
 
58
 
59
  // Scroll to top when tab changes
60
  useEffect(() => {
61
+ // Force immediate scroll
62
+ window.scrollTo({ top: 0, left: 0, behavior: 'instant' });
63
  if (contentRef.current) {
64
+ contentRef.current.scrollTo({ top: 0, left: 0, behavior: 'instant' });
65
  }
66
+
67
+ // Safety delay for async renders
68
+ const timer = setTimeout(() => {
69
+ window.scrollTo(0, 0);
70
+ if (contentRef.current) contentRef.current.scrollTo(0, 0);
71
+ }, 100);
72
+
73
+ return () => clearTimeout(timer);
74
  }, [activeTab]);
75
 
76
  useEffect(() => {
frontend/components/AgentAnalysis.tsx CHANGED
@@ -3,6 +3,7 @@
3
  import { useState, useEffect } from "react";
4
  import type { AnalysisResult, CompanyProfile, Tender } from "../lib/types";
5
  import { uploadDocument } from "../lib/api";
 
6
 
7
  type Props = {
8
  tender: Tender | null;
@@ -37,15 +38,7 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
37
  const [corral, setCorral] = useState<Array<{ file: File, text: string, analysis: AnalysisResult | null, id: string }>>([]);
38
  const [activeAnimalId, setActiveAnimalId] = useState<string | null>(null);
39
 
40
- // Auto-scroll to results when analysis is ready
41
- useEffect(() => {
42
- if (analysis && !isRunning) {
43
- const resultsSection = document.getElementById("analysis-results");
44
- if (resultsSection) {
45
- resultsSection.scrollIntoView({ behavior: "smooth", block: "start" });
46
- }
47
- }
48
- }, [analysis, isRunning]);
49
 
50
 
51
  const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -201,17 +194,62 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
201
  <p className="text-slate-400 text-lg leading-relaxed">{tender?.buyer}</p>
202
 
203
  {tender && (
204
- <div className="mt-8 flex flex-wrap gap-4">
205
- <div className="rounded-2xl bg-white/5 p-4 border border-white/5">
206
- <p className="text-[10px] uppercase text-slate-500 font-bold mb-1">Estimated Amount</p>
207
- <p className="text-lg font-semibold text-white">
208
- {tender.estimated_amount ? new Intl.NumberFormat("es-CL", { style: "currency", currency: "CLP", maximumFractionDigits: 0 }).format(tender.estimated_amount) : "Not disclosed"}
209
  </p>
210
  </div>
211
- <div className="rounded-2xl bg-white/5 p-4 border border-white/5">
212
- <p className="text-[10px] uppercase text-slate-500 font-bold mb-1">Closing Date</p>
213
- <p className="text-lg font-semibold text-white">{tender.closing_date}</p>
 
 
 
 
214
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  </div>
216
  )}
217
  </div>
@@ -526,6 +564,20 @@ export default function AgentAnalysis({ tender, companyProfile, analysis, onAnal
526
  </div>
527
  </div>
528
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
  </div>
530
  );
531
  }
 
3
  import { useState, useEffect } from "react";
4
  import type { AnalysisResult, CompanyProfile, Tender } from "../lib/types";
5
  import { uploadDocument } from "../lib/api";
6
+ import AgentChat from "./AgentChat";
7
 
8
  type Props = {
9
  tender: Tender | null;
 
38
  const [corral, setCorral] = useState<Array<{ file: File, text: string, analysis: AnalysisResult | null, id: string }>>([]);
39
  const [activeAnimalId, setActiveAnimalId] = useState<string | null>(null);
40
 
41
+ // Removed auto-scroll to keep user at the top during demo recordings
 
 
 
 
 
 
 
 
42
 
43
 
44
  const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
 
194
  <p className="text-slate-400 text-lg leading-relaxed">{tender?.buyer}</p>
195
 
196
  {tender && (
197
+ <div className="mt-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
198
+ <div className="rounded-2xl bg-white/5 p-4 border border-white/5 group hover:bg-white/10 transition-colors">
199
+ <p className="text-[10px] uppercase text-slate-500 font-bold mb-1 tracking-widest">Investment</p>
200
+ <p className="text-lg font-black text-white">
201
+ {tender.estimated_amount ? new Intl.NumberFormat("es-CL", { style: "currency", currency: tender.currency || "CLP", maximumFractionDigits: 0 }).format(tender.estimated_amount) : "N/A"}
202
  </p>
203
  </div>
204
+ <div className="rounded-2xl bg-white/5 p-4 border border-white/5 group hover:bg-white/10 transition-colors">
205
+ <p className="text-[10px] uppercase text-slate-500 font-bold mb-1 tracking-widest">Closing Date</p>
206
+ <p className="text-lg font-black text-white">{tender.closing_date || "TBD"}</p>
207
+ </div>
208
+ <div className="rounded-2xl bg-white/5 p-4 border border-white/5 group hover:bg-white/10 transition-colors">
209
+ <p className="text-[10px] uppercase text-slate-500 font-bold mb-1 tracking-widest">Region</p>
210
+ <p className="text-lg font-black text-white truncate" title={tender.region}>{tender.region || "Nacional"}</p>
211
  </div>
212
+ <div className="rounded-2xl bg-white/5 p-4 border border-white/5 group hover:bg-white/10 transition-colors">
213
+ <p className="text-[10px] uppercase text-slate-500 font-bold mb-1 tracking-widest">Sector</p>
214
+ <p className="text-lg font-black text-white truncate">{tender.sector || "General"}</p>
215
+ </div>
216
+ </div>
217
+ )}
218
+
219
+ {tender?.description && (
220
+ <div className="mt-8 p-6 rounded-2xl bg-white/[0.02] border border-white/5">
221
+ <h4 className="text-[10px] font-bold uppercase text-slate-500 mb-3 tracking-[0.2em]">Detailed Scope</h4>
222
+ <p className="text-sm text-slate-400 leading-relaxed max-h-32 overflow-y-auto custom-scrollbar pr-2 whitespace-pre-wrap">
223
+ {tender.description}
224
+ </p>
225
+ </div>
226
+ )}
227
+
228
+ {tender?.items && tender.items.length > 0 && (
229
+ <div className="mt-8 overflow-hidden rounded-2xl border border-white/5 bg-white/[0.01]">
230
+ <table className="w-full text-left text-[10px]">
231
+ <thead className="bg-white/5 text-slate-500 uppercase font-black tracking-widest">
232
+ <tr>
233
+ <th className="px-6 py-3">Item Name</th>
234
+ <th className="px-6 py-3 text-right">Qty</th>
235
+ </tr>
236
+ </thead>
237
+ <tbody className="divide-y divide-white/5">
238
+ {tender.items.slice(0, 3).map((item, idx) => (
239
+ <tr key={idx} className="hover:bg-white/[0.02]">
240
+ <td className="px-6 py-3 text-slate-300 font-medium truncate max-w-[200px]">{item.name}</td>
241
+ <td className="px-6 py-3 text-right text-cyan font-mono font-bold">{item.quantity} {item.unit}</td>
242
+ </tr>
243
+ ))}
244
+ {tender.items.length > 3 && (
245
+ <tr>
246
+ <td colSpan={2} className="px-6 py-2 text-center text-[9px] text-slate-600 italic">
247
+ + {tender.items.length - 3} more items...
248
+ </td>
249
+ </tr>
250
+ )}
251
+ </tbody>
252
+ </table>
253
  </div>
254
  )}
255
  </div>
 
564
  </div>
565
  </div>
566
  )}
567
+
568
+ {/* Expert Consultation Chat */}
569
+ {tender && (
570
+ <div className="mt-12 animate-in fade-in slide-in-from-bottom-8 duration-700">
571
+ <div className="flex items-center gap-4 mb-6 px-2">
572
+ <div className="w-10 h-10 rounded-2xl bg-purple-500/10 flex items-center justify-center text-xl shadow-lg shadow-purple-500/10">💬</div>
573
+ <div>
574
+ <h3 className="text-2xl font-black text-white tracking-tight">Expert Agent Consultation</h3>
575
+ <p className="text-slate-500 text-sm">Deep-dive into specific questions with our specialized AI agents.</p>
576
+ </div>
577
+ </div>
578
+ <AgentChat tender={tender} companyProfile={companyProfile} />
579
+ </div>
580
+ )}
581
  </div>
582
  );
583
  }
frontend/components/AgentChat.tsx CHANGED
@@ -21,8 +21,9 @@ const agents = [
21
  ];
22
 
23
  const models = [
 
 
24
  "Gemini 2.5 Flash",
25
- "DeepSeek-V3.2 (Featherless)",
26
  "Qwen-2.5 (Featherless)",
27
  ];
28
 
 
21
  ];
22
 
23
  const models = [
24
+ "Llama-3.3-70B (Groq)",
25
+ "Llama-3.1-8B (Groq)",
26
  "Gemini 2.5 Flash",
 
27
  "Qwen-2.5 (Featherless)",
28
  ];
29
 
frontend/components/TenderSearch.tsx CHANGED
@@ -69,6 +69,15 @@ export default function TenderSearch({ tenders, onSearch, onAnalyze, forceShowFo
69
  localStorage.setItem('andes_followed_tenders_full', JSON.stringify(followedTenders));
70
  }, [followedTenders]);
71
 
 
 
 
 
 
 
 
 
 
72
  const toggleFollow = (tender: Tender) => {
73
  setFollowedTenders(prev => {
74
  const isFollowing = prev.some(t => t.code === tender.code);
 
69
  localStorage.setItem('andes_followed_tenders_full', JSON.stringify(followedTenders));
70
  }, [followedTenders]);
71
 
72
+ // Force scroll to top when opening detail modal
73
+ useEffect(() => {
74
+ if (selectedTenderForModal) {
75
+ window.scrollTo({ top: 0, behavior: 'instant' });
76
+ const timer = setTimeout(() => window.scrollTo(0, 0), 100);
77
+ return () => clearTimeout(timer);
78
+ }
79
+ }, [selectedTenderForModal]);
80
+
81
  const toggleFollow = (tender: Tender) => {
82
  setFollowedTenders(prev => {
83
  const isFollowing = prev.some(t => t.code === tender.code);