import gradio as gr
import cv2
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):
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)
return img, text
def binary_classification(text):
if text.strip(): return binary(text)
raise gr.Error('Il testo è obbligatorio!')
def multi_classification(text):
if text.strip():
try: return multi(text)
except Exception as e: raise gr.Error(f'Errore nel modello: {str(e)}')
raise gr.Error('Il testo è obbligatorio!')
def file_change(file):
if isinstance(file, list): file = file[0]
if file: return cv2.imread(file)
return None
def image_classification(img):
if img is not None: return image(img)
raise gr.Error('L\'immagine è obbligatoria!')
def retina_classification(retina):
if retina is not None: return retina_detector(retina)
raise gr.Error('L\'immagine è obbligatoria!')
def bpo_dispatch_logic(text):
"""
Funzione Ponte: Chiama il modulo AI e decide l'azione di business.
Restituisce un aggiornamento COMPLETO del componente NER per pulire la grafica.
"""
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)
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("""
AI Platform
""")
# --- 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("""
🛠️ Model Architecture: NGT-BERT-Custom (DistilBERT)
📚 Training Data: Synthetic BPO Dataset (2025)
🎯 Tasks: Intent Classification (Multi-class), Entity Extraction (NER)
""")
# 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,
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, 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, 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_detector,
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"]
)