ValerioBotto commited on
Commit
0b0b69b
·
verified ·
1 Parent(s): 7eb8d70

Update frontend/src/App.tsx

Browse files
Files changed (1) hide show
  1. frontend/src/App.tsx +47 -93
frontend/src/App.tsx CHANGED
@@ -4,7 +4,7 @@ import { AppStep } from './types';
4
  import { IconFileUp, IconSend, IconBot, IconGear, IconSparkles, IconArrowRight, IconLayers, IconDatabase, IconWorkflow, IconGlobe } from './components/Icons';
5
  import ReactMarkdown from 'react-markdown';
6
 
7
- // COSTANTE BACKEND: Lasciata vuota perché frontend e backend sono nello stesso container su HF
8
  const BACKEND_URL = '';
9
 
10
  const App: React.FC = () => {
@@ -157,20 +157,14 @@ const App: React.FC = () => {
157
 
158
  {/* Header */}
159
  <header className={`glass-header fixed top-0 w-full z-50 py-3 md:py-4 px-4 md:px-12 flex justify-between items-center transition-opacity ${step === AppStep.CHAT || step === AppStep.HOW_IT_WORKS ? 'opacity-30 hover:opacity-100' : 'opacity-100'}`}>
160
- <div
161
- onClick={handleHomeClick}
162
- className="flex items-center gap-2 cursor-pointer group"
163
- >
164
  <div className="bg-[#FF6600] text-white p-1.5 md:p-2 rounded-lg group-hover:scale-105 transition-transform flex items-center justify-center">
165
  <IconBot className="w-6 h-6 md:w-8 h-8" />
166
  </div>
167
  <span className="text-xl md:text-2xl font-extrabold tracking-tighter transition-colors group-hover:text-[#FF6600]">RAG Chatbot</span>
168
  </div>
169
  <div>
170
- <button
171
- onClick={handleHowItWorksClick}
172
- className="group bg-[#FF6600] text-white w-10 h-10 md:w-auto md:px-4 md:py-2 rounded-lg font-bold text-sm hover:bg-black transition-all active:scale-95 shadow-lg shadow-[#FF6600]/20 flex items-center justify-center"
173
- >
174
  <span className="md:hidden text-black font-black text-lg group-hover:text-white transition-colors">?</span>
175
  <span className="hidden md:inline">Come funziona</span>
176
  </button>
@@ -182,7 +176,7 @@ const App: React.FC = () => {
182
 
183
  <div className={`w-full ${(step === AppStep.CHAT || step === AppStep.HOW_IT_WORKS) ? 'h-full flex flex-col' : 'max-w-4xl'}`}>
184
 
185
- {/* INITIAL STEPS (NAME & UPLOAD) */}
186
  {(step === AppStep.NAME_INPUT || step === AppStep.PDF_UPLOAD) && (
187
  <div className="text-center mb-10 md:mb-12 animate-in fade-in slide-in-from-bottom-4 duration-700 px-6">
188
  <h1 className="text-4xl md:text-6xl font-black mb-4 leading-tight">
@@ -195,10 +189,10 @@ const App: React.FC = () => {
195
 
196
  <div className="flex flex-row items-center justify-center gap-2 md:gap-8 mb-12">
197
  <div className="flex flex-col items-center gap-3 w-20 md:w-32 group cursor-default">
198
- <div className={`w-12 h-12 md:w-16 md:h-16 rounded-full border-2 flex items-center justify-center transition-all bg-gray-50 border-gray-100 text-gray-400 group-hover:text-[#FF6600] group-hover:border-[#FF6600]/30 group-hover:bg-[#FF6600]/5`}>
199
  <IconFileUp className="w-5 h-5 md:w-7 md:h-7" />
200
  </div>
201
- <p className={`text-[8px] md:text-[10px] font-bold tracking-widest uppercase transition-colors text-gray-400 group-hover:text-[#FF6600]`}>1. Upload</p>
202
  </div>
203
  <div><IconArrowRight className="text-gray-200 w-3 h-3 md:w-5 md:h-5 animate-pulse" /></div>
204
  <div className="flex flex-col items-center gap-3 w-20 md:w-32 group cursor-default">
@@ -223,18 +217,11 @@ const App: React.FC = () => {
223
 
224
  {step === AppStep.NAME_INPUT && (
225
  <div className="flex justify-center py-2 animate-in fade-in slide-in-from-top-4 duration-500 px-6">
226
- <div className="bg-white border border-black/5 p-8 md:p-10 rounded-[2.5rem] shadow-2xl max-w-md w-full orange-glow transition-all">
227
  <form onSubmit={handleNameSubmit} className="flex flex-col gap-6 md:gap-8">
228
  <div>
229
  <label className="block text-xs font-bold uppercase tracking-[0.2em] text-gray-400 mb-4 ml-2">Inserisci il tuo nome o User ID</label>
230
- <input
231
- type="text"
232
- value={userData.name}
233
- onChange={(e) => setUserData({...userData, name: e.target.value})}
234
- placeholder="Es: Laura"
235
- className="w-full bg-gray-50 border-2 border-transparent focus:border-[#FF6600]/30 focus:bg-white outline-none px-6 md:px-8 py-4 md:py-5 rounded-[1.5rem] text-lg md:text-xl font-semibold text-black placeholder:text-gray-300 transition-all shadow-sm"
236
- autoFocus
237
- />
238
  </div>
239
  <button type="submit" disabled={!userData.name.trim()} className="bg-[#FF6600] text-white font-bold py-4 md:py-5 rounded-[1.5rem] hover:bg-black shadow-lg shadow-[#FF6600]/20 transition-all transform active:scale-95 disabled:opacity-30">Continua</button>
240
  </form>
@@ -245,7 +232,7 @@ const App: React.FC = () => {
245
  {step === AppStep.PDF_UPLOAD && (
246
  <div className="flex flex-col items-center py-2 animate-in fade-in slide-in-from-bottom-8 duration-700 px-6">
247
  <div className="text-center mb-8">
248
- <h3 className="text-2xl md:text-3xl font-black text-black mb-2 tracking-tighter uppercase font-outfit">Ciao <span className="text-[#FF6600]">{userData.name}</span></h3>
249
  <p className="text-gray-400 font-medium">Siamo pronti ad analizzare il tuo file.</p>
250
  </div>
251
  <div className="bg-white border-2 border-dashed border-gray-200 p-8 md:p-12 rounded-[3rem] max-w-2xl w-full flex flex-col items-center justify-center gap-6 hover:border-[#FF6600] group transition-all cursor-pointer relative shadow-sm">
@@ -295,41 +282,28 @@ const App: React.FC = () => {
295
  <div className="flex flex-col gap-1 pt-1">
296
  <h2 className="text-xl font-normal text-gray-500 font-verdana leading-tight">
297
  <span className="text-[#FF6600] font-medium">{userData.name}</span>, il file{" "}
298
- <span className="text-black font-medium hover:text-[#FF6600] cursor-pointer transition-colors" title={userData.fileName}>
299
  "{truncateFileName(userData.fileName || '', 40)}"
300
- </span>{" "}
301
- è pronto.
302
  </h2>
303
- <p className="text-sm font-light text-gray-400">
304
- Chiedimi qualunque cosa!
305
- </p>
306
  </div>
307
  </div>
308
  </div>
309
  );
310
  }
311
-
312
  const isUser = msg.role === 'user';
313
  return (
314
- <div
315
- key={idx}
316
- className={`flex ${isUser ? 'justify-end' : 'justify-start'} animate-in fade-in duration-300`}
317
- >
318
  <div className={`flex gap-4 max-w-[85%] ${isUser ? 'flex-row-reverse' : 'flex-row'}`}>
319
  {!isUser && (
320
  <div className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center flex-shrink-0 mt-1">
321
  <IconBot className="w-7 h-7" />
322
  </div>
323
  )}
324
- <div className={`p-4 ${
325
- isUser
326
- ? 'bg-gray-100 text-gray-800 rounded-2xl rounded-tr-none'
327
- : 'text-gray-700 leading-relaxed text-[15px] markdown-container'
328
- }`}>
329
  <div className="font-verdana">
330
- <ReactMarkdown>
331
- {msg.content}
332
- </ReactMarkdown>
333
  </div>
334
  <p className="text-[9px] mt-2 opacity-30 font-bold tracking-tighter">
335
  {msg.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
@@ -358,54 +332,30 @@ const App: React.FC = () => {
358
 
359
  <div className="px-6 pb-10 pt-4 bg-white">
360
  <form onSubmit={handleSendMessage} className="relative max-w-2xl mx-auto">
361
- <div className="relative flex items-center bg-gray-50 border border-gray-100 rounded-[2.5rem] p-2 focus-within:bg-white focus-within:ring-2 focus-within:ring-[#FF6600]/10 transition-all shadow-sm">
362
  <div className="relative flex-shrink-0">
363
- <button
364
- type="button"
365
- onClick={() => setShowPlusMenu(!showPlusMenu)}
366
- className={`w-12 h-12 flex items-center justify-center rounded-full transition-all ${showPlusMenu ? 'bg-black text-white' : 'bg-[#FF6600] text-white hover:bg-black'}`}
367
- >
368
  <span className={`text-2xl transition-transform font-light ${showPlusMenu ? 'rotate-45' : ''}`}>+</span>
369
  </button>
370
  {showPlusMenu && (
371
- <div className="absolute bottom-full left-0 mb-4 bg-white border border-gray-100 rounded-2xl shadow-xl p-2 min-w-[150px] animate-in fade-in slide-in-from-bottom-2">
372
- <button
373
- onClick={() => {
374
- setStep(AppStep.PDF_UPLOAD);
375
- setShowPlusMenu(false);
376
- }}
377
- className="w-full text-left px-4 py-3 text-sm font-semibold hover:bg-gray-50 rounded-xl transition-colors flex items-center gap-3 text-gray-700"
378
- >
379
- <IconFileUp className="w-4 h-4 text-[#FF6600]" />
380
- Cambia PDF
381
  </button>
382
  </div>
383
  )}
384
  </div>
385
- <input
386
- type="text"
387
- value={inputValue}
388
- onChange={(e) => setInputValue(e.target.value)}
389
- placeholder="Scrivi un messaggio..."
390
- className="flex-grow bg-transparent border-none outline-none px-4 md:px-6 py-4 text-[14px] md:text-[15px] text-gray-800 placeholder:text-gray-300 font-verdana"
391
- />
392
- <button
393
- type="submit"
394
- disabled={!inputValue.trim() || isTyping}
395
- className="text-[#FF6600] w-10 h-10 md:w-12 md:h-12 flex items-center justify-center flex-shrink-0 rounded-full hover:bg-[#FF6600]/10 disabled:opacity-10 transition-all"
396
- >
397
  <IconSend className="w-5 h-5 md:w-6 md:h-6" />
398
  </button>
399
  </div>
400
- <p className="text-[6px] text-center text-gray-300 mt-4 tracking-[0.2em] uppercase font-bold px-4">
401
- Qualsiasi sistema, per quanto sofisticato sia, può commettere errori e non può sostituirsi ad un attenta lettura del documento
402
- </p>
403
  </form>
404
  </div>
405
  </div>
406
  )}
407
 
408
- {/* HOW IT WORKS STEP - AGGIORNATA */}
409
  {step === AppStep.HOW_IT_WORKS && (
410
  <div className="flex-grow overflow-y-auto px-6 py-10 chat-scroll animate-in fade-in duration-700">
411
  <div className="max-w-4xl mx-auto space-y-12 pb-20">
@@ -415,52 +365,57 @@ const App: React.FC = () => {
415
  </div>
416
 
417
  <div className="grid md:grid-cols-2 gap-8">
 
418
  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
419
  <div className="bg-[#FF6600] text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
420
  <IconFileUp className="w-6 h-6" />
421
  </div>
422
  <h3 className="text-xl font-bold mb-3">Ingestion & Struttura</h3>
423
  <p className="text-gray-600 font-light text-sm leading-relaxed">
424
- Il PDF viene processato per estrarre la <strong>gerarchia logica</strong>. Ricostruiamo tabelle e sezioni, garantendo che l'informazione non perda mai il suo contesto originale durante la scomposizione.
425
  </p>
426
  </div>
427
 
 
428
  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
429
  <div className="bg-black text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
430
  <IconLayers className="w-6 h-6" />
431
  </div>
432
- <h3 className="text-xl font-bold mb-3">Chunking Ricorsivo</h3>
433
  <p className="text-gray-600 font-light text-sm leading-relaxed">
434
- Il testo viene diviso in frammenti ottimizzati per gli embedding, mantenendo sovrapposizioni intelligenti per non interrompere la coerenza semantica tra i nodi del database.
435
  </p>
436
  </div>
437
 
 
438
  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
439
  <div className="bg-black text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
440
  <IconDatabase className="w-6 h-6" />
441
  </div>
442
  <h3 className="text-xl font-bold mb-3">Knowledge Graph (Neo4j)</h3>
443
  <p className="text-gray-600 font-light text-sm leading-relaxed">
444
- I frammenti diventano nodi in <strong>Neo4j</strong> collegati per relazione logica e semantica, permettendo una navigazione dei dati multimodale a grafo.
445
  </p>
446
  </div>
447
 
 
448
  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
449
  <div className="bg-black text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
450
  <IconWorkflow className="w-6 h-6" />
451
  </div>
452
- <h3 className="text-xl font-bold mb-3">LangGraph Orchestration</h3>
453
  <p className="text-gray-500 font-light text-sm leading-relaxed">
454
- L'intero flusso è gestito da un sistema a stati finiti che coordina i nodi di analisi, recupero e validazione per percorsi logici rigorosi.
455
  </p>
456
  </div>
457
  </div>
458
 
 
459
  <div className="bg-gray-50 border border-gray-100 p-10 rounded-[3rem] shadow-inner">
460
  <div className="text-center mb-10">
461
  <h3 className="text-2xl font-black mb-2 font-outfit">Recupero <span className="text-[#FF6600]">Ibrido & Intelligente</span></h3>
462
  <p className="text-gray-500 text-sm font-light max-w-2xl mx-auto">
463
- L'LLM agisce come Orchestratore Decisionale per determinare la rotta di recupero ottimale.
464
  </p>
465
  </div>
466
 
@@ -469,19 +424,20 @@ const App: React.FC = () => {
469
  <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
470
  <div className="flex items-center gap-3 mb-3">
471
  <div className="bg-[#FF6600]/10 px-2 py-1 rounded md font-bold text-[#FF6600] text-[10px] uppercase">Routing</div>
472
- <h4 className="font-bold text-sm">Mistral Strategic Router</h4>
473
  </div>
474
  <p className="text-xs text-gray-600 leading-relaxed font-light">
475
- Utilizziamo Mistral per decidere la via di recupero: se la domanda contiene entità specifiche, formula una <strong>Query Cypher</strong> diretta.
476
  </p>
477
  </div>
 
478
  <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
479
  <div className="flex items-center gap-3 mb-3">
480
  <div className="bg-black/5 px-2 py-1 rounded md font-bold text-gray-500 text-[10px] uppercase">Vector</div>
481
  <h4 className="font-bold text-sm">Ricerca Vettoriale</h4>
482
  </div>
483
  <p className="text-xs text-gray-600 leading-relaxed font-light">
484
- Attivata per quesiti concettuali o descrittivi per identificare i frammenti semanticamente più vicini alla query.
485
  </p>
486
  </div>
487
  </div>
@@ -490,19 +446,20 @@ const App: React.FC = () => {
490
  <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
491
  <div className="flex items-center gap-3 mb-3">
492
  <div className="bg-[#FF6600]/10 px-2 py-1 rounded md font-bold text-[#FF6600] text-[10px] uppercase">Entities</div>
493
- <h4 className="font-bold text-sm">GLiNER Extraction</h4>
494
  </div>
495
  <p className="text-xs text-gray-600 leading-relaxed font-light">
496
- Identifichiamo entità chiave (nomi, luoghi, termini tecnici) per mappare accuratamente la query sul grafo di conoscenza.
497
  </p>
498
  </div>
 
499
  <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
500
  <div className="flex items-center gap-3 mb-3">
501
  <div className="bg-black/5 px-2 py-1 rounded md font-bold text-gray-500 text-[10px] uppercase">Rerank</div>
502
- <h4 className="font-bold text-sm">BGE-Reranker-v2-m3</h4>
503
  </div>
504
  <p className="text-xs text-gray-600 leading-relaxed font-light">
505
- I risultati grezzi vengono filtrati da un <strong>Cross-Encoder</strong> per garantire che solo il contesto più pertinente arrivi alla sintesi.
506
  </p>
507
  </div>
508
  </div>
@@ -516,7 +473,7 @@ const App: React.FC = () => {
516
  </div>
517
  <h3 className="text-2xl font-black mb-3 font-outfit">Ricerca Globale</h3>
518
  <p className="text-gray-600 font-light text-sm leading-relaxed">
519
- Se lo score di pertinenza locale è basso, il sistema attiva automaticamente una ricerca globale sull'intera base di conoscenza del documento.
520
  </p>
521
  </div>
522
  </div>
@@ -527,15 +484,12 @@ const App: React.FC = () => {
527
  </div>
528
  <h3 className="text-2xl font-black font-outfit">Sintesi Finale</h3>
529
  <p className="text-gray-600 font-light leading-relaxed">
530
- L'LLM finale agisce esclusivamente da sintetizzatore, producendo una risposta ancorata a fonti certe e validate dal processo di recupero.
531
  </p>
532
  </div>
533
 
534
  <div className="flex justify-center pt-10">
535
- <button
536
- onClick={returnFromHowItWorks}
537
- className="bg-black text-white px-12 py-5 rounded-2xl font-bold hover:bg-[#FF6600] transition-all shadow-xl active:scale-95"
538
- >
539
  Torna alla Chat
540
  </button>
541
  </div>
 
4
  import { IconFileUp, IconSend, IconBot, IconGear, IconSparkles, IconArrowRight, IconLayers, IconDatabase, IconWorkflow, IconGlobe } from './components/Icons';
5
  import ReactMarkdown from 'react-markdown';
6
 
7
+ // COSTANTE BACKEND: Vuota perché il frontend è servito dallo stesso server su HF
8
  const BACKEND_URL = '';
9
 
10
  const App: React.FC = () => {
 
157
 
158
  {/* Header */}
159
  <header className={`glass-header fixed top-0 w-full z-50 py-3 md:py-4 px-4 md:px-12 flex justify-between items-center transition-opacity ${step === AppStep.CHAT || step === AppStep.HOW_IT_WORKS ? 'opacity-30 hover:opacity-100' : 'opacity-100'}`}>
160
+ <div onClick={handleHomeClick} className="flex items-center gap-2 cursor-pointer group">
 
 
 
161
  <div className="bg-[#FF6600] text-white p-1.5 md:p-2 rounded-lg group-hover:scale-105 transition-transform flex items-center justify-center">
162
  <IconBot className="w-6 h-6 md:w-8 h-8" />
163
  </div>
164
  <span className="text-xl md:text-2xl font-extrabold tracking-tighter transition-colors group-hover:text-[#FF6600]">RAG Chatbot</span>
165
  </div>
166
  <div>
167
+ <button onClick={handleHowItWorksClick} className="group bg-[#FF6600] text-white w-10 h-10 md:w-auto md:px-4 md:py-2 rounded-lg font-bold text-sm hover:bg-black transition-all active:scale-95 shadow-lg shadow-[#FF6600]/20 flex items-center justify-center">
 
 
 
168
  <span className="md:hidden text-black font-black text-lg group-hover:text-white transition-colors">?</span>
169
  <span className="hidden md:inline">Come funziona</span>
170
  </button>
 
176
 
177
  <div className={`w-full ${(step === AppStep.CHAT || step === AppStep.HOW_IT_WORKS) ? 'h-full flex flex-col' : 'max-w-4xl'}`}>
178
 
179
+ {/* INITIAL STEPS */}
180
  {(step === AppStep.NAME_INPUT || step === AppStep.PDF_UPLOAD) && (
181
  <div className="text-center mb-10 md:mb-12 animate-in fade-in slide-in-from-bottom-4 duration-700 px-6">
182
  <h1 className="text-4xl md:text-6xl font-black mb-4 leading-tight">
 
189
 
190
  <div className="flex flex-row items-center justify-center gap-2 md:gap-8 mb-12">
191
  <div className="flex flex-col items-center gap-3 w-20 md:w-32 group cursor-default">
192
+ <div className="w-12 h-12 md:w-16 md:h-16 rounded-full border-2 flex items-center justify-center transition-all bg-gray-50 border-gray-100 text-gray-400 group-hover:text-[#FF6600] group-hover:border-[#FF6600]/30 group-hover:bg-[#FF6600]/5">
193
  <IconFileUp className="w-5 h-5 md:w-7 md:h-7" />
194
  </div>
195
+ <p className="text-[8px] md:text-[10px] font-bold tracking-widest uppercase text-gray-400 group-hover:text-[#FF6600]">1. Upload</p>
196
  </div>
197
  <div><IconArrowRight className="text-gray-200 w-3 h-3 md:w-5 md:h-5 animate-pulse" /></div>
198
  <div className="flex flex-col items-center gap-3 w-20 md:w-32 group cursor-default">
 
217
 
218
  {step === AppStep.NAME_INPUT && (
219
  <div className="flex justify-center py-2 animate-in fade-in slide-in-from-top-4 duration-500 px-6">
220
+ <div className="bg-white border border-black/5 p-8 md:p-10 rounded-[2.5rem] shadow-2xl max-w-md w-full orange-glow">
221
  <form onSubmit={handleNameSubmit} className="flex flex-col gap-6 md:gap-8">
222
  <div>
223
  <label className="block text-xs font-bold uppercase tracking-[0.2em] text-gray-400 mb-4 ml-2">Inserisci il tuo nome o User ID</label>
224
+ <input type="text" value={userData.name} onChange={(e) => setUserData({...userData, name: e.target.value})} placeholder="Es: Laura" className="w-full bg-gray-50 border-2 border-transparent focus:border-[#FF6600]/30 focus:bg-white outline-none px-6 md:px-8 py-4 md:py-5 rounded-[1.5rem] text-lg md:text-xl font-semibold text-black shadow-sm" autoFocus />
 
 
 
 
 
 
 
225
  </div>
226
  <button type="submit" disabled={!userData.name.trim()} className="bg-[#FF6600] text-white font-bold py-4 md:py-5 rounded-[1.5rem] hover:bg-black shadow-lg shadow-[#FF6600]/20 transition-all transform active:scale-95 disabled:opacity-30">Continua</button>
227
  </form>
 
232
  {step === AppStep.PDF_UPLOAD && (
233
  <div className="flex flex-col items-center py-2 animate-in fade-in slide-in-from-bottom-8 duration-700 px-6">
234
  <div className="text-center mb-8">
235
+ <h3 className="text-2xl md:text-3xl font-black text-black mb-2 tracking-tighter uppercase">Ciao <span className="text-[#FF6600]">{userData.name}</span></h3>
236
  <p className="text-gray-400 font-medium">Siamo pronti ad analizzare il tuo file.</p>
237
  </div>
238
  <div className="bg-white border-2 border-dashed border-gray-200 p-8 md:p-12 rounded-[3rem] max-w-2xl w-full flex flex-col items-center justify-center gap-6 hover:border-[#FF6600] group transition-all cursor-pointer relative shadow-sm">
 
282
  <div className="flex flex-col gap-1 pt-1">
283
  <h2 className="text-xl font-normal text-gray-500 font-verdana leading-tight">
284
  <span className="text-[#FF6600] font-medium">{userData.name}</span>, il file{" "}
285
+ <span className="text-black font-medium hover:text-[#FF6600] cursor-pointer" title={userData.fileName}>
286
  "{truncateFileName(userData.fileName || '', 40)}"
287
+ </span> è pronto.
 
288
  </h2>
289
+ <p className="text-sm font-light text-gray-400">Chiedimi qualunque cosa!</p>
 
 
290
  </div>
291
  </div>
292
  </div>
293
  );
294
  }
 
295
  const isUser = msg.role === 'user';
296
  return (
297
+ <div key={idx} className={`flex ${isUser ? 'justify-end' : 'justify-start'} animate-in fade-in duration-300`}>
 
 
 
298
  <div className={`flex gap-4 max-w-[85%] ${isUser ? 'flex-row-reverse' : 'flex-row'}`}>
299
  {!isUser && (
300
  <div className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center flex-shrink-0 mt-1">
301
  <IconBot className="w-7 h-7" />
302
  </div>
303
  )}
304
+ <div className={`p-4 ${isUser ? 'bg-gray-100 text-gray-800 rounded-2xl rounded-tr-none' : 'text-gray-700 markdown-container'}`}>
 
 
 
 
305
  <div className="font-verdana">
306
+ <ReactMarkdown>{msg.content}</ReactMarkdown>
 
 
307
  </div>
308
  <p className="text-[9px] mt-2 opacity-30 font-bold tracking-tighter">
309
  {msg.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
 
332
 
333
  <div className="px-6 pb-10 pt-4 bg-white">
334
  <form onSubmit={handleSendMessage} className="relative max-w-2xl mx-auto">
335
+ <div className="relative flex items-center bg-gray-50 border border-gray-100 rounded-[2.5rem] p-2 focus-within:bg-white focus-within:ring-2 focus-within:ring-[#FF6600]/10 shadow-sm">
336
  <div className="relative flex-shrink-0">
337
+ <button type="button" onClick={() => setShowPlusMenu(!showPlusMenu)} className={`w-12 h-12 flex items-center justify-center rounded-full transition-all ${showPlusMenu ? 'bg-black text-white' : 'bg-[#FF6600] text-white hover:bg-black'}`}>
 
 
 
 
338
  <span className={`text-2xl transition-transform font-light ${showPlusMenu ? 'rotate-45' : ''}`}>+</span>
339
  </button>
340
  {showPlusMenu && (
341
+ <div className="absolute bottom-full left-0 mb-4 bg-white border border-gray-100 rounded-2xl shadow-xl p-2 min-w-[150px] animate-in fade-in">
342
+ <button onClick={() => { setStep(AppStep.PDF_UPLOAD); setShowPlusMenu(false); }} className="w-full text-left px-4 py-3 text-sm font-semibold hover:bg-gray-50 rounded-xl flex items-center gap-3 text-gray-700">
343
+ <IconFileUp className="w-4 h-4 text-[#FF6600]" /> Cambia PDF
 
 
 
 
 
 
 
344
  </button>
345
  </div>
346
  )}
347
  </div>
348
+ <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder="Scrivi un messaggio..." className="flex-grow bg-transparent border-none outline-none px-4 md:px-6 py-4 text-[14px] text-gray-800 font-verdana" />
349
+ <button type="submit" disabled={!inputValue.trim() || isTyping} className="text-[#FF6600] w-10 h-10 md:w-12 md:h-12 flex items-center justify-center flex-shrink-0 rounded-full hover:bg-[#FF6600]/10 disabled:opacity-10">
 
 
 
 
 
 
 
 
 
 
350
  <IconSend className="w-5 h-5 md:w-6 md:h-6" />
351
  </button>
352
  </div>
 
 
 
353
  </form>
354
  </div>
355
  </div>
356
  )}
357
 
358
+ {/* HOW IT WORKS STEP - AGGIORNATA CON LE TUE SPIEGAZIONI */}
359
  {step === AppStep.HOW_IT_WORKS && (
360
  <div className="flex-grow overflow-y-auto px-6 py-10 chat-scroll animate-in fade-in duration-700">
361
  <div className="max-w-4xl mx-auto space-y-12 pb-20">
 
365
  </div>
366
 
367
  <div className="grid md:grid-cols-2 gap-8">
368
+ {/* Step 1 */}
369
  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
370
  <div className="bg-[#FF6600] text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
371
  <IconFileUp className="w-6 h-6" />
372
  </div>
373
  <h3 className="text-xl font-bold mb-3">Ingestion & Struttura</h3>
374
  <p className="text-gray-600 font-light text-sm leading-relaxed">
375
+ Il PDF non viene solo "letto", ma processato per estrarre la <strong>gerarchia logica</strong>. Utilizziamo librerie specializzate per ricostruire tabelle, titoli e sezioni, garantendo che l'informazione non perda mai il suo contesto originale durante la scomposizione.
376
  </p>
377
  </div>
378
 
379
+ {/* Step 2 */}
380
  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
381
  <div className="bg-black text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
382
  <IconLayers className="w-6 h-6" />
383
  </div>
384
+ <h3 className="text-xl font-bold mb-3">Chunking</h3>
385
  <p className="text-gray-600 font-light text-sm leading-relaxed">
386
+ Applichiamo una strategia di <strong>chunking ricorsivo</strong>. Il testo viene diviso in frammenti ottimizzati per gli embedding, mantenendo sovrapposizioni intelligenti tra i paragrafi per non interrompere la coerenza semantica tra i nodi del database.
387
  </p>
388
  </div>
389
 
390
+ {/* Step 3 */}
391
  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
392
  <div className="bg-black text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
393
  <IconDatabase className="w-6 h-6" />
394
  </div>
395
  <h3 className="text-xl font-bold mb-3">Knowledge Graph (Neo4j)</h3>
396
  <p className="text-gray-600 font-light text-sm leading-relaxed">
397
+ I chunk e le entità vengono mappati in <strong>Neo4j</strong>. Ogni frammento diventa un nodo collegato non solo vettorialmente, ma anche per relazione logica (es. "appartiene alla sezione X"). Questo permette una navigazione dei dati multimodale: semantica e a grafo.
398
  </p>
399
  </div>
400
 
401
+ {/* Step 4 */}
402
  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
403
  <div className="bg-black text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
404
  <IconWorkflow className="w-6 h-6" />
405
  </div>
406
+ <h3 className="text-xl font-bold mb-3">LangGraph</h3>
407
  <p className="text-gray-500 font-light text-sm leading-relaxed">
408
+ L'intero flusso è gestito da <strong>LangGraph</strong>. Un sistema a stati finiti che coordina i nodi di analisi, recupero e validazione. Questo garantisce che ogni query segua un percorso logico rigoroso prima di produrre un output.
409
  </p>
410
  </div>
411
  </div>
412
 
413
+ {/* Sezione Strategia Ibrida */}
414
  <div className="bg-gray-50 border border-gray-100 p-10 rounded-[3rem] shadow-inner">
415
  <div className="text-center mb-10">
416
  <h3 className="text-2xl font-black mb-2 font-outfit">Recupero <span className="text-[#FF6600]">Ibrido & Intelligente</span></h3>
417
  <p className="text-gray-500 text-sm font-light max-w-2xl mx-auto">
418
+ l'LLM agisce esclusivamente come Orchestratore Decisionale: analizza l'intento della domanda per determinare la rotta di recupero ottimale. Non 'pesca' i dati dalla sua memoria, non 'inventa' nulla. Piuttosto, sceglie se azionare il motore di Ricerca Vettoriale o se comporre una Query per interrogare direttamente il DB.
419
  </p>
420
  </div>
421
 
 
424
  <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
425
  <div className="flex items-center gap-3 mb-3">
426
  <div className="bg-[#FF6600]/10 px-2 py-1 rounded md font-bold text-[#FF6600] text-[10px] uppercase">Routing</div>
427
+ <h4 className="font-bold text-sm">Mistral come Router Strategico</h4>
428
  </div>
429
  <p className="text-xs text-gray-600 leading-relaxed font-light">
430
+ Utilizziamo <strong>Mistral</strong> per decidere la via di recupero. Se la domanda contiene entità specifiche già mappate nel DB, Mistral formula una <strong>Query Cypher</strong> basata sulle NE estratte, ottenendo dati certi in millisecondi direttamente dal database a grafo. Altrimenti, si prioritizza una strategia di recupero vettoriale, o ibrida.
431
  </p>
432
  </div>
433
+
434
  <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
435
  <div className="flex items-center gap-3 mb-3">
436
  <div className="bg-black/5 px-2 py-1 rounded md font-bold text-gray-500 text-[10px] uppercase">Vector</div>
437
  <h4 className="font-bold text-sm">Ricerca Vettoriale</h4>
438
  </div>
439
  <p className="text-xs text-gray-600 leading-relaxed font-light">
440
+ Il sistema attiva una ricerca vettoriale quando il router identifica quesiti di natura concettuale o descrittiva. Utilizziamo il modello <strong>multilingual-e5-large</strong> (1024 dim), permettendo di individuare correlazioni semantiche profonde tra la domanda e il documento.
441
  </p>
442
  </div>
443
  </div>
 
446
  <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
447
  <div className="flex items-center gap-3 mb-3">
448
  <div className="bg-[#FF6600]/10 px-2 py-1 rounded md font-bold text-[#FF6600] text-[10px] uppercase">Entities</div>
449
+ <h4 className="font-bold text-sm">Named Entity Extraction (GLiNER)</h4>
450
  </div>
451
  <p className="text-xs text-gray-600 leading-relaxed font-light">
452
+ Sfruttiamo <strong>GLiNER</strong> per identificare entità chiave (nomi, luoghi, termini tecnici). Queste entità permettono a Mistral di generare istantaneamente Query Cypher mirate, garantendo un recupero dati deterministico direttamente dal database a grafo Neo4j.
453
  </p>
454
  </div>
455
+
456
  <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
457
  <div className="flex items-center gap-3 mb-3">
458
  <div className="bg-black/5 px-2 py-1 rounded md font-bold text-gray-500 text-[10px] uppercase">Rerank</div>
459
+ <h4 className="font-bold text-sm">Reranking-v2-m3</h4>
460
  </div>
461
  <p className="text-xs text-gray-600 leading-relaxed font-light">
462
+ I risultati grezzi del database vengono filtrati da un <strong>Cross-Encoder (BGE-Reranker-v2-m3)</strong>. Questo modello ri-analizza la pertinenza di ogni chunk rispetto alla domanda originale, scartando il "rumore" e passando solo il contesto più pertinente.
463
  </p>
464
  </div>
465
  </div>
 
473
  </div>
474
  <h3 className="text-2xl font-black mb-3 font-outfit">Ricerca Globale</h3>
475
  <p className="text-gray-600 font-light text-sm leading-relaxed">
476
+ Se la ricerca vettoriale sul documento attivo produce uno score di pertinenza basso (inferiore a 0.7), il sistema attiva automaticamente una <strong>Ricerca globale</strong>. Questa scansione estende l'analisi a tutti i documenti precedentemente indicizzati nel database Neo4j per verificare se l'informazione necessaria è presente in altri PDF.
477
  </p>
478
  </div>
479
  </div>
 
484
  </div>
485
  <h3 className="text-2xl font-black font-outfit">Sintesi Finale</h3>
486
  <p className="text-gray-600 font-light leading-relaxed">
487
+ L'LLM finale non genera testo libero ma agisce esclusivamente da sintetizzatore: riceve i dati filtrati dal Reranker e produce una risposta che è una diretta conseguenza dei documenti. Ogni affermazione è ancorata a una fonte certa, riducendo drasticamente il rischio di allucinazioni.
488
  </p>
489
  </div>
490
 
491
  <div className="flex justify-center pt-10">
492
+ <button onClick={returnFromHowItWorks} className="bg-black text-white px-12 py-5 rounded-2xl font-bold hover:bg-[#FF6600] transition-all shadow-xl active:scale-95">
 
 
 
493
  Torna alla Chat
494
  </button>
495
  </div>