jackmedda commited on
Commit
025f2ca
·
1 Parent(s): 71692ea

Trying fix with subprocess

Browse files
app.py CHANGED
@@ -1,823 +1,5 @@
1
- import argparse
2
- import json
3
- import os
4
- import random
5
- from functools import partial
6
-
7
- import gradio as gr
8
-
9
- from app.utils.poi_search_utils import (
10
- clear_all_selections,
11
- get_selection_summary,
12
- pois_list,
13
- search_pois,
14
- toggle_poi_selection,
15
- )
16
- from app.utils.recommender_poi_utils import (
17
- add_poi_to_completed,
18
- back_to_recommendations,
19
- get_recommendations_ui,
20
- show_recommendation_list,
21
- view_poi_details,
22
- )
23
- from app.utils.survey_utils import (
24
- feedback_message,
25
- handle_all_answered_responses,
26
- handle_next_navigation,
27
- handle_prev_navigation,
28
- handle_submit_survey,
29
- submit_exp_feedback,
30
- submit_rec_feedback,
31
- update_advanced_responses,
32
- )
33
- from app.utils.user_profile_utils import (
34
- login_user,
35
- mark_profile_complete,
36
- update_user_selected_pois,
37
- update_user_sensory_aversions
38
- )
39
-
40
- RESOURCE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources")
41
-
42
- with open(os.path.join(RESOURCE_DIR, "aversion_config_questions.json"), "r") as f:
43
- AVERSIONS_QUESTIONS = json.load(f)
44
- with open(os.path.join(RESOURCE_DIR, "aversions.json"), "r") as f:
45
- AVERSIONS = json.load(f)
46
- AVERSIONS = {v["Name"]: v for v in AVERSIONS}
47
-
48
-
49
- feedback_message_delay = 1500
50
- js_feedback_message_delay = f"""() => {{
51
- return new Promise(resolve => {{
52
- setTimeout(() => {{
53
- resolve();
54
- }}, {feedback_message_delay});
55
- }});
56
- }}"""
57
-
58
-
59
- def create_main_app(self):
60
- with gr.Blocks(
61
- title="Sistema di Raccomandazione di Punti di Interesse con Personalizzazione Sensoriale",
62
- theme=gr.themes.Soft(),
63
- css="""
64
- footer{display:none !important}
65
- .navigation-container {
66
- background: #f8f9fa;
67
- padding: 15px;
68
- border-radius: 10px;
69
- margin-bottom: 20px;
70
- border-left: 4px solid #007bff;
71
- }
72
- .page-container {
73
- min-height: 500px;
74
- padding: 20px;
75
- }
76
- .poi-card {
77
- transition: all 0.3s ease;
78
- }
79
- .poi-card:hover {
80
- transform: scale(1.02);
81
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
82
- }
83
- .search-container {
84
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
85
- padding: 30px;
86
- border-radius: 15px;
87
- color: white;
88
- margin-bottom: 30px;
89
- }
90
- .continue-button {
91
- background-color: #28a745 !important;
92
- border-color: #28a745 !important;
93
- font-size: 16px !important;
94
- padding: 12px 24px !important;
95
- }
96
- .continue-button:hover {
97
- background-color: #218838 !important;
98
- border-color: #1e7e34 !important;
99
- }
100
- .clear-button {
101
- background-color: #dc3545 !important;
102
- border-color: #dc3545 !important;
103
- }
104
- .clear-button:hover {
105
- background-color: #c82333 !important;
106
- border-color: #bd2130 !important;
107
- }
108
- .sensory-config-main-container {
109
- max-width: 900px;
110
- margin: 0 auto;
111
- padding: 20px;
112
- }
113
- .header-section {
114
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
115
- color: white;
116
- padding: 20px;
117
- border-radius: 12px;
118
- margin-bottom: 25px;
119
- text-align: center;
120
- }
121
- .radio-aversion-grid {
122
- display: grid;
123
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
124
- gap: 20px;
125
- margin-bottom: 25px;
126
- }
127
- .radio-aversion-card {
128
- background: #f8f9fa;
129
- border: 1px solid #e9ecef;
130
- border-radius: 12px;
131
- padding: 15px;
132
- }
133
- .continue-to-recommendations-button {
134
- background-color: #28a745 !important;
135
- border-color: #28a745 !important;
136
- font-size: 16px !important;
137
- padding: 12px 30px !important;
138
- width: 100% !important;
139
- }
140
- .continue-to-recommendations-button:hover {
141
- background-color: #218838 !important;
142
- border-color: #1e7e34 !important;
143
- }
144
- .feedback-indicator {
145
- margin-top: 5px;
146
- }
147
- .back-button-custom {
148
- background-color: #32568f !important;
149
- border-color: #32568f !important;
150
- color: white !important;
151
- }
152
- .back-button-custom:hover {
153
- background-color: #132137 !important;
154
- border-color: #132137 !important;
155
- }
156
- .login-button {
157
- background-color: #007bff !important;
158
- border-color: #007bff !important;
159
- }
160
- .login-button:hover {
161
- background-color: #0056b3 !important;
162
- border-color: #004085 !important;
163
- }
164
- #poi-search-input {
165
- color: black;
166
- }
167
- """,
168
- ) as app:
169
- # Stati globali dell'applicazione
170
- app_user_id = gr.State("")
171
- current_page = gr.State("login") # "login", "poi_selection", "sensory_config", "recommendations"
172
- selected_poi_names = gr.State(set())
173
- recommendations_state = gr.State([])
174
- selected_poi_index = gr.State(-1)
175
- completed_survey_pois = gr.State(set())
176
- current_poi_info = gr.State({}) # Store current POI info for survey
177
- user_status = gr.State("") # "new_user", "existing_incomplete", "existing_complete"
178
-
179
- # Container per le diverse pagine
180
- with gr.Column(elem_classes="page-container"):
181
- # Pagina 1: Login/Profile
182
- with gr.Column(visible=True) as login_page:
183
- gr.Markdown("# 👤 Login / Registrazione")
184
-
185
- # Interfaccia di login semplificata
186
- with gr.Row():
187
- user_id_input = gr.Textbox(
188
- placeholder="es: mario_rossi, alice_123, user001...",
189
- label="🆔 Inserisci il tuo ID Utente",
190
- info="Scegli un ID univoco che userai per accedere al sistema",
191
- elem_id="user-id-input",
192
- scale=2
193
- )
194
- login_button = gr.Button(
195
- "🚀 Accedi / Registrati",
196
- variant="primary",
197
- elem_classes="action-button login-button",
198
- )
199
-
200
- login_status_message = gr.Markdown("", visible=False)
201
-
202
- with gr.Row(visible=False) as login_actions:
203
- start_setup_btn = gr.Button(
204
- "📝 Configura Profilo", variant="secondary"
205
- )
206
- go_to_recommendations_btn = gr.Button(
207
- "🎯 Vai alle Raccomandazioni", variant="primary"
208
- )
209
-
210
- with gr.Column(visible=False) as poi_selection_page:
211
- with gr.Column(elem_classes="search-container"):
212
- gr.Markdown("# 🔍 Cerca e seleziona i **Luoghi di Interesse** che preferisci cliccando sulla card corrispondente")
213
- gr.Markdown(
214
- "Esplora i luoghi disponibili e seleziona quelli che preferisci maggiormente. La ricerca è dinamica: inizia a digitare per filtrare i risultati."
215
- )
216
-
217
- # Search input (dynamic, no button needed)
218
- search_input = gr.Textbox(
219
- placeholder="Cerca per nome, descrizione o località...",
220
- label="",
221
- show_label=False,
222
- container=False,
223
- elem_id="poi-search-input",
224
- )
225
-
226
- with gr.Column():
227
- # Selection summary
228
- selection_summary = gr.Markdown(get_selection_summary(set()))
229
-
230
- # Navigation buttons
231
- with gr.Row():
232
- clear_button = gr.Button(
233
- "🗑️ Cancella Selezioni",
234
- variant="secondary",
235
- elem_classes="clear-button",
236
- scale=1,
237
- )
238
- continue_to_sensory_btn = gr.Button(
239
- "Continua alla Configurazione Sensoriale ➡️",
240
- variant="primary",
241
- elem_classes="continue_to_sensory_btn",
242
- scale=3,
243
- )
244
-
245
- # POI display area
246
- poi_display = gr.HTML(
247
- value=search_pois("", set()), elem_id="poi-display-area"
248
- )
249
-
250
- # Create hidden buttons for each POI
251
- poi_buttons = {}
252
- for poi in pois_list:
253
- poi_name = poi["name"]
254
- safe_name = (
255
- poi_name.replace(" ", "-").replace("'", "").replace('"', "")
256
- )
257
- button = gr.Button(
258
- poi_name, visible=False, elem_id=f"poi-btn-{safe_name}"
259
- )
260
- poi_buttons[poi_name] = button
261
-
262
- with gr.Column(
263
- visible=False, elem_classes="sensory-config-main-container"
264
- ) as sensory_config_page:
265
- # Header compatto
266
- gr.HTML("""
267
- <div class="header-section">
268
- <h2 style="margin: 0 0 10px 0;">⚙️ Configura le tue Preferenze Sensoriali</h2>
269
- <p style="margin: 0; opacity: 0.9;">Indica il tuo livello di sensibilità per ogni caratteristica</p>
270
- </div>
271
- """)
272
-
273
- aversion_radios = []
274
- with gr.Column():
275
- for i, aversion_question in enumerate(AVERSIONS_QUESTIONS):
276
- question = aversion_question.get("question", "")
277
- aversion_name = aversion_question.get("aversion_name", "")
278
- aversion = AVERSIONS[aversion_name]
279
-
280
- with gr.Group(elem_classes="radio-aversion-card"):
281
- with gr.Column():
282
- # Titolo compatto con icona
283
- gr.HTML(f"""
284
- <div style="display: flex; align-items: center; margin-bottom: 8px;">
285
- <span style="font-size: 20px; margin-right: 10px;">{aversion['Icon']} {aversion_name}</span>
286
- <div>
287
- <div style="font-size: 14px; color: #D7D7D7; margin-top: 2px;">{question}</div>
288
- </div>
289
- </div>
290
- """)
291
-
292
- # Quanto ti dà fastidio questo fenomeno sensoriale?
293
- av_radio = gr.Radio(
294
- choices=[("Nessuno", 1), ("Poco", 2), ("Moderato", 3), ("Molto", 4), ("Estremo", 5)],
295
- value=3,
296
- show_label=False,
297
- interactive=True,
298
- elem_id=f"radio-{aversion_name.lower().replace(' ', '-')}",
299
- )
300
-
301
- aversion_radios.append(av_radio)
302
-
303
- # Pulsante continua
304
- with gr.Row():
305
- continue_to_recommendations_button = gr.Button(
306
- "Continua alle Raccomandazioni →",
307
- variant="primary",
308
- elem_classes="continue-to-recommendations-button",
309
- size="lg",
310
- )
311
-
312
- with gr.Column(visible=False) as recommendations_page:
313
- with gr.Column(visible=True) as recommendations_list_page:
314
- gr.Markdown(
315
- "# Sistema di Raccomandazione di Punti di Interesse"
316
- )
317
- gr.Markdown("""
318
- Questo sistema raccomanda punti di interesse personalizzati per le preferenze sensoriali degli utenti.
319
- Ogni raccomandazione include informazioni sensoriali dettagliate e spiegazioni personalizzate.
320
- """)
321
-
322
- recommendation_list = gr.HTML(label="Raccomandazioni")
323
-
324
- with gr.Row(visible=False):
325
- select_buttons = [
326
- gr.Button(f"Select_{i}", elem_id=f"select-button-{i}")
327
- for i in range(args.top_k)
328
- ]
329
-
330
- with gr.Column(visible=False) as details_page_block:
331
- gr.Markdown("# Dettagli POI")
332
- poi_details = gr.HTML(label="Dettagli Punto di Interesse")
333
-
334
- with gr.Column(visible=False) as recommendation_feedback_block:
335
- gr.Markdown("## Ti è piaciuto questo luogo che ti abbiamo suggerito?")
336
-
337
- with gr.Row(equal_height=True):
338
- with gr.Column(scale=1):
339
- rec_like_btn = gr.Button("👍 Sì", size="lg")
340
- with gr.Column(scale=1):
341
- rec_dislike_btn = gr.Button("👎 No", size="lg")
342
-
343
- recommendation_feedback_result = gr.State(False)
344
- recommendation_feedback_submitted = gr.State(False)
345
- recommendation_feedback_message = gr.HTML(visible=False)
346
-
347
- # SIMPLE SURVEY
348
- with gr.Column(visible=False) as simple_survey_block:
349
- gr.Markdown("## Ti è stata utile questa spiegazione?")
350
-
351
- with gr.Row(equal_height=True):
352
- with gr.Column(scale=1):
353
- exp_like_btn = gr.Button("👍 Sì", size="lg")
354
- with gr.Column(scale=1):
355
- exp_dislike_btn = gr.Button("👎 No", size="lg")
356
-
357
- simple_survey_result = gr.State(False)
358
- simple_feedback_submitted = gr.State(False)
359
- simple_feedback_message = gr.HTML(visible=False)
360
-
361
- # ADVANCED SURVEY
362
- with gr.Column(visible=False) as advanced_survey_block:
363
- gr.Markdown("## Valuta questa spiegazione della raccomandazione")
364
-
365
- # Stato per tenere traccia del gruppo corrente
366
- advanced_current_group_idx = gr.State(0)
367
-
368
- # Carica i dati del questionario
369
- from app.utils.survey_utils import LIKERT_OPTIONS, SURVEY_STATEMENTS
370
-
371
- # Contenitori per i gruppi di affermazioni
372
- advanced_containers = []
373
- advanced_responses = [[], [], []]
374
- statements = SURVEY_STATEMENTS.copy()
375
- random.shuffle(statements)
376
-
377
- # Prendiamo il massimo di affermazioni per gruppo
378
- max_statements_per_group = 2 # Per i primi due gruppi
379
- for i in range(3):
380
- with gr.Column(visible=(i == 0)) as container:
381
- gr.Markdown(f"### Parte {i + 1} di 3")
382
-
383
- group_responses = []
384
- # Creiamo spazio per il massimo numero di affermazioni possibili
385
- for j in range(3 if i == 2 else max_statements_per_group):
386
- response = gr.Radio(
387
- choices=LIKERT_OPTIONS,
388
- label=statements[i * max_statements_per_group + j],
389
- type="value",
390
- value=None,
391
- )
392
- group_responses.append(response)
393
-
394
- advanced_responses[i] = group_responses
395
- advanced_containers.append(container)
396
-
397
- # Navigazione tra i gruppi
398
- with gr.Row():
399
- advanced_prev_btn = gr.Button("← Indietro", interactive=False)
400
- advanced_next_btn = gr.Button("Avanti →")
401
- advanced_submit_btn = gr.Button(
402
- "Conferma Risposte", visible=False
403
- )
404
-
405
- advanced_feedback_submitted = gr.State(False)
406
- advanced_feedback_message = gr.HTML(visible=False)
407
-
408
- with gr.Column(visible=False) as back_to_recommendations_block:
409
- gr.HTML(
410
- '<hr style="margin: 80px 0 2px 0; border-top: 1px solid #ddd;">'
411
- )
412
- back_to_recommendations_button = gr.Button(
413
- "← Torna alla lista di raccomandazioni",
414
- variant="secondary",
415
- elem_classes="back-button-custom",
416
- )
417
-
418
- def handle_login(user_id_value):
419
- """Gestisce il processo di login"""
420
- success, message, login_status = login_user(user_id_value)
421
-
422
- if not success:
423
- return (
424
- gr.update(value=message, visible=True), # status_message
425
- gr.update(visible=False), # login_actions
426
- gr.update(visible=False), # setup_profile_button
427
- gr.update(visible=False), # proceed_to_recommendations_button
428
- user_id_value, # current_user_id
429
- login_status, # login_status
430
- )
431
-
432
- # Determina quale pulsante mostrare
433
- show_setup = login_status in ["new_user", "existing_incomplete"]
434
- show_proceed = login_status == "existing_complete"
435
-
436
- return (
437
- gr.update(value=message, visible=True), # status_message
438
- gr.update(visible=True), # login_actions
439
- gr.update(visible=show_setup), # setup_profile_button
440
- gr.update(visible=show_proceed), # proceed_to_recommendations_button
441
- user_id_value, # current_user_id
442
- login_status, # login_status
443
- )
444
-
445
- def handle_poi_click(poi_name, selected_names, query):
446
- """Handle POI click - toggle selection"""
447
- print(f"🔥 POI Button clicked: {poi_name}")
448
- return toggle_poi_selection(poi_name, selected_names, query)
449
-
450
- def handle_proceed_to_sensory_config(user_id, selected_names):
451
- """Handle proceeding to next step"""
452
- if not selected_names:
453
- gr.Warning("⚠️ Seleziona la card di almeno un Punto di Interesse per continuare!")
454
- return
455
-
456
- update_user_selected_pois(user_id, selected_names)
457
- gr.Info(f"✅ Procedendo con {len(selected_names)} Punti di Interesse selezionati")
458
- return navigate_to_sensory_config()
459
-
460
- def navigate_to_poi_selection():
461
- """Naviga alla selezione POI"""
462
- return (
463
- gr.update(visible=False), # login_page
464
- gr.update(visible=True), # poi_selection_page
465
- gr.update(visible=False), # sensory_config_page
466
- gr.update(visible=False), # recommendations_page
467
- "poi_selection", # current_page
468
- )
469
-
470
- def navigate_to_sensory_config():
471
- """Naviga alla configurazione sensoriale"""
472
- return (
473
- gr.update(visible=False), # login_page
474
- gr.update(visible=False), # poi_selection_page
475
- gr.update(visible=True), # sensory_config_page
476
- gr.update(visible=False), # recommendations_page
477
- "sensory_config", # current_page
478
- )
479
-
480
- def navigate_to_recommendations():
481
- """Naviga alle raccomandazioni"""
482
- return (
483
- gr.update(visible=False), # login_page
484
- gr.update(visible=False), # poi_selection_page
485
- gr.update(visible=False), # sensory_config_page
486
- gr.update(visible=True), # recommendations_page
487
- "recommendations", # current_page
488
- )
489
-
490
- def complete_profile_setup(user_id):
491
- """Completa la configurazione del profilo"""
492
- if user_id:
493
- mark_profile_complete(user_id)
494
- gr.Info(f"✅ Profilo {user_id} completato con successo!")
495
- return navigate_to_recommendations()
496
-
497
- login_button.click(
498
- fn=handle_login,
499
- inputs=[user_id_input],
500
- outputs=[
501
- login_status_message,
502
- login_actions,
503
- start_setup_btn,
504
- go_to_recommendations_btn,
505
- app_user_id,
506
- user_status,
507
- ],
508
- )
509
-
510
- start_setup_btn.click(
511
- fn=navigate_to_poi_selection,
512
- outputs=[
513
- login_page,
514
- poi_selection_page,
515
- sensory_config_page,
516
- recommendations_page,
517
- current_page,
518
- ],
519
- )
520
-
521
- go_to_recommendations_btn.click(
522
- fn=complete_profile_setup,
523
- inputs=[app_user_id],
524
- outputs=[
525
- login_page,
526
- poi_selection_page,
527
- sensory_config_page,
528
- recommendations_page,
529
- current_page,
530
- ],
531
- ).then(
532
- fn=partial(get_recommendations_ui, k=args.top_k),
533
- inputs=[app_user_id],
534
- outputs=[recommendations_state, app_user_id, recommendation_list],
535
- )
536
-
537
- # Dynamic search - triggers on every keystroke
538
- search_input.change(
539
- fn=search_pois,
540
- inputs=[search_input, selected_poi_names],
541
- outputs=[poi_display],
542
- )
543
-
544
- # Set up click handlers for each POI button
545
- for poi_name, button in poi_buttons.items():
546
- button.click(
547
- fn=partial(handle_poi_click, poi_name),
548
- inputs=[selected_poi_names, search_input],
549
- outputs=[selected_poi_names, poi_display, selection_summary],
550
- )
551
-
552
- # Clear all selections
553
- clear_button.click(
554
- fn=clear_all_selections,
555
- inputs=[search_input],
556
- outputs=[selected_poi_names, poi_display, selection_summary],
557
- )
558
-
559
- continue_to_sensory_btn.click(
560
- fn=handle_proceed_to_sensory_config,
561
- inputs=[app_user_id, selected_poi_names],
562
- outputs=[
563
- login_page,
564
- poi_selection_page,
565
- sensory_config_page,
566
- recommendations_page,
567
- current_page,
568
- ],
569
- )
570
-
571
- # Salva preferenze
572
- continue_to_recommendations_button.click(
573
- fn=update_user_sensory_aversions,
574
- inputs=[app_user_id, *aversion_radios],
575
- ).then(
576
- fn=complete_profile_setup,
577
- inputs=[app_user_id],
578
- outputs=[
579
- login_page,
580
- poi_selection_page,
581
- sensory_config_page,
582
- recommendations_page,
583
- current_page,
584
- ],
585
- ).then(
586
- fn=partial(get_recommendations_ui, k=args.top_k),
587
- inputs=[app_user_id],
588
- outputs=[recommendations_state, app_user_id, recommendation_list],
589
- )
590
-
591
- for i, btn in enumerate(select_buttons):
592
- btn.click(
593
- fn=lambda current_idx=i: current_idx,
594
- inputs=[],
595
- outputs=[selected_poi_index],
596
- ).then(
597
- fn=view_poi_details,
598
- inputs=[recommendations_state, app_user_id, selected_poi_index, completed_survey_pois],
599
- outputs=[
600
- recommendations_list_page,
601
- details_page_block,
602
- poi_details,
603
- recommendation_feedback_block,
604
- back_to_recommendations_block,
605
- current_poi_info,
606
- ],
607
- )
608
-
609
- surveys_objects = [
610
- recommendation_feedback_result,
611
- recommendation_feedback_submitted,
612
- recommendation_feedback_message,
613
- simple_survey_result,
614
- simple_feedback_submitted,
615
- simple_feedback_message,
616
- advanced_current_group_idx,
617
- advanced_feedback_submitted,
618
- advanced_feedback_message,
619
- ]
620
-
621
- back_to_recommendations_button.click(
622
- fn=lambda: back_to_recommendations(advanced_responses, advanced_containers),
623
- inputs=[],
624
- outputs=[
625
- recommendations_list_page,
626
- details_page_block,
627
- poi_details,
628
- recommendation_feedback_block,
629
- simple_survey_block,
630
- advanced_survey_block,
631
- back_to_recommendations_block,
632
- *surveys_objects,
633
- *[radio for group in advanced_responses for radio in group],
634
- *advanced_containers,
635
- ],
636
- ).then(
637
- fn=show_recommendation_list,
638
- inputs=[recommendations_state, completed_survey_pois],
639
- outputs=[recommendation_list]
640
- )
641
-
642
- rec_like_btn.click(
643
- fn=feedback_message,
644
- inputs=[],
645
- outputs=[
646
- recommendation_feedback_message,
647
- ],
648
- show_progress="hidden",
649
- ).then(
650
- fn=lambda: submit_rec_feedback(True),
651
- inputs=[],
652
- outputs=[
653
- recommendation_feedback_result,
654
- recommendation_feedback_submitted,
655
- recommendation_feedback_message,
656
- simple_survey_block,
657
- recommendation_feedback_block,
658
- ],
659
- js=js_feedback_message_delay,
660
- show_progress="hidden",
661
- )
662
-
663
- rec_dislike_btn.click(
664
- fn=feedback_message,
665
- inputs=[],
666
- outputs=[
667
- recommendation_feedback_message,
668
- ],
669
- show_progress="hidden",
670
- ).then(
671
- fn=lambda: submit_rec_feedback(False),
672
- inputs=[],
673
- outputs=[
674
- recommendation_feedback_result,
675
- recommendation_feedback_submitted,
676
- recommendation_feedback_message,
677
- simple_survey_block,
678
- recommendation_feedback_block,
679
- ],
680
- js=js_feedback_message_delay,
681
- show_progress="hidden",
682
- )
683
-
684
- # Configurazione eventi per il questionario semplice
685
- exp_like_btn.click(
686
- fn=feedback_message,
687
- inputs=[],
688
- outputs=[
689
- simple_feedback_message,
690
- ],
691
- show_progress="hidden",
692
- ).then(
693
- fn=lambda: submit_exp_feedback(True),
694
- inputs=[],
695
- outputs=[
696
- simple_survey_result,
697
- simple_feedback_submitted,
698
- simple_feedback_message,
699
- advanced_survey_block,
700
- simple_survey_block,
701
- advanced_prev_btn,
702
- advanced_next_btn,
703
- advanced_submit_btn,
704
- ],
705
- js=js_feedback_message_delay,
706
- show_progress="hidden",
707
- )
708
-
709
- exp_dislike_btn.click(
710
- fn=feedback_message,
711
- inputs=[],
712
- outputs=[
713
- simple_feedback_message,
714
- ],
715
- show_progress="hidden",
716
- ).then(
717
- fn=lambda: submit_exp_feedback(False),
718
- inputs=[],
719
- outputs=[
720
- simple_survey_result,
721
- simple_feedback_submitted,
722
- simple_feedback_message,
723
- advanced_survey_block,
724
- simple_survey_block,
725
- advanced_prev_btn,
726
- advanced_next_btn,
727
- advanced_submit_btn,
728
- ],
729
- js=js_feedback_message_delay,
730
- show_progress="hidden",
731
- )
732
-
733
- # Create a list of outputs for navigation functions
734
- navigation_outputs = [
735
- advanced_current_group_idx, # next_group_idx
736
- advanced_prev_btn, # prev_btn interactivity update
737
- advanced_next_btn, # next_btn visibility update
738
- advanced_submit_btn, # submit_btn visibility update
739
- ] + advanced_containers # container updates
740
-
741
- advanced_next_btn.click(
742
- fn=partial(handle_next_navigation, advanced_responses, advanced_containers),
743
- inputs=[advanced_current_group_idx],
744
- outputs=navigation_outputs,
745
- ).then(
746
- fn=partial(handle_all_answered_responses, advanced_responses),
747
- inputs=[advanced_current_group_idx],
748
- outputs=[advanced_submit_btn, advanced_next_btn],
749
- )
750
-
751
- advanced_prev_btn.click(
752
- fn=partial(handle_prev_navigation, advanced_containers),
753
- inputs=[
754
- advanced_current_group_idx,
755
- ],
756
- outputs=navigation_outputs,
757
- )
758
-
759
- for group_idx, radio_group in enumerate(advanced_responses):
760
- for radio_idx, radio_button in enumerate(radio_group):
761
- radio_button.change(
762
- fn=partial(
763
- update_advanced_responses,
764
- group_idx,
765
- radio_idx,
766
- advanced_responses,
767
- ),
768
- inputs=[radio_button],
769
- outputs=[],
770
- ).then(
771
- fn=partial(handle_all_answered_responses, advanced_responses),
772
- inputs=[advanced_current_group_idx],
773
- outputs=[advanced_submit_btn, advanced_next_btn],
774
- )
775
-
776
- advanced_submit_btn.click(
777
- fn=partial(
778
- handle_submit_survey,
779
- advanced_responses,
780
- ),
781
- inputs=[
782
- app_user_id,
783
- current_poi_info,
784
- recommendation_feedback_result,
785
- simple_survey_result,
786
- ],
787
- outputs=[
788
- advanced_feedback_submitted,
789
- advanced_feedback_message,
790
- advanced_prev_btn,
791
- advanced_next_btn,
792
- advanced_submit_btn,
793
- *advanced_containers,
794
- ],
795
- ).then(
796
- fn=add_poi_to_completed,
797
- inputs=[selected_poi_index, completed_survey_pois],
798
- outputs=[completed_survey_pois],
799
- )
800
-
801
- return app
802
 
