ninooo96 commited on
Commit
b24a01b
·
1 Parent(s): 35e2791

fix translation

Browse files
Files changed (1) hide show
  1. app.py +55 -148
app.py CHANGED
@@ -4,7 +4,6 @@ import libsql_experimental as libsql
4
  from qdrant_client import QdrantClient, models
5
  from sentence_transformers import SentenceTransformer
6
  import time
7
- import gradio as gr
8
  from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, pipeline
9
  from langdetect import detect, DetectorFactory
10
 
@@ -12,45 +11,54 @@ DetectorFactory.seed = 0
12
 
13
  # --- SETUP MODELLO UNIVERSALE (NLLB) ---
14
  print("Caricamento modello NLLB (Any -> Any)...")
15
- # NLLB-200 Distilled: Ottimo compromesso qualità/peso (600M params)
16
  model_name = "facebook/nllb-200-distilled-600M"
17
 
18
- # Carichiamo tokenizzatore e modello
19
  tokenizer = AutoTokenizer.from_pretrained(model_name)
20
  model_trans = AutoModelForSeq2SeqLM.from_pretrained(model_name)
21
-
22
- # Creiamo una pipeline di traduzione generica
23
- # Nota: NLLB usa codici lingua specifici (es. 'eng_Latn', 'ita_Latn')
24
  translator_pipe = pipeline("translation", model=model_trans, tokenizer=tokenizer, device=-1)
25
 
26
- # Mappa semplice per convertire i codici lingua
27
- LANG_CODES = {
 
28
  "en": "eng_Latn",
29
- "it": "ita_Latn"
 
 
 
 
 
 
 
 
30
  }
31
 
32
- def translate_wrapper(text, target_lang_code):
33
  """
34
  Funzione helper per tradurre con NLLB.
35
  """
36
  try:
37
- # Recupera il codice NLLB
38
- nllb_code = LANG_CODES.get(target_lang_code, "eng_Latn")
39
-
40
- target_lang_id = tokenizer.convert_tokens_to_ids(nllb_code)
 
 
 
 
 
41
 
 
42
  output = translator_pipe(
43
  text,
44
- forced_bos_token_id=target_lang_id
45
  )
46
  return output[0]['translation_text']
47
 
48
  except Exception as e:
49
  print(f"Errore NLLB: {e}")
50
- # In caso di errore restituisce il testo originale con un avviso visibile in console
51
  return text
52
 
53
- # --- SETUP ---
54
  model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2", device="cpu")
55
 
56
  QDRANT_URL = os.environ.get("QDRANT_URL")
@@ -59,10 +67,7 @@ TURSO_URL = os.environ.get("TURSO_URL")
59
  TURSO_TOKEN = os.environ.get("TURSO_TOKEN")
60
 
61
  try:
62
- client = QdrantClient(
63
- url=QDRANT_URL,
64
- api_key=QDRANT_API_KEY
65
- )
66
  except Exception as e:
67
  print(f"Errore Qdrant: {e}")
68
 
@@ -72,61 +77,26 @@ def get_turso_conn():
72
  COLLECTION_NAME = "books_collection"
73
  VECTOR_SIZE = 256
74
 
75
- def render_results_from_ids(ids, scores, target_lang="en"):
76
- """
77
- target_lang: 'en' (Inglese) o 'it' (Italiano)
78
- """
79
- if not ids: return "Nessun risultato trovato."
80
-
81
- # --- JAVASCRIPT PER FORZARE DARK MODE ---
82
  JS_FORCE_DARK = """
83
  function() {
84
  document.body.classList.add('dark');
85
  }
86
  """
87
 
