File size: 23,234 Bytes
0c6fe9c
 
2ff9250
4bdde62
3a24a7b
c469f55
 
 
55365d8
03a98ed
4bdde62
f1f081e
 
 
 
 
 
 
 
 
 
 
 
3a24a7b
 
 
4bdde62
 
 
 
 
 
 
 
 
 
 
 
 
 
0c6fe9c
 
 
1ad9db6
4bdde62
 
 
0c6fe9c
4bdde62
0c6fe9c
 
 
 
 
 
 
 
 
4bdde62
f1f081e
0c6fe9c
 
 
 
 
 
03a98ed
0c6fe9c
 
 
 
 
 
 
 
 
03a98ed
0c6fe9c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03a98ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a24a7b
03a98ed
0c6fe9c
 
 
 
 
 
 
 
 
03a98ed
 
 
 
 
3a24a7b
03a98ed
3a24a7b
 
 
 
03a98ed
3a24a7b
 
 
 
 
 
 
 
03a98ed
 
3a24a7b
 
 
 
 
 
 
 
 
 
 
 
 
 
03a98ed
4bdde62
3a24a7b
4bdde62
 
 
 
3a24a7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4bdde62
 
 
3a24a7b
4bdde62
 
3a24a7b
 
b9c5837
3a24a7b
1ad9db6
 
 
 
4bdde62
 
1ad9db6
 
 
 
 
 
 
 
 
 
 
 
 
 
4bdde62
1ad9db6
 
 
 
 
4bdde62
 
 
 
 
 
 
 
 
 
 
3a24a7b
4bdde62
 
 
3a24a7b
4bdde62
 
 
 
3a24a7b
 
4bdde62
 
 
 
 
 
 
 
 
 
 
b9c5837
4bdde62
 
 
 
 
 
3a24a7b
 
4bdde62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0c6fe9c
4bdde62
 
3a24a7b
4bdde62
 
3a24a7b
 
 
 
 
 
 
 
 
 
 
 
b9c5837
3a24a7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f1f081e
3a24a7b
 
 
 
 
 
 
0c6fe9c
4bdde62
 
 
3a24a7b
4bdde62
3a24a7b
4bdde62
3a24a7b
4bdde62
 
 
 
3a24a7b
4bdde62
3a24a7b
 
b9c5837
4bdde62
 
 
 
 
 
 
 
 
 
3a24a7b
0c6fe9c
4bdde62
 
 
 
 
 
 
 
 
 
b9c5837
4bdde62
 
 
 
 
 
 
 
3a24a7b
4bdde62
 
 
 
 
 
 
 
 
 
3a24a7b
4bdde62
0c6fe9c
4bdde62
 
3a24a7b
4bdde62
ef72586
 
 
 
3a24a7b
 
 
4bdde62
3a24a7b
 
4bdde62
ef72586
 
 
4bdde62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ef72586
 
 
 
4bdde62
ef72586
 
 
 
 
 
4bdde62
ef72586
 
 
 
 
 
 
 
 
 
4bdde62
 
 
 
 
 
 
 
ef72586
 
 
 
 
 
f1f081e
ef72586
 
 
 
 
 
3a24a7b
ef72586
3a24a7b
 
 
ef72586
 
3a24a7b
ef72586
3a24a7b
 
 
ef72586
 
3a24a7b
2ff9250
f1f081e
3a24a7b
 
 
 
 
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
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
from modules.utilities import logger
import time
import gradio as gr
import os
import modules.utilities.utils as utils
from modules.binary_classification import binary_classification as binary
from modules.image_classification import image_classification as image
from modules.multilabel_classification import multi_classification as multi
from modules.retina import predict_diabetic_retinopathy as retina_detector
from modules.bpo_dispatcher import predict_bpo_ticket
import modules.forecasting as forecast

# --- CONFIGURAZIONE TEMA ---
theme = gr.themes.Soft(
    primary_hue="purple", 
    neutral_hue="slate",
    font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
).set(
    block_title_text_weight="700",
    block_shadow="*shadow_drop_lg",
    block_label_background_fill="*primary_100", 
)

PATH_ROOT = "./data/gallery"
PATH_RETINA = "./data/gallery/retinopaty" 
PATH_XRAY = "./data/gallery/xray"   
PATH_FORECAST = "./data/export"

