Woziii commited on
Commit
0d840ea
·
verified ·
1 Parent(s): 148c9cd

Upload 3 files

Browse files
Files changed (3) hide show
  1. interface_wizard.py +693 -0
  2. text_analyzer.py +187 -0
  3. wizard_style.css +222 -0
interface_wizard.py ADDED
@@ -0,0 +1,693 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Module définissant l'interface utilisateur Gradio sous forme d'assistant progressif (wizard).
3
+ Cette version permet une navigation par étapes avec aperçu en temps réel.
4
+ """
5
+ import gradio as gr
6
+ from utils import collect_author_info, ensure_default_supports
7
+ from text_analyzer import analyze_work_description, get_explanation
8
+ from config import (CONTRACT_TYPES, CESSION_MODES, ADDITIONAL_RIGHTS,
9
+ AUTHOR_TYPES, CIVILITY_OPTIONS, SUPPORTS_OPTIONS)
10
+ import time
11
+
12
+
13
+ def create_wizard_interface(generate_pdf_fn, preview_contract_fn):
14
+ """
15
+ Crée l'interface utilisateur Gradio avec navigation progressive par étapes.
16
+
17
+ Args:
18
+ generate_pdf_fn: Fonction pour générer le PDF
19
+ preview_contract_fn: Fonction pour prévisualiser le contrat
20
+
21
+ Returns:
22
+ gr.Blocks: L'interface Gradio configurée
23
+ """
24
+ # Définir le nombre total d'étapes
25
+ TOTAL_STEPS = 6
26
+
27
+ with gr.Blocks(title="Assistant de Contrats de Cession", css="style.css") as demo:
28
+ # Variables d'état pour stocker les données entre les étapes
29
+ current_step = gr.State(value=1)
30
+ contract_data = gr.State(value={
31
+ "type_contrat": [],
32
+ "type_cession": "Gratuite",
33
+ "droits_cedes": [],
34
+ "exclusivite": False,
35
+ "auteur_type": "Personne physique",
36
+ "auteur_info": {},
37
+ "description_oeuvre": "",
38
+ "description_image": "",
39
+ "supports": [],
40
+ "remuneration": ""
41
+ })
42
+
43
+ gr.Markdown("# Assistant de Création de Contrat de Cession")
44
+ gr.Markdown("Cet assistant vous guide pas à pas dans la création d'un contrat adapté à vos besoins spécifiques.")
45
+
46
+ # Layout principal en deux colonnes
47
+ with gr.Row():
48
+ # COLONNE GAUCHE - FORMULAIRE PROGRESSIF
49
+ with gr.Column(scale=3):
50
+ # Indicateur de progression
51
+ progress_bar = gr.Slider(
52
+ minimum=1,
53
+ maximum=TOTAL_STEPS,
54
+ value=1,
55
+ step=1,
56
+ interactive=False,
57
+ label="Progression"
58
+ )
59
+ progress_text = gr.Markdown("**Étape 1 sur 6**: Type d'œuvre")
60
+
61
+ # ===== ÉTAPE 1: DESCRIPTION ET TYPE DE CONTRAT =====
62
+ with gr.Group(visible=True) as step1_group:
63
+ gr.Markdown("## Décrivez votre projet")
64
+ gr.Markdown("""
65
+ Décrivez en quelques mots l'œuvre ou le contenu pour lequel vous souhaitez établir un contrat.
66
+ Exemples: "Une chanson que j'ai composée", "Des photos de mannequins", "Un logo pour une entreprise", etc.
67
+ """)
68
+
69
+ project_description = gr.Textbox(
70
+ label="Description de votre projet",
71
+ placeholder="Ex: Une vidéo où je me filme en train de jouer ma composition au piano",
72
+ lines=3
73
+ )
74
+
75
+ analyze_btn = gr.Button("Analyser mon projet", variant="secondary")
76
+
77
+ contract_type_suggestion = gr.Markdown(
78
+ value="Complétez la description et cliquez sur 'Analyser mon projet' pour obtenir une suggestion.",
79
+ elem_id="contract-suggestion"
80
+ )
81
+
82
+ gr.Markdown("### Type de contrat nécessaire")
83
+ contract_type = gr.CheckboxGroup(
84
+ CONTRACT_TYPES,
85
+ label="Sélectionnez le(s) type(s) de contrat",
86
+ value=[]
87
+ )
88
+
89
+ # ===== ÉTAPE 2: MODE DE CESSION ET DROITS =====
90
+ with gr.Group(visible=False) as step2_group:
91
+ gr.Markdown("## Mode de cession et droits")
92
+
93
+ gr.Markdown("### Mode de cession")
94
+ gr.Markdown("""
95
+ La cession peut se faire à titre gratuit ou onéreux (moyennant rémunération).
96
+ Une cession gratuite limite les droits cédés aux droits de base (reproduction et représentation).
97
+ """)
98
+
99
+ cession_mode = gr.Radio(
100
+ CESSION_MODES,
101
+ label="La cession se fait-elle à titre gratuit ou onéreux?",
102
+ value="Gratuite"
103
+ )
104
+
105
+ # Droits cédés (visible uniquement si onéreux)
106
+ with gr.Group(visible=False) as group_rights:
107
+ gr.Markdown("### Droits supplémentaires (cession onéreuse)")
108
+ gr.Markdown("""
109
+ Pour une cession onéreuse, vous pouvez céder des droits supplémentaires.
110
+ Les droits de reproduction et de représentation sont toujours inclus.
111
+ """)
112
+
113
+ additional_rights = gr.CheckboxGroup(
114
+ ADDITIONAL_RIGHTS,
115
+ label="Sélectionnez les droits supplémentaires à céder",
116
+ value=[]
117
+ )
118
+
119
+ gr.Markdown("### Exclusivité")
120
+ gr.Markdown("""
121
+ L'exclusivité signifie que le cédant ne pourra pas exploiter lui-même l'œuvre
122
+ ni céder les mêmes droits à d'autres personnes pendant la durée du contrat.
123
+ """)
124
+
125
+ exclusivity = gr.Checkbox(
126
+ label="Cession exclusive",
127
+ value=False,
128
+ info="Cochez cette case pour une cession exclusive"
129
+ )
130
+
131
+ # ===== ÉTAPE 3: INFORMATIONS SUR L'AUTEUR/MODÈLE =====
132
+ with gr.Group(visible=False) as step3_group:
133
+ gr.Markdown("## Informations sur l'auteur/modèle")
134
+
135
+ author_type = gr.Radio(
136
+ AUTHOR_TYPES,
137
+ label="L'auteur/modèle est:",
138
+ value="Personne physique"
139
+ )
140
+
141
+ # Personne physique
142
+ with gr.Group() as group_physical_person:
143
+ civility = gr.Radio(
144
+ CIVILITY_OPTIONS,
145
+ label="Civilité",
146
+ value="M."
147
+ )
148
+
149
+ with gr.Row():
150
+ last_name = gr.Textbox(
151
+ label="Nom",
152
+ placeholder="Nom de famille"
153
+ )
154
+ first_name = gr.Textbox(
155
+ label="Prénom",
156
+ placeholder="Prénom"
157
+ )
158
+
159
+ with gr.Row():
160
+ birth_date = gr.Textbox(
161
+ label="Date de naissance (facultatif)",
162
+ placeholder="JJ/MM/AAAA"
163
+ )
164
+ nationality = gr.Textbox(
165
+ label="Nationalité",
166
+ placeholder="Ex: française"
167
+ )
168
+
169
+ address = gr.Textbox(
170
+ label="Adresse complète",
171
+ placeholder="Numéro, rue, code postal, ville"
172
+ )
173
+
174
+ contact_physical = gr.Textbox(
175
+ label="Moyen de contact (email, téléphone)",
176
+ placeholder="Email et/ou téléphone"
177
+ )
178
+
179
+ # Personne morale
180
+ with gr.Group(visible=False) as group_legal_entity:
181
+ company_name = gr.Textbox(
182
+ label="Nom de la société",
183
+ placeholder="Dénomination sociale"
184
+ )
185
+
186
+ with gr.Row():
187
+ legal_status = gr.Textbox(
188
+ label="Statut juridique",
189
+ placeholder="Ex: SARL, SAS, EURL, etc."
190
+ )
191
+ rcs_number = gr.Textbox(
192
+ label="Numéro RCS",
193
+ placeholder="Ex: 123 456 789 R.C.S. Paris"
194
+ )
195
+
196
+ company_address = gr.Textbox(
197
+ label="Adresse du siège social",
198
+ placeholder="Adresse complète du siège"
199
+ )
200
+
201
+ contact_company = gr.Textbox(
202
+ label="Moyen de contact (email, téléphone)",
203
+ placeholder="Email et/ou téléphone"
204
+ )
205
+
206
+ # ===== ÉTAPE 4: DESCRIPTION DE L'ŒUVRE/IMAGE =====
207
+ with gr.Group(visible=False) as step4_group:
208
+ description_title = gr.Markdown("## Description détaillée")
209
+
210
+ # Description de l'œuvre (visible si contrat de droits d'auteur)
211
+ with gr.Group(visible=True) as group_work_description:
212
+ gr.Markdown("### Description de l'œuvre")
213
+ gr.Markdown("""
214
+ Décrivez précisément l'œuvre concernée par la cession de droits.
215
+ Cette description sera intégrée dans le contrat pour identifier sans ambiguïté l'objet de la cession.
216
+ """)
217
+
218
+ work_description = gr.Textbox(
219
+ label="Description de l'œuvre",
220
+ placeholder="Titre, format, dimensions, support, technique utilisée, date de création, etc.",
221
+ lines=5
222
+ )
223
+
224
+ # Description de l'image (visible si contrat de droits à l'image)
225
+ with gr.Group(visible=False) as group_image_description:
226
+ gr.Markdown("### Description des images")
227
+ gr.Markdown("""
228
+ Décrivez précisément les images ou vidéos concernées par la cession du droit à l'image.
229
+ Précisez le contexte, la date et le lieu de prise de vue, le nombre d'images concernées, etc.
230
+ """)
231
+
232
+ image_description = gr.Textbox(
233
+ label="Description des images/vidéos",
234
+ placeholder="Ex: Séance photo réalisée le [date] à [lieu], comprenant X photographies où apparaît [nom du modèle]",
235
+ lines=5
236
+ )
237
+
238
+ # ===== ÉTAPE 5: SUPPORTS D'EXPLOITATION =====
239
+ with gr.Group(visible=False) as step5_group:
240
+ gr.Markdown("## Supports d'exploitation")
241
+ gr.Markdown("""
242
+ Sélectionnez les supports sur lesquels l'œuvre et/ou l'image pourra être exploitée.
243
+ Le site web et Discord de Tellers sont automatiquement inclus.
244
+ """)
245
+
246
+ exploitation_supports = gr.CheckboxGroup(
247
+ SUPPORTS_OPTIONS,
248
+ label="Sur quels supports les droits seront-ils exploités?",
249
+ value=["Réseaux sociaux (Facebook, Instagram, Twitter, etc.)"]
250
+ )
251
+
252
+ # Rémunération (visible uniquement si onéreux)
253
+ with gr.Group(visible=False) as group_remuneration:
254
+ gr.Markdown("### Rémunération")
255
+ gr.Markdown("""
256
+ Précisez les modalités de rémunération pour cette cession onéreuse.
257
+ Cela peut être un montant forfaitaire ou proportionnel aux recettes.
258
+ """)
259
+
260
+ remuneration_details = gr.Textbox(
261
+ label="Modalités de rémunération",
262
+ placeholder="Ex: 500€ versés à la signature, 5% des recettes versés trimestriellement",
263
+ lines=3
264
+ )
265
+
266
+ # ===== ÉTAPE 6: VALIDATION ET GÉNÉRATION =====
267
+ with gr.Group(visible=False) as step6_group:
268
+ gr.Markdown("## Validation et génération du contrat")
269
+ gr.Markdown("""
270
+ Vous avez complété toutes les étapes nécessaires.
271
+ Vérifiez le contrat dans l'aperçu à droite, puis générez le PDF final.
272
+ """)
273
+
274
+ gr.Markdown("### Options de génération")
275
+ contract_name = gr.Textbox(
276
+ label="Nom du fichier PDF (optionnel)",
277
+ placeholder="Ex: Contrat_Cession_Dupont_2025",
278
+ value=""
279
+ )
280
+
281
+ # Indicateur de génération
282
+ with gr.Group(visible=False) as generation_status_group:
283
+ generation_status = gr.Markdown("Préparation du contrat en cours...")
284
+ generation_progress = gr.Slider(
285
+ minimum=0,
286
+ maximum=100,
287
+ value=0,
288
+ step=1,
289
+ interactive=False,
290
+ label="Progression"
291
+ )
292
+
293
+ # Boutons de navigation entre les étapes
294
+ with gr.Row():
295
+ back_button = gr.Button("Précédent", variant="secondary")
296
+ next_button = gr.Button("Suivant", variant="primary")
297
+
298
+ # Bouton de génération (visible uniquement à la dernière étape)
299
+ with gr.Row(visible=False) as generate_button_row:
300
+ generate_button = gr.Button("Générer le PDF", variant="primary", elem_id="generate-btn")
301
+
302
+ # COLONNE DROITE - PRÉVISUALISATION EN TEMPS RÉEL
303
+ with gr.Column(scale=2):
304
+ # En-tête de prévisualisation
305
+ preview_header = gr.Markdown("## Aperçu du contrat en temps réel")
306
+ preview_info = gr.Markdown(
307
+ "Au fur et à mesure que vous remplissez le formulaire, votre contrat se construit ici."
308
+ )
309
+
310
+ # Prévisualisation du contrat
311
+ contract_preview = gr.Markdown(
312
+ value="*Commencez à remplir le formulaire pour voir l'aperçu du contrat*",
313
+ elem_id="contract-preview"
314
+ )
315
+
316
+ # Zone de téléchargement (visible uniquement après génération)
317
+ with gr.Group(visible=False) as download_group:
318
+ gr.Markdown("### Téléchargement")
319
+ pdf_output = gr.File(label="Votre contrat est prêt!")
320
+
321
+ # ===== FONCTIONS DE NAVIGATION ET MISE À JOUR =====
322
+
323
+ # Fonction pour mettre à jour l'indicateur de progression
324
+ def update_progress(step):
325
+ progress_text_value = f"**Étape {step} sur {TOTAL_STEPS}**: "
326
+
327
+ if step == 1:
328
+ progress_text_value += "Type d'œuvre"
329
+ elif step == 2:
330
+ progress_text_value += "Mode de cession et droits"
331
+ elif step == 3:
332
+ progress_text_value += "Informations sur l'auteur/modèle"
333
+ elif step == 4:
334
+ progress_text_value += "Description détaillée"
335
+ elif step == 5:
336
+ progress_text_value += "Supports d'exploitation"
337
+ elif step == 6:
338
+ progress_text_value += "Validation et génération"
339
+
340
+ return step, progress_text_value
341
+
342
+ # Fonction pour analyser la description et suggérer le type de contrat
343
+ def analyze_project(description):
344
+ """Analyse la description et suggère le type de contrat approprié."""
345
+ if not description.strip():
346
+ return "Veuillez fournir une description pour obtenir une suggestion.", []
347
+
348
+ detected_types = analyze_work_description(description)
349
+ explanation = get_explanation(detected_types)
350
+
351
+ return explanation, detected_types
352
+
353
+ # Associer le bouton d'analyse à la fonction
354
+ analyze_btn.click(
355
+ fn=analyze_project,
356
+ inputs=[project_description],
357
+ outputs=[contract_type_suggestion, contract_type]
358
+ )
359
+
360
+ # Fonction pour naviguer à l'étape suivante
361
+ def next_step(current, data,
362
+ # Étape 1
363
+ project_desc, contract_types,
364
+ # Étape 2
365
+ cession_type, rights, is_exclusive,
366
+ # Étape 3
367
+ author_type_val, civility_val, last_name_val, first_name_val, birth_date_val,
368
+ nationality_val, address_val, contact_physical_val, company_name_val,
369
+ legal_status_val, rcs_val, company_address_val, contact_company_val,
370
+ # Étape 4
371
+ work_desc, image_desc,
372
+ # Étape 5
373
+ supports_val, remuneration_val):
374
+ """Passe à l'étape suivante et met à jour les données du contrat."""
375
+
376
+ # Mettre à jour les données en fonction de l'étape actuelle
377
+ if current == 1:
378
+ data["project_description"] = project_desc
379
+ data["type_contrat"] = contract_types
380
+ elif current == 2:
381
+ data["type_cession"] = cession_type
382
+ data["droits_cedes"] = rights if rights else []
383
+ data["exclusivite"] = is_exclusive
384
+ elif current == 3:
385
+ data["auteur_type"] = author_type_val
386
+
387
+ # Recueillir les informations sur l'auteur en fonction du type
388
+ if author_type_val == "Personne physique":
389
+ author_info = {
390
+ "gentille": civility_val,
391
+ "nom": last_name_val,
392
+ "prenom": first_name_val,
393
+ "date_naissance": birth_date_val,
394
+ "nationalite": nationality_val,
395
+ "adresse": address_val,
396
+ "contact": contact_physical_val
397
+ }
398
+ else:
399
+ author_info = {
400
+ "nom_societe": company_name_val,
401
+ "statut": legal_status_val,
402
+ "rcs": rcs_val,
403
+ "siege": company_address_val,
404
+ "contact": contact_company_val
405
+ }
406
+
407
+ data["auteur_info"] = author_info
408
+ elif current == 4:
409
+ data["description_oeuvre"] = work_desc
410
+ data["description_image"] = image_desc
411
+ elif current == 5:
412
+ data["supports"] = supports_val
413
+ data["remuneration"] = remuneration_val
414
+
415
+ # Si c'est la dernière étape, ne pas avancer
416
+ if current >= TOTAL_STEPS:
417
+ current = TOTAL_STEPS
418
+ else:
419
+ current += 1
420
+
421
+ # Visibilité des groupes en fonction de la nouvelle étape
422
+ step1_visibility = (current == 1)
423
+ step2_visibility = (current == 2)
424
+ step3_visibility = (current == 3)
425
+ step4_visibility = (current == 4)
426
+ step5_visibility = (current == 5)
427
+ step6_visibility = (current == 6)
428
+
429
+ # Visibilité conditionnelle des droits supplémentaires et rémunération
430
+ rights_visibility = (current == 2 and cession_type == "Onéreuse")
431
+ remuneration_visibility = (current == 5 and data["type_cession"] == "Onéreuse")
432
+
433
+ # Visibilité des champs de description en fonction du type de contrat
434
+ show_work_desc = True
435
+ show_image_desc = False
436
+
437
+ if current == 4:
438
+ show_work_desc = "Auteur (droits d'auteur)" in data["type_contrat"]
439
+ show_image_desc = "Image (droit à l'image)" in data["type_contrat"]
440
+
441
+ # Visibilité du type de personne
442
+ show_physical_person = (current == 3 and author_type_val == "Personne physique")
443
+ show_legal_entity = (current == 3 and author_type_val == "Personne morale")
444
+
445
+ # Visibilité du bouton de génération (uniquement à la dernière étape)
446
+ show_generate_button = (current == TOTAL_STEPS)
447
+
448
+ # Mettre à jour l'aperçu du contrat
449
+ preview = preview_contract(data)
450
+
451
+ # Mettre à jour la progression
452
+ new_progress, progress_text_val = update_progress(current)
453
+
454
+ return (
455
+ # État mis à jour
456
+ current, data,
457
+ # Progression
458
+ new_progress, progress_text_val,
459
+ # Visibilité des étapes
460
+ gr.update(visible=step1_visibility), gr.update(visible=step2_visibility),
461
+ gr.update(visible=step3_visibility), gr.update(visible=step4_visibility),
462
+ gr.update(visible=step5_visibility), gr.update(visible=step6_visibility),
463
+ # Visibilité conditionnelle
464
+ gr.update(visible=rights_visibility), gr.update(visible=remuneration_visibility),
465
+ gr.update(visible=show_work_desc), gr.update(visible=show_image_desc),
466
+ gr.update(visible=show_physical_person), gr.update(visible=show_legal_entity),
467
+ gr.update(visible=show_generate_button),
468
+ # Aperçu du contrat
469
+ preview
470
+ )
471
+
472
+ # Fonction pour naviguer à l'étape précédente
473
+ def previous_step(current, data):
474
+ """Revient à l'étape précédente."""
475
+
476
+ if current <= 1:
477
+ current = 1
478
+ else:
479
+ current -= 1
480
+
481
+ # Visibilité des groupes en fonction de la nouvelle étape
482
+ step1_visibility = (current == 1)
483
+ step2_visibility = (current == 2)
484
+ step3_visibility = (current == 3)
485
+ step4_visibility = (current == 4)
486
+ step5_visibility = (current == 5)
487
+ step6_visibility = (current == 6)
488
+
489
+ # Visibilité conditionnelle des droits supplémentaires et rémunération
490
+ rights_visibility = (current == 2 and data["type_cession"] == "Onéreuse")
491
+ remuneration_visibility = (current == 5 and data["type_cession"] == "Onéreuse")
492
+
493
+ # Visibilité des champs de description en fonction du type de contrat
494
+ show_work_desc = True
495
+ show_image_desc = False
496
+
497
+ if current == 4:
498
+ show_work_desc = "Auteur (droits d'auteur)" in data["type_contrat"]
499
+ show_image_desc = "Image (droit à l'image)" in data["type_contrat"]
500
+
501
+ # Visibilité du type de personne
502
+ show_physical_person = (current == 3 and data["auteur_type"] == "Personne physique")
503
+ show_legal_entity = (current == 3 and data["auteur_type"] == "Personne morale")
504
+
505
+ # Visibilité du bouton de génération (uniquement à la dernière étape)
506
+ show_generate_button = (current == TOTAL_STEPS)
507
+
508
+ # Mettre à jour l'aperçu du contrat
509
+ preview = preview_contract(data)
510
+
511
+ # Mettre à jour la progression
512
+ new_progress, progress_text_val = update_progress(current)
513
+
514
+ return (
515
+ # État mis à jour
516
+ current, data,
517
+ # Progression
518
+ new_progress, progress_text_val,
519
+ # Visibilité des étapes
520
+ gr.update(visible=step1_visibility), gr.update(visible=step2_visibility),
521
+ gr.update(visible=step3_visibility), gr.update(visible=step4_visibility),
522
+ gr.update(visible=step5_visibility), gr.update(visible=step6_visibility),
523
+ # Visibilité conditionnelle
524
+ gr.update(visible=rights_visibility), gr.update(visible=remuneration_visibility),
525
+ gr.update(visible=show_work_desc), gr.update(visible=show_image_desc),
526
+ gr.update(visible=show_physical_person), gr.update(visible=show_legal_entity),
527
+ gr.update(visible=show_generate_button),
528
+ # Aperçu du contrat
529
+ preview
530
+ )
531
+
532
+ # Fonction pour mettre à jour l'affichage en fonction du mode de cession
533
+ def update_cession_mode_display(mode):
534
+ """Met à jour l'affichage des champs liés au mode de cession."""
535
+ is_onereux = (mode == "Onéreuse")
536
+ return gr.update(visible=is_onereux)
537
+
538
+ # Fonction pour mettre à jour l'affichage en fonction du type d'auteur
539
+ def update_author_type_display(type_val):
540
+ """Met à jour l'affichage des champs liés au type d'auteur."""
541
+ is_physical = (type_val == "Personne physique")
542
+ return gr.update(visible=is_physical), gr.update(visible=not is_physical)
543
+
544
+ # Fonction pour générer le PDF
545
+ def generate_pdf(contract_data, filename):
546
+ """Génère le PDF du contrat avec indication de progression."""
547
+
548
+ # Mise à jour de l'interface pour indiquer le début de la génération
549
+ yield gr.update(visible=True), gr.update(value="Préparation des données..."), 0, gr.update(visible=False), None
550
+ time.sleep(0.5)
551
+
552
+ # Étape 1: Préparation du contrat (25%)
553
+ yield gr.update(visible=True), gr.update(value="Construction du contrat..."), 25, gr.update(visible=False), None
554
+ time.sleep(0.5)
555
+
556
+ # Étape 2: Mise en forme (50%)
557
+ yield gr.update(visible=True), gr.update(value="Mise en forme du document..."), 50, gr.update(visible=False), None
558
+ time.sleep(0.5)
559
+
560
+ # Étape 3: Génération du PDF (75%)
561
+ yield gr.update(visible=True), gr.update(value="Génération du PDF..."), 75, gr.update(visible=False), None
562
+
563
+ # Appel à la fonction de génération réelle
564
+ pdf_path = generate_pdf_fn(
565
+ contract_data["type_contrat"],
566
+ contract_data["type_cession"],
567
+ contract_data["auteur_type"],
568
+ contract_data["auteur_info"],
569
+ contract_data["description_oeuvre"],
570
+ contract_data["description_image"],
571
+ contract_data["supports"],
572
+ contract_data["droits_cedes"],
573
+ contract_data["remuneration"],
574
+ contract_data["exclusivite"]
575
+ )
576
+
577
+ # Finalisation (100%)
578
+ yield gr.update(visible=True), gr.update(value="Contrat PDF généré avec succès!"), 100, gr.update(visible=True), pdf_path
579
+
580
+ # Fonction simplifiée pour prévisualiser le contrat
581
+ def preview_contract(data):
582
+ """Génère un aperçu HTML formaté du contrat."""
583
+
584
+ # Vérifier qu'il y a suffisamment de données pour prévisualiser
585
+ if not data.get("type_contrat"):
586
+ return "*Complétez au moins le type de contrat pour voir l'aperçu*"
587
+
588
+ # Appeler la fonction de prévisualisation
589
+ try:
590
+ preview_text = preview_contract_fn(
591
+ data.get("type_contrat", []),
592
+ data.get("type_cession", "Gratuite"),
593
+ data.get("auteur_type", "Personne physique"),
594
+ data.get("auteur_info", {}),
595
+ data.get("description_oeuvre", ""),
596
+ data.get("description_image", ""),
597
+ data.get("supports", []),
598
+ data.get("droits_cedes", []),
599
+ data.get("remuneration", ""),
600
+ data.get("exclusivite", False)
601
+ )
602
+
603
+ # Conversion en HTML avec mise en évidence des données utilisateur
604
+ preview_html = preview_text.replace("\n", "<br>")
605
+
606
+ # Mettre en évidence les titres
607
+ for ligne in preview_text.split("\n"):
608
+ if ligne.strip().startswith("ARTICLE") or ligne.strip().isupper():
609
+ preview_html = preview_html.replace(ligne, f"<h3>{ligne}</h3>")
610
+
611
+ return preview_html
612
+ except Exception as e:
613
+ return f"*Erreur de prévisualisation: {str(e)}*"
614
+
615
+ # ===== ÉVÉNEMENTS =====
616
+
617
+ # Navigation entre les étapes
618
+ next_button.click(
619
+ fn=next_step,
620
+ inputs=[
621
+ current_step, contract_data,
622
+ # Étape 1
623
+ project_description, contract_type,
624
+ # Étape 2
625
+ cession_mode, additional_rights, exclusivity,
626
+ # Étape 3
627
+ author_type, civility, last_name, first_name, birth_date,
628
+ nationality, address, contact_physical, company_name,
629
+ legal_status, rcs_number, company_address, contact_company,
630
+ # Étape 4
631
+ work_description, image_description,
632
+ # Étape 5
633
+ exploitation_supports, remuneration_details
634
+ ],
635
+ outputs=[
636
+ current_step, contract_data,
637
+ # Progression
638
+ progress_bar, progress_text,
639
+ # Visibilité des étapes
640
+ step1_group, step2_group, step3_group, step4_group, step5_group, step6_group,
641
+ # Visibilité conditionnelle
642
+ group_rights, group_remuneration,
643
+ group_work_description, group_image_description,
644
+ group_physical_person, group_legal_entity,
645
+ generate_button_row,
646
+ # Aperçu du contrat
647
+ contract_preview
648
+ ]
649
+ )
650
+
651
+ back_button.click(
652
+ fn=previous_step,
653
+ inputs=[current_step, contract_data],
654
+ outputs=[
655
+ current_step, contract_data,
656
+ # Progression
657
+ progress_bar, progress_text,
658
+ # Visibilité des étapes
659
+ step1_group, step2_group, step3_group, step4_group, step5_group, step6_group,
660
+ # Visibilité conditionnelle
661
+ group_rights, group_remuneration,
662
+ group_work_description, group_image_description,
663
+ group_physical_person, group_legal_entity,
664
+ generate_button_row,
665
+ # Aperçu du contrat
666
+ contract_preview
667
+ ]
668
+ )
669
+
670
+ # Mise à jour des affichages conditionnels
671
+ cession_mode.change(
672
+ fn=update_cession_mode_display,
673
+ inputs=[cession_mode],
674
+ outputs=[group_rights]
675
+ )
676
+
677
+ author_type.change(
678
+ fn=update_author_type_display,
679
+ inputs=[author_type],
680
+ outputs=[group_physical_person, group_legal_entity]
681
+ )
682
+
683
+ # Génération du PDF
684
+ generate_button.click(
685
+ fn=generate_pdf,
686
+ inputs=[contract_data, contract_name],
687
+ outputs=[
688
+ generation_status_group, generation_status,
689
+ generation_progress, download_group, pdf_output
690
+ ]
691
+ )
692
+
693
+ return demo
text_analyzer.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Module d'analyse de texte pour détecter automatiquement le type de contrat nécessaire.
3
+ """
4
+
5
+ def analyze_work_description(description):
6
+ """
7
+ Analyse la description fournie par l'utilisateur pour déterminer le type de contrat approprié.
8
+
9
+ Args:
10
+ description (str): Description fournie par l'utilisateur
11
+
12
+ Returns:
13
+ list: Liste des types de contrats nécessaires ["Auteur (droits d'auteur)", "Image (droit à l'image)"]
14
+ """
15
+ # Normaliser le texte pour l'analyse
16
+ texte = description.lower()
17
+
18
+ # Initialiser les scores
19
+ score_auteur = 0
20
+ score_image = 0
21
+
22
+ # DROITS D'AUTEUR - Mots-clés par catégorie
23
+ mots_textuels = ["livre", "roman", "nouvelle", "poème", "article", "essai", "mémoire", "thèse", "scénario",
24
+ "manuscrit", "rédaction", "texte", "écriture", "publication", "écrit", "éditorial", "blog",
25
+ "ouvrage", "brochure", "journal", "magazine", "recueil", "rédactionnel"]
26
+
27
+ mots_musicaux = ["musique", "composition", "partition", "chanson", "mélodie", "arrangement", "œuvre musicale",
28
+ "symphonie", "album", "single", "son", "enregistrement", "morceau", "opus", "instrumental",
29
+ "concert", "orchestration", "chant", "lyrics", "paroles", "refrain", "harmonie", "acoustique",
30
+ "piano", "guitare", "violon", "batterie", "chanter", "interprétation musicale"]
31
+
32
+ mots_graphiques = ["peinture", "dessin", "illustration", "graphisme", "logo", "infographie", "design",
33
+ "tableau", "esquisse", "croquis", "fresque", "œuvre plastique", "affiche", "aquarelle",
34
+ "sculpture", "gravure", "sérigraphie", "street art", "graffiti", "photographie artistique",
35
+ "collage", "estampe", "lithographie", "pochette", "couverture"]
36
+
37
+ mots_numeriques = ["logiciel", "site web", "application", "code", "programme", "script", "algorithme",
38
+ "jeu vidéo", "développement", "appli", "interface", "plateforme", "solution digitale",
39
+ "nft", "token", "crypto", "blockchain", "web3", "intelligence artificielle", "ia",
40
+ "données", "base de données", "système", "api", "plugin", "extension", "widget"]
41
+
42
+ mots_architecturaux = ["plan", "maquette", "conception architecturale", "design d'espace", "modélisation 3D",
43
+ "architecture", "structure", "bâtiment", "édifice", "monument", "urbanisme",
44
+ "aménagement", "paysagisme", "intérieur", "déco", "décoration"]
45
+
46
+ mots_autres_auteur = ["concept", "idée", "invention", "savoir-faire", "méthode", "formule", "base de données",
47
+ "compilation", "collection", "œuvre", "création", "synthèse", "travail", "production",
48
+ "droit d'auteur", "propriété intellectuelle", "copyright", "brevet"]
49
+
50
+ # DROITS À L'IMAGE - Mots-clés par catégorie
51
+ mots_image_personne = ["photographie", "portrait", "silhouette", "apparence", "visage", "corps", "identité visuelle",
52
+ "selfie", "avatar", "photo d'identité", "figure", "physionomie", "traits", "mannequin",
53
+ "modèle photo", "acteur", "actrice", "comédien", "comédienne", "figurant", "témoignage vidéo"]
54
+
55
+ mots_image_contexte = ["pose", "séance photo", "shooting", "mannequin", "modèle", "figurant", "mise en scène",
56
+ "studio photo", "apparition", "photoshoot", "objectif", "capturer", "appareil photo",
57
+ "caméra", "filmer", "enregistrer", "photographier", "filmer", "enregistrer"]
58
+
59
+ mots_image_visibilité = ["identifiable", "reconnaissable", "apparaître", "figurer", "posant", "visible",
60
+ "présent", "participation", "exposé", "exhibé", "montré", "droit à l'image",
61
+ "consentement", "image", "vidéo"]
62
+
63
+ mots_image_supports = ["photo", "image", "pellicule", "cliché", "instantané", "polaroid", "négatif",
64
+ "diapositive", "tirage", "impression photographique", "portrait", "photomaton"]
65
+
66
+ # COMBINAISON DROITS D'AUTEUR + DROITS À L'IMAGE
67
+ mots_videos = ["vidéo", "film", "court-métrage", "clip", "documentaire", "reportage", "captation", "tournage",
68
+ "filmé", "filmage", "enregistrement vidéo", "séquence", "rushes", "montage vidéo", "movie",
69
+ "cinéma", "réalisation", "vidéaste", "youtubeur", "youtubeuse", "influenceur", "influenceuse",
70
+ "vlog", "tiktok", "instagram", "content creator", "créateur de contenu"]
71
+
72
+ mots_performances = ["performance", "spectacle", "concert", "prestation", "apparition", "interprétation",
73
+ "récital", "show", "émission", "interview", "représentation", "apparition publique",
74
+ "scène", "plateaux", "théâtre", "danse", "chorégraphie", "ballet", "opéra", "music-hall",
75
+ "one man show", "stand-up", "humoriste", "conférence", "conférencier"]
76
+
77
+ mots_digital_personnel = ["stream", "livestream", "webinaire", "podcast vidéo", "tutoriel vidéo", "cours filmé",
78
+ "formation vidéo", "diffusion en direct", "chaîne youtube", "vlog", "direct",
79
+ "twitch", "live", "enregistrement zoom", "vidéoconférence", "stories", "reels"]
80
+
81
+ mots_œuvres_mixtes = ["œuvre audiovisuelle", "multimédia", "installation artistique interactive",
82
+ "réalité virtuelle", "réalité augmentée", "performance audiovisuelle", "mapping vidéo",
83
+ "projection", "hologramme", "expo interactive", "jeu immersif"]
84
+
85
+ # Vérifier les correspondances pour les droits d'auteur
86
+ for mot in mots_textuels + mots_musicaux + mots_graphiques + mots_numeriques + mots_architecturaux + mots_autres_auteur:
87
+ if mot in texte or any(m in texte for m in mot.split()):
88
+ score_auteur += 1
89
+
90
+ # Vérifier les correspondances pour les droits à l'image
91
+ for mot in mots_image_personne + mots_image_contexte + mots_image_visibilité + mots_image_supports:
92
+ if mot in texte or any(m in texte for m in mot.split()):
93
+ score_image += 1
94
+
95
+ # Vérifier les correspondances pour la combinaison (augmente les deux scores)
96
+ for mot in mots_videos + mots_performances + mots_digital_personnel + mots_œuvres_mixtes:
97
+ if mot in texte or any(m in texte for m in mot.split()):
98
+ score_auteur += 1
99
+ score_image += 1
100
+
101
+ # Cas spéciaux nécessitant une analyse plus contextuelle
102
+ if "chant" in texte or "chante" in texte or "chanson" in texte:
103
+ score_auteur += 1
104
+ if any(mot in texte for mot in ["vidéo", "film", "enregistrement vidéo", "youtube", "clip"]):
105
+ score_image += 1
106
+
107
+ if "exposit" in texte: # exposition/expositions
108
+ score_auteur += 1
109
+ if any(mot in texte for mot in ["photo", "portrait", "modèle", "personne"]):
110
+ score_image += 1
111
+
112
+ # Analyser les combinaisons courantes
113
+ if "voix" in texte:
114
+ score_auteur += 1
115
+ if "visage" in texte or "image" in texte or "vidéo" in texte:
116
+ score_image += 1
117
+
118
+ # Déterminer le type de contrat
119
+ types_contrat = []
120
+ if score_auteur > 0:
121
+ types_contrat.append("Auteur (droits d'auteur)")
122
+ if score_image > 0:
123
+ types_contrat.append("Image (droit à l'image)")
124
+
125
+ # Si aucun type détecté, suggérer les deux par sécurité
126
+ if not types_contrat:
127
+ return ["Auteur (droits d'auteur)", "Image (droit à l'image)"]
128
+
129
+ return types_contrat
130
+
131
+
132
+ def get_explanation(detected_types):
133
+ """
134
+ Génère une explication claire des types de contrats détectés.
135
+
136
+ Args:
137
+ detected_types (list): Types de contrats détectés
138
+
139
+ Returns:
140
+ str: Explication pour l'utilisateur
141
+ """
142
+ if len(detected_types) == 2:
143
+ return """
144
+ J'ai détecté que vous avez besoin d'un **contrat combiné de cession de droits d'auteur et de droits à l'image**.
145
+
146
+ Ce type de contrat est adapté lorsque:
147
+ - L'œuvre est protégée par le droit d'auteur (texte, musique, design, etc.)
148
+ - ET l'image d'une personne est visible et exploitée (vidéo, photo, etc.)
149
+
150
+ Vous pouvez modifier cette sélection si nécessaire.
151
+ """
152
+
153
+ elif "Auteur (droits d'auteur)" in detected_types:
154
+ return """
155
+ J'ai détecté que vous avez besoin d'un **contrat de cession de droits d'auteur** uniquement.
156
+
157
+ Ce type de contrat est adapté pour les œuvres protégées comme:
158
+ - Textes, livres, articles
159
+ - Musiques, compositions
160
+ - Dessins, peintures, designs
161
+ - Logiciels, sites web
162
+ - Et autres créations originales
163
+
164
+ Si votre projet implique également l'image reconnaissable d'une personne, vous pourriez aussi avoir besoin d'un contrat de droits à l'image.
165
+ """
166
+
167
+ elif "Image (droit à l'image)" in detected_types:
168
+ return """
169
+ J'ai détecté que vous avez besoin d'un **contrat de cession de droits à l'image** uniquement.
170
+
171
+ Ce type de contrat est adapté lorsque:
172
+ - L'image ou la vidéo d'une personne est utilisée
173
+ - La personne est identifiable ou reconnaissable
174
+ - Il n'y a pas d'œuvre originale protégée par le droit d'auteur
175
+
176
+ Si votre projet implique également une œuvre originale, vous pourriez aussi avoir besoin d'un contrat de cession de droits d'auteur.
177
+ """
178
+
179
+ else:
180
+ return """
181
+ Je n'ai pas pu déterminer automatiquement le type de contrat nécessaire.
182
+
183
+ Veuillez sélectionner manuellement:
184
+ - Contrat de droits d'auteur: pour les œuvres originales (textes, musiques, designs, etc.)
185
+ - Contrat de droits à l'image: pour l'utilisation de l'image d'une personne
186
+ - Les deux: si les deux aspects sont présents
187
+ """
wizard_style.css ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Style personnalisé pour l'interface du générateur de contrats en mode wizard */
2
+
3
+ /* Style global */
4
+ body {
5
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
6
+ }
7
+
8
+ /* Style pour le conteneur principal */
9
+ .gradio-container {
10
+ max-width: 1400px;
11
+ margin: 0 auto;
12
+ }
13
+
14
+ /* Style pour la barre de progression */
15
+ .progress-container {
16
+ margin-bottom: 20px;
17
+ }
18
+
19
+ /* Style pour l'aperçu du contrat */
20
+ #contract-preview {
21
+ max-height: 700px;
22
+ overflow-y: auto;
23
+ padding: 25px;
24
+ border: 1px solid #ddd;
25
+ border-radius: 8px;
26
+ background-color: #f9f9f9;
27
+ font-family: 'Times New Roman', Times, serif;
28
+ line-height: 1.6;
29
+ font-size: 14px;
30
+ margin-top: 10px;
31
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
32
+ }
33
+
34
+ /* Style pour la suggestion de type de contrat */
35
+ #contract-suggestion {
36
+ padding: 15px;
37
+ border-left: 4px solid #3b82f6;
38
+ background-color: #f0f7ff;
39
+ margin: 15px 0;
40
+ border-radius: 0 4px 4px 0;
41
+ }
42
+
43
+ /* Style pour les titres dans la prévisualisation */
44
+ #contract-preview h3 {
45
+ color: #222;
46
+ font-weight: bold;
47
+ margin-top: 15px;
48
+ margin-bottom: 5px;
49
+ border-bottom: 1px solid #ccc;
50
+ padding-bottom: 5px;
51
+ }
52
+
53
+ /* Style pour mettre en évidence les saisies utilisateur */
54
+ #contract-preview strong {
55
+ color: #0056b3;
56
+ font-weight: bold;
57
+ }
58
+
59
+ /* Style pour le bouton de génération */
60
+ #generate-btn {
61
+ background-color: #2563eb;
62
+ color: white;
63
+ font-weight: bold;
64
+ padding: 12px 24px;
65
+ border-radius: 5px;
66
+ transition: all 0.3s;
67
+ font-size: 16px;
68
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
69
+ }
70
+
71
+ #generate-btn:hover {
72
+ background-color: #1d4ed8;
73
+ box-shadow: 0 4px 8px rgba(0,0,0,0.15);
74
+ transform: translateY(-2px);
75
+ }
76
+
77
+ /* Style pour les boutons de navigation */
78
+ .navigation-buttons {
79
+ margin-top: 20px;
80
+ display: flex;
81
+ justify-content: space-between;
82
+ }
83
+
84
+ /* Style pour les titres de section */
85
+ .gradio-container h2 {
86
+ color: #1e40af;
87
+ border-bottom: 2px solid #e5e7eb;
88
+ padding-bottom: 10px;
89
+ margin-top: 30px;
90
+ margin-bottom: 20px;
91
+ font-size: 24px;
92
+ }
93
+
94
+ .gradio-container h3 {
95
+ color: #333;
96
+ margin-top: 20px;
97
+ font-size: 18px;
98
+ }
99
+
100
+ /* Style pour les groupes */
101
+ .gradio-container .gr-group {
102
+ border: 1px solid #e5e7eb;
103
+ border-radius: 8px;
104
+ padding: 15px;
105
+ margin-bottom: 20px;
106
+ background-color: #fff;
107
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
108
+ }
109
+
110
+ /* Style pour les labels des champs */
111
+ .gradio-container label {
112
+ font-weight: 600;
113
+ color: #4b5563;
114
+ margin-bottom: 5px;
115
+ }
116
+
117
+ /* Style pour les champs du formulaire */
118
+ .gradio-container input[type="text"],
119
+ .gradio-container textarea {
120
+ border: 1px solid #d1d5db;
121
+ border-radius: 6px;
122
+ padding: 10px 14px;
123
+ transition: all 0.3s;
124
+ box-shadow: 0 1px 2px rgba(0,0,0,0.05);
125
+ }
126
+
127
+ .gradio-container input[type="text"]:focus,
128
+ .gradio-container textarea:focus {
129
+ border-color: #3b82f6;
130
+ outline: none;
131
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
132
+ }
133
+
134
+ /* Style pour les informations/descriptions */
135
+ .gradio-container .info {
136
+ color: #6b7280;
137
+ font-size: 14px;
138
+ margin-top: 4px;
139
+ }
140
+
141
+ /* Style pour les étapes du wizard */
142
+ .step-number {
143
+ display: inline-block;
144
+ width: 30px;
145
+ height: 30px;
146
+ background-color: #3b82f6;
147
+ color: white;
148
+ border-radius: 50%;
149
+ text-align: center;
150
+ line-height: 30px;
151
+ margin-right: 10px;
152
+ font-weight: bold;
153
+ }
154
+
155
+ /* Style pour les informations d'aide */
156
+ .help-text {
157
+ background-color: #f3f4f6;
158
+ padding: 12px;
159
+ border-radius: 6px;
160
+ margin-bottom: 15px;
161
+ border-left: 4px solid #9ca3af;
162
+ }
163
+
164
+ /* Style pour l'indicateur de progression */
165
+ .progress-indicator {
166
+ display: flex;
167
+ justify-content: space-between;
168
+ align-items: center;
169
+ margin-bottom: 30px;
170
+ }
171
+
172
+ .progress-step {
173
+ flex: 1;
174
+ text-align: center;
175
+ position: relative;
176
+ }
177
+
178
+ .progress-step:not(:last-child):after {
179
+ content: '';
180
+ position: absolute;
181
+ top: 50%;
182
+ right: 0;
183
+ width: 100%;
184
+ height: 2px;
185
+ background-color: #e5e7eb;
186
+ z-index: 1;
187
+ }
188
+
189
+ .progress-step.active:not(:last-child):after,
190
+ .progress-step.completed:not(:last-child):after {
191
+ background-color: #3b82f6;
192
+ }
193
+
194
+ .progress-step-circle {
195
+ width: 24px;
196
+ height: 24px;
197
+ border-radius: 50%;
198
+ background-color: #e5e7eb;
199
+ display: inline-flex;
200
+ justify-content: center;
201
+ align-items: center;
202
+ position: relative;
203
+ z-index: 2;
204
+ }
205
+
206
+ .progress-step.active .progress-step-circle,
207
+ .progress-step.completed .progress-step-circle {
208
+ background-color: #3b82f6;
209
+ color: white;
210
+ }
211
+
212
+ .progress-step-text {
213
+ font-size: 12px;
214
+ margin-top: 8px;
215
+ color: #6b7280;
216
+ }
217
+
218
+ .progress-step.active .progress-step-text,
219
+ .progress-step.completed .progress-step-text {
220
+ color: #1e40af;
221
+ font-weight: 600;
222
+ }