88
- # --- CSS GLOBALE ---
89
  GLOBAL_CSS = """
90
- /* Sfondo pagina e testo */
91
- body, .gradio-container {
92
- background-color: #0b0f19 !important;
93
- color: #e5e7eb !important;
94
- }
95
-
96
- /* Animazione Spinner */
97
- .loader {
98
- border: 6px solid #374151;
99
- border-radius: 50%;
100
- border-top: 6px solid #3b82f6;
101
- border-bottom: 6px solid #ef4444;
102
- width: 40px;
103
- height: 40px;
104
- -webkit-animation: spin 1s linear infinite;
105
- animation: spin 1s linear infinite;
106
- margin: 0 auto;
107
- }
108
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
109
-
110
- /* Bottoni Dataset */
111
- #book_cards button {
112
- background-color: #1f2937 !important;
113
- color: #e5e7eb !important;
114
- border: 1px solid #374151 !important;
115
- }
116
- #book_cards button:hover {
117
- background-color: #374151 !important;
118
- border-color: #60a5fa !important;
119
- }
120
  #book_cards .text-sm { color: #9ca3af !important; }
121
-
122
- /* Stile Card Risultati */
123
  .card-force-dark { color: #e5e7eb !important; }
124
  .card-force-dark h3 { color: #60a5fa !important; margin-top: 0 !important; }
125
  .card-force-dark p, .card-force-dark b, .card-force-dark span { color: #d1d5db !important; }
126
  .card-force-dark summary { color: #fbbf24 !important; }
127
  """
128
 
