ramedde commited on
Commit
b58170d
·
verified ·
1 Parent(s): 8dc3f9f

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1150
app.py DELETED
@@ -1,1150 +0,0 @@
1
- """
2
- Cours de Droit – Hugging Face Space
3
- llama-server (pre-built binary) via OpenAI-compatible HTTP API.
4
- """
5
- import os, json, time, tempfile, random, threading, sys
6
- import requests
7
-
8
- import gradio as gr
9
- from gtts import gTTS
10
-
11
- # ── llama-server connection ──────────────────────────────────────────────────
12
- LLAMA_URL = os.getenv("LLAMA_SERVER_URL", "http://127.0.0.1:8080")
13
- CHAT_ENDPOINT = f"{LLAMA_URL}/v1/chat/completions"
14
- _server_ok = False
15
- _server_lock = threading.Lock()
16
-
17
- def _check_server():
18
- """Background thread: poll until llama-server is ready."""
19
- global _server_ok
20
- print("[law-app] Waiting for llama-server...")
21
- for _ in range(60): # up to 120 s
22
- try:
23
- r = requests.get(f"{LLAMA_URL}/health", timeout=2)
24
- if r.status_code == 200:
25
- _server_ok = True
26
- print("[law-app] llama-server ready ✓")
27
- return
28
- except Exception:
29
- pass
30
- time.sleep(2)
31
- print("[law-app] llama-server not reachable – keyword fallback active")
32
-
33
- threading.Thread(target=_check_server, daemon=True).start()
34
-
35
- # ── Leçons ───────────────────────────────────────────────────────────────────
36
- LESSONS = [
37
- {
38
- "title": "Leçon 1 — Les principes directeurs et la preuve pénale",
39
- "text": """
40
- Cours de procédure pénale. Les principes du procès pénal et la preuve.
41
- Première partie : Le droit à un procès équitable.
42
- Consacré par l'article préliminaire du Code de procédure pénale et l'article 6 de la
43
- Convention européenne des droits de l'homme (CEDH), le droit à un procès équitable
44
- irrigue l'ensemble de la matière. Il implique l'accès à un tribunal indépendant et
45
- impartial, le respect du délai raisonnable, et la garantie des droits de la défense
46
- (égalité des armes, principe du contradictoire).
47
-
48
- Deuxième partie : La charge de la preuve et la présomption d'innocence.
49
- La preuve incombe au ministère public (la partie poursuivante), qui doit renverser
50
- la présomption d'innocence dont bénéficie toute personne suspectée ou poursuivie.
51
- Le doute doit toujours profiter à l'accusé (in dubio pro reo). Des présomptions de
52
- culpabilité exceptionnelles existent (ex: infractions douanières), mais elles doivent
53
- rester réfragables (pouvoir être renversées par la défense) et proportionnées.
54
-
55
- Troisième partie : Le principe de l'intime conviction et la liberté de la preuve.
56
- En vertu de l'article 427 du Code de procédure pénale, hors les cas où la loi en dispose
57
- autrement, les infractions peuvent être établies par tout mode de preuve et le juge
58
- décide d'après son intime conviction. Le juge ne peut fonder sa décision que sur des
59
- preuves qui lui sont apportées au cours des débats et contradictoirement discutées.
60
-
61
- Quatrième partie : La loyauté de la preuve.
62
- Les autorités publiques (policiers, magistrats) sont soumises à un devoir de loyauté.
63
- La provocation policière à l'infraction est strictement interdite car elle vicie la
64
- procédure. En revanche, la jurisprudence tolère la simple provocation à la preuve,
65
- à condition qu'elle ne constitue pas un stratagème actif poussant l'individu à
66
- s'auto-incriminer (ex: sonorisation déloyale de cellules de garde à vue censurée
67
- par l'Assemblée plénière de la Cour de cassation le 6 mars 2015). À l'inverse,
68
- les preuves apportées par des particuliers, même obtenues de façon illicite, sont
69
- recevables et soumises au débat contradictoire.
70
- """,
71
- "questions": [
72
- {"q": "Quels textes fondamentaux garantissent le droit à un procès équitable en droit français ?",
73
- "hint": "Article préliminaire du Code de procédure pénale, Article 6 de la CEDH"},
74
- {"q": "Quelle est la différence entre la provocation à l'infraction et la provocation à la preuve ?",
75
- "hint": "La provocation à l'infraction (création de l'infraction par la police) est interdite, la provocation à la preuve est tolérée sous conditions (absence de stratagème déloyal). Pensez à l'auto-incrimination."},
76
- {"q": "Que signifie le principe de l'intime conviction (Article 427 du CPP) ?",
77
- "hint": "Liberté de la preuve, le juge évalue librement la valeur des preuves débattues contradictoirement à l'audience."},
78
- ],
79
- },
80
- {
81
- "title": "Leçon 2 — La police judiciaire, l'enquête et la garde à vue",
82
- "text": """
83
- Cours de procédure pénale. Les acteurs de l'enquête et les mesures de contrainte.
84
- Première partie : Les organes de la police judiciaire (PJ).
85
- La police judiciaire est chargée de constater les infractions, d'en rassembler les
86
- preuves et d'en chercher les auteurs. Elle agit sous la direction du procureur de la
87
- République et le contrôle de la chambre de l'instruction. Il faut distinguer les
88
- Officiers de Police Judiciaire (OPJ), qui ont des pouvoirs étendus (décider d'une
89
- garde à vue, mener des perquisitions), des Agents de Police Judiciaire (APJ) qui
90
- les secondent.
91
-
92
- Deuxième partie : Les cadres d'enquête.
93
- L'enquête de flagrance s'applique aux crimes et délits qui se commettent ou viennent
94
- de se commettre. En raison de l'urgence, la police dispose de pouvoirs coercitifs
95
- importants (perquisitions sans assentiment). Elle dure 8 jours (prolongeable 8 jours).
96
- L'enquête préliminaire intervient en l'absence de flagrance. Elle est en principe non
97
- coercitive (ex: perquisition avec assentiment exprès), sauf autorisation spéciale du
98
- Juge des libertés et de la détention (JLD) pour les infractions les plus graves.
99
-
100
- Troisième partie : La garde à vue.
101
- Réformée en profondeur par la loi du 14 avril 2011 à la suite de condamnations par la
102
- CEDH, la garde à vue est une mesure de contrainte décidée par un OPJ. Elle suppose des
103
- raisons plausibles de soupçonner qu'une personne a commis ou tenté de commettre un crime
104
- ou un délit puni d'une peine d'emprisonnement. Sa durée initiale est de 24h,
105
- renouvelable sous contrôle du procureur, voire prolongeable jusqu'à 96h (terrorisme,
106
- criminalité organisée) ou 144h par un magistrat.
107
- Dès le début de la mesure, le suspect bénéficie de droits fondamentaux : droit au
108
- silence, droit de faire prévenir un proche, examen médical, et surtout droit à
109
- l'assistance d'un avocat lors des auditions et confrontations.
110
- """,
111
- "questions": [
112
- {"q": "Quelle différence majeure existe-t-il entre un OPJ et un APJ ?",
113
- "hint": "L'OPJ (Officier) a le pouvoir de prendre des mesures coercitives comme le placement en garde à vue, l'APJ (Agent) le seconde."},
114
- {"q": "Qu'est-ce qui caractérise l'enquête de flagrance par rapport à l'enquête préliminaire ?",
115
- "hint": "Infraction qui se commet ou vient de se commettre. Pouvoirs coercitifs immédiats de la police sans avoir besoin du consentement de la personne."},
116
- {"q": "Quels sont les droits notifiés à un suspect dès son placement en garde à vue ?",
117
- "hint": "Droit au silence, assistance d'un avocat dès la première heure, examen médical, information d'un proche."},
118
- ],
119
- },
120
- {
121
- "title": "Leçon 3 — L'action publique, l'action civile et la prescription",
122
- "text": """
123
- Cours de procédure pénale. Les actions nées de l'infraction.
124
- Première partie : L'action publique et l'action civile.
125
- Toute infraction donne naissance à deux actions. L'action publique vise à l'application
126
- d'une peine pour réparer le trouble à l'ordre social. Elle est exercée par le Ministère
127
- public (le Parquet, magistrats soumis à une hiérarchie).
128
- L'action civile vise la réparation du préjudice privé causé par l'infraction. Elle est
129
- exercée par la victime. La victime peut déclencher l'action publique en se constituant
130
- partie civile (saisine du juge d'instruction ou citation directe).
131
-
132
- Deuxième partie : L'extinction de l'action publique et la prescription.
133
- L'action publique peut s'éteindre par le décès du mis en cause, l'amnistie, l'abrogation
134
- de la loi pénale, la chose jugée ou la prescription.
135
- La loi du 27 février 2017 a profondément réformé les délais de prescription de l'action
136
- publique, les allongeant à : 1 an pour les contraventions, 6 ans pour les délits, et
137
- 20 ans pour les crimes (des délais plus longs existent pour le terrorisme ou les
138
- crimes sexuels sur mineurs).
139
-
140
- Troisième partie : L'infraction occulte et dissimulée.
141
- La loi de 2017 a consacré la jurisprudence sur le point de départ de la prescription.
142
- Pour une infraction occulte (qui ne peut être connue en raison de ses éléments constitutifs)
143
- ou dissimulée (manœuvres de l'auteur pour empêcher sa découverte, ex: abus de biens sociaux),
144
- le délai de prescription ne court qu'à partir du jour où l'infraction a pu être constatée.
145
- Cependant, la loi a fixé un "délai butoir" : l'action publique ne peut plus être engagée
146
- au-delà de 12 ans (délits) ou 30 ans (crimes) à compter de la commission des faits.
147
-
148
- Quatrième partie : Les alternatives aux poursuites.
149
- En vertu de l'opportunité des poursuites, le procureur peut classer sans suite,
150
- poursuivre, ou proposer des mesures alternatives. La composition pénale permet de
151
- proposer une amende ou des mesures de réparation à une personne qui reconnait les faits.
152
- La Convention Judiciaire d'Intérêt Public (CJIP), créée par la loi Sapin II (2016),
153
- permet à une personne morale d'échapper à une condamnation pénale en payant une
154
- forte amende de conformité.
155
- """,
156
- "questions": [
157
- {"q": "Quels sont les délais de droit commun de la prescription de l'action publique depuis la loi de 2017 ?",
158
- "hint": "1 an (contraventions), 6 ans (délits), 20 ans (crimes)."},
159
- {"q": "Comment fonctionne la prescription pour une infraction occulte ou dissimulée ?",
160
- "hint": "Le point de départ est reporté au jour où l'infraction est apparue/constatée, avec un délai butoir maximal de 12 ans ou 30 ans depuis les faits."},
161
- {"q": "Quelles sont les finalités respectives de l'action publique et de l'action civile ?",
162
- "hint": "Action publique = application d'une peine (trouble social) / Action civile = réparation du préjudice (dommages-intérêts)."},
163
- ],
164
- },
165
- {
166
- "title": "Leçon 4 — L'instruction préparatoire et les mesures de sûreté",
167
- "text": """
168
- Cours de procédure pénale. L'instruction par le juge d'instruction.
169
- Première partie : Le rôle et la saisine du juge d'instruction.
170
- L'instruction préparatoire est obligatoire en matière de crime, facultative pour les
171
- délits. Elle est menée par le juge d'instruction, un magistrat du siège indépendant.
172
- Il instruit "à charge et à décharge" pour manifester la vérité.
173
- Il est saisi in rem (il ne peut enquêter que sur les faits visés par le réquisitoire
174
- du procureur ou la plainte de la victime). S'il découvre des faits nouveaux, il a
175
- besoin d'un réquisitoire supplétif.
176
-
177
- Deuxième partie : Les statuts des personnes visées.
178
- Le juge peut placer une personne sous le statut de témoin assisté s'il existe de
179
- simples indices vraisemblables.
180
- Il ne peut prononcer une mise en examen que s'il existe des "indices graves ou concordants"
181
- rendant vraisemblable la participation aux faits. Le mis en examen dispose de droits
182
- larges (demandes d'actes, requêtes en nullité, avocat).
183
-
184
- Troisième partie : La détention provisoire et le contrôle judiciaire.
185
- La liberté est le principe, la détention l'exception. Le juge d'instruction a perdu
186
- le pouvoir d'incarcérer depuis 2000. C'est le Juge des Libertés et de la Détention (JLD)
187
- qui peut prononcer la détention provisoire si le contrôle judiciaire (obligations à respecter,
188
- ex: interdiction de paraître) ou l'Assignation à Résidence sous Surveillance Électronique
189
- (ARSE) se révèlent insuffisants.
190
- La détention provisoire doit être strictement motivée (risque de fuite, protection
191
- des preuves, trouble à l'ordre public) et est limitée dans le temps.
192
-
193
- Quatrième partie : La clôture de l'instruction.
194
- À la fin de ses investigations, le juge rend une ordonnance de règlement. Soit un
195
- non-lieu (charges insuffisantes), soit une ordonnance de renvoi (devant le tribunal
196
- correctionnel) ou de mise en accusation (devant la Cour d'assises). Les actes
197
- irréguliers accomplis pendant l'instruction peuvent être sanctionnés par la nullité,
198
- prononcée par la Chambre de l'instruction.
199
- """,
200
- "questions": [
201
- {"q": "Que signifie la règle de la saisine « in rem » du juge d'instruction ?",
202
- "hint": "Le juge ne peut instruire que sur les faits expressément visés dans le réquisitoire du parquet ou la plainte. Il a besoin d'un réquisitoire supplétif pour des faits nouveaux."},
203
- {"q": "Quelle est la condition posée par l'article 80-1 du CPP pour mettre une personne en examen ?",
204
- "hint": "L'existence d'indices graves ou concordants de participation aux faits."},
205
- {"q": "Quel magistrat est compétent pour placer une personne en détention provisoire au cours de l'instruction ?",
206
- "hint": "Le Juge des libertés et de la détention (JLD), sur saisine du juge d'instruction ou du procureur."},
207
- ],
208
- },
209
- {
210
- "title": "Leçon 5 — Les juridictions de jugement et les voies de recours",
211
- "text": """
212
- Cours de procédure pénale. Le procès pénal et les recours.
213
- Première partie : Les juridictions de droit commun.
214
- La compétence matérielle répartit les affaires selon la gravité :
215
- - Le Tribunal de police (juge unique) statue sur les contraventions.
216
- - Le Tribunal correctionnel (formation collégiale ou juge unique) juge les délits.
217
- - La Cour d'assises juge les crimes. Composée de 3 magistrats professionnels et
218
- d'un jury populaire (6 citoyens en premier ressort, 9 en appel). Les décisions sont
219
- prises à la majorité et depuis 2012, les arrêts d'assises doivent être motivés.
220
- Une expérimentation (Loi de programmation 2018-2022) a créé les Cours Criminelles
221
- Départementales pour juger les crimes punis de 15 à 20 ans de réclusion sans jury populaire.
222
-
223
- Deuxième partie : Les modes de comparution (matière correctionnelle).
224
- Le Parquet peut opter pour plusieurs voies de poursuites rapides : la comparution
225
- immédiate (délits punis d'au moins 2 ans, ou 6 mois si flagrance), la Comparution
226
- sur Reconnaissance Préalable de Culpabilité (CRPC, ou "plaider-coupable",
227
- qui suppose l'accord du prévenu et l'homologation du juge), ou l'ordonnance pénale
228
- (procédure sans audience).
229
-
230
- Troisième partie : Les voies de recours ordinaires.
231
- L'appel est une voie de réformation qui soumet l'affaire à la Cour d'appel
232
- (Chambre des appels correctionnels) ou à une cour d'assises d'appel. L'appel a un
233
- effet suspensif et dévolutif. Il interdit d'aggraver le sort de l'appelant s'il est
234
- seul à faire appel (prohibition de la reformatio in pejus).
235
- L'opposition permet de rétracter un jugement rendu par défaut (en l'absence du prévenu
236
- non informé). Le même tribunal rejuge l'affaire.
237
-
238
- Quatrième partie : Les voies de recours extraordinaires.
239
- Le pourvoi en cassation est jugé par la Chambre criminelle de la Cour de cassation.
240
- Elle ne rejuge pas les faits, mais contrôle uniquement l'exacte application de la loi
241
- par les juges du fond.
242
- La demande en révision ou en réexamen (réformée en 2014) permet d'annuler une
243
- condamnation pénale définitive en cas de "fait nouveau" faisant naître un doute sur
244
- la culpabilité, ou suite à une condamnation de la France par la CEDH.
245
- """,
246
- "questions": [
247
- {"q": "Quelle est la composition de la Cour d'assises en premier ressort ?",
248
- "hint": "3 magistrats professionnels (la cour) et un jury populaire composé de 6 citoyens (jurés)."},
249
- {"q": "Qu'est-ce que la procédure de CRPC ?",
250
- "hint": "Comparution sur Reconnaissance Préalable de Culpabilité. Un 'plaider-coupable' où le procureur propose une peine que le prévenu accepte, avant homologation par le juge du siège."},
251
- {"q": "Le pourvoi en cassation représente-t-il un troisième degré de juridiction ?",
252
- "hint": "Non. La Cour de cassation est juge du droit et non du fait. Elle contrôle l'application de la loi sans rejuger les faits."},
253
- ],
254
- },
255
- ]
256
-
257
- LISTEN_SECONDS = 10 # 900 en production (15 min)
258
- PASS_SCORE = 6
259
-
260
- # ── Cache audio par leçon ─────────────────────────────────────────────────────
261
- _audio_cache = {}
262
- _audio_lock = threading.Lock()
263
-
264
- def get_lecture_audio(lesson_idx):
265
- if lesson_idx in _audio_cache and os.path.exists(_audio_cache[lesson_idx]):
266
- return _audio_cache[lesson_idx]
267
- with _audio_lock:
268
- if lesson_idx in _audio_cache and os.path.exists(_audio_cache[lesson_idx]):
269
- return _audio_cache[lesson_idx]
270
- try:
271
- text = LESSONS[lesson_idx]["text"]
272
- tts = gTTS(text=text, lang="fr", slow=False)
273
- path = tempfile.mktemp(suffix=".mp3")
274
- tts.save(path)
275
- _audio_cache[lesson_idx] = path
276
- return path
277
- except Exception as e:
278
- print(f"[law-app] gTTS erreur : {e}")
279
- return None
280
-
281
- # ── État ──────────────────────────────────────────────────────────────────────
282
- def fresh():
283
- return json.dumps({"phase": "idle", "listen_start": None,
284
- "question_idx": None, "lesson_idx": 0})
285
-
286
- def S(s): return json.loads(s)
287
- def D(d): return json.dumps(d)
288
-
289
- # ── Évaluation ────────────────────────────────────────────────────────────────
290
- _SYSTEM_PROMPT = (
291
- "Tu es un professeur de droit français strict et bienveillant. "
292
- "Tu évalues les réponses des étudiants et réponds UNIQUEMENT en JSON valide sur une seule ligne.\n\n"
293
- "# Exemples de comportement attendu :\n\n"
294
- "Question : Quelles sont les finalités respectives de l'action publique et de l'action civile ?\n"
295
- "Réponse de l'étudiant : je ne sais pas\n"
296
- "Éléments attendus : Action publique = application d'une peine (trouble social) / Action civile = réparation du préjudice (dommages-intérêts).\n"
297
- '{"score": 0, "feedback": "Aucune réponse n\'a été proposée. Pour rappel, l\'action publique vise à sanctionner le trouble social par une peine, tandis que l\'action civile cherche à réparer le préjudice subi."}\n\n'
298
- "Question : Quelles sont les finalités respectives de l'action publique et de l'action civile ?\n"
299
- "Réponse de l'étudiant : lacion publique a pour but d'apication de pain laction civil a pour but domager le victime\n"
300
- "Éléments attendus : Action publique = application d'une peine (trouble social) / Action civile = réparation du préjudice (dommages-intérêts).\n"
301
- '{"score": 7, "feedback": "Les notions de peine et de dédommagement sont comprises, mais l\'orthographe et la formulation doivent être grandement améliorées."}\n\n'
302
- "Question : Quel magistrat est compétent pour placer une personne en détention provisoire au cours de l'instruction ?\n"
303
- "Réponse de l'étudiant : juge dinstrucion\n"
304
- "Éléments attendus : Le Juge des libertés et de la détention (JLD), sur saisine du juge d'instruction ou du procureur.\n"
305
- '{"score": 2, "feedback": "Le juge d\'instruction saisit le dossier mais n\'est pas compétent pour décider de la détention provisoire ; cette décision relève exclusivement du Juge des libertés et de la détention (JLD)."}\n\n'
306
- "Question : Quelle différence majeure existe-t-il entre un OPJ et un APJ ?\n"
307
- "Réponse de l'étudiant : l apj est t'une police administrative aopg police judiciare\n"
308
- "Éléments attendus : L'OPJ (Officier) a le pouvoir de prendre des mesures coercitives comme le placement en garde à vue, l'APJ (Agent) le seconde.\n"
309
- '{"score": 1, "feedback": "C\'est incorrect. L\'APJ et l\'OPJ relèvent tous deux de la police judiciaire. La différence majeure réside dans les pouvoirs coercitifs (comme décider d\'une garde à vue) réservés à l\'OPJ, que l\'APJ seconde."}'
310
- )
311
-
312
- def _keyword_fallback(answer, hint, reason=""):
313
- kw = [w.strip().lower() for w in hint.split(",")]
314
- hits = sum(1 for k in kw if k in answer.lower())
315
- score = min(10, round((hits / max(len(kw), 1)) * 10))
316
- label = f"(Sans IA – {reason} – " if reason else "(Sans IA – "
317
- return score, f"{label}mots-clés : {hits}/{len(kw)})"
318
-
319
- def evaluate_answer(question, answer, hint):
320
- if not _server_ok:
321
- return _keyword_fallback(answer, hint, reason="serveur indisponible")
322
-
323
- user_msg = (
324
- f"Question : {question}\n"
325
- f"Réponse de l'étudiant : {answer}\n"
326
- f"Éléments attendus : {hint}\n\n"
327
- "Évalue cette réponse sur 10 et donne un commentaire bref en français.\n"
328
- "Réponds UNIQUEMENT avec ce JSON sur une seule ligne, sans rien d'autre :\n"
329
- '{"score": <entier 0-10>, "feedback": "<commentaire bref>"}'
330
- )
331
-
332
- payload = {
333
- "model": "local",
334
- "messages": [
335
- {"role": "system", "content": _SYSTEM_PROMPT},
336
- {"role": "user", "content": user_msg},
337
- ],
338
- "max_tokens": 150,
339
- "temperature": 0.1,
340
- "stop": ["\n\n", "###"],
341
- }
342
-
343
- try:
344
- resp = requests.post(CHAT_ENDPOINT, json=payload, timeout=60)
345
- resp.raise_for_status()
346
- text = resp.json()["choices"][0]["message"]["content"].strip()
347
- print(f"[law-app] Réponse modèle : {text}")
348
- start = text.find("{")
349
- end = text.rfind("}") + 1
350
- if start == -1 or end == 0:
351
- raise ValueError("Pas de JSON dans la réponse")
352
- data = json.loads(text[start:end])
353
- return max(0, min(10, int(data["score"]))), data.get("feedback", "")
354
- except Exception as e:
355
- print(f"[law-app] Erreur éval : {e}")
356
- return _keyword_fallback(answer, hint, reason=str(e))
357
-
358
- # ── Handlers ──────────────────────────────────────────────────────────────────
359
- def start_listening(ss):
360
- state = S(ss)
361
- lesson_idx = state.get("lesson_idx", 0)
362
- lesson = LESSONS[lesson_idx]
363
- state.update({"phase": "listen", "listen_start": time.time(), "question_idx": None})
364
- audio = get_lecture_audio(lesson_idx)
365
- total = len(LESSONS)
366
- msg = (
367
- f"🎧 **{lesson['title']}** ({lesson_idx + 1}/{total})\n\n"
368
- "Écoutez pendant **15 minutes** complètes, puis cliquez sur **🔄 Vérifier le temps**."
369
- )
370
- return (
371
- audio, msg,
372
- gr.update(visible=True),
373
- gr.update(visible=False),
374
- gr.update(visible=False),
375
- gr.update(visible=False),
376
- gr.update(interactive=False, value="⏳ Écoute en cours…"),
377
- D(state),
378
- )
379
-
380
- def check_timer(ss):
381
- state = S(ss)
382
- if state["phase"] != "listen" or not state["listen_start"]:
383
- return gr.update(), "⚠️ Lancez d'abord l'écoute.", ss
384
- rem = max(0, LISTEN_SECONDS - (time.time() - state["listen_start"]))
385
- m, s = divmod(int(rem), 60)
386
- if rem <= 0:
387
- return (
388
- gr.update(interactive=True, value="✅ Accéder au Quiz"),
389
- "✅ Durée atteinte ! Cliquez sur **Accéder au Quiz**.",
390
- ss,
391
- )
392
- return (
393
- gr.update(interactive=False, value=f"⏳ Encore {m:02d}:{s:02d}"),
394
- f"🎧 Patientez encore **{m} min {s:02d} s**.",
395
- ss,
396
- )
397
-
398
- def open_quiz(ss):
399
- state = S(ss)
400
- elapsed = time.time() - (state["listen_start"] or 0)
401
- if elapsed < LISTEN_SECONDS:
402
- m, s = divmod(int(LISTEN_SECONDS - elapsed), 60)
403
- return (
404
- gr.update(visible=True), gr.update(visible=False),
405
- gr.update(visible=False), gr.update(visible=False),
406
- f"⚠️ Patientez encore **{m} min {s:02d} s**.", "", "", ss,
407
- )
408
- lesson_idx = state.get("lesson_idx", 0)
409
- idx = random.randrange(len(LESSONS[lesson_idx]["questions"]))
410
- state.update({"phase": "quiz", "question_idx": idx})
411
- return (
412
- gr.update(visible=False), gr.update(visible=True),
413
- gr.update(visible=False), gr.update(visible=False),
414
- "", LESSONS[lesson_idx]["questions"][idx]["q"], "", D(state),
415
- )
416
-
417
- # ── Submit avec désactivation immédiate du bouton ─────────────────────────────
418
- def submit_answer(answer, ss):
419
- state = S(ss)
420
- lesson_idx = state.get("lesson_idx", 0)
421
-
422
- if state["phase"] != "quiz":
423
- yield (
424
- gr.update(visible=True), gr.update(visible=False),
425
- gr.update(visible=False), gr.update(visible=False),
426
- "",
427
- gr.update(value="📤 Soumettre", interactive=True),
428
- gr.update(interactive=True),
429
- ss,
430
- )
431
- return
432
-
433
- # Étape 1 : désactiver immédiatement bouton + textbox
434
- yield (
435
- gr.update(visible=False), gr.update(visible=True),
436
- gr.update(visible=False), gr.update(visible=False),
437
- "",
438
- gr.update(value="⏳ Réponse soumise — Correction en cours…", interactive=False),
439
- gr.update(interactive=False),
440
- ss,
441
- )
442
-
443
- # Étape 2 : évaluation IA
444
- q = LESSONS[lesson_idx]["questions"][state["question_idx"]]
445
- score, fb = evaluate_answer(q["q"], answer, q["hint"])
446
- total = len(LESSONS)
447
- is_last = (lesson_idx >= total - 1)
448
-
449
- if score >= PASS_SCORE:
450
- state["phase"] = "pass"
451
- result = (
452
- f"## 🎉 Bravo ! Leçon {lesson_idx + 1}/{total} réussie.\n\n"
453
- f"**Score : {score}/10**\n\n**Commentaire :** {fb}"
454
- )
455
- next_label = "🏆 Voir mon certificat" if is_last else f"➡️ {LESSONS[lesson_idx + 1]['title']}"
456
- else:
457
- state["phase"] = "fail"
458
- result = (
459
- f"## ❌ Score insuffisant : {score}/10\n\n"
460
- f"**Commentaire :** {fb}\n\n"
461
- f"Seuil requis : **{PASS_SCORE}/10**. "
462
- "Veuillez **réécouter la leçon** avant de retenter."
463
- )
464
- next_label = "🔄 Réécouter la leçon"
465
-
466
- # Étape 3 : afficher résultat, réactiver bouton, mettre à jour next_btn
467
- yield (
468
- gr.update(visible=False), gr.update(visible=False),
469
- gr.update(visible=True), gr.update(visible=False),
470
- result,
471
- gr.update(value="📤 Soumettre", interactive=True),
472
- gr.update(value="", interactive=True),
473
- D(state),
474
- )
475
-
476
- def handle_next(ss):
477
- state = S(ss)
478
- lesson_idx = state.get("lesson_idx", 0)
479
- total = len(LESSONS)
480
-
481
- if state["phase"] == "fail":
482
- return start_listening(D(state))
483
-
484
- if lesson_idx >= total - 1:
485
- state["phase"] = "done"
486
- cert = (
487
- "# 🏆 Félicitations !\n\n"
488
- "Vous avez complété avec succès les **5 leçons** du cours de procédure pénale :\n\n"
489
- "✅ Leçon 1 — Les principes directeurs et la preuve pénale\n\n"
490
- "✅ Leçon 2 — La police judiciaire, l'enquête et la garde à vue\n\n"
491
- "✅ Leçon 3 — L'action publique, l'action civile et la prescription\n\n"
492
- "✅ Leçon 4 — L'instruction préparatoire et les mesures de sûreté\n\n"
493
- "✅ Leçon 5 — Les juridictions de jugement et les voies de recours\n\n"
494
- "---\n\n"
495
- "*Ce certificat atteste de votre participation et de votre réussite "
496
- "aux évaluations du cours de procédure pénale.*"
497
- )
498
- return (
499
- None, cert,
500
- gr.update(visible=False),
501
- gr.update(visible=False),
502
- gr.update(visible=False),
503
- gr.update(visible=True),
504
- gr.update(interactive=False, value="✅ Terminé"),
505
- D(state),
506
- )
507
- else:
508
- state["lesson_idx"] = lesson_idx + 1
509
- state["listen_start"] = None
510
- return start_listening(D(state))
511
-
512
- def restart_from_cert(ss):
513
- return start_listening(fresh())
514
-
515
- # ── UI ────────────────────────────────────────────────────────────────────────
516
- CSS = "footer{display:none!important;} #title{text-align:center;}"
517
-
518
- with gr.Blocks(title="Cours de Droit", css=CSS) as demo:
519
- session = gr.State(fresh())
520
-
521
- gr.Markdown(
522
- "# ⚖️ Cours de Droit — Formation Juridique\n"
523
- "Écoutez chaque leçon · Répondez à la question · Progressez leçon par leçon",
524
- elem_id="title",
525
- )
526
-
527
- with gr.Column(visible=True) as listen_col:
528
- gr.Markdown("## 🎧 Étape 1 — Écoute de la leçon")
529
- listen_msg = gr.Markdown("Cliquez sur **Démarrer** pour commencer.")
530
- audio_out = gr.Audio(label="Cours audio (français)", autoplay=False, interactive=False)
531
- timer_info = gr.Markdown("")
532
- with gr.Row():
533
- start_btn = gr.Button("▶️ Démarrer l'écoute", variant="primary")
534
- check_btn = gr.Button("🔄 Vérifier le temps", variant="secondary")
535
- quiz_btn = gr.Button("✅ Accéder au Quiz", variant="secondary", interactive=False)
536
-
537
- with gr.Column(visible=False) as quiz_col:
538
- gr.Markdown("## 📝 Étape 2 — Question d'évaluation")
539
- question_md = gr.Markdown("")
540
- answer_box = gr.Textbox(label="Votre réponse", placeholder="Rédigez ici…", lines=5)
541
- submit_btn = gr.Button("��� Soumettre", variant="primary")
542
-
543
- with gr.Column(visible=False) as result_col:
544
- gr.Markdown("## 📊 Résultat")
545
- result_md = gr.Markdown("")
546
- next_btn = gr.Button("➡️ Leçon suivante", variant="primary")
547
-
548
- with gr.Column(visible=False) as cert_col:
549
- cert_md = gr.Markdown("")
550
- restart_btn = gr.Button("🔄 Recommencer depuis le début", variant="secondary")
551
-
552
- # ── Câblage ───────────────────────────────────────────────────────────────
553
- LISTEN_OUT = [audio_out, listen_msg,
554
- listen_col, quiz_col, result_col, cert_col,
555
- quiz_btn, session]
556
-
557
- start_btn.click(start_listening, [session], LISTEN_OUT)
558
- check_btn.click(check_timer, [session], [quiz_btn, timer_info, session])
559
-
560
- QUIZ_OUT = [listen_col, quiz_col, result_col, cert_col,
561
- timer_info, question_md, answer_box, session]
562
- quiz_btn.click(open_quiz, [session], QUIZ_OUT)
563
-
564
- # submit_answer est un générateur → outputs inclut submit_btn et answer_box
565
- RES_OUT = [listen_col, quiz_col, result_col, cert_col,
566
- result_md, submit_btn, answer_box, session]
567
- submit_btn.click(submit_answer, [answer_box, session], RES_OUT)
568
- answer_box.submit(submit_answer, [answer_box, session], RES_OUT)
569
-
570
- next_btn.click(handle_next, [session], LISTEN_OUT)
571
- restart_btn.click(restart_from_cert, [session], LISTEN_OUT)
572
-
573
- if __name__ == "__main__":
574
- demo.queue()
575
- demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)"""
576
- Cours de Droit – Hugging Face Space
577
- llama-server (pre-built binary) via OpenAI-compatible HTTP API.
578
- """
579
- import os, json, time, tempfile, random, threading, sys
580
- import requests
581
-
582
- import gradio as gr
583
- from gtts import gTTS
584
-
585
- # ── llama-server connection ──────────────────────────────────────────────────
586
- LLAMA_URL = os.getenv("LLAMA_SERVER_URL", "http://127.0.0.1:8080")
587
- CHAT_ENDPOINT = f"{LLAMA_URL}/v1/chat/completions"
588
- _server_ok = False
589
- _server_lock = threading.Lock()
590
-
591
- def _check_server():
592
- """Background thread: poll until llama-server is ready."""
593
- global _server_ok
594
- print("[law-app] Waiting for llama-server...")
595
- for _ in range(60): # up to 120 s
596
- try:
597
- r = requests.get(f"{LLAMA_URL}/health", timeout=2)
598
- if r.status_code == 200:
599
- _server_ok = True
600
- print("[law-app] llama-server ready ✓")
601
- return
602
- except Exception:
603
- pass
604
- time.sleep(2)
605
- print("[law-app] llama-server not reachable – keyword fallback active")
606
-
607
- threading.Thread(target=_check_server, daemon=True).start()
608
-
609
- # ── Leçons ───────────────────────────────────────────────────────────────────
610
- LESSONS = [
611
- {
612
- "title": "Leçon 1 — Les principes directeurs et la preuve pénale",
613
- "text": """
614
- Cours de procédure pénale. Les principes du procès pénal et la preuve.
615
- Première partie : Le droit à un procès équitable.
616
- Consacré par l'article préliminaire du Code de procédure pénale et l'article 6 de la
617
- Convention européenne des droits de l'homme (CEDH), le droit à un procès équitable
618
- irrigue l'ensemble de la matière. Il implique l'accès à un tribunal indépendant et
619
- impartial, le respect du délai raisonnable, et la garantie des droits de la défense
620
- (égalité des armes, principe du contradictoire).
621
-
622
- Deuxième partie : La charge de la preuve et la présomption d'innocence.
623
- La preuve incombe au ministère public (la partie poursuivante), qui doit renverser
624
- la présomption d'innocence dont bénéficie toute personne suspectée ou poursuivie.
625
- Le doute doit toujours profiter à l'accusé (in dubio pro reo). Des présomptions de
626
- culpabilité exceptionnelles existent (ex: infractions douanières), mais elles doivent
627
- rester réfragables (pouvoir être renversées par la défense) et proportionnées.
628
-
629
- Troisième partie : Le principe de l'intime conviction et la liberté de la preuve.
630
- En vertu de l'article 427 du Code de procédure pénale, hors les cas où la loi en dispose
631
- autrement, les infractions peuvent être établies par tout mode de preuve et le juge
632
- décide d'après son intime conviction. Le juge ne peut fonder sa décision que sur des
633
- preuves qui lui sont apportées au cours des débats et contradictoirement discutées.
634
-
635
- Quatrième partie : La loyauté de la preuve.
636
- Les autorités publiques (policiers, magistrats) sont soumises à un devoir de loyauté.
637
- La provocation policière à l'infraction est strictement interdite car elle vicie la
638
- procédure. En revanche, la jurisprudence tolère la simple provocation à la preuve,
639
- à condition qu'elle ne constitue pas un stratagème actif poussant l'individu à
640
- s'auto-incriminer (ex: sonorisation déloyale de cellules de garde à vue censurée
641
- par l'Assemblée plénière de la Cour de cassation le 6 mars 2015). À l'inverse,
642
- les preuves apportées par des particuliers, même obtenues de façon illicite, sont
643
- recevables et soumises au débat contradictoire.
644
- """,
645
- "questions": [
646
- {"q": "Quels textes fondamentaux garantissent le droit à un procès équitable en droit français ?",
647
- "hint": "Article préliminaire du Code de procédure pénale, Article 6 de la CEDH"},
648
- {"q": "Quelle est la différence entre la provocation à l'infraction et la provocation à la preuve ?",
649
- "hint": "La provocation à l'infraction (création de l'infraction par la police) est interdite, la provocation à la preuve est tolérée sous conditions (absence de stratagème déloyal). Pensez à l'auto-incrimination."},
650
- {"q": "Que signifie le principe de l'intime conviction (Article 427 du CPP) ?",
651
- "hint": "Liberté de la preuve, le juge évalue librement la valeur des preuves débattues contradictoirement à l'audience."},
652
- ],
653
- },
654
- {
655
- "title": "Leçon 2 — La police judiciaire, l'enquête et la garde à vue",
656
- "text": """
657
- Cours de procédure pénale. Les acteurs de l'enquête et les mesures de contrainte.
658
- Première partie : Les organes de la police judiciaire (PJ).
659
- La police judiciaire est chargée de constater les infractions, d'en rassembler les
660
- preuves et d'en chercher les auteurs. Elle agit sous la direction du procureur de la
661
- République et le contrôle de la chambre de l'instruction. Il faut distinguer les
662
- Officiers de Police Judiciaire (OPJ), qui ont des pouvoirs étendus (décider d'une
663
- garde à vue, mener des perquisitions), des Agents de Police Judiciaire (APJ) qui
664
- les secondent.
665
-
666
- Deuxième partie : Les cadres d'enquête.
667
- L'enquête de flagrance s'applique aux crimes et délits qui se commettent ou viennent
668
- de se commettre. En raison de l'urgence, la police dispose de pouvoirs coercitifs
669
- importants (perquisitions sans assentiment). Elle dure 8 jours (prolongeable 8 jours).
670
- L'enquête préliminaire intervient en l'absence de flagrance. Elle est en principe non
671
- coercitive (ex: perquisition avec assentiment exprès), sauf autorisation spéciale du
672
- Juge des libertés et de la détention (JLD) pour les infractions les plus graves.
673
-
674
- Troisième partie : La garde à vue.
675
- Réformée en profondeur par la loi du 14 avril 2011 à la suite de condamnations par la
676
- CEDH, la garde à vue est une mesure de contrainte décidée par un OPJ. Elle suppose des
677
- raisons plausibles de soupçonner qu'une personne a commis ou tenté de commettre un crime
678
- ou un délit puni d'une peine d'emprisonnement. Sa durée initiale est de 24h,
679
- renouvelable sous contrôle du procureur, voire prolongeable jusqu'à 96h (terrorisme,
680
- criminalité organisée) ou 144h par un magistrat.
681
- Dès le début de la mesure, le suspect bénéficie de droits fondamentaux : droit au
682
- silence, droit de faire prévenir un proche, examen médical, et surtout droit à
683
- l'assistance d'un avocat lors des auditions et confrontations.
684
- """,
685
- "questions": [
686
- {"q": "Quelle différence majeure existe-t-il entre un OPJ et un APJ ?",
687
- "hint": "L'OPJ (Officier) a le pouvoir de prendre des mesures coercitives comme le placement en garde à vue, l'APJ (Agent) le seconde."},
688
- {"q": "Qu'est-ce qui caractérise l'enquête de flagrance par rapport à l'enquête préliminaire ?",
689
- "hint": "Infraction qui se commet ou vient de se commettre. Pouvoirs coercitifs immédiats de la police sans avoir besoin du consentement de la personne."},
690
- {"q": "Quels sont les droits notifiés à un suspect dès son placement en garde à vue ?",
691
- "hint": "Droit au silence, assistance d'un avocat dès la première heure, examen médical, information d'un proche."},
692
- ],
693
- },
694
- {
695
- "title": "Leçon 3 — L'action publique, l'action civile et la prescription",
696
- "text": """
697
- Cours de procédure pénale. Les actions nées de l'infraction.
698
- Première partie : L'action publique et l'action civile.
699
- Toute infraction donne naissance à deux actions. L'action publique vise à l'application
700
- d'une peine pour réparer le trouble à l'ordre social. Elle est exercée par le Ministère
701
- public (le Parquet, magistrats soumis à une hiérarchie).
702
- L'action civile vise la réparation du préjudice privé causé par l'infraction. Elle est
703
- exercée par la victime. La victime peut déclencher l'action publique en se constituant
704
- partie civile (saisine du juge d'instruction ou citation directe).
705
-
706
- Deuxième partie : L'extinction de l'action publique et la prescription.
707
- L'action publique peut s'éteindre par le décès du mis en cause, l'amnistie, l'abrogation
708
- de la loi pénale, la chose jugée ou la prescription.
709
- La loi du 27 février 2017 a profondément réformé les délais de prescription de l'action
710
- publique, les allongeant à : 1 an pour les contraventions, 6 ans pour les délits, et
711
- 20 ans pour les crimes (des délais plus longs existent pour le terrorisme ou les
712
- crimes sexuels sur mineurs).
713
-
714
- Troisième partie : L'infraction occulte et dissimulée.
715
- La loi de 2017 a consacré la jurisprudence sur le point de départ de la prescription.
716
- Pour une infraction occulte (qui ne peut être connue en raison de ses éléments constitutifs)
717
- ou dissimulée (manœuvres de l'auteur pour empêcher sa découverte, ex: abus de biens sociaux),
718
- le délai de prescription ne court qu'à partir du jour où l'infraction a pu être constatée.
719
- Cependant, la loi a fixé un "délai butoir" : l'action publique ne peut plus être engagée
720
- au-delà de 12 ans (délits) ou 30 ans (crimes) à compter de la commission des faits.
721
-
722
- Quatrième partie : Les alternatives aux poursuites.
723
- En vertu de l'opportunité des poursuites, le procureur peut classer sans suite,
724
- poursuivre, ou proposer des mesures alternatives. La composition pénale permet de
725
- proposer une amende ou des mesures de réparation à une personne qui reconnait les faits.
726
- La Convention Judiciaire d'Intérêt Public (CJIP), créée par la loi Sapin II (2016),
727
- permet à une personne morale d'échapper à une condamnation pénale en payant une
728
- forte amende de conformité.
729
- """,
730
- "questions": [
731
- {"q": "Quels sont les délais de droit commun de la prescription de l'action publique depuis la loi de 2017 ?",
732
- "hint": "1 an (contraventions), 6 ans (délits), 20 ans (crimes)."},
733
- {"q": "Comment fonctionne la prescription pour une infraction occulte ou dissimulée ?",
734
- "hint": "Le point de départ est reporté au jour où l'infraction est apparue/constatée, avec un délai butoir maximal de 12 ans ou 30 ans depuis les faits."},
735
- {"q": "Quelles sont les finalités respectives de l'action publique et de l'action civile ?",
736
- "hint": "Action publique = application d'une peine (trouble social) / Action civile = réparation du préjudice (dommages-intérêts)."},
737
- ],
738
- },
739
- {
740
- "title": "Leçon 4 — L'instruction préparatoire et les mesures de sûreté",
741
- "text": """
742
- Cours de procédure pénale. L'instruction par le juge d'instruction.
743
- Première partie : Le rôle et la saisine du juge d'instruction.
744
- L'instruction préparatoire est obligatoire en matière de crime, facultative pour les
745
- délits. Elle est menée par le juge d'instruction, un magistrat du siège indépendant.
746
- Il instruit "à charge et à décharge" pour manifester la vérité.
747
- Il est saisi in rem (il ne peut enquêter que sur les faits visés par le réquisitoire
748
- du procureur ou la plainte de la victime). S'il découvre des faits nouveaux, il a
749
- besoin d'un réquisitoire supplétif.
750
-
751
- Deuxième partie : Les statuts des personnes visées.
752
- Le juge peut placer une personne sous le statut de témoin assisté s'il existe de
753
- simples indices vraisemblables.
754
- Il ne peut prononcer une mise en examen que s'il existe des "indices graves ou concordants"
755
- rendant vraisemblable la participation aux faits. Le mis en examen dispose de droits
756
- larges (demandes d'actes, requêtes en nullité, avocat).
757
-
758
- Troisième partie : La détention provisoire et le contrôle judiciaire.
759
- La liberté est le principe, la détention l'exception. Le juge d'instruction a perdu
760
- le pouvoir d'incarcérer depuis 2000. C'est le Juge des Libertés et de la Détention (JLD)
761
- qui peut prononcer la détention provisoire si le contrôle judiciaire (obligations à respecter,
762
- ex: interdiction de paraître) ou l'Assignation à Résidence sous Surveillance Électronique
763
- (ARSE) se révèlent insuffisants.
764
- La détention provisoire doit être strictement motivée (risque de fuite, protection
765
- des preuves, trouble à l'ordre public) et est limitée dans le temps.
766
-
767
- Quatrième partie : La clôture de l'instruction.
768
- À la fin de ses investigations, le juge rend une ordonnance de règlement. Soit un
769
- non-lieu (charges insuffisantes), soit une ordonnance de renvoi (devant le tribunal
770
- correctionnel) ou de mise en accusation (devant la Cour d'assises). Les actes
771
- irréguliers accomplis pendant l'instruction peuvent être sanctionnés par la nullité,
772
- prononcée par la Chambre de l'instruction.
773
- """,
774
- "questions": [
775
- {"q": "Que signifie la règle de la saisine « in rem » du juge d'instruction ?",
776
- "hint": "Le juge ne peut instruire que sur les faits expressément visés dans le réquisitoire du parquet ou la plainte. Il a besoin d'un réquisitoire supplétif pour des faits nouveaux."},
777
- {"q": "Quelle est la condition posée par l'article 80-1 du CPP pour mettre une personne en examen ?",
778
- "hint": "L'existence d'indices graves ou concordants de participation aux faits."},
779
- {"q": "Quel magistrat est compétent pour placer une personne en détention provisoire au cours de l'instruction ?",
780
- "hint": "Le Juge des libertés et de la détention (JLD), sur saisine du juge d'instruction ou du procureur."},
781
- ],
782
- },
783
- {
784
- "title": "Leçon 5 — Les juridictions de jugement et les voies de recours",
785
- "text": """
786
- Cours de procédure pénale. Le procès pénal et les recours.
787
- Première partie : Les juridictions de droit commun.
788
- La compétence matérielle répartit les affaires selon la gravité :
789
- - Le Tribunal de police (juge unique) statue sur les contraventions.
790
- - Le Tribunal correctionnel (formation collégiale ou juge unique) juge les délits.
791
- - La Cour d'assises juge les crimes. Composée de 3 magistrats professionnels et
792
- d'un jury populaire (6 citoyens en premier ressort, 9 en appel). Les décisions sont
793
- prises à la majorité et depuis 2012, les arrêts d'assises doivent être motivés.
794
- Une expérimentation (Loi de programmation 2018-2022) a créé les Cours Criminelles
795
- Départementales pour juger les crimes punis de 15 à 20 ans de réclusion sans jury populaire.
796
-
797
- Deuxième partie : Les modes de comparution (matière correctionnelle).
798
- Le Parquet peut opter pour plusieurs voies de poursuites rapides : la comparution
799
- immédiate (délits punis d'au moins 2 ans, ou 6 mois si flagrance), la Comparution
800
- sur Reconnaissance Préalable de Culpabilité (CRPC, ou "plaider-coupable",
801
- qui suppose l'accord du prévenu et l'homologation du juge), ou l'ordonnance pénale
802
- (procédure sans audience).
803
-
804
- Troisième partie : Les voies de recours ordinaires.
805
- L'appel est une voie de réformation qui soumet l'affaire à la Cour d'appel
806
- (Chambre des appels correctionnels) ou à une cour d'assises d'appel. L'appel a un
807
- effet suspensif et dévolutif. Il interdit d'aggraver le sort de l'appelant s'il est
808
- seul à faire appel (prohibition de la reformatio in pejus).
809
- L'opposition permet de rétracter un jugement rendu par défaut (en l'absence du prévenu
810
- non informé). Le même tribunal rejuge l'affaire.
811
-
812
- Quatrième partie : Les voies de recours extraordinaires.
813
- Le pourvoi en cassation est jugé par la Chambre criminelle de la Cour de cassation.
814
- Elle ne rejuge pas les faits, mais contrôle uniquement l'exacte application de la loi
815
- par les juges du fond.
816
- La demande en révision ou en réexamen (réformée en 2014) permet d'annuler une
817
- condamnation pénale définitive en cas de "fait nouveau" faisant naître un doute sur
818
- la culpabilité, ou suite à une condamnation de la France par la CEDH.
819
- """,
820
- "questions": [
821
- {"q": "Quelle est la composition de la Cour d'assises en premier ressort ?",
822
- "hint": "3 magistrats professionnels (la cour) et un jury populaire composé de 6 citoyens (jurés)."},
823
- {"q": "Qu'est-ce que la procédure de CRPC ?",
824
- "hint": "Comparution sur Reconnaissance Préalable de Culpabilité. Un 'plaider-coupable' où le procureur propose une peine que le prévenu accepte, avant homologation par le juge du siège."},
825
- {"q": "Le pourvoi en cassation représente-t-il un troisième degré de juridiction ?",
826
- "hint": "Non. La Cour de cassation est juge du droit et non du fait. Elle contrôle l'application de la loi sans rejuger les faits."},
827
- ],
828
- },
829
- ]
830
-
831
- LISTEN_SECONDS = 10 # 900 en production (15 min)
832
- PASS_SCORE = 6
833
-
834
- # ── Cache audio par leçon ─────────────────────────────────────────────────────
835
- _audio_cache = {}
836
- _audio_lock = threading.Lock()
837
-
838
- def get_lecture_audio(lesson_idx):
839
- if lesson_idx in _audio_cache and os.path.exists(_audio_cache[lesson_idx]):
840
- return _audio_cache[lesson_idx]
841
- with _audio_lock:
842
- if lesson_idx in _audio_cache and os.path.exists(_audio_cache[lesson_idx]):
843
- return _audio_cache[lesson_idx]
844
- try:
845
- text = LESSONS[lesson_idx]["text"]
846
- tts = gTTS(text=text, lang="fr", slow=False)
847
- path = tempfile.mktemp(suffix=".mp3")
848
- tts.save(path)
849
- _audio_cache[lesson_idx] = path
850
- return path
851
- except Exception as e:
852
- print(f"[law-app] gTTS erreur : {e}")
853
- return None
854
-
855
- # ── État ──────────────────────────────────────────────────────────────────────
856
- def fresh():
857
- return json.dumps({"phase": "idle", "listen_start": None,
858
- "question_idx": None, "lesson_idx": 0})
859
-
860
- def S(s): return json.loads(s)
861
- def D(d): return json.dumps(d)
862
-
863
- # ── Évaluation ────────────────────────────────────────────────────────────────
864
- _SYSTEM_PROMPT = (
865
- "Tu es un professeur de droit français strict et bienveillant. "
866
- "Tu évalues les réponses des étudiants et réponds UNIQUEMENT en JSON valide sur une seule ligne.\n\n"
867
- "# Exemples de comportement attendu :\n\n"
868
- "Question : Quelles sont les finalités respectives de l'action publique et de l'action civile ?\n"
869
- "Réponse de l'étudiant : je ne sais pas\n"
870
- "Éléments attendus : Action publique = application d'une peine (trouble social) / Action civile = réparation du préjudice (dommages-intérêts).\n"
871
- '{"score": 0, "feedback": "Aucune réponse n\'a été proposée. Pour rappel, l\'action publique vise à sanctionner le trouble social par une peine, tandis que l\'action civile cherche à réparer le préjudice subi."}\n\n'
872
- "Question : Quelles sont les finalités respectives de l'action publique et de l'action civile ?\n"
873
- "Réponse de l'étudiant : lacion publique a pour but d'apication de pain laction civil a pour but domager le victime\n"
874
- "Éléments attendus : Action publique = application d'une peine (trouble social) / Action civile = réparation du préjudice (dommages-intérêts).\n"
875
- '{"score": 7, "feedback": "Les notions de peine et de dédommagement sont comprises, mais l\'orthographe et la formulation doivent être grandement améliorées."}\n\n'
876
- "Question : Quel magistrat est compétent pour placer une personne en détention provisoire au cours de l'instruction ?\n"
877
- "Réponse de l'étudiant : juge dinstrucion\n"
878
- "Éléments attendus : Le Juge des libertés et de la détention (JLD), sur saisine du juge d'instruction ou du procureur.\n"
879
- '{"score": 2, "feedback": "Le juge d\'instruction saisit le dossier mais n\'est pas compétent pour décider de la détention provisoire ; cette décision relève exclusivement du Juge des libertés et de la détention (JLD)."}\n\n'
880
- "Question : Quelle différence majeure existe-t-il entre un OPJ et un APJ ?\n"
881
- "Réponse de l'étudiant : l apj est t'une police administrative aopg police judiciare\n"
882
- "Éléments attendus : L'OPJ (Officier) a le pouvoir de prendre des mesures coercitives comme le placement en garde à vue, l'APJ (Agent) le seconde.\n"
883
- '{"score": 1, "feedback": "C\'est incorrect. L\'APJ et l\'OPJ relèvent tous deux de la police judiciaire. La différence majeure réside dans les pouvoirs coercitifs (comme décider d\'une garde à vue) réservés à l\'OPJ, que l\'APJ seconde."}'
884
- )
885
-
886
- def _keyword_fallback(answer, hint, reason=""):
887
- kw = [w.strip().lower() for w in hint.split(",")]
888
- hits = sum(1 for k in kw if k in answer.lower())
889
- score = min(10, round((hits / max(len(kw), 1)) * 10))
890
- label = f"(Sans IA – {reason} – " if reason else "(Sans IA – "
891
- return score, f"{label}mots-clés : {hits}/{len(kw)})"
892
-
893
- def evaluate_answer(question, answer, hint):
894
- if not _server_ok:
895
- return _keyword_fallback(answer, hint, reason="serveur indisponible")
896
-
897
- user_msg = (
898
- f"Question : {question}\n"
899
- f"Réponse de l'étudiant : {answer}\n"
900
- f"Éléments attendus : {hint}\n\n"
901
- "Évalue cette réponse sur 10 et donne un commentaire bref en français.\n"
902
- "Réponds UNIQUEMENT avec ce JSON sur une seule ligne, sans rien d'autre :\n"
903
- '{"score": <entier 0-10>, "feedback": "<commentaire bref>"}'
904
- )
905
-
906
- payload = {
907
- "model": "local",
908
- "messages": [
909
- {"role": "system", "content": _SYSTEM_PROMPT},
910
- {"role": "user", "content": user_msg},
911
- ],
912
- "max_tokens": 150,
913
- "temperature": 0.1,
914
- "stop": ["\n\n", "###"],
915
- }
916
-
917
- try:
918
- resp = requests.post(CHAT_ENDPOINT, json=payload, timeout=60)
919
- resp.raise_for_status()
920
- text = resp.json()["choices"][0]["message"]["content"].strip()
921
- print(f"[law-app] Réponse modèle : {text}")
922
- start = text.find("{")
923
- end = text.rfind("}") + 1
924
- if start == -1 or end == 0:
925
- raise ValueError("Pas de JSON dans la réponse")
926
- data = json.loads(text[start:end])
927
- return max(0, min(10, int(data["score"]))), data.get("feedback", "")
928
- except Exception as e:
929
- print(f"[law-app] Erreur éval : {e}")
930
- return _keyword_fallback(answer, hint, reason=str(e))
931
-
932
- # ── Handlers ──────────────────────────────────────────────────────────────────
933
- def start_listening(ss):
934
- state = S(ss)
935
- lesson_idx = state.get("lesson_idx", 0)
936
- lesson = LESSONS[lesson_idx]
937
- state.update({"phase": "listen", "listen_start": time.time(), "question_idx": None})
938
- audio = get_lecture_audio(lesson_idx)
939
- total = len(LESSONS)
940
- msg = (
941
- f"🎧 **{lesson['title']}** ({lesson_idx + 1}/{total})\n\n"
942
- "Écoutez pendant **15 minutes** complètes, puis cliquez sur **🔄 Vérifier le temps**."
943
- )
944
- return (
945
- audio, msg,
946
- gr.update(visible=True),
947
- gr.update(visible=False),
948
- gr.update(visible=False),
949
- gr.update(visible=False),
950
- gr.update(interactive=False, value="⏳ Écoute en cours…"),
951
- D(state),
952
- )
953
-
954
- def check_timer(ss):
955
- state = S(ss)
956
- if state["phase"] != "listen" or not state["listen_start"]:
957
- return gr.update(), "⚠️ Lancez d'abord l'écoute.", ss
958
- rem = max(0, LISTEN_SECONDS - (time.time() - state["listen_start"]))
959
- m, s = divmod(int(rem), 60)
960
- if rem <= 0:
961
- return (
962
- gr.update(interactive=True, value="✅ Accéder au Quiz"),
963
- "✅ Durée atteinte ! Cliquez sur **Accéder au Quiz**.",
964
- ss,
965
- )
966
- return (
967
- gr.update(interactive=False, value=f"⏳ Encore {m:02d}:{s:02d}"),
968
- f"🎧 Patientez encore **{m} min {s:02d} s**.",
969
- ss,
970
- )
971
-
972
- def open_quiz(ss):
973
- state = S(ss)
974
- elapsed = time.time() - (state["listen_start"] or 0)
975
- if elapsed < LISTEN_SECONDS:
976
- m, s = divmod(int(LISTEN_SECONDS - elapsed), 60)
977
- return (
978
- gr.update(visible=True), gr.update(visible=False),
979
- gr.update(visible=False), gr.update(visible=False),
980
- f"⚠️ Patientez encore **{m} min {s:02d} s**.", "", "", ss,
981
- )
982
- lesson_idx = state.get("lesson_idx", 0)
983
- idx = random.randrange(len(LESSONS[lesson_idx]["questions"]))
984
- state.update({"phase": "quiz", "question_idx": idx})
985
- return (
986
- gr.update(visible=False), gr.update(visible=True),
987
- gr.update(visible=False), gr.update(visible=False),
988
- "", LESSONS[lesson_idx]["questions"][idx]["q"], "", D(state),
989
- )
990
-
991
- # ── Submit avec désactivation immédiate du bouton ─────────────────────────────
992
- def submit_answer(answer, ss):
993
- state = S(ss)
994
- lesson_idx = state.get("lesson_idx", 0)
995
-
996
- if state["phase"] != "quiz":
997
- yield (
998
- gr.update(visible=True), gr.update(visible=False),
999
- gr.update(visible=False), gr.update(visible=False),
1000
- "",
1001
- gr.update(value="📤 Soumettre", interactive=True),
1002
- gr.update(interactive=True),
1003
- ss,
1004
- )
1005
- return
1006
-
1007
- # Étape 1 : désactiver immédiatement bouton + textbox
1008
- yield (
1009
- gr.update(visible=False), gr.update(visible=True),
1010
- gr.update(visible=False), gr.update(visible=False),
1011
- "",
1012
- gr.update(value="⏳ Réponse soumise — Correction en cours…", interactive=False),
1013
- gr.update(interactive=False),
1014
- ss,
1015
- )
1016
-
1017
- # Étape 2 : évaluation IA
1018
- q = LESSONS[lesson_idx]["questions"][state["question_idx"]]
1019
- score, fb = evaluate_answer(q["q"], answer, q["hint"])
1020
- total = len(LESSONS)
1021
- is_last = (lesson_idx >= total - 1)
1022
-
1023
- if score >= PASS_SCORE:
1024
- state["phase"] = "pass"
1025
- result = (
1026
- f"## 🎉 Bravo ! Leçon {lesson_idx + 1}/{total} réussie.\n\n"
1027
- f"**Score : {score}/10**\n\n**Commentaire :** {fb}"
1028
- )
1029
- next_label = "🏆 Voir mon certificat" if is_last else f"➡️ {LESSONS[lesson_idx + 1]['title']}"
1030
- else:
1031
- state["phase"] = "fail"
1032
- result = (
1033
- f"## ❌ Score insuffisant : {score}/10\n\n"
1034
- f"**Commentaire :** {fb}\n\n"
1035
- f"Seuil requis : **{PASS_SCORE}/10**. "
1036
- "Veuillez **réécouter la leçon** avant de retenter."
1037
- )
1038
- next_label = "🔄 Réécouter la leçon"
1039
-
1040
- # Étape 3 : afficher résultat, réactiver bouton, mettre à jour next_btn
1041
- yield (
1042
- gr.update(visible=False), gr.update(visible=False),
1043
- gr.update(visible=True), gr.update(visible=False),
1044
- result,
1045
- gr.update(value="📤 Soumettre", interactive=True),
1046
- gr.update(value="", interactive=True),
1047
- D(state),
1048
- )
1049
-
1050
- def handle_next(ss):
1051
- state = S(ss)
1052
- lesson_idx = state.get("lesson_idx", 0)
1053
- total = len(LESSONS)
1054
-
1055
- if state["phase"] == "fail":
1056
- return start_listening(D(state))
1057
-
1058
- if lesson_idx >= total - 1:
1059
- state["phase"] = "done"
1060
- cert = (
1061
- "# 🏆 Félicitations !\n\n"
1062
- "Vous avez complété avec succès les **5 leçons** du cours de procédure pénale :\n\n"
1063
- "✅ Leçon 1 — Les principes directeurs et la preuve pénale\n\n"
1064
- "✅ Leçon 2 — La police judiciaire, l'enquête et la garde à vue\n\n"
1065
- "✅ Leçon 3 — L'action publique, l'action civile et la prescription\n\n"
1066
- "✅ Leçon 4 — L'instruction préparatoire et les mesures de sûreté\n\n"
1067
- "✅ Leçon 5 — Les juridictions de jugement et les voies de recours\n\n"
1068
- "---\n\n"
1069
- "*Ce certificat atteste de votre participation et de votre réussite "
1070
- "aux évaluations du cours de procédure pénale.*"
1071
- )
1072
- return (
1073
- None, cert,
1074
- gr.update(visible=False),
1075
- gr.update(visible=False),
1076
- gr.update(visible=False),
1077
- gr.update(visible=True),
1078
- gr.update(interactive=False, value="✅ Terminé"),
1079
- D(state),
1080
- )
1081
- else:
1082
- state["lesson_idx"] = lesson_idx + 1
1083
- state["listen_start"] = None
1084
- return start_listening(D(state))
1085
-
1086
- def restart_from_cert(ss):
1087
- return start_listening(fresh())
1088
-
1089
- # ── UI ────────────────────────────────────────────────────────────────────────
1090
- CSS = "footer{display:none!important;} #title{text-align:center;}"
1091
-
1092
- with gr.Blocks(title="Cours de Droit", css=CSS) as demo:
1093
- session = gr.State(fresh())
1094
-
1095
- gr.Markdown(
1096
- "# ⚖️ Cours de Droit — Formation Juridique\n"
1097
- "Écoutez chaque leçon · Répondez à la question · Progressez leçon par leçon",
1098
- elem_id="title",
1099
- )
1100
-
1101
- with gr.Column(visible=True) as listen_col:
1102
- gr.Markdown("## 🎧 Étape 1 — Écoute de la leçon")
1103
- listen_msg = gr.Markdown("Cliquez sur **Démarrer** pour commencer.")
1104
- audio_out = gr.Audio(label="Cours audio (français)", autoplay=False, interactive=False)
1105
- timer_info = gr.Markdown("")
1106
- with gr.Row():
1107
- start_btn = gr.Button("▶️ Démarrer l'écoute", variant="primary")
1108
- check_btn = gr.Button("🔄 Vérifier le temps", variant="secondary")
1109
- quiz_btn = gr.Button("✅ Accéder au Quiz", variant="secondary", interactive=False)
1110
-
1111
- with gr.Column(visible=False) as quiz_col:
1112
- gr.Markdown("## 📝 Étape 2 — Question d'évaluation")
1113
- question_md = gr.Markdown("")
1114
- answer_box = gr.Textbox(label="Votre réponse", placeholder="Rédigez ici…", lines=5)
1115
- submit_btn = gr.Button("📤 Soumettre", variant="primary")
1116
-
1117
- with gr.Column(visible=False) as result_col:
1118
- gr.Markdown("## 📊 Résultat")
1119
- result_md = gr.Markdown("")
1120
- next_btn = gr.Button("➡️ Leçon suivante", variant="primary")
1121
-
1122
- with gr.Column(visible=False) as cert_col:
1123
- cert_md = gr.Markdown("")
1124
- restart_btn = gr.Button("🔄 Recommencer depuis le début", variant="secondary")
1125
-
1126
- # ── Câblage ───────────────────────────────────────────────────────────────
1127
- LISTEN_OUT = [audio_out, listen_msg,
1128
- listen_col, quiz_col, result_col, cert_col,
1129
- quiz_btn, session]
1130
-
1131
- start_btn.click(start_listening, [session], LISTEN_OUT)
1132
- check_btn.click(check_timer, [session], [quiz_btn, timer_info, session])
1133
-
1134
- QUIZ_OUT = [listen_col, quiz_col, result_col, cert_col,
1135
- timer_info, question_md, answer_box, session]
1136
- quiz_btn.click(open_quiz, [session], QUIZ_OUT)
1137
-
1138
- # submit_answer est un générateur → outputs inclut submit_btn et answer_box
1139
- RES_OUT = [listen_col, quiz_col, result_col, cert_col,
1140
- result_md, submit_btn, answer_box, session]
1141
- submit_btn.click(submit_answer, [answer_box, session], RES_OUT)
1142
- answer_box.submit(submit_answer, [answer_box, session], RES_OUT)
1143
-
1144
- next_btn.click(handle_next, [session], LISTEN_OUT)
1145
- restart_btn.click(restart_from_cert, [session], LISTEN_OUT)
1146
-
1147
- if __name__ == "__main__":
1148
- demo.queue()
1149
- demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)
1150
-