# Crea cartella e file demo se non esistono
if not os.path.exists(PATH_FORECAST):
    os.makedirs(PATH_FORECAST)
    
demo_csv_path = os.path.join(PATH_FORECAST, "plan_sample.csv")

# Se il file non esiste, ORA generiamo 4 settimane di dati procedurali
if not os.path.exists(demo_csv_path):
    csv_content = forecast.generate_mock_export()
    with open(demo_csv_path, "w") as f:
        f.write(csv_content)

def forecast_logic(file, request: gr.Request):
    start_time = time.time()
    
    if file is None: raise gr.Error("Seleziona un file CSV")
    if isinstance(file, list): file = file[0]
    
    img, text = forecast.predict_workload(file)
    
    if img is None: raise gr.Error(text)
    
    elapsed_time = time.time() - start_time
    logger.log_interaction(
        request=request,
        module_name="AI Forecaster",
        action="Prediction",
        input_data=file,
        execution_time=elapsed_time
    )
    return img, text

def binary_classification(text, request: gr.Request):
    start_time = time.time()
    
    if not text.strip(): 
        raise gr.Error('Il testo è obbligatorio!')
    result = binary(text)

    elapsed_time = time.time() - start_time
    logger.log_interaction(
        request=request,
        module_name="Sentiment Analysis (BPO)",
        action="Prediction",
        input_data=text,
        execution_time=elapsed_time
    )
    return result

def multi_classification(text, request: gr.Request):
    start_time = time.time()
    
    if not text.strip():
        raise gr.Error('Il testo è obbligatorio!')  

    try: 
        result = multi(text)

        elapsed_time = time.time() - start_time
        logger.log_interaction(
            request=request,
            module_name="Smart Content Tagger",
            action="Prediction",
            input_data=text,
            execution_time=elapsed_time
        )
        return result
        
    except Exception as e: 
        raise gr.Error(f'Errore nel modello: {str(e)}')

def image_classification(img, request: gr.Request):
    start_time = time.time()
    result = image(img)    
    elapsed_time = time.time() - start_time
    logger.log_interaction(
        request=request,
        module_name="Chest X-Ray",
        action="Prediction",
        input_data=img, 
        execution_time=elapsed_time
    )
    return result

def retina_classification(retina, request: gr.Request):
    start_time = time.time()
    result = retina_detector(retina)
    elapsed_time = time.time() - start_time
    logger.log_interaction(
        request=request,
        module_name="Diabetic Retinopathy",
        action="Prediction",
        input_data=retina,
        execution_time=elapsed_time
    )
    return result

def bpo_dispatch_logic(text, request: gr.Request):

    start_time = time.time()
    
    try:
        intent, urgency, entities = predict_bpo_ticket(text)
        
        if intent is None:
             raise gr.Error("Errore nel modello BPO. Verifica i log.")
        
        top_intent = max(intent, key=intent.get) 
        
        action = "Inoltro generico"
        if top_intent == "Retention / Churn Risk":
            action = "🚨 ALERT: Assegnazione coda 'Retention' + Chiamata Outbound"
        elif top_intent == "Supporto Tecnico":
            action = "🛠️ Apertura Ticket JIRA (Livello 1) - Priorità Tecnica"
        elif top_intent == "Amministrazione / Billing":
            action = "💰 Verifica insoluti su SAP + Inoltro Backoffice Amm.vo"

        html_output = utils.render_ner_html(entities)

        elapsed_time = time.time() - start_time
        logger.log_interaction(
            request=request,
            module_name="BPO Dispatcher",
            action="Prediction",
            input_data=text,
            execution_time=elapsed_time
        )

        return intent, urgency, action, html_output

    except Exception as e:
        raise gr.Error(f"Errore nell'analisi: {str(e)}")