129
- # HTML dello spinner
130
  LOADING_HTML = """
131
  <div style="display: flex; justify-content: center; align-items: center; height: 100px; width: 100%; flex-direction: column;">
132
  <div class="loader"></div>
@@ -149,10 +119,8 @@ def render_results_from_ids(ids, scores, target_lang="en"):
149
  cursor = conn.execute(sql_query, tuple(ids))
150
  rows = cursor.fetchall()
151
  books_map = {row[0]: row for row in rows}
152
-
153
  for uid in ids:
154
- if uid in books_map:
155
- ordered_books.append(books_map[uid])
156
  except Exception as e:
157
  return f"Errore Database Turso: {str(e)}"
158
  finally:
@@ -161,37 +129,37 @@ def render_results_from_ids(ids, scores, target_lang="en"):
161
  html_output = "<div style='font-family: sans-serif; gap: 10px; display: flex; flex-direction: column;'>"
162
  for row in ordered_books:
163
  score = scores.get(row[0], 0.0)
164
-
165
  autore_clean = str(row[2]).replace('"', '').replace("[","").replace("]", "")
166
-
167
  original_summary = row[5]
 
168
  trama_display = "No summary available."
169
  lang_label = ""
170
 
171
- # --- LOGICA DI TRADUZIONE INTELLIGENTE ---
172
  if original_summary and len(original_summary) > 10:
173
  try:
174
- # Rileva lingua originale
175
  try:
176
- detected_lang = detect(original_summary)
177
  except:
178
- detected_lang = "unknown"
179
 
180
- # Se la lingua è già quella target, non fare nulla
181
- if detected_lang == target_lang:
182
  trama_display = original_summary
183
  lang_label = ""
184
 
185
- # Altrimenti traduci
186
  else:
187
- trama_display = translate_wrapper(original_summary, target_lang)
188
- lang_label = f"(Translated to {target_lang.upper()})"
189
 
190
  except Exception as e:
 
191
  trama_display = original_summary
192
  else:
193
  trama_display = original_summary
194
- # -----------------------------------------
195
 
196
  label_trama = "Leggi Trama" if target_lang == 'it' else "Read Summary"
197
 
@@ -216,11 +184,9 @@ def render_results_from_ids(ids, scores, target_lang="en"):
216
  def search_free_text_animated(query_text, max_results, target_lang):
217
  yield gr.update(visible=True), gr.update(visible=False)
218
  time.sleep(0.2)
219
-
220
  if not query_text:
221
  yield gr.update(visible=False), "Inserisci una richiesta!"
222
  return
223
-
224
  try:
225
  vec = model.encode(f"{query_text}")[:VECTOR_SIZE]
226
  hits_response = client.query_points(
@@ -232,60 +198,43 @@ def search_free_text_animated(query_text, max_results, target_lang):
232
  hits = hits_response.points
233
  ids = [hit.id for hit in hits]
234
  scores = {hit.id: hit.score for hit in hits}
235
-
236
  final_html = render_results_from_ids(ids, scores, target_lang)
237
-
238
  yield gr.update(visible=False), gr.update(value=final_html, visible=True)
239
-
240
  except Exception as e:
241
  yield gr.update(visible=False), f"Errore: {e}"
242
 
243
-
244
  def find_book_cards_animated(partial_title):
245
- # FASE 1: Spinner ON, Dataset OFF
246
  yield gr.update(visible=True), gr.update(visible=False), [], gr.update(value="", visible=False)
247
  time.sleep(0.3)
248
-
249
  if not partial_title or len(partial_title) < 2:
250
  yield gr.update(visible=False), gr.update(samples=[], visible=False), [], gr.update(visible=False)
251
  return
252
-
253
  conn = None
254
  try:
255
  conn = get_turso_conn()
256
  query = f"SELECT id, title, author, year FROM books WHERE title LIKE '%{partial_title}%' LIMIT 10"
257
  rows = conn.execute(query).fetchall()
258
-
259
  card_data = [[str(row[1]), str(row[2]).replace('"', '').replace("'", "").replace("[","").replace("]",""), str(row[3]).split('.')[0]] for row in rows]
260
  full_data_state = [{"id": row[0], "title": row[1]} for row in rows]
261
-
262
- # FASE 2: Spinner OFF, Dataset ON
263
  yield gr.update(visible=False), gr.update(samples=card_data, visible=True), full_data_state, gr.update(visible=False)
264
-
265
  except Exception as e:
266
- print(f"Error: {e}")
267
  yield gr.update(visible=False), gr.update(visible=False), [], gr.update(visible=False)
268
  finally:
269
  if conn: conn.close()
270
 
271
-
272
  def on_card_click_animated(selected_index, books_state_list, max_results, target_lang):
273
  yield gr.update(visible=True), gr.update(visible=False)
274
  time.sleep(0.2)
275
-
276
  if selected_index >= len(books_state_list):
277
  yield gr.update(visible=False), "Errore selezione."
278
  return
279
-
280
  book_obj = books_state_list[selected_index]
281
  source_id = book_obj["id"]
282
-
283
  try:
284
  points = client.retrieve(collection_name=COLLECTION_NAME, ids=[source_id], with_vectors=True)
285
  if not points:
286
  yield gr.update(visible=False), "ID non trovato."
287
  return
288
-
289
  existing_vector = points[0].vector
290
  hits_response = client.query_points(
291
  collection_name=COLLECTION_NAME,
@@ -297,28 +246,16 @@ def on_card_click_animated(selected_index, books_state_list, max_results, target
297
  ids = [hit.id for hit in hits_response.points]
298
  scores = {hit.id: hit.score for hit in hits_response.points}
299
  final_html = render_results_from_ids(ids, scores, target_lang)
300
-
301
  yield gr.update(visible=False), gr.update(value=final_html, visible=True)
302
-
303
  except Exception as e:
304
  yield gr.update(visible=False), f"Errore Backend: {e}"
305
 
306
- def clear_search_tab():
307
- """Pulisce il Tab Ricerca per Trama quando si passa all'altro."""
308
- return gr.update(value=""), gr.update(value="", visible=False)
309
-
310
- def clear_find_tab():
311
- """Pulisce il Tab Mi è piaciuto quando si passa all'altro."""
312
- return (
313
- gr.update(value=""), # txt_title
314
- gr.update(visible=False), # cards_view (Dataset)
315
- gr.update(value="", visible=False) # out_results
316
- )
317
 
318
  # --- INTERFACCIA ---
319
  with gr.Blocks(theme=gr.themes.Ocean(), css=GLOBAL_CSS, js=JS_FORCE_DARK) as demo:
320
  gr.Markdown("# 📚 AI Book Finder")
321
-
322
  books_state = gr.State([])
323
 
324
  with gr.Row():
