File size: 33,436 Bytes
aabd32c
 
 
 
 
 
0b0b69b
42b3ce6
558297d
aabd32c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558297d
aabd32c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558297d
aabd32c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558297d
aabd32c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0b0b69b
aabd32c
 
 
 
 
 
0b0b69b
aabd32c
 
 
 
 
 
 
 
 
 
 
0b0b69b
aabd32c
 
 
 
 
 
 
 
 
 
 
 
0b0b69b
aabd32c
 
0b0b69b
aabd32c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0b0b69b
aabd32c
 
 
0b0b69b
aabd32c
 
 
 
 
 
 
 
 
 
0b0b69b
aabd32c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0b0b69b
aabd32c
0b0b69b
aabd32c
0b0b69b
aabd32c
 
 
 
 
 
 
0b0b69b
aabd32c
 
 
 
 
 
0b0b69b
aabd32c
0b0b69b
aabd32c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0b0b69b
aabd32c
0b0b69b
aabd32c
 
 
0b0b69b
 
 
aabd32c
 
 
 
0b0b69b
 
aabd32c
 
 
 
 
 
 
 
0b0b69b
aabd32c
 
 
 
 
 
 
 
 
0b0b69b
aabd32c
 
 
 
 
 
0b0b69b
aabd32c
 
 
0b0b69b
aabd32c
 
 
 
0b0b69b
aabd32c
0b0b69b
aabd32c
 
 
0b0b69b
aabd32c
 
 
 
 
 
0b0b69b
aabd32c
 
 
0b0b69b
aabd32c
 
 
 
0b0b69b
aabd32c
0b0b69b
aabd32c
 
 
42b3ce6
0b0b69b
42b3ce6
 
 
 
0b0b69b
42b3ce6
 
 
 
 
 
 
 
0b0b69b
42b3ce6
 
0b0b69b
42b3ce6
 
0b0b69b
42b3ce6
 
 
 
 
 
0b0b69b
42b3ce6
 
 
 
 
 
 
 
0b0b69b
42b3ce6
 
0b0b69b
42b3ce6
 
0b0b69b
42b3ce6
 
 
0b0b69b
42b3ce6
 
0b0b69b
42b3ce6
 
 
 
 
 
 
 
 
 
 
 
 
0b0b69b
42b3ce6
 
 
 
 
 
 
 
 
 
0b0b69b
42b3ce6
 
 
 
0b0b69b
42b3ce6
 
 
aabd32c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
import React, { useState, useEffect, useRef } from 'react';
import type { Message, UserData } from './types';
import { AppStep } from './types';
import { IconFileUp, IconSend, IconBot, IconGear, IconSparkles, IconArrowRight, IconLayers, IconDatabase, IconWorkflow, IconGlobe } from './components/Icons';
import ReactMarkdown from 'react-markdown';

// COSTANTE BACKEND: Vuota perché il frontend è servito dallo stesso server su HF
const BACKEND_URL = '';

