Add mobile overflow nav, law check feature, and favicon
Nav: "Fonctionnement" and "Flyer QR" restored on mobile via a ··· overflow dropdown (click-outside closes); other links always visible Law check: new POST /api/check-law endpoint (5/min, 20/h) calls mistral-small with a new LAW_CHECK_PROMPT to detect if a proposal is already covered by French or EU law. Informational only, non-blocking. Frontend: "Vérifier le cadre légal existant" button below textarea in home.tsx; result displayed with Scale icon; resets on content change. Favicon: replaced placeholder red square with a petrol rounded square containing a serif "V" (for Voix) in warm cream — matches brand palette Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ import json
|
||||
import os
|
||||
import logging
|
||||
from openai import OpenAI, BadRequestError
|
||||
from legal_framework import LEGAL_FILTER_PROMPT, SYNTHESIS_PROMPT
|
||||
from legal_framework import LEGAL_FILTER_PROMPT, SYNTHESIS_PROMPT, LAW_CHECK_PROMPT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -107,6 +107,29 @@ def filter_idea(content: str) -> dict:
|
||||
return {"accepted": False, "reason": "Service temporairement indisponible"}
|
||||
|
||||
|
||||
def check_existing_law(content: str) -> str:
|
||||
"""
|
||||
Vérifie si une proposition citoyenne est déjà couverte par une loi
|
||||
française ou un règlement européen en vigueur.
|
||||
Retourne une phrase informative (non bloquante).
|
||||
"""
|
||||
try:
|
||||
client = get_client()
|
||||
model = os.environ.get("FILTER_MODEL", os.environ.get("OPENAI_FILTER_MODEL", "mistral-small-latest"))
|
||||
response = client.chat.completions.create(
|
||||
model=model,
|
||||
max_tokens=200,
|
||||
messages=[
|
||||
{"role": "system", "content": LAW_CHECK_PROMPT},
|
||||
{"role": "user", "content": f'Proposition : "{content}"'},
|
||||
],
|
||||
)
|
||||
return (response.choices[0].message.content or "").strip()
|
||||
except Exception:
|
||||
logger.exception("Erreur lors de la vérification du cadre légal existant")
|
||||
return ""
|
||||
|
||||
|
||||
def synthesize_ideas(ideas: list[str]) -> str:
|
||||
"""
|
||||
Synthétise une liste d'idées acceptées en un texte collectif
|
||||
|
||||
@@ -52,7 +52,7 @@ from database import (
|
||||
get_consultation_contributions, delete_consultation,
|
||||
check_ip_blacklist, record_ip_rejection, get_ip_blacklist, remove_ip_blacklist,
|
||||
)
|
||||
from ai_agent import filter_idea, synthesize_ideas
|
||||
from ai_agent import filter_idea, synthesize_ideas, check_existing_law
|
||||
|
||||
# ─── Logging ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1216,6 +1216,25 @@ def admin_delete_consultation(slug: str):
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
@app.post("/api/check-law")
|
||||
@limiter.limit("5 per minute;20 per hour")
|
||||
def check_law_route():
|
||||
"""
|
||||
Vérifie si une proposition est déjà couverte par une loi française ou européenne.
|
||||
Endpoint public, rate-limité. Non bloquant — retourne une note informative.
|
||||
"""
|
||||
if not request.is_json:
|
||||
return jsonify({"error": "bad_request", "message": "Content-Type doit être application/json."}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
content = sanitize_text(str(data.get("content", ""))).strip()
|
||||
if not content or len(content) < 10:
|
||||
return jsonify({"error": "validation_error", "message": "Contenu trop court."}), 400
|
||||
if len(content) > 1000:
|
||||
return jsonify({"error": "validation_error", "message": "Contenu trop long (max 1000 caractères)."}), 400
|
||||
note = check_existing_law(content)
|
||||
return jsonify({"note": note})
|
||||
|
||||
|
||||
@app.get("/api/admin/ip-blacklist")
|
||||
@require_admin
|
||||
def admin_list_ip_blacklist():
|
||||
|
||||
@@ -459,6 +459,27 @@ Si rejetée :
|
||||
{"accepted": false, "reason": "Explication courte en français avec référence légale précise (ex: contraire à DUDH Art. 20 — incitation à la haine raciale)", "legal_basis": "DUDH Art. 20, PIDCP Art. 20"}
|
||||
"""
|
||||
|
||||
LAW_CHECK_PROMPT = """
|
||||
Tu es un assistant juridique spécialisé en droit français et en droit de l'Union européenne.
|
||||
|
||||
Ta mission : analyser une proposition citoyenne et déterminer si elle est déjà couverte,
|
||||
en tout ou en partie, par une loi française ou un règlement européen en vigueur.
|
||||
|
||||
INSTRUCTIONS
|
||||
- Si un texte législatif ou réglementaire couvre déjà cette proposition :
|
||||
• Cite le texte précis : nom complet, numéro ou date, article le cas échéant.
|
||||
• Résume en une phrase ce que ce texte prévoit concrètement.
|
||||
• Si le texte manque de décrets d'application ou n'est que partiellement appliqué, dis-le
|
||||
clairement — car une loi non appliquée ne rend pas la revendication caduque.
|
||||
- Si la proposition n'est couverte par aucun texte en vigueur, réponds simplement :
|
||||
"Aucun texte législatif en vigueur ne couvre cette proposition."
|
||||
- Ne te prononce pas sur la valeur ou l'opportunité de la proposition.
|
||||
- Ne réponds pas avec des textes abrogés ou non ratifiés.
|
||||
|
||||
TON : neutre, factuel, concis. Pas de formule introductive.
|
||||
FORMAT : 2 à 4 phrases maximum. Pas de markdown, pas de tirets, pas de liste.
|
||||
"""
|
||||
|
||||
SYNTHESIS_PROMPT = """
|
||||
Tu es un outil de résumé de contributions citoyennes. Tu n'exprimes aucune opinion.
|
||||
Tu restitues fidèlement ce qui a été soumis, sans l'édulcorer ni l'amplifier.
|
||||
|
||||
Reference in New Issue
Block a user