@@ -326,61 +263,31 @@ with gr.Blocks(theme=gr.themes.Ocean(), css=GLOBAL_CSS, js=JS_FORCE_DARK) as dem
326
  lang_choice = gr.Dropdown(choices=["en", "it"], value="en", label="Lingua Trama / Summary Language")
327
 
328
  with gr.Tabs() as tabs:
329
-
330
- # TAB 1: Ricerca Libera
331
- with gr.Tab("🔎 Ricerca per Trama") as tab_trama: # Assegna variabile
332
  with gr.Row():
333
- txt_input = gr.Textbox(placeholder="Descrivi la trama, l'atmosfera o le emozioni che cerchi...", show_label=False, scale=4)
334
  btn_search = gr.Button("Cerca", variant="primary", scale=1)
335
 
336
- # TAB 2: Ricerca per Libro
337
- with gr.Tab("📖 Mi è piaciuto...") as tab_libro: # Assegna variabile
338
  with gr.Row():
339
- txt_title = gr.Textbox(placeholder="Scrivi il titolo, anche parziale", show_label=False, scale=4)
340
  btn_find = gr.Button("Trova", scale=1)
341
-
342
  loader_cards = gr.HTML(value=LOADING_HTML, visible=False)
343
-
344
- # DATASET
345
- cards_view = gr.Dataset(
346
- elem_id="book_cards",
347
- label="Seleziona il libro corretto:",
348
- components=[gr.Textbox(visible=False), gr.Textbox(visible=False), gr.Textbox(visible=False)],
349
- headers=["Titolo", "Autore", "Anno"],
350
- samples=[],
351
- visible=False,
352
- type="index"
353
- )
354
 
355
  loader_results = gr.HTML(value=LOADING_HTML, visible=False)
356
  out_results = gr.HTML(label="Consigli", visible=True)
357
 
358
- # --- EVENTI DI RICERCA ---
359
  btn_search.click(fn=search_free_text_animated, inputs=[txt_input, num_results, lang_choice], outputs=[loader_results, out_results])
360
  txt_input.submit(fn=search_free_text_animated, inputs=[txt_input, num_results, lang_choice], outputs=[loader_results, out_results])
361
 
362
  btn_find.click(fn=find_book_cards_animated, inputs=[txt_title], outputs=[loader_cards, cards_view, books_state, out_results])
363
  txt_title.submit(fn=find_book_cards_animated, inputs=[txt_title], outputs=[loader_cards, cards_view, books_state, out_results])
364
 
365
- cards_view.click(
366
- fn=on_card_click_animated,
367
- inputs=[cards_view, books_state, num_results, lang_choice],
368
- outputs=[loader_results, out_results]
369
- )
370
-
371
- # Quando clicco su "Ricerca per Trama", pulisco il Tab "Mi è piaciuto"
372
- tab_trama.select(
373
- fn=clear_find_tab,
374
- inputs=None,
375
- outputs=[txt_title, cards_view, out_results]
376
- )
377
-
378
- # Quando clicco su "Mi è piaciuto", pulisco il Tab "Ricerca per Trama" e i risultati globali
379
- tab_libro.select(
380
- fn=clear_search_tab,
381
- inputs=None,
382
- outputs=[txt_input, out_results]
383
- )
384
 
385
  print("Avvio Gradio...")
386
  demo.launch(share=True, debug=True)
 
4
  from qdrant_client import QdrantClient, models
5
  from sentence_transformers import SentenceTransformer
6
  import time
 
7
  from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, pipeline
8
  from langdetect import detect, DetectorFactory
9
 
 
11
 
12
  # --- SETUP MODELLO UNIVERSALE (NLLB) ---
13
  print("Caricamento modello NLLB (Any -> Any)...")
 
14
  model_name = "facebook/nllb-200-distilled-600M"
15
 
 
16
  tokenizer = AutoTokenizer.from_pretrained(model_name)
17
  model_trans = AutoModelForSeq2SeqLM.from_pretrained(model_name)
 
 
 
18
  translator_pipe = pipeline("translation", model=model_trans, tokenizer=tokenizer, device=-1)