803
 
804
  if __name__ == "__main__":
805
- parser = argparse.ArgumentParser(description="Run the POI Search UI")
806
- parser.add_argument(
807
- "user_type",
808
- choices=["autistic", "neurotypical"],
809
- help="Type of user for the interface",
810
- )
811
- parser.add_argument(
812
- "--top-k",
813
- type=int,
814
- default=5,
815
- help="Number of top recommendations to show",
816
- )
817
- parser.add_argument(
818
- "--port", type=int, default=7860, help="Port to run the Gradio app on"
819
- )
820
- parser.add_argument("--share", action="store_true", help="Share the app publicly")
821
- args = parser.parse_args()
822
- app = create_main_app(args.user_type)
823
- app.launch()
 
1
+ import subprocess
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
 
4
  if __name__ == "__main__":
5
+ subprocess.run(["python", "-m", "main", "autistic"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
html_items.py CHANGED
@@ -1,9 +1,9 @@
1
  import json
2
  import os
3
 
4
- from app import RESOURCE_DIR
5
- from app.utils.utils import get_aversion_color, get_qualitative_aversion
6
 
 
7
  with open(os.path.join(RESOURCE_DIR, "aversions.json"), "r") as f:
8
  aversions_data = json.load(f)
9
  AVERSIONS = {item["Name"]: item for item in aversions_data}
 
1
  import json
2
  import os
3
 
4
+ from utils.utils import get_aversion_color, get_qualitative_aversion
 
5
 
6
+ RESOURCE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources")
7
  with open(os.path.join(RESOURCE_DIR, "aversions.json"), "r") as f:
8
  aversions_data = json.load(f)
9
  AVERSIONS = {item["Name"]: item for item in aversions_data}
main.py ADDED
@@ -0,0 +1,823 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ import os
4
+ import random
5
+ from functools import partial
6
+
7
+ import gradio as gr
8
+
9
+ from utils.poi_search_utils import (
10
+ clear_all_selections,
11
+ get_selection_summary,
12
+ pois_list,
13
+ search_pois,
14
+ toggle_poi_selection,
15
+ )
16
+ from utils.recommender_poi_utils import (
17
+ add_poi_to_completed,
18
+ back_to_recommendations,
19
+ get_recommendations_ui,
20
+ show_recommendation_list,
21
+ view_poi_details,
22
+ )
23
+ from utils.survey_utils import (
24
+ feedback_message,
25
+ handle_all_answered_responses,
26
+ handle_next_navigation,
27
+ handle_prev_navigation,
28
+ handle_submit_survey,
29
+ submit_exp_feedback,
30
+ submit_rec_feedback,
31
+ update_advanced_responses,
32
+ )
33
+ from utils.user_profile_utils import (
34
+ login_user,
35
+ mark_profile_complete,
36
+ update_user_selected_pois,
37
+ update_user_sensory_aversions
38
+ )
39
+
40
+ RESOURCE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources")
41
+
42
+ with open(os.path.join(RESOURCE_DIR, "aversion_config_questions.json"), "r") as f:
43
+ AVERSIONS_QUESTIONS = json.load(f)
44
+ with open(os.path.join(RESOURCE_DIR, "aversions.json"), "r") as f:
45
+ AVERSIONS = json.load(f)
46
+ AVERSIONS = {v["Name"]: v for v in AVERSIONS}
47
+
48
+
49
+ feedback_message_delay = 1500
50
+ js_feedback_message_delay = f"""() => {{
51
+ return new Promise(resolve => {{
52
+ setTimeout(() => {{
53
+ resolve();
54
+ }}, {feedback_message_delay});
55
+ }});
56
+ }}"""
57
+
58
+
59
+ def create_main_app(self):
60
+ with gr.Blocks(
61
+ title="Sistema di Raccomandazione di Punti di Interesse con Personalizzazione Sensoriale",
62
+ theme=gr.themes.Soft(),
63
+ css="""
64
+ footer{display:none !important}
65
+ .navigation-container {
66
+ background: #f8f9fa;
67
+ padding: 15px;
68
+ border-radius: 10px;
69
+ margin-bottom: 20px;
70
+ border-left: 4px solid #007bff;
71
+ }
72
+ .page-container {
73
+ min-height: 500px;
74
+ padding: 20px;
75
+ }
76
+ .poi-card {
77
+ transition: all 0.3s ease;
78
+ }
79
+ .poi-card:hover {
80
+ transform: scale(1.02);
81
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
82
+ }
83
+ .search-container {
84
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
85
+ padding: 30px;
86
+ border-radius: 15px;
87
+ color: white;
88
+ margin-bottom: 30px;
89
+ }
90
+ .continue-button {
91
+ background-color: #28a745 !important;
92
+ border-color: #28a745 !important;
93
+ font-size: 16px !important;
94
+ padding: 12px 24px !important;
95
+ }
96
+ .continue-button:hover {
97
+ background-color: #218838 !important;
98
+ border-color: #1e7e34 !important;
99
+ }
100
+ .clear-button {
101
+ background-color: #dc3545 !important;
102
+ border-color: #dc3545 !important;
103
+ }
104
+ .clear-button:hover {
105
+ background-color: #c82333 !important;
106
+ border-color: #bd2130 !important;
107
+ }
108
+ .sensory-config-main-container {
109
+ max-width: 900px;
110
+ margin: 0 auto;
111
+ padding: 20px;
112
+ }
113
+ .header-section {
114
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
115
+ color: white;
116
+ padding: 20px;
117
+ border-radius: 12px;
118
+ margin-bottom: 25px;
119
+ text-align: center;
120
+ }
121
+ .radio-aversion-grid {
122
+ display: grid;
123
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
124
+ gap: 20px;
125
+ margin-bottom: 25px;
126
+ }
127
+ .radio-aversion-card {
128
+ background: #f8f9fa;
129
+ border: 1px solid #e9ecef;
130
+ border-radius: 12px;
131
+ padding: 15px;
132
+ }
133
+ .continue-to-recommendations-button {
134
+ background-color: #28a745 !important;
135
+ border-color: #28a745 !important;
136
+ font-size: 16px !important;
137
+ padding: 12px 30px !important;
138
+ width: 100% !important;
139
+ }
140
+ .continue-to-recommendations-button:hover {
141
+ background-color: #218838 !important;
142
+ border-color: #1e7e34 !important;
143
+ }
144
+ .feedback-indicator {
145
+ margin-top: 5px;
146
+ }
147
+ .back-button-custom {
148
+ background-color: #32568f !important;
149
+ border-color: #32568f !important;
150
+ color: white !important;
151
+ }
152
+ .back-button-custom:hover {
153
+ background-color: #132137 !important;
154
+ border-color: #132137 !important;
155
+ }
156
+ .login-button {
157
+ background-color: #007bff !important;
158
+ border-color: #007bff !important;
159
+ }
160
+ .login-button:hover {
161
+ background-color: #0056b3 !important;
162
+ border-color: #004085 !important;
163
+ }
164
+ #poi-search-input {
165
+ color: black;
166
+ }
167
+ """,
168
+ ) as app:
169
+ # Stati globali dell'applicazione
170
+ app_user_id = gr.State("")
171
+ current_page = gr.State("login") # "login", "poi_selection", "sensory_config", "recommendations"
172
+ selected_poi_names = gr.State(set())
173
+ recommendations_state = gr.State([])
174
+ selected_poi_index = gr.State(-1)
175
+ completed_survey_pois = gr.State(set())
176
+ current_poi_info = gr.State({}) # Store current POI info for survey
177
+ user_status = gr.State("") # "new_user", "existing_incomplete", "existing_complete"
178
+
179
+ # Container per le diverse pagine
180
+ with gr.Column(elem_classes="page-container"):
181
+ # Pagina 1: Login/Profile
182
+ with gr.Column(visible=True) as login_page:
183
+ gr.Markdown("# 👤 Login / Registrazione")
184
+
185
+ # Interfaccia di login semplificata
186
+ with gr.Row():
187
+ user_id_input = gr.Textbox(
188
+ placeholder="es: mario_rossi, alice_123, user001...",
189
+ label="🆔 Inserisci il tuo ID Utente",
190
+ info="Scegli un ID univoco che userai per accedere al sistema",
191
+ elem_id="user-id-input",
192
+ scale=2
193
+ )
194
+ login_button = gr.Button(
195
+ "🚀 Accedi / Registrati",
196
+ variant="primary",
197
+ elem_classes="action-button login-button",
198
+ )
199
+
200
+ login_status_message = gr.Markdown("", visible=False)
201
+
202
+ with gr.Row(visible=False) as login_actions:
203
+ start_setup_btn = gr.Button(
204
+ "📝 Configura Profilo", variant="secondary"
205
+ )
206
+ go_to_recommendations_btn = gr.Button(
207
+ "🎯 Vai alle Raccomandazioni", variant="primary"
208
+ )
209
+
210
+ with gr.Column(visible=False) as poi_selection_page:
211
+ with gr.Column(elem_classes="search-container"):
212
+ gr.Markdown("# 🔍 Cerca e seleziona i **Luoghi di Interesse** che preferisci cliccando sulla card corrispondente")
213
+ gr.Markdown(
214
+ "Esplora i luoghi disponibili e seleziona quelli che preferisci maggiormente. La ricerca è dinamica: inizia a digitare per filtrare i risultati."
215
+ )
216
+
217
+ # Search input (dynamic, no button needed)
218
+ search_input = gr.Textbox(
219
+ placeholder="Cerca per nome, descrizione o località...",
220
+ label="",
221
+ show_label=False,
222
+ container=False,
223
+ elem_id="poi-search-input",
224
+ )
225
+
226
+ with gr.Column():
227
+ # Selection summary
228
+ selection_summary = gr.Markdown(get_selection_summary(set()))
229
+
230
+ # Navigation buttons
231
+ with gr.Row():
232
+ clear_button = gr.Button(
233
+ "🗑️ Cancella Selezioni",
234
+ variant="secondary",
235
+ elem_classes="clear-button",
236
+ scale=1,
237
+ )
238
+ continue_to_sensory_btn = gr.Button(
239
+ "Continua alla Configurazione Sensoriale ➡️",
240
+ variant="primary",
241
+ elem_classes="continue_to_sensory_btn",
242
+ scale=3,
243
+ )
244
+
245
+ # POI display area
246
+ poi_display = gr.HTML(
247
+ value=search_pois("", set()), elem_id="poi-display-area"
248
+ )
249
+
250
+ # Create hidden buttons for each POI
251
+ poi_buttons = {}
252
+ for poi in pois_list:
253
+ poi_name = poi["name"]
254
+ safe_name = (
255
+ poi_name.replace(" ", "-").replace("'", "").replace('"', "")
256
+ )
257
+ button = gr.Button(
258
+ poi_name, visible=False, elem_id=f"poi-btn-{safe_name}"
259
+ )
260
+ poi_buttons[poi_name] = button
261
+
262
+ with gr.Column(
263
+ visible=False, elem_classes="sensory-config-main-container"
264
+ ) as sensory_config_page:
265
+ # Header compatto
266
+ gr.HTML("""
267
+ <div class="header-section">
268
+ <h2 style="margin: 0 0 10px 0;">⚙️ Configura le tue Preferenze Sensoriali</h2>
269
+ <p style="margin: 0; opacity: 0.9;">Indica il tuo livello di sensibilità per ogni caratteristica</p>
270
+ </div>
271
+ """)
272
+
273
+ aversion_radios = []
274
+ with gr.Column():
275
+ for i, aversion_question in enumerate(AVERSIONS_QUESTIONS):
276
+ question = aversion_question.get("question", "")
277
+ aversion_name = aversion_question.get("aversion_name", "")
278
+ aversion = AVERSIONS[aversion_name]
279
+
280
+ with gr.Group(elem_classes="radio-aversion-card"):
281
+ with gr.Column():
282
+ # Titolo compatto con icona
283
+ gr.HTML(f"""
284
+ <div style="display: flex; align-items: center; margin-bottom: 8px;">
285
+ <span style="font-size: 20px; margin-right: 10px;">{aversion['Icon']} {aversion_name}</span>
286
+ <div>
287
+ <div style="font-size: 14px; color: #D7D7D7; margin-top: 2px;">{question}</div>
288
+ </div>
289
+ </div>
290
+ """)
291
+
292
+ # Quanto ti dà fastidio questo fenomeno sensoriale?
293
+ av_radio = gr.Radio(
294
+ choices=[("Nessuno", 1), ("Poco", 2), ("Moderato", 3), ("Molto", 4), ("Estremo", 5)],
295
+ value=3,
296
+ show_label=False,
297
+ interactive=True,
298
+ elem_id=f"radio-{aversion_name.lower().replace(' ', '-')}",
299
+ )
300
+
301
+ aversion_radios.append(av_radio)
302
+
303
+ # Pulsante continua
304
+ with gr.Row():
305
+ continue_to_recommendations_button = gr.Button(
306
+ "Continua alle Raccomandazioni →",
307
+ variant="primary",
308
+ elem_classes="continue-to-recommendations-button",
309
+ size="lg",
310
+ )
311
+
312
+ with gr.Column(visible=False) as recommendations_page:
313
+ with gr.Column(visible=True) as recommendations_list_page:
314
+ gr.Markdown(
315
+ "# Sistema di Raccomandazione di Punti di Interesse"
316
+ )
317
+ gr.Markdown("""
318
+ Questo sistema raccomanda punti di interesse personalizzati per le preferenze sensoriali degli utenti.
319
+ Ogni raccomandazione include informazioni sensoriali dettagliate e spiegazioni personalizzate.
320
+ """)
321
+
322
+ recommendation_list = gr.HTML(label="Raccomandazioni")
323
+
324
+ with gr.Row(visible=False):
325
+ select_buttons = [
326
+ gr.Button(f"Select_{i}", elem_id=f"select-button-{i}")
327
+ for i in range(args.top_k)
328
+ ]
329
+
330
+ with gr.Column(visible=False) as details_page_block:
331
+ gr.Markdown("# Dettagli POI")
332
+ poi_details = gr.HTML(label="Dettagli Punto di Interesse")
333
+
334
+ with gr.Column(visible=False) as recommendation_feedback_block:
335
+ gr.Markdown("## Ti è piaciuto questo luogo che ti abbiamo suggerito?")
336
+
337
+ with gr.Row(equal_height=True):
338
+ with gr.Column(scale=1):
339
+ rec_like_btn = gr.Button("👍 Sì", size="lg")
340
+ with gr.Column(scale=1):
341
+ rec_dislike_btn = gr.Button("👎 No", size="lg")
342
+
343
+ recommendation_feedback_result = gr.State(False)
344
+ recommendation_feedback_submitted = gr.State(False)
345
+ recommendation_feedback_message = gr.HTML(visible=False)
346
+
347
+ # SIMPLE SURVEY
348
+ with gr.Column(visible=False) as simple_survey_block:
349
+ gr.Markdown("## Ti è stata utile questa spiegazione?")
350
+
351
+ with gr.Row(equal_height=True):
352
+ with gr.Column(scale=1):
353
+ exp_like_btn = gr.Button("👍 Sì", size="lg")
354
+ with gr.Column(scale=1):
355
+ exp_dislike_btn = gr.Button("👎 No", size="lg")
356
+
357
+ simple_survey_result = gr.State(False)
358
+ simple_feedback_submitted = gr.State(False)
359
+ simple_feedback_message = gr.HTML(visible=False)
360
+
361
+ # ADVANCED SURVEY
362
+ with gr.Column(visible=False) as advanced_survey_block:
363
+ gr.Markdown("## Valuta questa spiegazione della raccomandazione")
364
+
365
+ # Stato per tenere traccia del gruppo corrente
366
+ advanced_current_group_idx = gr.State(0)
367
+
368
+ # Carica i dati del questionario
369
+ from utils.survey_utils import LIKERT_OPTIONS, SURVEY_STATEMENTS
370
+
371
+ # Contenitori per i gruppi di affermazioni
372
+ advanced_containers = []
373
+ advanced_responses = [[], [], []]
374
+ statements = SURVEY_STATEMENTS.copy()
375
+ random.shuffle(statements)
376
+
377
+ # Prendiamo il massimo di affermazioni per gruppo
378
+ max_statements_per_group = 2 # Per i primi due gruppi
379
+ for i in range(3):
380
+ with gr.Column(visible=(i == 0)) as container:
381
+ gr.Markdown(f"### Parte {i + 1} di 3")
382
+
383
+ group_responses = []
384
+ # Creiamo spazio per il massimo numero di affermazioni possibili
385
+ for j in range(3 if i == 2 else max_statements_per_group):
386
+ response = gr.Radio(
387
+ choices=LIKERT_OPTIONS,
388
+ label=statements[i * max_statements_per_group + j],
389
+ type="value",
390
+ value=None,
391
+ )
392
+ group_responses.append(response)
393
+
394
+ advanced_responses[i] = group_responses
395
+ advanced_containers.append(container)
396
+
397
+ # Navigazione tra i gruppi
398
+ with gr.Row():
399
+ advanced_prev_btn = gr.Button("← Indietro", interactive=False)
400
+ advanced_next_btn = gr.Button("Avanti →")
401
+ advanced_submit_btn = gr.Button(
402
+ "Conferma Risposte", visible=False
403
+ )
404
+
405
+ advanced_feedback_submitted = gr.State(False)
406
+ advanced_feedback_message = gr.HTML(visible=False)
407
+
408
+ with gr.Column(visible=False) as back_to_recommendations_block:
409
+ gr.HTML(
410
+ '<hr style="margin: 80px 0 2px 0; border-top: 1px solid #ddd;">'
411
+ )
412
+ back_to_recommendations_button = gr.Button(
413
+ "← Torna alla lista di raccomandazioni",
414
+ variant="secondary",
415
+ elem_classes="back-button-custom",
416
+ )
417
+
418
+ def handle_login(user_id_value):
419
+ """Gestisce il processo di login"""
420
+ success, message, login_status = login_user(user_id_value)
421
+
422
+ if not success:
423
+ return (
424
+ gr.update(value=message, visible=True), # status_message
425
+ gr.update(visible=False), # login_actions
426
+ gr.update(visible=False), # setup_profile_button
427
+ gr.update(visible=False), # proceed_to_recommendations_button
428
+ user_id_value, # current_user_id
429
+ login_status, # login_status
430
+ )
431
+
432
+ # Determina quale pulsante mostrare
433
+ show_setup = login_status in ["new_user", "existing_incomplete"]
434
+ show_proceed = login_status == "existing_complete"
435
+
436
+ return (
437
+ gr.update(value=message, visible=True), # status_message
438
+ gr.update(visible=True), # login_actions
439
+ gr.update(visible=show_setup), # setup_profile_button
440
+ gr.update(visible=show_proceed), # proceed_to_recommendations_button
441
+ user_id_value, # current_user_id
442
+ login_status, # login_status
443
+ )
444
+
445
+ def handle_poi_click(poi_name, selected_names, query):
446
+ """Handle POI click - toggle selection"""
447
+ print(f"🔥 POI Button clicked: {poi_name}")
448
+ return toggle_poi_selection(poi_name, selected_names, query)
449
+
450
+ def handle_proceed_to_sensory_config(user_id, selected_names):
451
+ """Handle proceeding to next step"""
452
+ if not selected_names:
453
+ gr.Warning("⚠️ Seleziona la card di almeno un Punto di Interesse per continuare!")
454
+ return
455
+
456
+ update_user_selected_pois(user_id, selected_names)
457
+ gr.Info(f"✅ Procedendo con {len(selected_names)} Punti di Interesse selezionati")
458
+ return navigate_to_sensory_config()
459
+
460
+ def navigate_to_poi_selection():
461
+ """Naviga alla selezione POI"""
462
+ return (
463
+ gr.update(visible=False), # login_page
464
+ gr.update(visible=True), # poi_selection_page
465
+ gr.update(visible=False), # sensory_config_page
466
+ gr.update(visible=False), # recommendations_page
467
+ "poi_selection", # current_page
468
+ )
469
+
470
+ def navigate_to_sensory_config():
471
+ """Naviga alla configurazione sensoriale"""
472
+ return (
473
+ gr.update(visible=False), # login_page
474
+ gr.update(visible=False), # poi_selection_page
475
+ gr.update(visible=True), # sensory_config_page
476
+ gr.update(visible=False), # recommendations_page
477
+ "sensory_config", # current_page
478
+ )
479
+
480
+ def navigate_to_recommendations():
481
+ """Naviga alle raccomandazioni"""
482
+ return (
483
+ gr.update(visible=False), # login_page
484
+ gr.update(visible=False), # poi_selection_page
485
+ gr.update(visible=False), # sensory_config_page
486
+ gr.update(visible=True), # recommendations_page
487
+ "recommendations", # current_page
488
+ )
489
+
490
+ def complete_profile_setup(user_id):
491
+ """Completa la configurazione del profilo"""
492
+ if user_id:
493
+ mark_profile_complete(user_id)
494
+ gr.Info(f"✅ Profilo {user_id} completato con successo!")
495
+ return navigate_to_recommendations()
496
+
497
+ login_button.click(
498
+ fn=handle_login,
499
+ inputs=[user_id_input],
500
+ outputs=[
501
+ login_status_message,
502
+ login_actions,
503
+ start_setup_btn,
504
+ go_to_recommendations_btn,
505
+ app_user_id,
506
+ user_status,
507
+ ],
508
+ )
509
+
510
+ start_setup_btn.click(
511
+ fn=navigate_to_poi_selection,
512
+ outputs=[
513
+ login_page,
514
+ poi_selection_page,
515
+ sensory_config_page,
516
+ recommendations_page,
517
+ current_page,
518
+ ],
519
+ )
520
+
521
+ go_to_recommendations_btn.click(
522
+ fn=complete_profile_setup,
523
+ inputs=[app_user_id],
524
+ outputs=[
525
+ login_page,
526
+ poi_selection_page,
527
+ sensory_config_page,
528
+ recommendations_page,
529
+ current_page,
530
+ ],
531
+ ).then(
532
+ fn=partial(get_recommendations_ui, k=args.top_k),
533
+ inputs=[app_user_id],
534
+ outputs=[recommendations_state, app_user_id, recommendation_list],
535
+ )
536
+
537
+ # Dynamic search - triggers on every keystroke
538
+ search_input.change(
539
+ fn=search_pois,
540
+ inputs=[search_input, selected_poi_names],
541
+ outputs=[poi_display],
542
+ )
543
+
544
+ # Set up click handlers for each POI button
545
+ for poi_name, button in poi_buttons.items():
546
+ button.click(
547
+ fn=partial(handle_poi_click, poi_name),
548
+ inputs=[selected_poi_names, search_input],
549
+ outputs=[selected_poi_names, poi_display, selection_summary],
550
+ )
551
+
552
+ # Clear all selections
553
+ clear_button.click(
554
+ fn=clear_all_selections,
555
+ inputs=[search_input],
556
+ outputs=[selected_poi_names, poi_display, selection_summary],
557
+ )
558
+
559
+ continue_to_sensory_btn.click(
560
+ fn=handle_proceed_to_sensory_config,
561
+ inputs=[app_user_id, selected_poi_names],
562
+ outputs=[
563
+ login_page,
564
+ poi_selection_page,
565
+ sensory_config_page,
566
+ recommendations_page,
567
+ current_page,
568
+ ],
569
+ )
570
+
571
+ # Salva preferenze
572
+ continue_to_recommendations_button.click(
573
+ fn=update_user_sensory_aversions,
574
+ inputs=[app_user_id, *aversion_radios],
575
+ ).then(
576
+ fn=complete_profile_setup,
577
+ inputs=[app_user_id],
578
+ outputs=[
579
+ login_page,
580
+ poi_selection_page,
581
+ sensory_config_page,
582
+ recommendations_page,
583
+ current_page,
584
+ ],
585
+ ).then(
586
+ fn=partial(get_recommendations_ui, k=args.top_k),
587
+ inputs=[app_user_id],
588
+ outputs=[recommendations_state, app_user_id, recommendation_list],
589
+ )
590
+
591
+ for i, btn in enumerate(select_buttons):
592
+ btn.click(
593
+ fn=lambda current_idx=i: current_idx,
594
+ inputs=[],
595
+ outputs=[selected_poi_index],
596
+ ).then(
597
+ fn=view_poi_details,
598
+ inputs=[recommendations_state, app_user_id, selected_poi_index, completed_survey_pois],
599
+ outputs=[
600
+ recommendations_list_page,
601
+ details_page_block,
602
+ poi_details,
603
+ recommendation_feedback_block,
604
+ back_to_recommendations_block,
605
+ current_poi_info,
606
+ ],
607
+ )
608
+
609
+ surveys_objects = [
610
+ recommendation_feedback_result,
611
+ recommendation_feedback_submitted,
612
+ recommendation_feedback_message,
613
+ simple_survey_result,
614
+ simple_feedback_submitted,
615
+ simple_feedback_message,
616
+ advanced_current_group_idx,
617
+ advanced_feedback_submitted,
618
+ advanced_feedback_message,
619
+ ]
620
+
621
+ back_to_recommendations_button.click(
622
+ fn=lambda: back_to_recommendations(advanced_responses, advanced_containers),
623
+ inputs=[],
624
+ outputs=[
625
+ recommendations_list_page,
626
+ details_page_block,
627
+ poi_details,
628
+ recommendation_feedback_block,
629
+ simple_survey_block,
630
+ advanced_survey_block,
631
+ back_to_recommendations_block,
632
+ *surveys_objects,
633
+ *[radio for group in advanced_responses for radio in group],
634
+ *advanced_containers,
635
+ ],
636
+ ).then(
637
+ fn=show_recommendation_list,
638
+ inputs=[recommendations_state, completed_survey_pois],
639
+ outputs=[recommendation_list]
640
+ )
641
+
642
+ rec_like_btn.click(
643
+ fn=feedback_message,
644
+ inputs=[],
645
+ outputs=[
646
+ recommendation_feedback_message,
647
+ ],
648
+ show_progress="hidden",
649
+ ).then(
650
+ fn=lambda: submit_rec_feedback(True),
651
+ inputs=[],
652
+ outputs=[
653
+ recommendation_feedback_result,
654
+ recommendation_feedback_submitted,
655
+ recommendation_feedback_message,
656
+ simple_survey_block,
657
+ recommendation_feedback_block,
658
+ ],
659
+ js=js_feedback_message_delay,
660
+ show_progress="hidden",
661
+ )
662
+
663
+ rec_dislike_btn.click(
664
+ fn=feedback_message,
665
+ inputs=[],
666
+ outputs=[
667
+ recommendation_feedback_message,
668
+ ],
669
+ show_progress="hidden",
670
+ ).then(
671
+ fn=lambda: submit_rec_feedback(False),
672
+ inputs=[],
673
+ outputs=[
674
+ recommendation_feedback_result,
675
+ recommendation_feedback_submitted,
676
+ recommendation_feedback_message,
677
+ simple_survey_block,
678
+ recommendation_feedback_block,
679
+ ],
680
+ js=js_feedback_message_delay,
681
+ show_progress="hidden",
682
+ )
683
+
684
+ # Configurazione eventi per il questionario semplice
685
+ exp_like_btn.click(
686
+ fn=feedback_message,
687
+ inputs=[],
688
+ outputs=[
689
+ simple_feedback_message,
690
+ ],
691
+ show_progress="hidden",
692
+ ).then(
693
+ fn=lambda: submit_exp_feedback(True),
694
+ inputs=[],
695
+ outputs=[
696
+ simple_survey_result,
697
+ simple_feedback_submitted,
698
+ simple_feedback_message,
699
+ advanced_survey_block,
700
+ simple_survey_block,
701
+ advanced_prev_btn,
702
+ advanced_next_btn,
703
+ advanced_submit_btn,
704
+ ],
705
+ js=js_feedback_message_delay,
706
+ show_progress="hidden",
707
+ )
708
+
709
+ exp_dislike_btn.click(
710
+ fn=feedback_message,
711
+ inputs=[],
712
+ outputs=[
713
+ simple_feedback_message,
714
+ ],
715
+ show_progress="hidden",
716
+ ).then(
717
+ fn=lambda: submit_exp_feedback(False),
718
+ inputs=[],
719
+ outputs=[
720
+ simple_survey_result,
721
+ simple_feedback_submitted,
722
+ simple_feedback_message,
723
+ advanced_survey_block,
724
+ simple_survey_block,
725
+ advanced_prev_btn,
726
+ advanced_next_btn,
727
+ advanced_submit_btn,
728
+ ],
729
+ js=js_feedback_message_delay,
730
+ show_progress="hidden",
731
+ )
732
+
733
+ # Create a list of outputs for navigation functions
734
+ navigation_outputs = [
735
+ advanced_current_group_idx, # next_group_idx
736
+ advanced_prev_btn, # prev_btn interactivity update
737
+ advanced_next_btn, # next_btn visibility update
738
+ advanced_submit_btn, # submit_btn visibility update
739
+ ] + advanced_containers # container updates
740
+
741
+ advanced_next_btn.click(
742
+ fn=partial(handle_next_navigation, advanced_responses, advanced_containers),
743
+ inputs=[advanced_current_group_idx],
744
+ outputs=navigation_outputs,
745
+ ).then(
746
+ fn=partial(handle_all_answered_responses, advanced_responses),
747
+ inputs=[advanced_current_group_idx],
748
+ outputs=[advanced_submit_btn, advanced_next_btn],
749
+ )
750
+
751
+ advanced_prev_btn.click(
752
+ fn=partial(handle_prev_navigation, advanced_containers),
753
+ inputs=[
754
+ advanced_current_group_idx,
755
+ ],
756
+ outputs=navigation_outputs,
757
+ )
758
+
759
+ for group_idx, radio_group in enumerate(advanced_responses):
760
+ for radio_idx, radio_button in enumerate(radio_group):
761
+ radio_button.change(
762
+ fn=partial(
763
+ update_advanced_responses,
764
+ group_idx,
765
+ radio_idx,
766
+ advanced_responses,
767
+ ),
768
+ inputs=[radio_button],
769
+ outputs=[],
770
+ ).then(
771
+ fn=partial(handle_all_answered_responses, advanced_responses),
772
+ inputs=[advanced_current_group_idx],
773
+ outputs=[advanced_submit_btn, advanced_next_btn],
774
+ )
775
+
776
+ advanced_submit_btn.click(
777
+ fn=partial(
778
+ handle_submit_survey,
779
+ advanced_responses,
780
+ ),
781
+ inputs=[
782
+ app_user_id,
783
+ current_poi_info,
784
+ recommendation_feedback_result,
785
+ simple_survey_result,
786
+ ],
787
+ outputs=[
788
+ advanced_feedback_submitted,
789
+ advanced_feedback_message,
790
+ advanced_prev_btn,
791
+ advanced_next_btn,
792
+ advanced_submit_btn,
793
+ *advanced_containers,
794
+ ],
795
+ ).then(
796
+ fn=add_poi_to_completed,
797
+ inputs=[selected_poi_index, completed_survey_pois],
798
+ outputs=[completed_survey_pois],
799
+ )
800
+
801
+ return app
802
+
803
+
804
+ if __name__ == "__main__":
805
+ parser = argparse.ArgumentParser(description="Run the POI Search UI")
806
+ parser.add_argument(
807
+ "user_type",
808
+ choices=["autistic", "neurotypical"],
809
+ help="Type of user for the interface",
810
+ )
811
+ parser.add_argument(
812
+ "--top-k",
813
+ type=int,
814
+ default=5,
815
+ help="Number of top recommendations to show",
816
+ )
817
+ parser.add_argument(
818
+ "--port", type=int, default=7860, help="Port to run the Gradio app on"
819
+ )
820
+ parser.add_argument("--share", action="store_true", help="Share the app publicly")
821
+ args = parser.parse_args()
822
+ app = create_main_app(args.user_type)
823
+ app.launch()
utils/poi_search_utils.py CHANGED
@@ -1,7 +1,7 @@
1
  import json
2
  import os
3
 
4
- from app.html_items import create_poi_cards_list
5
 
6
 
7
  RESOURCE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "resources")
 
1
  import json
2
  import os
3
 
4
+ from html_items import create_poi_cards_list
5
 
6
 
7
  RESOURCE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "resources")
