Update app.py
Browse files
app.py
CHANGED
|
@@ -32,11 +32,19 @@ NOTE_TITLE_LABELS = {
|
|
| 32 |
}
|
| 33 |
|
| 34 |
TABLE_TITLE_LABELS = {
|
| 35 |
-
"it": "Facilities disponibili presso il laboratori DTS",
|
| 36 |
-
"en": "Facilities available at DTS laboratories",
|
| 37 |
-
"fr": "Installations disponibles dans les laboratoires DTS",
|
| 38 |
-
"es": "Instalaciones disponibles en los laboratorios DTS",
|
| 39 |
-
"de": "Verfügbare Facilities in den DTS-Labors",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
}
|
| 41 |
|
| 42 |
|
|
@@ -240,34 +248,42 @@ ROUTINE_MAP = {
|
|
| 240 |
def chiedi_al_db(domanda: str):
|
| 241 |
"""
|
| 242 |
Restituisce:
|
| 243 |
-
-
|
| 244 |
- note (testo della nota, nella lingua della query)
|
| 245 |
-
-
|
| 246 |
- risultati_view_basic (tradotto)
|
| 247 |
- risultati_view_basic (in state)
|
| 248 |
- risultati_view_full (in state)
|
| 249 |
- search_file (per download .xlsx unico con query + nota + tabella completa)
|
|
|
|
| 250 |
"""
|
| 251 |
|
| 252 |
empty_df = pd.DataFrame()
|
| 253 |
|
|
|
|
| 254 |
if not domanda or not domanda.strip():
|
| 255 |
-
|
| 256 |
-
|
|
|
|
| 257 |
return (
|
| 258 |
-
|
| 259 |
"Inserisci una domanda.",
|
| 260 |
-
|
| 261 |
empty_df,
|
| 262 |
empty_df,
|
| 263 |
empty_df,
|
| 264 |
None,
|
|
|
|
| 265 |
)
|
| 266 |
|
|
|
|
| 267 |
target_lang = detect_language(domanda)
|
| 268 |
lang_name = SUPPORTED_LANGS.get(target_lang, "Italian")
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
| 271 |
|
| 272 |
prompt = f"""
|
| 273 |
Sei un assistente che lavora su una tabella di metodiche analitiche.
|
|
@@ -352,29 +368,35 @@ Non aggiungere nessun testo prima o dopo il JSON.
|
|
| 352 |
except json.JSONDecodeError:
|
| 353 |
error_msg = f"Errore nel parsing della risposta del modello:\n{raw_text}"
|
| 354 |
return (
|
| 355 |
-
|
| 356 |
error_msg,
|
| 357 |
-
|
| 358 |
empty_df,
|
| 359 |
empty_df,
|
| 360 |
empty_df,
|
| 361 |
None,
|
|
|
|
| 362 |
)
|
| 363 |
|
| 364 |
indices = data.get("row_indices", [])
|
| 365 |
note = data.get("note", "")
|
| 366 |
|
|
|
|
| 367 |
if not indices:
|
| 368 |
return (
|
| 369 |
-
|
| 370 |
note,
|
| 371 |
-
|
| 372 |
empty_df,
|
| 373 |
empty_df,
|
| 374 |
empty_df,
|
| 375 |
None,
|
|
|
|
| 376 |
)
|
| 377 |
|
|
|
|
|
|
|
|
|
|
| 378 |
risultati_df = df.iloc[indices].copy()
|
| 379 |
|
| 380 |
# =========================================
|
|
@@ -459,7 +481,6 @@ Non aggiungere nessun testo prima o dopo il JSON.
|
|
| 459 |
|
| 460 |
# 4) elimina la colonna Keyword & Keyphrases (from Labs) se presente
|
| 461 |
if "Keyword & Keyphrases (from Labs)" in view_full.columns:
|
| 462 |
-
# NON fa parte delle colonne "more info"
|
| 463 |
view_full = view_full.drop(columns=["Keyword & Keyphrases (from Labs)"])
|
| 464 |
|
| 465 |
# 5) elimina colonne che sono solo 'null' o vuote,
|
|
@@ -500,7 +521,6 @@ Non aggiungere nessun testo prima o dopo il JSON.
|
|
| 500 |
# FILE SCARICABILE UNICO: "Scarica ricerca"
|
| 501 |
# =========================================
|
| 502 |
|
| 503 |
-
|
| 504 |
from openpyxl import Workbook
|
| 505 |
from openpyxl.styles import Alignment, Font, PatternFill
|
| 506 |
|
|
@@ -538,19 +558,37 @@ Non aggiungere nessun testo prima o dopo il JSON.
|
|
| 538 |
cell_query_label.font = label_font
|
| 539 |
ws.cell(row=current_row, column=2, value=domanda)
|
| 540 |
# fusione celle B1:D1 per il testo della query
|
| 541 |
-
ws.merge_cells(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 542 |
|
| 543 |
# 2) Analisi della domanda tramite IA (stessa riga: titolo + nota)
|
| 544 |
current_row += 1
|
| 545 |
-
cell_note_title = ws.cell(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 546 |
cell_note_title.font = label_font
|
| 547 |
ws.cell(row=current_row, column=2, value=note)
|
| 548 |
# fusione celle B2:D2 per il testo della nota
|
| 549 |
-
ws.merge_cells(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
|
| 551 |
# 3) Titolo tabella delle facilities
|
| 552 |
current_row += 1
|
| 553 |
-
cell_table_title = ws.cell(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
cell_table_title.font = label_font
|
| 555 |
|
| 556 |
# 4) Intestazioni della tabella (vista FULL con colonne "More info")
|
|
@@ -588,14 +626,18 @@ Non aggiungere nessun testo prima o dopo il JSON.
|
|
| 588 |
wb.save(tmp_report.name)
|
| 589 |
search_file = tmp_report.name
|
| 590 |
|
|
|
|
|
|
|
|
|
|
| 591 |
return (
|
| 592 |
-
|
| 593 |
note,
|
| 594 |
-
|
| 595 |
view_basic_translated,
|
| 596 |
view_basic_translated,
|
| 597 |
view_full_translated,
|
| 598 |
search_file,
|
|
|
|
| 599 |
)
|
| 600 |
|
| 601 |
|
|
@@ -611,6 +653,25 @@ def toggle_more_info(show_more: bool, df_basic: pd.DataFrame, df_full: pd.DataFr
|
|
| 611 |
return pd.DataFrame()
|
| 612 |
|
| 613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
# =========================================
|
| 615 |
# 4) INTERFACCIA GRADIO
|
| 616 |
# =========================================
|
|
@@ -725,6 +786,10 @@ with gr.Blocks(title="DTS Analytical Capabilities", css=custom_css) as demo:
|
|
| 725 |
|
| 726 |
search_file_out = gr.File(label="Scarica ricerca (.xlsx)")
|
| 727 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
btn.click(
|
| 729 |
fn=chiedi_al_db,
|
| 730 |
inputs=domanda,
|
|
@@ -736,14 +801,32 @@ with gr.Blocks(title="DTS Analytical Capabilities", css=custom_css) as demo:
|
|
| 736 |
df_basic_state,
|
| 737 |
df_full_state,
|
| 738 |
search_file_out,
|
|
|
|
| 739 |
],
|
| 740 |
)
|
| 741 |
|
|
|
|
| 742 |
show_more.change(
|
| 743 |
fn=toggle_more_info,
|
| 744 |
inputs=[show_more, df_basic_state, df_full_state],
|
| 745 |
outputs=table_out,
|
| 746 |
)
|
| 747 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
if __name__ == "__main__":
|
| 749 |
demo.launch()
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
TABLE_TITLE_LABELS = {
|
| 35 |
+
"it": "Facilities disponibili presso il laboratori DTS\n(in ordine decrescente di attinenza)",
|
| 36 |
+
"en": "Facilities available at DTS laboratories\n(in decreasing order of relevance)",
|
| 37 |
+
"fr": "Installations disponibles dans les laboratoires DTS\n(par ordre décroissant de pertinence)",
|
| 38 |
+
"es": "Instalaciones disponibles en los laboratorios DTS\n(en orden decreciente de pertinencia)",
|
| 39 |
+
"de": "Verfügbare Facilities in den DTS-Labors\n(in absteigender Reihenfolge der Relevanz)",
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
NEW_QUESTION_LABELS = {
|
| 43 |
+
"it": "Nuova domanda",
|
| 44 |
+
"en": "New question",
|
| 45 |
+
"fr": "Nouvelle question",
|
| 46 |
+
"es": "Nueva pregunta",
|
| 47 |
+
"de": "Neue Frage",
|
| 48 |
}
|
| 49 |
|
| 50 |
|
|
|
|
| 248 |
def chiedi_al_db(domanda: str):
|
| 249 |
"""
|
| 250 |
Restituisce:
|
| 251 |
+
- note_title_md (testo sopra la nota, in grassetto, nella lingua della query)
|
| 252 |
- note (testo della nota, nella lingua della query)
|
| 253 |
+
- table_title_md (testo sopra la tabella, in grassetto, nella lingua della query)
|
| 254 |
- risultati_view_basic (tradotto)
|
| 255 |
- risultati_view_basic (in state)
|
| 256 |
- risultati_view_full (in state)
|
| 257 |
- search_file (per download .xlsx unico con query + nota + tabella completa)
|
| 258 |
+
- new_question_label (etichetta del pulsante "Nuova domanda" nella lingua corretta)
|
| 259 |
"""
|
| 260 |
|
| 261 |
empty_df = pd.DataFrame()
|
| 262 |
|
| 263 |
+
# Caso: nessuna domanda inserita
|
| 264 |
if not domanda or not domanda.strip():
|
| 265 |
+
note_title_plain = NOTE_TITLE_LABELS["it"]
|
| 266 |
+
table_title_plain = TABLE_TITLE_LABELS["it"]
|
| 267 |
+
new_question_label = NEW_QUESTION_LABELS["it"]
|
| 268 |
return (
|
| 269 |
+
f"**{note_title_plain}**",
|
| 270 |
"Inserisci una domanda.",
|
| 271 |
+
f"**{table_title_plain}**",
|
| 272 |
empty_df,
|
| 273 |
empty_df,
|
| 274 |
empty_df,
|
| 275 |
None,
|
| 276 |
+
new_question_label,
|
| 277 |
)
|
| 278 |
|
| 279 |
+
# Riconoscimento lingua
|
| 280 |
target_lang = detect_language(domanda)
|
| 281 |
lang_name = SUPPORTED_LANGS.get(target_lang, "Italian")
|
| 282 |
+
note_title_plain = NOTE_TITLE_LABELS.get(target_lang, NOTE_TITLE_LABELS["it"])
|
| 283 |
+
table_title_plain = TABLE_TITLE_LABELS.get(target_lang, TABLE_TITLE_LABELS["it"])
|
| 284 |
+
new_question_label = NEW_QUESTION_LABELS.get(
|
| 285 |
+
target_lang, NEW_QUESTION_LABELS["it"]
|
| 286 |
+
)
|
| 287 |
|
| 288 |
prompt = f"""
|
| 289 |
Sei un assistente che lavora su una tabella di metodiche analitiche.
|
|
|
|
| 368 |
except json.JSONDecodeError:
|
| 369 |
error_msg = f"Errore nel parsing della risposta del modello:\n{raw_text}"
|
| 370 |
return (
|
| 371 |
+
f"**{note_title_plain}**",
|
| 372 |
error_msg,
|
| 373 |
+
f"**{table_title_plain}**",
|
| 374 |
empty_df,
|
| 375 |
empty_df,
|
| 376 |
empty_df,
|
| 377 |
None,
|
| 378 |
+
new_question_label,
|
| 379 |
)
|
| 380 |
|
| 381 |
indices = data.get("row_indices", [])
|
| 382 |
note = data.get("note", "")
|
| 383 |
|
| 384 |
+
# Se non ci sono righe selezionate
|
| 385 |
if not indices:
|
| 386 |
return (
|
| 387 |
+
f"**{note_title_plain}**",
|
| 388 |
note,
|
| 389 |
+
f"**{table_title_plain}**",
|
| 390 |
empty_df,
|
| 391 |
empty_df,
|
| 392 |
empty_df,
|
| 393 |
None,
|
| 394 |
+
new_question_label,
|
| 395 |
)
|
| 396 |
|
| 397 |
+
# ATTENZIONE: df.iloc(indices) preserva l'ordine della lista "indices".
|
| 398 |
+
# Quindi, se il modello restituisce la riga più attinente per prima,
|
| 399 |
+
# le righe saranno già in ordine decrescente di matching.
|
| 400 |
risultati_df = df.iloc[indices].copy()
|
| 401 |
|
| 402 |
# =========================================
|
|
|
|
| 481 |
|
| 482 |
# 4) elimina la colonna Keyword & Keyphrases (from Labs) se presente
|
| 483 |
if "Keyword & Keyphrases (from Labs)" in view_full.columns:
|
|
|
|
| 484 |
view_full = view_full.drop(columns=["Keyword & Keyphrases (from Labs)"])
|
| 485 |
|
| 486 |
# 5) elimina colonne che sono solo 'null' o vuote,
|
|
|
|
| 521 |
# FILE SCARICABILE UNICO: "Scarica ricerca"
|
| 522 |
# =========================================
|
| 523 |
|
|
|
|
| 524 |
from openpyxl import Workbook
|
| 525 |
from openpyxl.styles import Alignment, Font, PatternFill
|
| 526 |
|
|
|
|
| 558 |
cell_query_label.font = label_font
|
| 559 |
ws.cell(row=current_row, column=2, value=domanda)
|
| 560 |
# fusione celle B1:D1 per il testo della query
|
| 561 |
+
ws.merge_cells(
|
| 562 |
+
start_row=current_row,
|
| 563 |
+
start_column=2,
|
| 564 |
+
end_row=current_row,
|
| 565 |
+
end_column=4,
|
| 566 |
+
)
|
| 567 |
|
| 568 |
# 2) Analisi della domanda tramite IA (stessa riga: titolo + nota)
|
| 569 |
current_row += 1
|
| 570 |
+
cell_note_title = ws.cell(
|
| 571 |
+
row=current_row,
|
| 572 |
+
column=1,
|
| 573 |
+
value=note_title_plain,
|
| 574 |
+
)
|
| 575 |
cell_note_title.font = label_font
|
| 576 |
ws.cell(row=current_row, column=2, value=note)
|
| 577 |
# fusione celle B2:D2 per il testo della nota
|
| 578 |
+
ws.merge_cells(
|
| 579 |
+
start_row=current_row,
|
| 580 |
+
start_column=2,
|
| 581 |
+
end_row=current_row,
|
| 582 |
+
end_column=4,
|
| 583 |
+
)
|
| 584 |
|
| 585 |
# 3) Titolo tabella delle facilities
|
| 586 |
current_row += 1
|
| 587 |
+
cell_table_title = ws.cell(
|
| 588 |
+
row=current_row,
|
| 589 |
+
column=1,
|
| 590 |
+
value=table_title_plain,
|
| 591 |
+
)
|
| 592 |
cell_table_title.font = label_font
|
| 593 |
|
| 594 |
# 4) Intestazioni della tabella (vista FULL con colonne "More info")
|
|
|
|
| 626 |
wb.save(tmp_report.name)
|
| 627 |
search_file = tmp_report.name
|
| 628 |
|
| 629 |
+
# Output:
|
| 630 |
+
# - i titoli per la UI sono in grassetto (Markdown)
|
| 631 |
+
# - il pulsante "Nuova domanda" usa l'etichetta nella lingua della query
|
| 632 |
return (
|
| 633 |
+
f"**{note_title_plain}**",
|
| 634 |
note,
|
| 635 |
+
f"**{table_title_plain}**",
|
| 636 |
view_basic_translated,
|
| 637 |
view_basic_translated,
|
| 638 |
view_full_translated,
|
| 639 |
search_file,
|
| 640 |
+
new_question_label,
|
| 641 |
)
|
| 642 |
|
| 643 |
|
|
|
|
| 653 |
return pd.DataFrame()
|
| 654 |
|
| 655 |
|
| 656 |
+
def reset_app():
|
| 657 |
+
"""
|
| 658 |
+
Resetta i contenuti per una nuova domanda:
|
| 659 |
+
- svuota la textbox
|
| 660 |
+
- cancella nota, titoli, tabella, stato e file
|
| 661 |
+
"""
|
| 662 |
+
empty_df = pd.DataFrame()
|
| 663 |
+
return (
|
| 664 |
+
"", # domanda
|
| 665 |
+
"", # note_title_md
|
| 666 |
+
"", # note_out
|
| 667 |
+
"", # table_title_md
|
| 668 |
+
empty_df, # table_out
|
| 669 |
+
empty_df, # df_basic_state
|
| 670 |
+
empty_df, # df_full_state
|
| 671 |
+
None, # search_file_out
|
| 672 |
+
)
|
| 673 |
+
|
| 674 |
+
|
| 675 |
# =========================================
|
| 676 |
# 4) INTERFACCIA GRADIO
|
| 677 |
# =========================================
|
|
|
|
| 786 |
|
| 787 |
search_file_out = gr.File(label="Scarica ricerca (.xlsx)")
|
| 788 |
|
| 789 |
+
# Pulsante in fondo pagina per "Nuova domanda"
|
| 790 |
+
new_question_btn = gr.Button("Nuova domanda")
|
| 791 |
+
|
| 792 |
+
# Collegamento bottone "Cerca"
|
| 793 |
btn.click(
|
| 794 |
fn=chiedi_al_db,
|
| 795 |
inputs=domanda,
|
|
|
|
| 801 |
df_basic_state,
|
| 802 |
df_full_state,
|
| 803 |
search_file_out,
|
| 804 |
+
new_question_btn, # aggiorna l'etichetta nella lingua della query
|
| 805 |
],
|
| 806 |
)
|
| 807 |
|
| 808 |
+
# Checkbox More info
|
| 809 |
show_more.change(
|
| 810 |
fn=toggle_more_info,
|
| 811 |
inputs=[show_more, df_basic_state, df_full_state],
|
| 812 |
outputs=table_out,
|
| 813 |
)
|
| 814 |
|
| 815 |
+
# Bottone "Nuova domanda" → reset
|
| 816 |
+
new_question_btn.click(
|
| 817 |
+
fn=reset_app,
|
| 818 |
+
inputs=None,
|
| 819 |
+
outputs=[
|
| 820 |
+
domanda,
|
| 821 |
+
note_title_md,
|
| 822 |
+
note_out,
|
| 823 |
+
table_title_md,
|
| 824 |
+
table_out,
|
| 825 |
+
df_basic_state,
|
| 826 |
+
df_full_state,
|
| 827 |
+
search_file_out,
|
| 828 |
+
],
|
| 829 |
+
)
|
| 830 |
+
|
| 831 |
if __name__ == "__main__":
|
| 832 |
demo.launch()
|