with gr.Blocks(title="NGT AI Platform", theme=theme, css_paths="style.css") as demo:

    # --- SIDEBAR (FILE EXPLORER) ---
    with gr.Sidebar(position="left", width=300, visible=False) as main_sidebar:
        gr.Markdown("### 📂 Risorse Demo")
        gr.Markdown("Seleziona i file di esempio:")

        sidebar_explorer = gr.FileExplorer(
            root_dir=PATH_ROOT, 
            glob="**/*", 
            ignore_glob="*.h5,*.json,*.py,*.pyc", 
            file_count="single",
            label="Archivio File",
            height=400,
            interactive=True
        )
        
        gr.Markdown("---")
        gr.Markdown("**Info Sistema:**")
        gr.Markdown("🟢 Server: Online")
        gr.Markdown("🟣 GPU: N/A (CPU Mode)")

    # --- MAIN CONTENT ---
    with gr.Column():
    
        # --- HEADER ---
        with gr.Row(elem_classes="header-row"):
            with gr.Column(scale=0, min_width=80, elem_classes="logo-container"):
                gr.Image(value="data/icon.png", show_label=False, show_download_button=False, show_share_button=False, container=False, show_fullscreen_button=False, interactive=False, height=80, width=80)
            with gr.Column(scale=1, elem_classes="header-text-col"):
                gr.Markdown("""<h1>AI Platform</h1><div class='subheader'>Advanced Machine Learning Solutions</div>""")
        
        # --- BPO INTELLIGENT DISPATCHER ---
        with gr.Tab("🧩 BPO Dispatcher") as tab_bpo:
            gr.Markdown("""
            # 🧩 Intelligent Ticket Routing & NER
            Sistema proprietario per l'analisi automatica dei ticket di assistenza. Il modello identifica l'intento, l'urgenza e i dati sensibili del cliente.
            """)
            
            with gr.Row(elem_classes="responsive-row"):
                # INPUT
                with gr.Column(scale=1):
                    bpo_input = gr.Textbox(lines=8, placeholder="Incolla qui il contenuto della mail o del ticket...", label="Contenuto Ticket / Email")
                    analyze_btn_bpo = gr.Button("⚡ Analizza Richiesta", variant="primary")
                    gr.HTML("""
                    <div class='model-card'>
                        <strong>🛠️ Model Architecture:</strong> NGT-BERT-Custom (DistilBERT)<br>
                        <strong>📚 Training Data:</strong> Synthetic BPO Dataset (2025)<br>
                        <strong>🎯 Tasks:</strong> Intent Classification (Multi-class), Entity Extraction (NER)
                    </div>
                    """)

                # OUTPUT
                with gr.Column(scale=1):
                    with gr.Group():
                        gr.Markdown("#### 📋 Analisi Processata", elem_classes="h4-margin")
                        bpo_intent_output = gr.Label(num_top_classes=3, label="Intento Rilevato")
                        with gr.Row():
                            bpo_urgency_output = gr.Textbox(label="Livello Urgenza", scale=1)
                            bpo_action_output = gr.Textbox(label="Azione Consigliata (Auto)", scale=1)
                        
                        gr.Markdown("#### 🔍 Dati Estratti (NER)", elem_classes="h4-margin")
                        bpo_ner_output = gr.HTML(label="Visualizzazione Entità")

            gr.Examples(
                examples=[
                    ["Buongiorno, vi scrivo perché la fattura n. 99283 del mese scorso è sbagliata. Non ho consumato così tanto. Il mio codice cliente è 4599201. Attendo rettifica urgente."],
                    ["Salve, il servizio non funziona da ieri. Mi dà errore 504 sul router. Risolvete subito per favore!"],
                    ["Vorrei disdire il contratto con decorrenza immediata se non mi risolvete il problema."]
                ],
                inputs=bpo_input
            )
            
            analyze_btn_bpo.click(
                bpo_dispatch_logic, 
                inputs=bpo_input, 
                outputs=[bpo_intent_output, bpo_urgency_output, bpo_action_output, bpo_ner_output]
            )

        # --- AI FORECASTER ---
        with gr.Tab("🔮 AI Forecaster") as tab_forecast:
            gr.Markdown("""
            # 🔮 AI Brain: Predictive Planning
            Modulo interattivo per la pianificazione dei turni. Confronta in tempo reale il forecast AI con i dati storici effettivi.
            """)
            
            with gr.Row(elem_classes="responsive-row"):
                with gr.Column(scale=1):
                    gr.Markdown("### 1. Configurazione")
                    with gr.Row():
                        gr.Dropdown(["Customer Care"], label="Business Unit", value="Customer Care")
                        gr.Dropdown(["Ass. Tecnica"], label="Reparto", value="Ass. Tecnica")
                        gr.Dropdown(["Inbound Calls"], label="Attività", value="Inbound Calls")
                
                with gr.Column(scale=1):
                    gr.Markdown("### 2. Dati Storici")
                    with gr.Row():
                        forecast_file = gr.File(
                            label="Seleziona Export (.csv)", 
                            file_types=[".csv"], 
                            height=100,
                            interactive=False
                        )
            with gr.Row(elem_classes="responsive-row"):
                forecast_btn = gr.Button("🔮 Genera\nGrafico", variant="primary", scale=1)

            with gr.Row(elem_classes="responsive-row"):
                with gr.Column():
                    gr.Markdown("### 📊 Dashboard Interattiva")
                    # show_label=False rende il grafico più pulito senza il titolino grigio sopra
                    forecast_plot = gr.Plot(show_label=False)

            with gr.Row(elem_classes="responsive-row"):
                forecast_stats = gr.Textbox(label="KPI Backtesting & Performance", lines=3)
            
            forecast_btn.click(
                forecast_logic, 
                inputs=forecast_file, 
                outputs=[forecast_plot, forecast_stats]
            )
        
        # --- Sentiment Analysis (BPO) ---
        with gr.Tab("📢 Sentiment Analysis (BPO)") as tab_sentiment:
            
            # 1. HEADER E GUIDA UTENTE
            gr.Markdown("""
            # 😠/😍 Analizzatore di Sentiment (Dominio Helpdesk)
            
            **ATTENZIONE:** Questo modello è **altamente specializzato** nel dominio dell'Assistenza Clienti (Telco/Energy).
            
            * ✅ **Usa questo modulo per:** Ticket di guasti, lamentele amministrative, feedback su operatori, richieste di disdetta.
            * ❌ **NON usare questo modulo per:** Frasi generiche ("Il cielo è blu"), recensioni di film, o linguaggio comune non tecnico.
            
            _Il modello potrebbe interpretare frasi generiche positive come negative se non contengono parole chiave del suo vocabolario specifico._
            """)
            
            # 2. DETTAGLI TECNICI (Nascosti in un Accordion per pulizia)
            with gr.Accordion("ℹ️ Come funziona questo modello?", open=False):
                gr.Markdown("""
                Questo sistema utilizza una rete neurale leggera addestrata su un dataset proprietario di **1.200 interazioni reali** cliente-operatore.
                
                * **Preprocessing:** Lemmatizzazione Spacy + Rimozione Stopwords (con Whitelist per le negazioni).
                * **Architettura:** Dense Neural Network con Dropout e L2 Regularization.
                * **Focus:** È calibrato per rilevare l'urgenza nascosta anche in frasi apparentemente calme.
                """)
            
            # 3. INTERFACCIA
            with gr.Row(elem_classes="responsive-row"):
                with gr.Column():
                    # Input Text
                    sentiment_input = gr.Textbox(
                        label="Inserisci il testo del ticket o della mail", 
                        placeholder="Es: Non funziona internet e nessuno mi risponde...",
                        lines=3
                    )
                    
                    # ESEMPI CLICCABILI (Fondamentali per guidare l'utente!)
                    gr.Examples(
                        examples=[
                            ["L'assistenza ricevuta è stata pessima, sono deluso."],
                            ["Il router funziona benissimo, grazie per la velocità."],
                            ["Non ho ancora ricevuto la fattura di gennaio."],
                            ["Sono due giorni che ho la linea ferma, è inaccettabile!"],
                            ["L'operatore Marco è stato gentilissimo e ha risolto tutto."]
                        ],
                        inputs=sentiment_input,
                        label="Prova questi esempi BPO:"
                    )
                    
                    sentiment_btn = gr.Button("Analizza Sentiment", variant="primary")
                    
                with gr.Column():
                    # Output Label (Percentuali)
                    sentiment_output = gr.Label(num_top_classes=2, label="Risultato Analisi")
            
            # 4. COLLEGAMENTO FUNZIONE
            sentiment_btn.click(
                fn=binary_classification, 
                inputs=sentiment_input, 
                outputs=sentiment_output
            )
        
        # --- News Classification (AI Editor) ---
        with gr.Tab("📰 Smart Content Tagger") as tab_news:
            
            # 1. HEADER & CONTESTO
            gr.Markdown("""
            # 🏷️ Classificazione Editoriale Automatica
            
            Questo modulo simula un **assistente editoriale AI**. Analizza il testo di un articolo o di un lancio di agenzia e suggerisce la categoria tematica corretta per l'archiviazione.
            
            * **Categorie Supportate:** `Economia`, `Politica`, `Scienza & Tecnica`, `Sport`, `Storia`.
            * **Tecnologia:** Deep Learning su sequenze di testo (Embedding layer).
            """)
            
            with gr.Row(elem_classes="responsive-row"):
                # COLONNA INPUT (L'Articolo)
                with gr.Column(scale=3):
                    multi_input = gr.Textbox(
                        label="Testo dell'articolo", 
                        placeholder="Incolla qui il titolo o il corpo del testo (es. news ANSA, Reuters...)",
                        lines=6
                    )
                    
                    # Esempi calibrati sulle tue 5 classi
                    gr.Examples(
                        examples=[
                            ["L'inflazione nell'area euro scende al 2.4%, la BCE valuta il taglio dei tassi di interesse."], # Economia
                            ["Il Parlamento ha approvato il nuovo decreto legge con 200 voti favorevoli. Il Premier esprime soddisfazione."], # Politica
                            ["La sonda spaziale ha inviato nuove immagini ad alta risoluzione della superficie di Marte, rivelando tracce di antichi fiumi."], # Scienza
                            ["Finale incredibile allo stadio: la squadra di casa ribalta il risultato al 90° minuto e vola in testa alla classifica."], # Sport
                            ["Durante gli scavi a Pompei sono emersi nuovi affreschi risalenti al I secolo d.C. perfettamente conservati."] # Storia
                        ],
                        inputs=multi_input,
                        label="Prova questi lanci d'agenzia:"
                    )
                    
                    analyze_btn_multi = gr.Button("🏷️ Classifica Articolo", variant="primary")

                # COLONNA OUTPUT (I Tag)
                with gr.Column(scale=2):
                    with gr.Group():
                        gr.Markdown("### 📊 Categorie Rilevate", elem_classes="h4-margin")
                        # Usiamo un Label con top_classes=5 per vedere la distribuzione completa
                        multi_output = gr.Label(num_top_classes=5, label="Confidenza del Modello")
                    
            analyze_btn_multi.click(multi_classification, inputs=multi_input, outputs=multi_output)
        
        # --- Chest X-Ray Diagnostics ---
        with gr.Tab("🩻 Chest X-Ray Diagnostics") as tab_xray:
            
            # 1. DISCLAIMER MEDICO (Fondamentale)
            gr.Markdown("""
            # 🩻 Analisi Radiografica Toracica (Supporto Decisionale)
            
            **DISCLAIMER:** Questo modulo è un prototipo di ricerca AI. **NON sostituisce il parere di un medico.**
            Il sistema è addestrato per identificare pattern visivi associati a:
            * **Polmonite** (Pneumonia)
            * **Tubercolosi** (Tuberculosis)
            
            *Utilizzare gli esempi di radiografiche frontali (Chest X-Ray) recuperabili nella sidebar laterale.*
            """)
            
            with gr.Row(elem_classes="responsive-row"):
                # COLONNA INPUT
                with gr.Column(scale=1):
                    image_input = gr.Image(type="numpy", label="Seleziona Radiografia", height=400, interactive=False)
                    analyze_btn_img = gr.Button("🏥 Avvia Diagnosi AI", variant="primary")

                # COLONNA OUTPUT
                with gr.Column(scale=1):
                    with gr.Group():
                        gr.Markdown("#### 📋 Referto AI", elem_classes="h4-margin")
                        output_label = gr.Label(num_top_classes=4, label="Probabilità Patologia")
                    
            analyze_btn_img.click(image_classification, inputs=image_input, outputs=output_label)

        # --- Diabetic Retinopathy ---
        with gr.Tab("👁️ Diabetic Retinopathy") as tab_retina:
            gr.Markdown("""
            # 👁️ Screening Retinopatia Diabetica
            **DISCLAIMER:** Questo modulo è un prototipo di ricerca AI. **NON sostituisce il parere di un medico.** Sistema di supporto decisionale. Analizza scansioni del fondo oculare.
            
            *Utilizzare gli esempi di retinografie digitali recuperabili nella sidebar laterale.*            
            """)
            
            with gr.Row(elem_classes="responsive-row"):
                # COLONNA INPUT
                with gr.Column(scale=1):
                    image_input_dr = gr.Image(
                        type="numpy", 
                        label="Seleziona Scansione Retinica", 
                        height=400,
                        sources=["upload", "clipboard"],
                        interactive=False
                    )
                    analyze_btn_dr = gr.Button("🏥 Analisi Fondo Oculare", variant="primary")

                # COLONNA OUTPUT
                with gr.Column(scale=1):
                    with gr.Group():
                        gr.Markdown("### 📋 Esito Screening", elem_classes="h4-margin")
                        # Output 1: La Diagnosi (Testo)
                        output_dr_diagnosis = gr.Label(label="Diagnosi AI")
                        # Output 2: La Percentuale (Testo/Numero)
                        output_dr_prob = gr.Label(label="Livello di Confidenza (Rischio)")
                    
            analyze_btn_dr.click(
                retina_classification, 
                inputs=image_input_dr, 
                outputs=[output_dr_diagnosis, output_dr_prob]
            )

     
    ui_outputs = [main_sidebar, sidebar_explorer, image_input, image_input_dr]

    # 1. Click-to-Load (Rimane invariato)
    sidebar_explorer.change(
        fn=utils.global_file_loader, 
        inputs=sidebar_explorer,
        outputs=[bpo_input, forecast_file, image_input, image_input_dr, multi_input, sentiment_input]
    )
    
    # 2. ESET TOTALE
    # Questa funzione restituisce valori "vuoti" per tutti i campi sensibili
    def reset_all_fields():
        return (
            None,   # 1. bpo_input
            None,   # 2. bpo_intent
            None,   # 3. bpo_urgency
            None,   # 4. bpo_action
            None,   # 5. bpo_ner
            None,   # 6. forecast_file
            None,   # 7. forecast_plot
            None,   # 8. forecast_stats
            None,   # 9. image_input
            None,   # 10. output_label
            None,   # 11. image_input_dr
            None,   # 12. output_dr_diag
            None,   # 13. output_dr_prob
            None,   # 14. multi_input
            None,   # 15. multi_output
            None,   # 16. sentiment_input
            None    # 17. sentiment_output
        )

    reset_outputs = [
        bpo_input, bpo_intent_output, bpo_urgency_output, bpo_action_output, bpo_ner_output,
        forecast_file, forecast_plot, forecast_stats,
        image_input, output_label,
        image_input_dr, output_dr_diagnosis, output_dr_prob,
        multi_input, multi_output,
        sentiment_input, sentiment_output
    ]


    # 3. GESTIONE CAMBIO TAB (RESET + SIDEBAR)
    
    # Per i Tab NLP: Disabilita Sidebar + Resetta TUTTO
    tab_bpo.select(
        fn=utils.disable_sidebar, 
        outputs=ui_outputs
    ).then(
        fn=reset_all_fields, outputs=reset_outputs
    )

    tab_forecast.select(
        fn=lambda: utils.enable_sidebar(PATH_FORECAST), 
        outputs=ui_outputs
    ).then(
        fn=reset_all_fields, 
        outputs=reset_outputs
    )

    tab_news.select(
        fn=utils.disable_sidebar, 
        outputs=ui_outputs
    ).then(
        fn=reset_all_fields, outputs=reset_outputs
    )

    tab_sentiment.select(
        fn=utils.disable_sidebar, 
        outputs=ui_outputs
    ).then(
        fn=reset_all_fields, outputs=reset_outputs
    )
    
    # Per i Tab VISION: Abilita Sidebar specifica + Resetta TUTTO
    tab_xray.select(
        fn=lambda: utils.enable_sidebar(PATH_XRAY), 
        outputs=ui_outputs
    ).then(
        fn=reset_all_fields, outputs=reset_outputs
    )
    
    tab_retina.select(
        fn=lambda: utils.enable_sidebar(PATH_RETINA), 
        outputs=ui_outputs
    ).then(
        fn=reset_all_fields, outputs=reset_outputs
    )

if __name__ == "__main__":
    demo.launch(
        server_name="0.0.0.0", 
        server_port=7860, 
        allowed_paths=["data"]
    )