utils/recommender_poi_utils.py CHANGED
@@ -4,9 +4,9 @@ import os
4
  import random
5
 
6
  import gradio as gr
7
- from app.html_items import create_poi_html, create_recommendation_item
8
 
9
- from app.utils.survey_utils import reset_surveys
10
 
11
 
12
  RESOURCE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "resources")
 
4
  import random
5
 
6
  import gradio as gr
7
+ from html_items import create_poi_html, create_recommendation_item
8
 
9
+ from utils.survey_utils import reset_surveys
10
 
11
 
12
  RESOURCE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "resources")
utils/sensory_config_utils.py CHANGED
@@ -1,7 +1,7 @@
1
  import json
2
  import os
3
 
4
- from app.utils import get_aversion_color, get_qualitative_aversion
5
 
6
 
7
  RESOURCE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "resources")
 
1
  import json
2
  import os
3
 
4
+ from utils.utils import get_aversion_color, get_qualitative_aversion
5
 
6
 
7
  RESOURCE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "resources")
utils/user_profile_utils.py CHANGED
@@ -3,7 +3,7 @@ import json
3
  import os
4
  from typing import Optional
5
 
6
- from app.utils.sensory_config_utils import extract_sensory_preferences
7
 
8
  RESOURCE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "resources")
9
  USER_PROFILES_FILE = os.path.join(RESOURCE_DIR, os.pardir, "database", "user_profiles.json")
 
3
  import os
4
  from typing import Optional
5
 
6
+ from utils.sensory_config_utils import extract_sensory_preferences
7
 
8
  RESOURCE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "resources")
9
  USER_PROFILES_FILE = os.path.join(RESOURCE_DIR, os.pardir, "database", "user_profiles.json")