const App: React.FC = () => {
  const [step, setStep] = useState<AppStep>(AppStep.NAME_INPUT);
  const [previousStep, setPreviousStep] = useState<AppStep | null>(null);
  const [userData, setUserData] = useState<UserData>({ name: '' });
  const [progress, setProgress] = useState(0);
  const [messages, setMessages] = useState<Message[]>([]);
  const [inputValue, setInputValue] = useState('');
  const [isTyping, setIsTyping] = useState(false);
  const [showPlusMenu, setShowPlusMenu] = useState(false);
  const chatEndRef = useRef<HTMLDivElement>(null);

  const scrollToBottom = () => {
    chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  };

  useEffect(() => {
    scrollToBottom();
  }, [messages, isTyping]);

  const handleNameSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (userData.name.trim()) {
      setStep(AppStep.PDF_UPLOAD);
    }
  };

  const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (file) {
      setUserData({ ...userData, fileName: file.name });
      setStep(AppStep.PROCESSING);
      setProgress(10); 

      const formData = new FormData();
      formData.append('file', file);
      formData.append('user_id', userData.name);

      const progressInterval = setInterval(() => {
        setProgress(prev => (prev < 90 ? prev + 5 : prev));
      }, 800);

      try {
        const response = await fetch(`${BACKEND_URL}/upload`, {
          method: 'POST',
          body: formData,
        });

        clearInterval(progressInterval);

        if (response.ok) {
          setProgress(100);
          setTimeout(() => {
            setStep(AppStep.CHAT);
            setMessages([{ 
              role: 'ai', 
              content: 'PRESET_WELCOME', 
              timestamp: new Date() 
            }]);
          }, 600);
        } else {
          throw new Error("Errore durante l'elaborazione del server");
        }
      } catch (error) {
        clearInterval(progressInterval);
        console.error("Errore durante l'ingestione:", error);
        alert("Errore nel caricamento del file. Assicurati che il backend sia attivo.");
        setStep(AppStep.PDF_UPLOAD);
      }
    }
  };

  const truncateFileName = (name: string, limit = 25) => {
    if (name.length <= limit) return name;
    return name.substring(0, limit) + '...';
  };

  const handleHomeClick = () => {
    setStep(AppStep.NAME_INPUT);
    setUserData({ name: '' });
    setMessages([]);
    setProgress(0);
  };

  const handleHowItWorksClick = () => {
    setPreviousStep(step);
    setStep(AppStep.HOW_IT_WORKS);
  };

  const returnFromHowItWorks = () => {
    if (previousStep) {
      setStep(previousStep);
    } else {
      setStep(AppStep.NAME_INPUT);
    }
  };

  const handleSendMessage = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!inputValue.trim() || isTyping) return;

    const userMsg: Message = {
      role: 'user',
      content: inputValue,
      timestamp: new Date()
    };

    setMessages(prev => [...prev, userMsg]);
    setInputValue('');
    setIsTyping(true);

    try {
      const queryParams = new URLSearchParams({
        query: inputValue,
        filename: userData.fileName || "documento",
        user_id: userData.name
      });

      const response = await fetch(`${BACKEND_URL}/chat?${queryParams}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
      });

      if (!response.ok) throw new Error('Errore nella risposta del server');

      const data = await response.json();

      setMessages(prev => [...prev, {
        role: 'ai',
        content: data.answer,
        timestamp: new Date()
      }]);
    } catch (error) {
      console.error("Errore chat:", error);
      setMessages(prev => [...prev, {
        role: 'ai',
        content: "Scusa, si è verificato un errore nel collegamento con il server. Assicurati che il backend sia attivo.",
        timestamp: new Date()
      }]);
    } finally {
      setIsTyping(false);
    }
  };

  const isInitialPage = step === AppStep.NAME_INPUT || step === AppStep.PDF_UPLOAD || step === AppStep.PROCESSING;

  return (
    <div className="min-h-screen w-screen flex flex-col font-outfit bg-white overflow-x-hidden">
      
      {/* Header */}
      <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'}`}>
        <div onClick={handleHomeClick} className="flex items-center gap-2 cursor-pointer group">
          <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">
            <IconBot className="w-6 h-6 md:w-8 h-8" />
          </div>
          <span className="text-xl md:text-2xl font-extrabold tracking-tighter transition-colors group-hover:text-[#FF6600]">RAG Chatbot</span>
        </div>
        <div>
          <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">
            <span className="md:hidden text-black font-black text-lg group-hover:text-white transition-colors">?</span>
            <span className="hidden md:inline">Come funziona</span>
          </button>
        </div>
      </header>

      {/* Main Container */}
      <main className={`flex-grow flex flex-col items-center ${(step === AppStep.CHAT || step === AppStep.HOW_IT_WORKS) ? 'h-screen pt-20' : 'pt-28 md:pt-32'}`}>
        
        <div className={`w-full ${(step === AppStep.CHAT || step === AppStep.HOW_IT_WORKS) ? 'h-full flex flex-col' : 'max-w-4xl'}`}>
          
          {/* INITIAL STEPS */}
          {(step === AppStep.NAME_INPUT || step === AppStep.PDF_UPLOAD) && (
            <div className="text-center mb-10 md:mb-12 animate-in fade-in slide-in-from-bottom-4 duration-700 px-6">
              <h1 className="text-4xl md:text-6xl font-black mb-4 leading-tight">
                Il Tuo Assistente PDF <br/> 
                <span className="text-[#FF6600]">Intelligente</span>
              </h1>
              <h2 className="text-base md:text-xl text-gray-600 font-light max-w-2xl mx-auto mb-10">
                Carica il tuo PDF e lascialo analizzare dal nostro sistema: Poni domande in chat in linguaggio naturale e ottieni risposte mirate basate esclusivamente sul contenuto del file.
              </h2>

              <div className="flex flex-row items-center justify-center gap-2 md:gap-8 mb-12">
                <div className="flex flex-col items-center gap-3 w-20 md:w-32 group cursor-default">
                  <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">
                    <IconFileUp className="w-5 h-5 md:w-7 md:h-7" />
                  </div>
                  <p className="text-[8px] md:text-[10px] font-bold tracking-widest uppercase text-gray-400 group-hover:text-[#FF6600]">1. Upload</p>
                </div>
                <div><IconArrowRight className="text-gray-200 w-3 h-3 md:w-5 md:h-5 animate-pulse" /></div>
                <div className="flex flex-col items-center gap-3 w-20 md:w-32 group cursor-default">
                  <div className="w-12 h-12 md:w-16 md:h-16 rounded-full bg-gray-50 border-2 border-gray-100 flex items-center justify-center text-gray-400 group-hover:text-[#FF6600] group-hover:border-[#FF6600]/30 group-hover:bg-[#FF6600]/5 transition-all">
                    <div className="relative">
                       <IconGear className="w-5 h-5 md:w-7 md:h-7 animate-gear-rotate" />
                       <span className="absolute -top-1 -right-1 bg-[#FF6600] text-white text-[6px] md:text-[8px] font-bold px-1 rounded-sm">PDF</span>
                    </div>
                  </div>
                  <p className="text-[8px] md:text-[10px] font-bold tracking-widest uppercase text-gray-400 group-hover:text-[#FF6600] transition-colors">2. Analisi</p>
                </div>
                <div><IconArrowRight className="text-gray-200 w-3 h-3 md:w-5 md:h-5 animate-pulse" /></div>
                <div className="flex flex-col items-center gap-3 w-20 md:w-32 group cursor-default">
                  <div className="w-12 h-12 md:w-16 md:h-16 rounded-full bg-gray-50 border-2 border-gray-100 flex items-center justify-center text-gray-400 shadow-sm group-hover:text-[#FF6600] group-hover:border-[#FF6600]/30 group-hover:bg-[#FF6600]/5 transition-all">
                    <IconSparkles className="w-5 h-5 md:w-7 md:h-7" />
                  </div>
                  <p className="text-[8px] md:text-[10px] font-bold tracking-widest uppercase text-gray-400 group-hover:text-[#FF6600] transition-colors">3. Chat</p>
                </div>
              </div>
            </div>
          )}

          {step === AppStep.NAME_INPUT && (
            <div className="flex justify-center py-2 animate-in fade-in slide-in-from-top-4 duration-500 px-6">
              <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">
                <form onSubmit={handleNameSubmit} className="flex flex-col gap-6 md:gap-8">
                  <div>
                    <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>
                    <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 />
                  </div>
                  <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>
                </form>
              </div>
            </div>
          )}

          {step === AppStep.PDF_UPLOAD && (
            <div className="flex flex-col items-center py-2 animate-in fade-in slide-in-from-bottom-8 duration-700 px-6">
              <div className="text-center mb-8">
                <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>
                <p className="text-gray-400 font-medium">Siamo pronti ad analizzare il tuo file.</p>
              </div>
              <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">
                <input type="file" accept="application/pdf" onChange={handleFileUpload} className="absolute inset-0 opacity-0 cursor-pointer" />
                <div className="bg-gray-50 p-4 md:p-6 rounded-full group-hover:bg-[#FF6600]/10 transition-colors">
                  <IconFileUp className="w-10 h-10 md:w-12 md:h-12 text-gray-300 group-hover:text-[#FF6600]" />
                </div>
                <div className="text-center">
                  <p className="text-lg md:text-xl font-bold mb-1">Carica il documento ed inizia a chattare</p>
                  <p className="text-gray-400 text-sm font-medium">Trascina qui il file o clicca per sfogliare</p>
                </div>
              </div>
            </div>
          )}

          {step === AppStep.PROCESSING && (
            <div className="flex-grow flex flex-col items-center justify-center py-16 gap-8 animate-in fade-in px-6">
              <div className="relative w-20 h-20 md:w-24 md:h-24">
                <div className="absolute inset-0 border-4 border-gray-50 rounded-full"></div>
                <div className="absolute inset-0 border-4 border-t-[#FF6600] rounded-full animate-spin"></div>
              </div>
              <div className="text-center max-md:max-w-xs max-w-md">
                <h3 className="text-xl md:text-2xl font-extrabold mb-2">Stiamo processando il tuo documento...</h3>
                <p className="text-gray-500 mb-8 font-light">Quasi pronto...</p>
                <div className="w-full bg-gray-50 h-2 rounded-full overflow-hidden">
                  <div className="bg-[#FF6600] h-full transition-all duration-300" style={{ width: `${progress}%` }}></div>
                </div>
              </div>
            </div>
          )}

          {/* CHAT STEP */}
          {step === AppStep.CHAT && (
            <div className="flex-grow flex flex-col w-full max-w-3xl mx-auto h-full overflow-hidden">
              <div className="flex-grow overflow-y-auto px-6 py-10 space-y-12 chat-scroll">
                {messages.map((msg, idx) => {
                  if (msg.content === 'PRESET_WELCOME') {
                    return (
                      <div key={idx} className="animate-in slide-in-from-bottom-4 duration-700">
                        <div className="flex items-start gap-5">
                           <div className="w-12 h-12 rounded-2xl bg-[#FF6600] flex items-center justify-center shadow-lg shadow-[#FF6600]/20 flex-shrink-0">
                             <div className="relative flex items-center justify-center w-full h-full">
                               <IconBot className="w-9 h-9" />
                               <div className="absolute -top-1 -right-1 bg-white text-black text-[7px] font-black px-1 rounded-sm border border-black/5 uppercase">PDF</div>
                             </div>
                           </div>
                           <div className="flex flex-col gap-1 pt-1">
                            <h2 className="text-xl font-normal text-gray-500 font-verdana leading-tight">
                              <span className="text-[#FF6600] font-medium">{userData.name}</span>, il file{" "}
                              <span className="text-black font-medium hover:text-[#FF6600] cursor-pointer" title={userData.fileName}>
                                "{truncateFileName(userData.fileName || '', 40)}" 
                              </span> è pronto.
                            </h2>
                            <p className="text-sm font-light text-gray-400">Chiedimi qualunque cosa!</p>
                          </div>
                        </div>
                      </div>
                    );
                  }
                  const isUser = msg.role === 'user';
                  return (
                    <div key={idx} className={`flex ${isUser ? 'justify-end' : 'justify-start'} animate-in fade-in duration-300`}>
                      <div className={`flex gap-4 max-w-[85%] ${isUser ? 'flex-row-reverse' : 'flex-row'}`}>
                        {!isUser && (
                          <div className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center flex-shrink-0 mt-1">
                            <IconBot className="w-7 h-7" />
                          </div>
                        )}
                        <div className={`p-4 ${isUser ? 'bg-gray-100 text-gray-800 rounded-2xl rounded-tr-none' : 'text-gray-700 markdown-container'}`}>
                          <div className="font-verdana">
                            <ReactMarkdown>{msg.content}</ReactMarkdown>
                          </div>
                          <p className="text-[9px] mt-2 opacity-30 font-bold tracking-tighter">
                            {msg.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
                          </p>
                        </div>
                      </div>
                    </div>
                  );
                })}
                {isTyping && (
                  <div className="flex justify-start animate-in fade-in">
                    <div className="flex gap-4">
                      <div className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center">
                        <IconBot className="w-7 h-7 opacity-30 animate-pulse" />
                      </div>
                      <div className="flex gap-1.5 items-center py-2">
                        <span className="w-1.5 h-1.5 bg-gray-200 rounded-full animate-bounce"></span>
                        <span className="w-1.5 h-1.5 bg-gray-200 rounded-full animate-bounce delay-150"></span>
                        <span className="w-1.5 h-1.5 bg-gray-200 rounded-full animate-bounce delay-300"></span>
                      </div>
                    </div>
                  </div>
                )}
                <div ref={chatEndRef} />
              </div>

              <div className="px-6 pb-10 pt-4 bg-white">
                <form onSubmit={handleSendMessage} className="relative max-w-2xl mx-auto">
                  <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">
                    <div className="relative flex-shrink-0">
                      <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'}`}>
                        <span className={`text-2xl transition-transform font-light ${showPlusMenu ? 'rotate-45' : ''}`}>+</span>
                      </button>
                      {showPlusMenu && (
                        <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">
                          <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">
                            <IconFileUp className="w-4 h-4 text-[#FF6600]" /> Cambia PDF
                          </button>
                        </div>
                      )}
                    </div>
                    <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" />
                    <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">
                      <IconSend className="w-5 h-5 md:w-6 md:h-6" />
                    </button>
                  </div>
                </form>
              </div>
            </div>
          )}

          {/* HOW IT WORKS STEP - AGGIORNATA CON LE TUE SPIEGAZIONI */}
          {step === AppStep.HOW_IT_WORKS && (
            <div className="flex-grow overflow-y-auto px-6 py-10 chat-scroll animate-in fade-in duration-700">
              <div className="max-w-4xl mx-auto space-y-12 pb-20">
                <div className="text-center mb-16">
                  <h1 className="text-4xl md:text-5xl font-black mb-4 font-outfit">Architettura <span className="text-[#FF6600]">GraphRAG</span></h1>
                  <p className="text-gray-500 font-light text-lg">Un sistema di recupero deterministico potenziato da orchestratori LLM.</p>
                </div>

                <div className="grid md:grid-cols-2 gap-8">
                  {/* Step 1 */}
                  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
                    <div className="bg-[#FF6600] text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
                      <IconFileUp className="w-6 h-6" />
                    </div>
                    <h3 className="text-xl font-bold mb-3">Ingestion & Struttura</h3>
                    <p className="text-gray-600 font-light text-sm leading-relaxed">
                      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.
                    </p>
                  </div>

                  {/* Step 2 */}
                  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
                    <div className="bg-black text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
                      <IconLayers className="w-6 h-6" />
                    </div>
                    <h3 className="text-xl font-bold mb-3">Chunking</h3>
                    <p className="text-gray-600 font-light text-sm leading-relaxed">
                      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.
                    </p>
                  </div>

                  {/* Step 3 */}
                  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
                    <div className="bg-black text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
                      <IconDatabase className="w-6 h-6" />
                    </div>
                    <h3 className="text-xl font-bold mb-3">Knowledge Graph (Neo4j)</h3>
                    <p className="text-gray-600 font-light text-sm leading-relaxed">
                      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.
                    </p>
                  </div>

                  {/* Step 4 */}
                  <div className="bg-white border border-gray-100 p-8 rounded-3xl shadow-sm hover:orange-glow transition-all">
                    <div className="bg-black text-white w-12 h-12 rounded-xl flex items-center justify-center mb-6">
                      <IconWorkflow className="w-6 h-6" />
                    </div>
                    <h3 className="text-xl font-bold mb-3">LangGraph</h3>
                    <p className="text-gray-500 font-light text-sm leading-relaxed">
                      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.
                    </p>
                  </div>
                </div>

                {/* Sezione Strategia Ibrida */}
                <div className="bg-gray-50 border border-gray-100 p-10 rounded-[3rem] shadow-inner">
                  <div className="text-center mb-10">
                    <h3 className="text-2xl font-black mb-2 font-outfit">Recupero <span className="text-[#FF6600]">Ibrido & Intelligente</span></h3>
                    <p className="text-gray-500 text-sm font-light max-w-2xl mx-auto">
                      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.
                    </p>
                  </div>

                  <div className="grid md:grid-cols-2 gap-8">
                    <div className="space-y-6">
                      <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
                        <div className="flex items-center gap-3 mb-3">
                          <div className="bg-[#FF6600]/10 px-2 py-1 rounded md font-bold text-[#FF6600] text-[10px] uppercase">Routing</div>
                          <h4 className="font-bold text-sm">Mistral come Router Strategico</h4>
                        </div>
                        <p className="text-xs text-gray-600 leading-relaxed font-light">
                          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.
                        </p>
                      </div>
                      
                      <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
                        <div className="flex items-center gap-3 mb-3">
                          <div className="bg-black/5 px-2 py-1 rounded md font-bold text-gray-500 text-[10px] uppercase">Vector</div>
                          <h4 className="font-bold text-sm">Ricerca Vettoriale</h4>
                        </div>
                        <p className="text-xs text-gray-600 leading-relaxed font-light">
                          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.
                        </p>
                      </div>
                    </div>

                    <div className="space-y-6">
                      <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
                        <div className="flex items-center gap-3 mb-3">
                          <div className="bg-[#FF6600]/10 px-2 py-1 rounded md font-bold text-[#FF6600] text-[10px] uppercase">Entities</div>
                          <h4 className="font-bold text-sm">Named Entity Extraction (GLiNER)</h4>
                        </div>
                        <p className="text-xs text-gray-600 leading-relaxed font-light">
                          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.
                        </p>
                      </div>

                      <div className="bg-white p-6 rounded-2xl shadow-sm border border-black/5">
                        <div className="flex items-center gap-3 mb-3">
                          <div className="bg-black/5 px-2 py-1 rounded md font-bold text-gray-500 text-[10px] uppercase">Rerank</div>
                          <h4 className="font-bold text-sm">Reranking-v2-m3</h4>
                        </div>
                        <p className="text-xs text-gray-600 leading-relaxed font-light">
                          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.
                        </p>
                      </div>
                    </div>
                  </div>
                </div>

                <div className="bg-white border border-gray-100 p-8 rounded-[3rem] shadow-sm hover:orange-glow transition-all max-w-2xl mx-auto">
                  <div className="flex flex-col items-center text-center">
                    <div className="bg-[#FF6600] text-white w-14 h-14 rounded-2xl flex items-center justify-center mb-6 shadow-lg shadow-[#FF6600]/20">
                      <IconGlobe className="w-8 h-8" />
                    </div>
                    <h3 className="text-2xl font-black mb-3 font-outfit">Ricerca Globale</h3>
                    <p className="text-gray-600 font-light text-sm leading-relaxed">
                      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.
                    </p>
                  </div>
                </div>

                <div className="flex flex-col items-center text-center max-w-2xl mx-auto space-y-6">
                  <div className="bg-[#FF6600] text-white w-16 h-16 rounded-2xl flex items-center justify-center shadow-lg shadow-[#FF6600]/20">
                    <IconBot className="w-10 h-10" />
                  </div>
                  <h3 className="text-2xl font-black font-outfit">Sintesi Finale</h3>
                  <p className="text-gray-600 font-light leading-relaxed">
                    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.
                  </p>
                </div>

                <div className="flex justify-center pt-10">
                  <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">
                    Torna alla Chat
                  </button>
                </div>
              </div>
            </div>
          )}
        </div>
      </main>

      {/* Developer Footer */}
      {isInitialPage && (
        <div className="w-full mt-20 py-12 px-6 border-t border-gray-100 flex flex-col items-center gap-6 animate-in fade-in slide-in-from-bottom-4 duration-1000 bg-white">
          <div className="flex items-center gap-8">
            <a href="https://github.com/ValerioBotto" target="_blank" rel="noopener noreferrer" className="group flex flex-col items-center gap-2">
              <div className="p-3 bg-gray-50 rounded-xl group-hover:bg-black group-hover:text-white transition-all">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"></path><path d="M9 18c-4.51 2-5-2-7-2"></path></svg>
              </div>
              <span className="text-[10px] font-bold uppercase tracking-widest text-gray-400 group-hover:text-black">GitHub</span>
            </a>
            <a href="https://www.linkedin.com/in/valerio-botto-4844b2190/" target="_blank" rel="noopener noreferrer" className="group flex flex-col items-center gap-2">
              <div className="p-3 bg-gray-50 rounded-xl group-hover:bg-[#0077b5] group-hover:text-white transition-all">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect width="4" height="12" x="2" y="9"></rect><circle cx="4" cy="4" r="2"></circle></svg>
              </div>
              <span className="text-[10px] font-bold uppercase tracking-widest text-gray-400 group-hover:text-[#0077b5]">LinkedIn</span>
            </a>
          </div>
          <div className="text-center">
            <p className="text-[10px] text-gray-300 font-black tracking-[0.4em] uppercase">
              Developed by <span className="text-black">Valerio Botto</span>
            </p>
          </div>
        </div>
      )}
    </div>
  );
};

export default App;