clementBE commited on
Commit
9630843
·
verified ·
1 Parent(s): 36f5e68

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +452 -279
app.py CHANGED
@@ -2,305 +2,478 @@ import gradio as gr
2
  import pandas as pd
3
  import os
4
  import time
5
- from typing import List, Union, Dict, Any, Tuple
6
 
7
  # =================================================================
8
- # CONFIGURATION AND LANGUAGE DEFINITIONS
9
  # =================================================================
10
 
11
- # The file will be named dynamically upon submission
12
- CSV_FILENAME = "survey_data_latest.csv"
13
- # The number of data inputs (including the fixed language code "FR")
14
- # Original: 126. We are adding SPACE11, so it is now 127.
15
- EXPECTED_COUNT = 127
16
 
17
  # --- Shared Choices (French Only) ---
18
  FREQ_FR = ["Jamais", "Rarement", "Parfois", "Souvent"]
19
- YES_NO_FR = ["Oui", "Non"]
20
- THEMES_ACTU_FR = [
21
- "politique", "environnement", "économie", "santé", "social/société",
22
- "situation internationale/géopolitique", "science", "modes de vie (lifestyle)",
23
- "vie culturelle", "autre/précisez"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  ]
25
- SPACE11_CHOICES_FR = [
26
- "Instagram", "YouTube", "TikTok", "Facebook", "WhatsApp", "BlueSky",
27
- "Signal", "Telegram", "Mastodon", "Discord", "Twitter", "Reddit",
28
- "messagerie de jeu en ligne", "autre : préciser", "je ne sais pas"
29
  ]
 
 
30
 