19
 
20
+ # Mappa per convertire i codici semplici (langdetect) nei codici NLLB
21
+ # Se il libro è in una lingua non in lista, useremo 'eng_Latn' come fallback
22
+ CODE_TO_NLLB = {
23
  "en": "eng_Latn",
24
+ "it": "ita_Latn",
25
+ "fr": "fra_Latn",
26
+ "es": "spa_Latn",
27
+ "de": "deu_Latn",
28
+ "pt": "por_Latn",
29
+ "nl": "nld_Latn",
30
+ "ru": "rus_Cyrl",
31
+ "zh": "zho_Hans",
32
+ "ja": "jpn_Jpan"
33
  }
34
 
35
+ def translate_wrapper(text, source_lang_simple, target_lang_simple):
36
  """
37
  Funzione helper per tradurre con NLLB.
38
  """
39
  try:
40
+ # Convertiamo i codici (es. 'en' -> 'eng_Latn')
41
+ src_nllb = CODE_TO_NLLB.get(source_lang_simple, "eng_Latn") # Default Inglese se sconosciuto
42
+ tgt_nllb = CODE_TO_NLLB.get(target_lang_simple, "eng_Latn")
43
+
44
+ # Diciamo al tokenizer qual è la lingua di partenza
45
+ tokenizer.src_lang = src_nllb
46
+
47
+ # Otteniamo l'ID della lingua di arrivo
48
+ target_lang_id = tokenizer.convert_tokens_to_ids(tgt_nllb)
49
 
50
+ # Eseguiamo la traduzione
51
  output = translator_pipe(
52
  text,
53
+ forced_bos_token_id=target_lang_id, # Forza lingua output
54
  )
55
  return output[0]['translation_text']
56
 
57
  except Exception as e:
58
  print(f"Errore NLLB: {e}")
 
59
  return text
60
 
61
+ # --- SETUP DATABASES & EMBEDDER ---
62
  model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2", device="cpu")
63
 
64
  QDRANT_URL = os.environ.get("QDRANT_URL")
 
67
  TURSO_TOKEN = os.environ.get("TURSO_TOKEN")
68
 
69
  try:
70
+ client = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY)
 
 
 
71
  except Exception as e:
72
  print(f"Errore Qdrant: {e}")
73
 
 
77
  COLLECTION_NAME = "books_collection"
78
  VECTOR_SIZE = 256
79
 
80
+ # --- JAVASCRIPT & CSS ---
 
 
 
 
 
 
81
  JS_FORCE_DARK = """
82
  function() {
83
  document.body.classList.add('dark');
84
  }
85
  """
86
 
 
87
  GLOBAL_CSS = """
88
+ body, .gradio-container { background-color: #0b0f19 !important; color: #e5e7eb !important; }
89
+ .loader { border: 6px solid #374151; border-radius: 50%; border-top: 6px solid #3b82f6; border-bottom: 6px solid #ef4444; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
91
+ #book_cards button { background-color: #1f2937 !important; color: #e5e7eb !important; border: 1px solid #374151 !important; }
92
+ #book_cards button:hover { background-color: #374151 !important; border-color: #60a5fa !important; }
 
 
 
 
 
 
 
 
 
93
  #book_cards .text-sm { color: #9ca3af !important; }
 
 
94
  .card-force-dark { color: #e5e7eb !important; }
95
  .card-force-dark h3 { color: #60a5fa !important; margin-top: 0 !important; }
96
  .card-force-dark p, .card-force-dark b, .card-force-dark span { color: #d1d5db !important; }
97
  .card-force-dark summary { color: #fbbf24 !important; }
98
  """