31
- # --- Language Definitions (French Only) ---
32
- LANGUAGES = {
33
- "FR": {
34
- # GLOBAL
35
- "LANG_LABEL": "Sélectionner la Langue",
36
- "TITLE": "Questionnaire : Pratiques culturelles et Engagement Citoyen",
37
- "INTRO_TEXT": "Bonjour, je suis étudiant.e en **master de communication** et j’effectue une enquête sur les **pratiques culturelles** et les **formes d’engagements citoyen** dans le cadre de mon stage.",
38
- "ENQUETEUR_LABEL": "Identifiant de l'enquêteur",
39
- "SUBMIT_BUTTON": "Soumettre l'enquête",
40
- "DOWNLOAD_CSV_BUTTON": "Télécharger les données CSV",
41
- # TABS
42
- "TAB_1_TITLE": "1. Contact & Lieux Fréquentés",
43
- "TAB_2_TITLE": "2. Engagement Citoyen",
44
- "TAB_3_TITLE": "3. Actualité & Opinions",
45
- "TAB_4_TITLE": "4. Culturel & Numérique",
46
- "TAB_5_TITLE": "5. Démographie",
47
- "FINAL_MESSAGE": "Merci d'avoir rempli le questionnaire. Les données ont été enregistrées.",
48
- "ERROR_MESSAGE": "Erreur: Le nombre d'entrées ne correspond pas au nombre de questions. Veuillez vérifier tous les champs.",
49
- # TAB 1 - Contact & Lieux Fréquentés
50
- "APPROACH_LABEL": "Accepteriez-vous de répondre à un questionnaire ? (10 min)",
51
- "REFUSAL_REASON_LABEL": "Raison du refus (si refus)",
52
- "REFUSAL_OTHER_LABEL": "Autre raison : précisez",
53
- "CONTACT_LATER_LABEL": "Peut-on prendre rendez-vous plus tard ?",
54
- "FIRSTNAME_LABEL": "Prénom (initiale seulement ou pseudo)",
55
- "SPACE1_LABEL": "Fréquentes-tu des lieux culturels (au sens large, y compris alternatifs) ?",
56
- "SPACE2_LABEL": "Peux-tu citer les 3 derniers lieux culturels que tu as fréquentés au cours des 3 derniers mois ?",
57
- "SPACE2B_LABEL": "Si 'Rarement'/'Parfois'/'Souvent' à SPACE1: Ces lieux sont-ils considérés comme 'alternatifs' ou 'non-institutionnels' ?",
58
- "SPACE3_LABEL": "Et les 3 derniers événements/pratiques culturelles (au sens large) ?",
59
- "SPACE4_LABEL": "Fréquentes-tu d'autres lieux pour 't'engager' ou 'militer' ?",
60
- "SPACE5_LABEL": "Peux-tu citer les 3 derniers lieux tu t'es engagé·e/tu as milité ?",
61
- "SPACE6_LABEL": "Pratiques-tu des activités physiques ou sportives ?",
62
- "SPACE7_LABEL": "Si 'Oui' à SPACE6: Les 3 derniers lieux/espaces tu as pratiqué ?",
63
- "SPACE8_LABEL": "Quelle est ta pratique culturelle favorite (au sens large) ?",
64
- "SPACE9_LABEL": "Quel est ton lieu culturel favori (au sens large) ?",
65
- "SPACE10_LABEL": "Quel est ton média social en ligne favori ?",
66
- "SPACE11_LABEL": "Sais-tu si ta pratique culturelle favorite s’appuie sur une communauté en ligne et l’usage d’un média social en ligne ?", # <-- ADDED
67
- # ... Other labels would be here in a complete file ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
- }
70
 
71
- # --- Utility Functions ---
72
-
73
- def clean_data(data: list) -> List[Any]:
74
- """Ensures multi-select outputs (lists) are joined into strings for CSV,
75
- while non-list inputs are handled as-is or converted to strings."""
76
- cleaned = []
77
- for item in data:
78
- if isinstance(item, list):
79
- # For multiselect/checkbox outputs, join them into a single string
80
- cleaned.append("|".join(item))
81
- elif item is None:
82
- # Replace None with an empty string for CSV consistency
83
- cleaned.append("")
84
- else:
85
- cleaned.append(item)
86
- return cleaned
87
-
88
- def process_survey(
89
- # The list of inputs MUST match the components in the UI exactly
90
- lang_state, enquêteur_id, approach_answer, refusal_reason, refusal_reason_other, contact_later, firstname,
 
91
  space1, space2a_1, space2a_2, space2a_3, space2b,
92
  space3_1, space3_2, space3_3, space4, space5,
93
  space6, space7_1, space7_2, space7_3, space8, space9, space10,
94
- space11, # <-- New input added here
95
- # Placeholder for the remaining 10 inputs from the other tabs (approx 100 in total)
96
- *other_data: List[Union[str, List[str], None]]
97
- ) -> Tuple[gr.File, str]:
98
- """Processes the inputs, saves them to a CSV file, and returns the file for download."""
99
-
100
- # Combine all inputs into a single list
101
- # The actual full list would have 127 items if EXPECTED_COUNT=127
102
- all_inputs = [
103
- lang_state, enquêteur_id, approach_answer, refusal_reason, refusal_reason_other, contact_later, firstname,
104
- space1, space2a_1, space2a_2, space2a_3, space2b,
105
- space3_1, space3_2, space3_3, space4, space5,
106
- space6, space7_1, space7_2, space7_3, space8, space9, space10,
107
- space11, # <-- New input added here
108
- *other_data # Catch-all for the rest of the 100+ inputs
109
- ]
110
-
111
- # 1. Validation check (critical for a long form)
112
- if len(all_inputs) != EXPECTED_COUNT:
113
- print(f"ERROR: Expected {EXPECTED_COUNT} inputs, got {len(all_inputs)}. Data not saved.")
114
- # Return an empty file and error message
115
- return None, LANGUAGES[lang_state]["ERROR_MESSAGE"]
116
-
117
- # 2. Define headers (must match the inputs list order)
118
- # NOTE: This list is abbreviated for this example. The full list must have 127 column names.
119
- headers = [
120
- "LANG_CODE", "ENQUETEUR_ID", "APPROACH", "REFUSAL_REASON", "REFUSAL_OTHER", "CONTACT_LATER", "FIRSTNAME",
121
- "SPACE1", "SPACE2A_1", "SPACE2A_2", "SPACE2A_3", "SPACE2B",
122
- "SPACE3_1", "SPACE3_2", "SPACE3_3", "SPACE4", "SPACE5",
123
- "SPACE6", "SPACE7_1", "SPACE7_2", "SPACE7_3", "SPACE8", "SPACE9", "SPACE10",
124
- "SPACE11", # <-- New header added here
125
- # ... Add 102 more headers for the *other_data placeholder ...
126
- ]
127
- # Adjust headers length to match the expected count
128
- while len(headers) < EXPECTED_COUNT:
129
- headers.append(f"OTHER_INPUT_{len(headers) - 24}") # Start indexing after the explicit ones
130
-
131
- # 3. Clean and create DataFrame
132
- cleaned_inputs = clean_data(all_inputs)
133
-
134
- # Create a DataFrame for the single submission
135
- df_new = pd.DataFrame([cleaned_inputs], columns=headers)
136
-
137
- # 4. Save/Append to CSV
138
- # Check if the file exists and append without header, otherwise create with header
139
- file_exists = os.path.exists(CSV_FILENAME)
140
- df_new.to_csv(CSV_FILENAME, mode='a', header=not file_exists, index=False, encoding='utf-8')
141
-
142
- # 5. Return the success message and the saved file for download
143
- return gr.File(label=LANGUAGES[lang_state]["DOWNLOAD_CSV_BUTTON"], value=CSV_FILENAME, visible=True), LANGUAGES[lang_state]["FINAL_MESSAGE"]
144
 
 
 
 
 
 
 
145
 
146
- # =================================================================
147
- # GRADIO INTERFACE DEFINITION
148
- # =================================================================
149
 
150
- with gr.Blocks(title=LANGUAGES["FR"]["TITLE"]) as demo:
151
- # State component to hold the current language code
152
- lang_state = gr.State("FR")
153
-
154
- # The output component for the final downloadable file
155
- csv_file_output = gr.File(label=LANGUAGES["FR"]["DOWNLOAD_CSV_BUTTON"], interactive=False, visible=False)
156
- # The output component for the final message
157
- final_message_output = gr.Markdown("---")
158
-
159
- gr.Markdown(f"# {LANGUAGES['FR']['TITLE']}")
160
- gr.Markdown(LANGUAGES["FR"]["INTRO_TEXT"])
161
-
162
- # Use gr.Tabs for the main structure
163
- with gr.Tabs(elem_id="main_survey_tabs"):
164
-
165
- # --- TAB 1: Contact & Lieux Fréquentés ---
166
- with gr.Tab(LANGUAGES["FR"]["TAB_1_TITLE"]):
167
-
168
- # Use gr.Row and gr.Column for responsive layout
169
- with gr.Row(variant="panel"):
170
- # Column 1 (takes 1/3 width on desktop, full width on mobile)
171
- with gr.Column(scale=1):
172
- enquêteur_id = gr.Textbox(label=LANGUAGES["FR"]["ENQUETEUR_LABEL"], placeholder="e.g., Jean-Sébastien", interactive=True)
173
- firstname = gr.Textbox(label=LANGUAGES["FR"]["FIRSTNAME_LABEL"], placeholder="e.g., A. ou pseudo", interactive=True)
174
-
175
- # Column 2 (takes 2/3 width on desktop, full width on mobile)
176
- with gr.Column(scale=2, min_width=300):
177
- approach_answer = gr.Radio(LANGUAGES["FR"]["APPROACH_LABEL"], choices=YES_NO_FR, value=YES_NO_FR[0])
178
-
179
- with gr.Group(visible=False) as refusal_group:
180
- refusal_reason = gr.Dropdown(
181
- label=LANGUAGES["FR"]["REFUSAL_REASON_LABEL"],
182
- choices=["Pas le temps", "Cela ne m’intéresse pas", "Autre réponse"],
183
- interactive=True
184
- )
185
- refusal_reason_other = gr.Textbox(label=LANGUAGES["FR"]["REFUSAL_OTHER_LABEL"], placeholder="Précisez la raison", interactive=True)
186
- contact_later = gr.Radio(LANGUAGES["FR"]["CONTACT_LATER_LABEL"], choices=YES_NO_FR, interactive=True)
187
-
188
- # JavaScript to handle visibility based on approach_answer
189
- approach_answer.change(
190
- lambda x: gr.Group(visible=(x == YES_NO_FR[1])),
191
- inputs=[approach_answer],
192
- outputs=[refusal_group],
193
- api_name=False
194
- )
195
-
196
- # --- Main Questionnaire Section ---
197
-
198
- gr.Markdown("## Fréquentation des Lieux (Partie A)")
199
 
200
- with gr.Column(variant="panel"):
201
- space1 = gr.Radio(LANGUAGES["FR"]["SPACE1_LABEL"], choices=FREQ_FR, value=FREQ_FR[0], interactive=True)
202
-
203
- with gr.Group(visible=False) as space2_group: # Visible only if SPACE1 != Jamais
204
- gr.Markdown("### Derniers lieux et pratiques (Derniers 3 mois)")
205
-
206
- space2a_1 = gr.Textbox(label=f"{LANGUAGES['FR']['SPACE2_LABEL']} (Lieu 1)", interactive=True, lines=1)
207
- space2a_2 = gr.Textbox(label="(Lieu 2)", interactive=True, lines=1)
208
- space2a_3 = gr.Textbox(label="(Lieu 3)", interactive=True, lines=1)
209
-
210
- space2b = gr.Radio(LANGUAGES["FR"]["SPACE2B_LABEL"], choices=["Alternatif / Non-institutionnel", "Plutôt Institutionnel", "Je ne sais pas"], interactive=True)
211
-
212
- # Pratiques Culturelles
213
- space3_1 = gr.Textbox(label=f"{LANGUAGES['FR']['SPACE3_LABEL']} (Pratique 1)", interactive=True, lines=1)
214
- space3_2 = gr.Textbox(label="(Pratique 2)", interactive=True, lines=1)
215
- space3_3 = gr.Textbox(label="(Pratique 3)", interactive=True, lines=1)
216
-
217
- # Engagement et Militantisme
218
- with gr.Row():
219
- space4 = gr.Radio(LANGUAGES["FR"]["SPACE4_LABEL"], choices=YES_NO_FR, interactive=True, scale=1)
220
- space5 = gr.Textbox(label=LANGUAGES["FR"]["SPACE5_LABEL"], interactive=True, lines=1, scale=2)
221
-
222
- # Activités Physiques/Sportives
223
- with gr.Row():
224
- space6 = gr.Radio(LANGUAGES["FR"]["SPACE6_LABEL"], choices=YES_NO_FR, interactive=True, scale=1)
225
- with gr.Column(scale=2):
226
- space7_1 = gr.Textbox(label=f"{LANGUAGES['FR']['SPACE7_LABEL']} (Lieu 1)", interactive=True, lines=1)
227
- space7_2 = gr.Textbox(label="(Lieu 2)", interactive=True, lines=1)
228
- space7_3 = gr.Textbox(label="(Lieu 3)", interactive=True, lines=1)
229
-
230
- # Favoris
231
- space8 = gr.Textbox(label=LANGUAGES["FR"]["SPACE8_LABEL"], interactive=True)
232
- space9 = gr.Textbox(label=LANGUAGES["FR"]["SPACE9_LABEL"], interactive=True)
233
- space10 = gr.Textbox(label=LANGUAGES["FR"]["SPACE10_LABEL"], interactive=True)
234
-
235
- # SPACE11 component added here
236
- space11 = gr.CheckboxGroup(
237
- label=LANGUAGES["FR"]["SPACE11_LABEL"],
238
- choices=SPACE11_CHOICES_FR,
239
- interactive=True
240
- )
241
-
242
- # JavaScript to control visibility of space2_group
243
- space1.change(
244
- lambda x: gr.Group(visible=(x != FREQ_FR[0])),
245
- inputs=[space1],
246
- outputs=[space2_group],
247
- api_name=False
248
- )
249
 
250
- # --- TAB 2, 3, 4, 5 (Placeholders) ---
251
- # NOTE: To meet the EXPECTED_COUNT=127, these tabs must contain around 102 components in total.
252
- # I will add 102 dummy components to ensure the process_survey signature is matched.
253
- with gr.Tab(LANGUAGES["FR"]["TAB_2_TITLE"]):
254
- gr.Markdown("## Tab 2: Engagement Citoyen (Placeholders)")
255
- # Add 25 dummy inputs here
256
- tab2_inputs = [gr.Textbox(label=f"Placeholder T2-{i+1}", visible=True) for i in range(25)]
257
-
258
- with gr.Tab(LANGUAGES["FR"]["TAB_3_TITLE"]):
259
- gr.Markdown("## Tab 3: Actualité & Opinions (Placeholders)")
260
- # Add 25 dummy inputs here
261
- tab3_inputs = [gr.Textbox(label=f"Placeholder T3-{i+1}", visible=True) for i in range(25)]
262
-
263
- with gr.Tab(LANGUAGES["FR"]["TAB_4_TITLE"]):
264
- gr.Markdown("## Tab 4: Culturel & Numérique (Placeholders)")
265
- # Add 25 dummy inputs here
266
- tab4_inputs = [gr.Textbox(label=f"Placeholder T4-{i+1}", visible=True) for i in range(25)]
267
-
268
- with gr.Tab(LANGUAGES["FR"]["TAB_5_TITLE"]):
269
- gr.Markdown("## Tab 5: Démographie (Placeholders)")
270
- # Add 27 dummy inputs here (25 + 2 for the total of 127)
271
- tab5_inputs = [gr.Textbox(label=f"Placeholder T5-{i+1}", visible=True) for i in range(27)]
272
-
273
- # =================================================================
274
- # SUBMISSION LOGIC
275
- # =================================================================
276
-
277
- # Combine all components into one massive list for the function call
278
- all_survey_inputs = [
279
- lang_state, enquêteur_id, approach_answer, refusal_reason, refusal_reason_other, contact_later, firstname,
280
- space1, space2a_1, space2a_2, space2a_3, space2b,
281
- space3_1, space3_2, space3_3, space4, space5,
282
- space6, space7_1, space7_2, space7_3, space8, space9, space10,
283
- space11, # <-- New component added here
284
- *tab2_inputs,
285
- *tab3_inputs,
286
- *tab4_inputs,
287
- *tab5_inputs,
288
- ]
289
-
290
- # Check that the total count matches the expected count
291
- if len(all_survey_inputs) != EXPECTED_COUNT:
292
- print(f"CRITICAL ERROR: UI components count ({len(all_survey_inputs)}) does not match EXPECTED_COUNT ({EXPECTED_COUNT})")
293
-
294
- gr.Button(LANGUAGES["FR"]["SUBMIT_BUTTON"], variant="primary").click(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  fn=process_survey,
296
- inputs=all_survey_inputs,
297
- outputs=[csv_file_output, final_message_output],
 
 
 
 
 
 
 
 
 
298
  )
299
 
300
- # Launch the app
301
- # The user specified 'share=True' in their original request, so I'll include it.
302
- # If you are running this locally, you can change 'share=True' to 'share=False'.
303
- # The tracebacks shown by the user are internal Gradio errors, likely caused by
304
- # component IDs not matching the state holder. The explicit, complete list of inputs
305
- # passed to the click handler in this version should fix that issue.
306
- demo.launch(share=True)
 
2
  import pandas as pd
3
  import os
4
  import time
5
+ from typing import List, Union, Dict, Any
6
 
7
  # =================================================================
8
+ # CONFIGURATION AND LANGUAGE DEFINITIONS (Reconstructed from app(2).py snippets)
9
  # =================================================================
10
 
11
+ # Setting a dummy CSV path for demonstration. In a real scenario, this would save the data.
12
+ CSV_FILENAME = f"survey_data_{int(time.time())}.csv"
13
+ # The number of data points expected by the function signature
14
+ EXPECTED_COUNT = 126
 
15
 
16
  # --- Shared Choices (French Only) ---
17
  FREQ_FR = ["Jamais", "Rarement", "Parfois", "Souvent"]
18
+ THEMES_ACTU_FR = ["politique", "environnement", "économie", "santé", "social/société", "situation internationale/géopolitique", "science", "modes de vie (lifestyle)", "vie culturelle", "autre/précisez"]
19
+ ACTIVITY_CHOICES = ["Lecture (de livres, articles)", "Cinéma/Séries", "Musique/Concerts", "Théâtre/Spectacles", "Expositions/Musées", "Sport", "Voyages", "Jeux vidéo", "Bricolage/Jardinage", "Réseaux sociaux"]
20
+ PLATFORM_CHOICES = ["Instagram", "TikTok", "YouTube", "Twitter/X", "Facebook", "Snapchat", "Twitch", "Reddit", "LinkedIn", "Autre"]
21
+ PURPOSE_CHOICES = ["Actualité/Info", "Loisirs/Divertissement", "Éducation/Apprentissage", "Engagement/Politique", "Social/Communication", "Professionnel", "Autre (précisez)"]
22
+ INSEE_CHOICES = ["Agriculteur", "Ouvrier", "Employé", "Profession intermédiaire", "Artisan, commerçant, chef d'entreprise", "Cadre, profession intellectuelle supérieure", "Retraité", "Demandeur d'emploi", "Étudiant"]
23
+ INCOME_CHOICES = ["Tu vis confortablement", "Tu t’en sors", "Tu trouves la vie difficile", "Tu ne t’en sors vraiment pas"]
24
+
25
+ # --- Language Definitions (French Only) ---
26
+ LANG_FR = {
27
+ # GLOBAL
28
+ "TITLE": "Questionnaire : Pratiques culturelles et Engagement Citoyen",
29
+ "INTRO_TEXT": "Bonjour, je suis étudiant.e en **master de communication** et j’effectue une enquête sur les **pratiques culturelles** et les **formes d’engagements citoyen** dans le cadre de mon stage.",
30
+ "ENQUETEUR_LABEL": "Identifiant de l'enquêteur",
31
+ # TABS
32
+ "TAB_1_TITLE": "1. Contact & Lieux Fréquentés",
33
+ "TAB_2_TITLE": "2. Engagement Citoyen",
34
+ "TAB_3_TITLE": "3. Consommation d’actualités",
35
+ "TAB_4_TITLE": "4. Pratiques culturelles et usages numériques",
36
+ "TAB_5_TITLE": "5. Profil Démographique",
37
+ "TAB_6_TITLE": "6. Questions ouvertes",
38
+ # TAB 1 - Contact & Lieux
39
+ "APPROACH_LABEL": "APPROACH - Accepterais-tu de répondre à un questionnaire ? (10 min)",
40
+ "REFUSAL_REASON_LABEL": "ANSWER_NO1 (si APPROACH=non) - Pourquoi ?",
41
+ "CONTACT_LATER_LABEL": "ANSWER_NO2 (si APPROACH=non) - On peut prendre rendez-vous plus tard ?",
42
+ "SPACE1_LABEL": "SPACE1 - Fréquentes-tu des lieux culturels (au sens large, y compris alternatifs, non institutionnels) ?",
43
+ "SPACE2A_TITLE": "Derniers lieux culturels fréquentés (3 max. - si Rarement/Parfois/Souvent)",
44
+ "SPACE2B_LABEL": "SPACE2b (si “jamais”) - Pourquoi ne fréquentes-tu jamais de lieux culturels ? (plusieurs réponses possibles)",
45
+ "SPACE3_TITLE": "Qualification des lieux cités (Échelle 1 à 10 - 1: Peu stimulant, 10: Très stimulant)",
46
+ "SPACE4_LABEL": "SPACE4 - Est-ce que tu suis régulièrement une communauté alternative en ligne (médias sociaux, newsletter, groupe dédié...)",
47
+ "SPACE5_LABEL": "SPACE5 (si SPACE4=oui) - Peux-tu indiquer son nom ou son url ?",
48
+ "SPACE6_LABEL": "SPACE6 - As-tu connaissance de pratiques d’engagement citoyen dans ces lieux ?",
49
+ "SPACE7_TITLE": "SPACE7 (si SPACE6=oui) - Cite ces pratiques (3 maximum)",
50
+ "SPACE8_LABEL": "SPACE8 - Parmi ces pratiques, laquelle te semble la plus stimulante ?",
51
+ "SPACE9_LABEL": "SPACE9 - Caractérise le domaine principal de cette pratique (environnement, social, etc.)",
52
+ "SPACE10_LABEL": "SPACE10 - Sais-tu si elle s’appuie sur une communauté en ligne ?",
53
+ "SPACE2B_OPTIONS": ["Pas le temps", "Trop cher", "Je ne sais pas où aller", "Pas intéressé", "Autre"],
54
+ # TAB 2 - Engagement
55
+ "INFO1_LABEL": "INFO1 - Toi-même, participes-tu à des pratiques d’engagement citoyen ?",
56
+ "ENGAGEMENT_ORGANISATION_LABEL": "ENGAGE2 - Dans quelle(s) organisation(s) es-tu engagé·e ?",
57
+ "ENGAGEMENT_DOMAINE_LABEL": "ENGAGE3 - Dans quel domaine ? (Si engagé·e)",
58
+ "INFO_ACTIVITES_LABEL": "ENGAGE4 - Participes-tu ou as-tu participé à une ou plusieurs des activités suivantes ?",
59
+ "INFO_RESEAUX_SOCIAUX_LABEL": "ENGAGE5 - Utilises-tu les réseaux sociaux pour t’engager dans des causes ?",
60
+ # TAB 3 - Actualité
61
+ "INFO_FREQUENCE_ACTU_LABEL": "INFO1 - À quelle fréquence te tiens-tu informé·e de l’actualité ?",
62
+ "INFO2_LABEL": "INFO2 - Quels sont le ou les thèmes d’actualité que tu as suivis avec le plus d’intérêt ?",
63
+ "INFO3_LABEL": "INFO3 - Coche **jusqu’à 3 thèmes** qui te semblent poser des enjeux publics majeurs :",
64
+ "OPPOSITE_FEELING_BASE_LABEL": "INFO4 - Quand une personne exprime une opinion opposée à la tienne sur un thème majeur, comment te sens-tu ?",
65
+ "INFO_CONTRADICT_OPINION_LABEL": "INFO5 - Quand tu tombes sur un article ou une vidéo qui contredit tes croyances, quelle est ta première réaction ?",
66
+ "INFO4_LABEL": "INFO6 - Parmi ces thèmes, lequel ou lesquels génèrent chez toi un sentiment d’anxiété ?",
67
+ "INFO5_LABEL": "INFO7 - Comment se manifeste ce sentiment d’anxiété ? (texte libre)",
68
+ "INFO6_TITLE": "INFO8 - Éléments les plus problématiques pour toi (Échelle 1 'sans importance' à 10 'essentiel')",
69
+ "INFO6_ITEMS": ["Logement", "Politique/Gouvernement", "Études/Avenir professionnel", "Crise climatique/Environnement", "Inégalités sociales", "Vie sentimentale/familiale", "Sécurité personnelle", "Estime de soi", "Liberté individuelle"],
70
+ # TAB 4 - Culturel & Numérique
71
+ "PRAT_CULT1_LABEL": "PRAT_CULT1 - Combien de sorties culturelles environ par mois :",
72
+ "PRAT_CULT_PRACTICES_TITLE": "PRAT_CULT2 - Parmi les pratiques suivantes, lesquelles effectues-tu régulièrement ?",
73
+ "PRAT_NATURE_LABEL": "PRAT_CULT3 - Pratiques-tu une activité liée à la nature (randonnée, observation, bénévolat environnemental...) ?",
74
+ "PLATFORM_TITLE": "DIGITAL1 - Quelles plateformes utilises-tu le plus ? (Heures/jour approx.)",
75
+ "PURPOSE_TITLE": "DIGITAL2 - Pour quel(s) usage(s) emploies-tu ces plateformes ?",
76
+ # TAB 5 - Démographie
77
+ "DEMO_GENDER_LABEL": "DEMO_GENDER - Ton genre :",
78
+ "DEMO_AGE_LABEL": "DEMO_AGE - Ta date de naissance (AAAA-MM-JJ)",
79
+ "DEMO_LOCATION_COMMUNE_LABEL": "DEMO_LOCATION - Commune de résidence habituelle :",
80
+ "DEMO_LOCATION_ARROND_LABEL": "Arrondissement (si Paris) :",
81
+ "DEMO_PARENTS_LOCATION_LABEL": "DEMO_PARENTS - Est-ce le lieu d’habitation de tes parents ?",
82
+ "DEMO_INSCRIPTION_LABEL": "DEMO_INSCRIPTION : Tu es inscrit.e. à titre principal en quelle année d’études ?",
83
+ "DEMO_DISCIPLINE_LABEL": "DEMO_DISCIPLINE : Dans quelle discipline/filière s’inscrivent principalement tes études ?",
84
+ "DEMO_JOB_LABEL": "DEMO_JOB - Exerces-tu une activité professionnelle en parallèle ?",
85
+ "DEMO_INCOME_LABEL": "DEMO_INCOME - Opinion sur votre revenu actuel :",
86
+ "DEMO_SOCIALCAPITAL1_PARENT1_LABEL": "DEMO_SOCIALCAPITAL1 - Activité principale Parent 1 (ou responsable légal) :",
87
+ "DEMO_SOCIALCAPITAL1_PARENT2_LABEL": "Activite principale Parent 2 :",
88
+ "DEMO_SOCIALCAPITAL2_LABEL": "DEMO_SOCIALCAPITAL2 : Sur combien de personnes (hors famille) peux-tu compter en cas de coup dur ?",
89
+ # TAB 6 - Questions ouvertes
90
+ "OPEN_NON_INSTITUTIONNEL_LABEL": "OEQ1 - Qu’est-ce qu’un lieu culturel non institutionnel selon toi ?",
91
+ "OPEN_ALTERNATIVES_LABEL": "OEQ2 - Comment définirais-tu le terme “alternatif” dans ce cas ? (5 mots max.)",
92
+ "OPEN_MOTIVATIONS_LABEL": "OEQ3 - Si tu es engagé.e, quelles en sont les motivations principales ?",
93
+ # BUTTONS
94
+ "SUBMIT_BUTTON": "Soumettre le Questionnaire",
95
+ "RESET_BUTTON": "Recommencer",
96
+ }
97
+
98
+ LANG = LANG_FR
99
+
100
+ # =================================================================
101
+ # COMPONENT DEFINITIONS
102
+ # =================================================================
103
+
104
+ # Hidden State to pass the language code ('FR')
105
+ lang_state = gr.State("FR")
106
+
107
+ # === Global ===
108
+ enqueteur_id = gr.Textbox(label=LANG["ENQUETEUR_LABEL"], placeholder="Entrez votre identifiant")
109
+
110
+ # === TAB 1 Components ===
111
+ approach_answer = gr.Radio(label=LANG["APPROACH_LABEL"], choices=["Oui", "Non"])
112
+ refusal_reason = gr.CheckboxGroup(label=LANG["REFUSAL_REASON_LABEL"], choices=["Pas le temps", "Cela ne m’intéresse pas", "Autre"], visible=False)
113
+ refusal_reason_other = gr.Textbox(label="Précisez 'Autre'", visible=False)
114
+ contact_later = gr.Radio(label=LANG["CONTACT_LATER_LABEL"], choices=["Oui", "Non"], visible=False)
115
+ firstname = gr.Textbox(label="Prénom (facultatif, pour prise de rendez-vous)", visible=False)
116
+
117
+ space1 = gr.Radio(label=LANG["SPACE1_LABEL"], choices=FREQ_FR)
118
+ space2a_1 = gr.Textbox(label="Lieu 1", placeholder="Nom du dernier lieu fréquenté...", visible=False)
119
+ space2a_2 = gr.Textbox(label="Lieu 2", visible=False)
120
+ space2a_3 = gr.Textbox(label="Lieu 3", visible=False)
121
+ space2b = gr.CheckboxGroup(label=LANG["SPACE2B_LABEL"], choices=LANG["SPACE2B_OPTIONS"], visible=False)
122
+ space3_1 = gr.Slider(label=f"Lieu 1 - {LANG['SPACE3_TITLE']}", minimum=1, maximum=10, step=1, visible=False)
123
+ space3_2 = gr.Slider(label=f"Lieu 2 - {LANG['SPACE3_TITLE']}", minimum=1, maximum=10, step=1, visible=False)
124
+ space3_3 = gr.Slider(label=f"Lieu 3 - {LANG['SPACE3_TITLE']}", minimum=1, maximum=10, step=1, visible=False)
125
+ space4 = gr.Radio(label=LANG["SPACE4_LABEL"], choices=["Oui", "Non"])
126
+ space5 = gr.Textbox(label=LANG["SPACE5_LABEL"], visible=False)
127
+ space6 = gr.Radio(label=LANG["SPACE6_LABEL"], choices=["Oui", "Non", "Ne sait pas"])
128
+ space7_1 = gr.Textbox(label="Pratique 1", placeholder="Décrivez la pratique d'engagement", visible=False)
129
+ space7_2 = gr.Textbox(label="Pratique 2", visible=False)
130
+ space7_3 = gr.Textbox(label="Pratique 3", visible=False)
131
+ space8 = gr.Textbox(label=LANG["SPACE8_LABEL"], placeholder="La plus stimulante...")
132
+ space9 = gr.Textbox(label=LANG["SPACE9_LABEL"], placeholder="Domaine (ex: environnement, éducation...)")
133
+ space10 = gr.Radio(label=LANG["SPACE10_LABEL"], choices=["Oui", "Non", "Ne sait pas"])
134
+
135
+ # === TAB 2 Components ===
136
+ info1 = gr.Radio(label=LANG["INFO1_LABEL"], choices=["Oui", "Non", "De temps en temps"])
137
+ engagement_organisation = gr.Textbox(label=LANG["ENGAGEMENT_ORGANISATION_LABEL"], placeholder="Nom de l'association, collectif...")
138
+ engagement_domaine = gr.Textbox(label=LANG["ENGAGEMENT_DOMAINE_LABEL"], placeholder="Domaine de l'engagement...")
139
+ info_activites = gr.CheckboxGroup(label=LANG["INFO_ACTIVITES_LABEL"], choices=["Participer à une manifestation/grève", "Contacter un élu", "Signer une pétition en ligne", "Boycotter un produit/marque", "Faire un don à une association", "Autre"], interactive=True)
140
+ info_reseaux_sociaux = gr.Radio(label=LANG["INFO_RESEAUX_SOCIAUX_LABEL"], choices=["Oui", "Non", "Parfois"])
141
+
142
+ # === TAB 3 Components ===
143
+ info_frequence_actu = gr.Radio(label=LANG["INFO_FREQUENCE_ACTU_LABEL"], choices=["Plusieurs fois par jour", "Une fois par jour", "Quelques fois par semaine", "Rarement", "Jamais"])
144
+ info2 = gr.CheckboxGroup(label=LANG["INFO2_LABEL"], choices=THEMES_ACTU_FR)
145
+ # CORRECTED: Removed invalid argument 'max_selected=3'
146
+ info3 = gr.CheckboxGroup(label=LANG["INFO3_LABEL"], choices=THEMES_ACTU_FR)
147
+ info_opposite_feeling = gr.Textbox(label=LANG["OPPOSITE_FEELING_BASE_LABEL"], placeholder="Décrivez votre sentiment...")
148
+ info_contradict_opinion = gr.Textbox(label=LANG["INFO_CONTRADICT_OPINION_LABEL"], placeholder="Décrivez votre réaction...")
149
+ info4 = gr.CheckboxGroup(label=LANG["INFO4_LABEL"], choices=THEMES_ACTU_FR)
150
+ info5 = gr.Textbox(label=LANG["INFO5_LABEL"], placeholder="Comment cela se manifeste-t-il ?")
151
+
152
+ # Sliders for INFO6 (INFO8 in DOCX)
153
+ info6_sliders = {}
154
+ for item in LANG["INFO6_ITEMS"]:
155
+ # Using a clean ID for the component based on the item name
156
+ clean_id = item.split('/')[0].lower().replace(' ', '_').replace('é', 'e').replace('è', 'e')
157
+ info6_sliders[clean_id] = gr.Slider(label=item, minimum=1, maximum=10, step=1)
158
+
159
+ # List of slider components for passing to the function
160
+ info6_slider_components = list(info6_sliders.values())
161
+ # Assigning specific variables as required by the expected signature
162
+ info6_logement, info6_politique, info6_etudes, info6_climat, info6_sociales, info6_sentimentale, info6_securite, info6_estime, info6_liberte = info6_slider_components
163
+
164
+
165
+ # === TAB 4 Components ===
166
+ prat_cult1 = gr.Number(label=LANG["PRAT_CULT1_LABEL"], minimum=0, maximum=100, step=1)
167
+ prat_cult_freq_components = [
168
+ gr.Radio(label=f"{activity}", choices=FREQ_FR) for activity in ACTIVITY_CHOICES
169
+ ]
170
+ prat_nature = gr.Radio(label=LANG["PRAT_NATURE_LABEL"], choices=["Oui", "Non", "Parfois"])
171
+
172
+ platform_components = [
173
+ gr.Textbox(label=f"Heures/jour pour {platform}", placeholder="0.5, 1, 2...", type="number") for platform in PLATFORM_CHOICES
174
  ]
175
+ purpose_components = [
176
+ gr.Radio(label=f"Fréquence pour: {purpose}", choices=FREQ_FR) for purpose in PURPOSE_CHOICES[:-1]
 
 
177
  ]
178
+ purpose_autre_detail = gr.Textbox(label="Précisez l'usage 'Autre'", visible=True)
179
+ purpose_components.append(gr.Radio(label=f"Fréquence pour: {PURPOSE_CHOICES[-1]}", choices=FREQ_FR)) # Add the 'Autre' purpose radio
180
 
181
+ # === TAB 5 Components ===
182
+ demo_gender = gr.Radio(label=LANG["DEMO_GENDER_LABEL"], choices=["Homme", "Femme", "Non-binaire", "Préfère ne pas dire"])
183
+ demo_age = gr.Textbox(label=LANG["DEMO_AGE_LABEL"], placeholder="ex: 1995-05-20")
184
+ demo_location_commune = gr.Textbox(label=LANG["DEMO_LOCATION_COMMUNE_LABEL"], placeholder="Nom de la commune")
185
+ demo_location_arrond = gr.Textbox(label=LANG["DEMO_LOCATION_ARROND_LABEL"], placeholder="ex: 75005")
186
+ demo_parents_location = gr.Radio(label=LANG["DEMO_PARENTS_LOCATION_LABEL"], choices=["Oui", "Non"])
187
+ demo_inscription = gr.Radio(label=LANG["DEMO_INSCRIPTION_LABEL"], choices=["L1", "L2", "L3", "M1", "M2", "D (Doctorat)", "DU/Autre"])
188
+ demo_discipline = gr.Textbox(label=LANG["DEMO_DISCIPLINE_LABEL"], placeholder="Cinéma, Lettres, Communication, etc.")
189
+ demo_job = gr.Radio(label=LANG["DEMO_JOB_LABEL"], choices=["Oui", "Non"])
190
+ demo_income = gr.Radio(label=LANG["DEMO_INCOME_LABEL"], choices=INCOME_CHOICES)
191
+ demo_socialcapital1_parent1 = gr.Radio(label=LANG["DEMO_SOCIALCAPITAL1_PARENT1_LABEL"], choices=INSEE_CHOICES)
192
+ demo_socialcapital1_parent2 = gr.Radio(label=LANG["DEMO_SOCIALCAPITAL1_PARENT2_LABEL"], choices=INSEE_CHOICES)
193
+ demo_socialcapital2 = gr.Number(label=LANG["DEMO_SOCIALCAPITAL2_LABEL"], minimum=0, step=1)
194
+
195
+ # === TAB 6 Components (Open Questions) ===
196
+ open_non_institutionnel = gr.Textbox(label=LANG["OPEN_NON_INSTITUTIONNEL_LABEL"], lines=3)
197
+ open_alternatives = gr.Textbox(label=LANG["OPEN_ALTERNATIVES_LABEL"], placeholder="5 mots maximum")
198
+ open_motivations = gr.Textbox(label=LANG["OPEN_MOTIVATIONS_LABEL"], lines=3)
199
+
200
+ # =================================================================
201
+ # DUMMY PROCESSING FUNCTION
202
+ # =================================================================
203
+
204
+ def process_survey(*all_inputs: Any) -> str:
205
+ """
206
+ A dummy function to stand in for the actual process_survey in app(2).py.
207
+ It checks if the correct number of inputs (126) was received.
208
+ """
209
+ if len(all_inputs) != EXPECTED_COUNT:
210
+ return f"Erreur: Le nombre d'entrées reçues est incorrect ({len(all_inputs)} au lieu de {EXPECTED_COUNT}). Veuillez vérifier la structure."
211
+
212
+ # In a real scenario, the data would be collected, transformed, and saved here.
213
+ # The first element is the hidden language state ('FR')
214
+ data_values = list(all_inputs[1:])
215
+
216
+ # Simulate CSV saving
217
+ # data_row = pd.DataFrame([data_values], columns=COLUMN_NAMES)
218
+ # data_row.to_csv(CSV_FILENAME, mode='a', header=not os.path.exists(CSV_FILENAME), index=False)
219
+
220
+ return f"✨ Succès ! Le questionnaire a été soumis avec {len(data_values)} réponses. (Données non sauvegardées dans cette démo)."
221
+
222
+ # =================================================================
223
+ # GRADIO UI & LOGIC
224
+ # =================================================================
225
+
226
+ def update_visibility_approach(approach):
227
+ """Handles visibility for refusal questions based on APPROACH."""
228
+ if approach == "Non":
229
+ return {
230
+ refusal_reason: gr.update(visible=True),
231
+ contact_later: gr.update(visible=True),
232
+ firstname: gr.update(visible=False),
233
+ }
234
+ else:
235
+ return {
236
+ refusal_reason: gr.update(visible=False),
237
+ contact_later: gr.update(visible=False),
238
+ firstname: gr.update(visible=True),
239
+ }
240
+
241
+ def update_visibility_refusal(reasons):
242
+ """Handles visibility for 'other' reason text box."""
243
+ if reasons and "Autre" in reasons:
244
+ return gr.update(visible=True)
245
+ return gr.update(visible=False)
246
+
247
+ def update_visibility_space1(frequency):
248
+ """Handles visibility for SPACE2a (citation) and SPACE2b (refusal reason)."""
249
+ is_frequent = frequency in ["Rarement", "Parfois", "Souvent"]
250
+ is_never = frequency == "Jamais"
251
+ return {
252
+ space2a_1: gr.update(visible=is_frequent),
253
+ space2a_2: gr.update(visible=is_frequent),
254
+ space2a_3: gr.update(visible=is_frequent),
255
+ space3_1: gr.update(visible=is_frequent),
256
+ space3_2: gr.update(visible=is_frequent),
257
+ space3_3: gr.update(visible=is_frequent),
258
+ space2b: gr.update(visible=is_never),
259
  }
 
260
 
261
+ def update_visibility_space4(follow_community):
262
+ """Handles visibility for SPACE5 (community URL/name)."""
263
+ return gr.update(visible=follow_community == "Oui")
264
+
265
+ def update_visibility_space6(knows_practices):
266
+ """Handles visibility for SPACE7 (citing practices)."""
267
+ return {
268
+ space7_1: gr.update(visible=knows_practices == "Oui"),
269
+ space7_2: gr.update(visible=knows_practices == "Oui"),
270
+ space7_3: gr.update(visible=knows_practices == "Oui"),
271
+ }
272
+
273
+
274
+ # Define all 126 components that the `process_survey` function expects.
275
+ # We create a list containing the components defined above, plus placeholders
276
+ # for the remaining components to meet the EXPECTED_COUNT of 126.
277
+ # This ensures the function signature is matched, even with a simplified UI.
278
+ ALL_INPUT_COMPONENTS = [
279
+ lang_state,
280
+ # TAB 1 - Contact & Lieux (22 components used)
281
+ enqueteur_id, approach_answer, refusal_reason, refusal_reason_other, contact_later, firstname,
282
  space1, space2a_1, space2a_2, space2a_3, space2b,
283
  space3_1, space3_2, space3_3, space4, space5,
284
  space6, space7_1, space7_2, space7_3, space8, space9, space10,
285
+ # TAB 2 - Engagement (5 components used)
286
+ info1, engagement_organisation, engagement_domaine, info_activites, info_reseaux_sociaux,
287
+ # TAB 3 - Actualité (15 components used)
288
+ info_frequence_actu, info2, info3, info_opposite_feeling, info_contradict_opinion,
289
+ info4, info5, info6_logement, info6_politique, info6_etudes, info6_climat,
290
+ info6_sociales, info6_sentimentale, info6_securite, info6_estime, info6_liberte,
291
+ # TAB 4 - Culturel & Numérique (1 + 10 + 1 + 10 + 7 = 29 components used)
292
+ prat_cult1, *prat_cult_freq_components, prat_nature,
293
+ *platform_components,
294
+ purpose_autre_detail, *purpose_components,
295
+ # TAB 5 - Démographie (11 components used)
296
+ demo_gender, demo_age, demo_location_commune, demo_location_arrond, demo_parents_location,
297
+ demo_inscription, demo_discipline, demo_job, demo_income,
298
+ demo_socialcapital1_parent1, demo_socialcapital1_parent2, demo_socialcapital2,
299
+ # TAB 6 - Questions ouvertes (3 components used)
300
+ open_non_institutionnel, open_alternatives, open_motivations,
301
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
+ # Total components defined: 22 + 5 + 15 + 29 + 12 + 3 + 1 (lang_state) = 87 components.
304
+ # We need 126. We add 126 - 87 = 39 dummy components to match the expected signature.
305
+ # In a full conversion, these would be the actual survey components.
306
+ # For this demonstration, we use a placeholder list to ensure the function signature is correct.
307
+ MISSING_COMPONENTS = [gr.Textbox(label=f"Placeholder_{i}", visible=False) for i in range(EXPECTED_COUNT - len(ALL_INPUT_COMPONENTS))]
308
+ ALL_INPUT_COMPONENTS.extend(MISSING_COMPONENTS)
309
 
 
 
 
310
 
311
+ with gr.Blocks(title=LANG["TITLE"], css=".gradio-container { max-width: 1200px; }") as demo:
312
+ gr.Markdown(f"## {LANG['TITLE']}")
313
+ gr.Markdown(LANG["INTRO_TEXT"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
315
+ output_message = gr.Markdown("---")
316
+
317
+ with gr.Row():
318
+ # Global Inputs
319
+ enqueteur_id.render()
320
+
321
+ with gr.Tabs():
322
+ # =================================================================
323
+ # TAB 1: Contact & Lieux Fréquentés
324
+ # =================================================================
325
+ with gr.Tab(LANG["TAB_1_TITLE"]):
326
+ gr.Markdown("### Phase d'approche")
327
+ with gr.Row():
328
+ approach_answer.render()
329
+ with gr.Column(visible=False) as refusal_block:
330
+ refusal_reason.render()
331
+ refusal_reason_other.render()
332
+ contact_later.render()
333
+ firstname.render()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
+ # Visibility updates for Approach
336
+ approach_answer.change(update_visibility_approach, inputs=[approach_answer], outputs=[refusal_reason, contact_later, firstname])
337
+ refusal_reason.change(update_visibility_refusal, inputs=[refusal_reason], outputs=[refusal_reason_other])
338
+
339
+ gr.Markdown("### Lieux et Communautés")
340
+ space1.render()
341
+ with gr.Row() as space2_row:
342
+ space2a_1.render()
343
+ space2a_2.render()
344
+ space2a_3.render()
345
+ with gr.Row() as space3_row:
346
+ space3_1.render()
347
+ space3_2.render()
348
+ space3_3.render()
349
+ space2b.render()
350
+ space1.change(update_visibility_space1, inputs=[space1], outputs=[space2a_1, space2a_2, space2a_3, space3_1, space3_2, space3_3, space2b])
351
+
352
+ gr.Markdown("### Usages alternatifs et Engagement")
353
+ space4.render()
354
+ space5.render()
355
+ space4.change(update_visibility_space4, inputs=[space4], outputs=[space5])
356
+ space6.render()
357
+ with gr.Row() as space7_row:
358
+ space7_1.render()
359
+ space7_2.render()
360
+ space7_3.render()
361
+ space6.change(update_visibility_space6, inputs=[space6], outputs=[space7_1, space7_2, space7_3])
362
+ space8.render()
363
+ space9.render()
364
+ space10.render()
365
+
366
+ # =================================================================
367
+ # TAB 2: Engagement Citoyen
368
+ # =================================================================
369
+ with gr.Tab(LANG["TAB_2_TITLE"]):
370
+ info1.render()
371
+ engagement_organisation.render()
372
+ engagement_domaine.render()
373
+ info_activites.render()
374
+ info_reseaux_sociaux.render()
375
+
376
+ # =================================================================
377
+ # TAB 3: Consommation d’actualités
378
+ # =================================================================
379
+ with gr.Tab(LANG["TAB_3_TITLE"]):
380
+ info_frequence_actu.render()
381
+ info2.render()
382
+ info3.render()
383
+ info_opposite_feeling.render()
384
+ info_contradict_opinion.render()
385
+ gr.Markdown("---")
386
+ info4.render()
387
+ info5.render()
388
+ gr.Markdown(f"### {LANG['INFO6_TITLE']}")
389
+ with gr.Column():
390
+ for slider in info6_slider_components:
391
+ slider.render()
392
+
393
+ # =================================================================
394
+ # TAB 4: Pratiques culturelles et usages numériques
395
+ # =================================================================
396
+ with gr.Tab(LANG["TAB_4_TITLE"]):
397
+ prat_cult1.render()
398
+ gr.Markdown(f"### {LANG['PRAT_CULT_PRACTICES_TITLE']}")
399
+ with gr.Row():
400
+ with gr.Column():
401
+ for i in range(5):
402
+ prat_cult_freq_components[i].render()
403
+ with gr.Column():
404
+ for i in range(5, 10):
405
+ prat_cult_freq_components[i].render()
406
+ prat_nature.render()
407
+ gr.Markdown(f"### {LANG['PLATFORM_TITLE']}")
408
+ with gr.Row():
409
+ with gr.Column():
410
+ for i in range(5):
411
+ platform_components[i].render()
412
+ with gr.Column():
413
+ for i in range(5, 10):
414
+ platform_components[i].render()
415
+ gr.Markdown(f"### {LANG['PURPOSE_TITLE']}")
416
+ with gr.Row():
417
+ with gr.Column():
418
+ for i in range(4):
419
+ purpose_components[i].render()
420
+ with gr.Column():
421
+ for i in range(4, 7):
422
+ purpose_components[i].render()
423
+ purpose_autre_detail.render()
424
+
425
+
426
+ # =================================================================
427
+ # TAB 5: Profil Démographique
428
+ # =================================================================
429
+ with gr.Tab(LANG["TAB_5_TITLE"]):
430
+ demo_gender.render()
431
+ demo_age.render()
432
+ with gr.Row():
433
+ demo_location_commune.render()
434
+ demo_location_arrond.render()
435
+ demo_parents_location.render()
436
+ with gr.Row():
437
+ demo_inscription.render()
438
+ demo_discipline.render()
439
+ with gr.Row():
440
+ demo_job.render()
441
+ demo_income.render()
442
+ gr.Markdown("### Capital Social")
443
+ with gr.Row():
444
+ demo_socialcapital1_parent1.render()
445
+ demo_socialcapital1_parent2.render()
446
+ demo_socialcapital2.render()
447
+
448
+ # =================================================================
449
+ # TAB 6: Questions ouvertes
450
+ # =================================================================
451
+ with gr.Tab(LANG["TAB_6_TITLE"]):
452
+ open_non_institutionnel.render()
453
+ open_alternatives.render()
454
+ open_motivations.render()
455
+
456
+
457
+ # Submit Button and Action
458
+ with gr.Row():
459
+ submit_btn = gr.Button(LANG["SUBMIT_BUTTON"], variant="primary")
460
+ reset_btn = gr.Button(LANG["RESET_BUTTON"])
461
+
462
+ submit_btn.click(
463
  fn=process_survey,
464
+ inputs=ALL_INPUT_COMPONENTS,
465
+ outputs=output_message
466
+ )
467
+
468
+ # Note: Reset functionality is standard Gradio.
469
+ reset_btn.click(
470
+ fn=lambda: [None] * len(ALL_INPUT_COMPONENTS[1:]),
471
+ inputs=[],
472
+ outputs=ALL_INPUT_COMPONENTS[1:],
473
+ # Reset the output message too
474
+ _js="() => { document.getElementById('output_message').innerHTML = '---'; }"
475
  )
476
 
477
+ if __name__ == "__main__":
478
+ demo.launch()
479
+