99
 
 
100
  LOADING_HTML = """
101
  <div style="display: flex; justify-content: center; align-items: center; height: 100px; width: 100%; flex-direction: column;">
102
  <div class="loader"></div>
 
119
  cursor = conn.execute(sql_query, tuple(ids))
120
  rows = cursor.fetchall()
121
  books_map = {row[0]: row for row in rows}
 
122
  for uid in ids:
123
+ if uid in books_map: ordered_books.append(books_map[uid])
 
124
  except Exception as e:
125
  return f"Errore Database Turso: {str(e)}"
126
  finally:
 
129
  html_output = "<div style='font-family: sans-serif; gap: 10px; display: flex; flex-direction: column;'>"
130
  for row in ordered_books:
131
  score = scores.get(row[0], 0.0)
 
132
  autore_clean = str(row[2]).replace('"', '').replace("[","").replace("]", "")
 
133
  original_summary = row[5]
134
+
135
  trama_display = "No summary available."
136
  lang_label = ""
137
 
138
+ # --- LOGICA TRADUZIONE ---
139
  if original_summary and len(original_summary) > 10:
140
  try:
141
+ # Rileva lingua ORIGINALE (Source)
142
  try:
143
+ detected_src = detect(original_summary)
144
  except:
145
+ detected_src = "en" # Fallback se fallisce il rilevamento
146
 
147
+ # Se la lingua è già quella target, ok
148
+ if detected_src == target_lang:
149
  trama_display = original_summary
150
  lang_label = ""
151
 
152
+ # Altrimenti traduci passando Source -> Target
153
  else:
154
+ trama_display = translate_wrapper(original_summary, detected_src, target_lang)
155
+ lang_label = f"(Translated from {detected_src.upper()})"
156
 
157
  except Exception as e:
158
+ print(f"Err render: {e}")
159
  trama_display = original_summary
160
  else:
161
  trama_display = original_summary
162
+ # -------------------------
163
 
164
  label_trama = "Leggi Trama" if target_lang == 'it' else "Read Summary"
165
 
 
184
  def search_free_text_animated(query_text, max_results, target_lang):
185
  yield gr.update(visible=True), gr.update(visible=False)
186
  time.sleep(0.2)
 
187
  if not query_text:
188
  yield gr.update(visible=False), "Inserisci una richiesta!"
189
  return
 
190
  try:
191
  vec = model.encode(f"{query_text}")[:VECTOR_SIZE]
192
  hits_response = client.query_points(
 
198
  hits = hits_response.points
199
  ids = [hit.id for hit in hits]
200
  scores = {hit.id: hit.score for hit in hits}
 
201
  final_html = render_results_from_ids(ids, scores, target_lang)
 
202
  yield gr.update(visible=False), gr.update(value=final_html, visible=True)
 
203
  except Exception as e:
204
  yield gr.update(visible=False), f"Errore: {e}"
205
 
 
206
  def find_book_cards_animated(partial_title):
 
207
  yield gr.update(visible=True), gr.update(visible=False), [], gr.update(value="", visible=False)
208
  time.sleep(0.3)
 
209
  if not partial_title or len(partial_title) < 2:
210
  yield gr.update(visible=False), gr.update(samples=[], visible=False), [], gr.update(visible=False)
211
  return
 
212
  conn = None
213
  try:
214
  conn = get_turso_conn()
215
  query = f"SELECT id, title, author, year FROM books WHERE title LIKE '%{partial_title}%' LIMIT 10"
216
  rows = conn.execute(query).fetchall()
 
217
  card_data = [[str(row[1]), str(row[2]).replace('"', '').replace("'", "").replace("[","").replace("]",""), str(row[3]).split('.')[0]] for row in rows]
218
  full_data_state = [{"id": row[0], "title": row[1]} for row in rows]
 
 
219
  yield gr.update(visible=False), gr.update(samples=card_data, visible=True), full_data_state, gr.update(visible=False)
 
220
  except Exception as e:
 
221
  yield gr.update(visible=False), gr.update(visible=False), [], gr.update(visible=False)
222
  finally:
223
  if conn: conn.close()
224
 
 
225
  def on_card_click_animated(selected_index, books_state_list, max_results, target_lang):
226
  yield gr.update(visible=True), gr.update(visible=False)
227
  time.sleep(0.2)
 
228
  if selected_index >= len(books_state_list):
229
  yield gr.update(visible=False), "Errore selezione."
230
  return
 
231
  book_obj = books_state_list[selected_index]
232
  source_id = book_obj["id"]
 
233
  try:
234
  points = client.retrieve(collection_name=COLLECTION_NAME, ids=[source_id], with_vectors=True)
235
  if not points:
236
  yield gr.update(visible=False), "ID non trovato."
237
  return
 
238
  existing_vector = points[0].vector
239
  hits_response = client.query_points(
240
  collection_name=COLLECTION_NAME,
 
246
  ids = [hit.id for hit in hits_response.points]
247
  scores = {hit.id: hit.score for hit in hits_response.points}
248
  final_html = render_results_from_ids(ids, scores, target_lang)
 
249
  yield gr.update(visible=False), gr.update(value=final_html, visible=True)
 
250
  except Exception as e:
251
  yield gr.update(visible=False), f"Errore Backend: {e}"
252
 
253
+ def clear_search_tab(): return gr.update(value=""), gr.update(value="", visible=False)
254
+ def clear_find_tab(): return gr.update(value=""), gr.update(visible=False), gr.update(value="", visible=False)
 
 
 
 
 
 
 
 
 
255
 
256
  # --- INTERFACCIA ---
257
  with gr.Blocks(theme=gr.themes.Ocean(), css=GLOBAL_CSS, js=JS_FORCE_DARK) as demo:
258
  gr.Markdown("# 📚 AI Book Finder")
 
259
  books_state = gr.State([])
260
 
261
  with gr.Row():
 
263
  lang_choice = gr.Dropdown(choices=["en", "it"], value="en", label="Lingua Trama / Summary Language")
264
 
265
  with gr.Tabs() as tabs:
266
+ with gr.Tab("🔎 Ricerca per Trama") as tab_trama:
 
 
267
  with gr.Row():
268
+ txt_input = gr.Textbox(placeholder="Descrivi la trama...", show_label=False, scale=4)
269
  btn_search = gr.Button("Cerca", variant="primary", scale=1)
270
 
271
+ with gr.Tab("📖 Mi è piaciuto...") as tab_libro:
 
272
  with gr.Row():
273
+ txt_title = gr.Textbox(placeholder="Scrivi il titolo...", show_label=False, scale=4)
274
  btn_find = gr.Button("Trova", scale=1)
 
275
  loader_cards = gr.HTML(value=LOADING_HTML, visible=False)
276
+ cards_view = gr.Dataset(elem_id="book_cards", label="Seleziona il libro:", components=[gr.Textbox(visible=False), gr.Textbox(visible=False), gr.Textbox(visible=False)], headers=["Titolo", "Autore", "Anno"], samples=[], visible=False, type="index")
 
 
 
 
 
 
 
 
 
 
277
 
278
  loader_results = gr.HTML(value=LOADING_HTML, visible=False)
279
  out_results = gr.HTML(label="Consigli", visible=True)
280
 
 
281
  btn_search.click(fn=search_free_text_animated, inputs=[txt_input, num_results, lang_choice], outputs=[loader_results, out_results])
282
  txt_input.submit(fn=search_free_text_animated, inputs=[txt_input, num_results, lang_choice], outputs=[loader_results, out_results])
283
 
284
  btn_find.click(fn=find_book_cards_animated, inputs=[txt_title], outputs=[loader_cards, cards_view, books_state, out_results])
285
  txt_title.submit(fn=find_book_cards_animated, inputs=[txt_title], outputs=[loader_cards, cards_view, books_state, out_results])
286
 
287
+ cards_view.click(fn=on_card_click_animated, inputs=[cards_view, books_state, num_results, lang_choice], outputs=[loader_results, out_results])
288
+
289
+ tab_trama.select(fn=clear_find_tab, inputs=None, outputs=[txt_title, cards_view, out_results])
290
+ tab_libro.select(fn=clear_search_tab, inputs=None, outputs=[txt_input, out_results])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
 
292
  print("Avvio Gradio...")
293
  demo.launch(share=True, debug=True)