Add a page detailing the platform's technical workings and AI transparency

Adds a new "Fonctionnement" page and integrates it into the app's routing and navigation to explain the AI's role, data handling, and limitations.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 26cc3ee2-cf8d-46e5-8c74-836cb5ae3a18
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/qrVKaka
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
pironantoine
2026-04-04 06:20:27 +00:00
parent 3bc7d92e6c
commit b604e21f70
2 changed files with 298 additions and 0 deletions
+3
View File
@@ -5,6 +5,7 @@ import { TooltipProvider } from "@/components/ui/tooltip";
import NotFound from "@/pages/not-found";
import Home from "@/pages/home";
import About from "@/pages/about";
import Transparence from "@/pages/transparence";
const queryClient = new QueryClient({
defaultOptions: {
@@ -25,6 +26,7 @@ function Navbar() {
<nav className="flex items-center gap-6 text-sm font-medium">
<Link href="/" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-manifesto-link">Manifeste</Link>
<Link href="/about" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-about-link">À propos</Link>
<Link href="/transparence" className="transition-colors hover:text-foreground/80 text-foreground/60" data-testid="nav-transparence-link">Fonctionnement</Link>
</nav>
</div>
</header>
@@ -39,6 +41,7 @@ function Router() {
<Switch>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/transparence" component={Transparence} />
<Route component={NotFound} />
</Switch>
</main>
@@ -0,0 +1,295 @@
import React from "react";
import { Link } from "wouter";
import { ArrowLeft, Bot, Filter, FileText, Database, AlertTriangle, CheckCircle, XCircle, ArrowRight } from "lucide-react";
import { Button } from "@/components/ui/button";
export default function Transparence() {
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto max-w-3xl px-4 md:px-8 py-12">
<Link href="/">
<Button variant="ghost" className="mb-8 -ml-3 text-muted-foreground">
<ArrowLeft className="mr-2 h-4 w-4" /> Retour
</Button>
</Link>
<header className="mb-12 space-y-4">
<h1 className="text-4xl md:text-5xl font-serif font-bold text-primary">
Comment ça fonctionne
</h1>
<p className="text-xl text-muted-foreground leading-relaxed">
Cette page décrit précisément le rôle de l'intelligence artificielle dans cette plateforme, les données collectées et les limites du système.
</p>
</header>
<div className="space-y-14">
{/* Flux général */}
<section className="space-y-6">
<div className="flex items-center gap-3 text-xl font-bold font-serif border-b border-border/50 pb-2">
<ArrowRight className="h-5 w-5 text-primary" />
<h2>Le parcours d'une contribution</h2>
</div>
<p className="text-foreground/90 leading-relaxed">
Quand vous soumettez une proposition, elle suit quatre étapes automatiques avant d'être visible et intégrée à la synthèse.
</p>
<ol className="space-y-4">
{[
{
num: "1",
titre: "Réception",
desc: "Votre texte est enregistré en base de données avec la date et l'heure. Le pseudonyme est facultatif. Aucune donnée d'identification (adresse IP, compte utilisateur) n'est associée à la contribution.",
},
{
num: "2",
titre: "Filtre de modération (IA)",
desc: "Un modèle de langage (GPT) analyse le texte pour vérifier sa conformité avec les droits fondamentaux reconnus par le droit international. Voir la section dédiée ci-dessous.",
},
{
num: "3",
titre: "Mise à jour de la synthèse (IA)",
desc: "Si la contribution est acceptée, un second modèle relit l'ensemble des contributions retenues et produit un résumé structuré, destiné à être transmis à des représentants politiques.",
},
{
num: "4",
titre: "Affichage",
desc: "La contribution apparaît dans le fil des contributions récentes. La synthèse mise à jour s'affiche immédiatement dans la colonne de droite.",
},
].map((step) => (
<li key={step.num} className="flex gap-4">
<span className="flex-shrink-0 w-7 h-7 rounded-full bg-primary text-primary-foreground flex items-center justify-center text-sm font-bold font-mono">
{step.num}
</span>
<div className="space-y-1">
<p className="font-semibold">{step.titre}</p>
<p className="text-foreground/80 leading-relaxed text-sm">{step.desc}</p>
</div>
</li>
))}
</ol>
</section>
{/* Filtre IA */}
<section className="space-y-6">
<div className="flex items-center gap-3 text-xl font-bold font-serif border-b border-border/50 pb-2">
<Filter className="h-5 w-5 text-primary" />
<h2>L'IA de modération</h2>
</div>
<div className="space-y-4">
<div className="bg-muted/40 border border-border/50 p-5 space-y-2">
<p className="font-mono text-xs font-semibold uppercase tracking-widest text-primary">Modèle utilisé</p>
<p className="text-foreground/90 text-sm leading-relaxed">
OpenAI GPT-4o mini par défaut (configurable via la variable d'environnement <code className="bg-muted px-1 py-0.5 rounded text-xs">OPENAI_FILTER_MODEL</code>). Le modèle est interrogé via l'API OpenAI — aucun fine-tuning ni entraînement n'est effectué sur les contributions des utilisateurs.
</p>
</div>
<div>
<p className="font-semibold mb-2">Ce que le filtre vérifie</p>
<p className="text-foreground/80 text-sm leading-relaxed mb-3">
Le modèle reçoit le texte de la contribution et une liste de critères fondés sur les textes internationaux suivants :
</p>
<ul className="text-sm space-y-1.5 text-foreground/80">
{[
"DUDH (ONU, 1948) Art. 1, 2, 7, 19, 20",
"PIDCP (ONU, 1966) Art. 20, 25, 26",
"CEDH (Conseil de l'Europe, 1950) — Art. 10, 14, 17",
"Charte des droits fondamentaux de l'UE (2000)",
"Convention internationale sur l'élimination de la discrimination raciale — CERD (ONU, 1965)",
"Statut de Rome (CPI, 1998) — incitation au génocide",
"Convention contre la torture (ONU, 1984)",
].map((t) => (
<li key={t} className="flex items-start gap-2">
<span className="text-primary mt-0.5">•</span>
<span>{t}</span>
</li>
))}
</ul>
</div>
<div>
<p className="font-semibold mb-2">Ce qui est refusé</p>
<ul className="text-sm space-y-1.5 text-foreground/80">
{[
"Appels à la haine ou à la discrimination (raciale, religieuse, sexuelle, etc.)",
"Incitation à la violence ou à la persécution d'un groupe",
"Apologie du génocide, de crimes contre l'humanité ou de crimes de guerre",
"Désinformation délibérée visant à nuire à un groupe protégé",
"Contenus portant atteinte à la dignité humaine au sens de l'Art. 1 de la DUDH",
].map((r) => (
<li key={r} className="flex items-start gap-2">
<XCircle className="h-3.5 w-3.5 text-red-500 flex-shrink-0 mt-0.5" />
<span>{r}</span>
</li>
))}
</ul>
</div>
<div>
<p className="font-semibold mb-2">Ce qui est accepté</p>
<p className="text-foreground/80 text-sm leading-relaxed">
Toute proposition qui exprime une opinion politique, sociale, économique ou environnementale — même controversée — dans le respect des droits fondamentaux. Le désaccord, la critique des institutions, la revendication et la protestation sont acceptés et bienvenus.
</p>
</div>
<div className="bg-muted/40 border border-border/50 p-5 space-y-2">
<p className="font-mono text-xs font-semibold uppercase tracking-widest text-primary">En cas de refus</p>
<p className="text-foreground/90 text-sm leading-relaxed">
Le motif du refus vous est communiqué avec la référence légale précise. La contribution n'est jamais rendue publique. Elle est conservée en base de données avec son statut (refusée) pour des raisons de traçabilité interne.
</p>
</div>
</div>
</section>
{/* Synthèse IA */}
<section className="space-y-6">
<div className="flex items-center gap-3 text-xl font-bold font-serif border-b border-border/50 pb-2">
<Bot className="h-5 w-5 text-primary" />
<h2>L'IA de synthèse</h2>
</div>
<div className="space-y-4">
<div className="bg-muted/40 border border-border/50 p-5 space-y-2">
<p className="font-mono text-xs font-semibold uppercase tracking-widest text-primary">Modèle utilisé</p>
<p className="text-foreground/90 text-sm leading-relaxed">
OpenAI GPT-4o par défaut (configurable via <code className="bg-muted px-1 py-0.5 rounded text-xs">OPENAI_SYNTHESIS_MODEL</code>). Un modèle plus puissant est utilisé ici pour produire un résumé de meilleure qualité.
</p>
</div>
<div>
<p className="font-semibold mb-2">Ce que l'IA produit</p>
<p className="text-foreground/80 text-sm leading-relaxed">
Le modèle reçoit l'intégralité des contributions acceptées et produit un résumé structuré par thèmes. L'objectif est de rendre le texte directement lisible et utilisable par un élu ou un service public : les idées sont regroupées, les priorités dégagées, les contradictions éventuelles mentionnées.
</p>
</div>
<div>
<p className="font-semibold mb-2">Ce que l'IA ne fait pas</p>
<ul className="text-sm space-y-1.5 text-foreground/80">
{[
"Elle n'invente pas d'idées qui ne sont pas dans les contributions",
"Elle n'écarte pas une contribution selon ses propres préférences",
"Elle ne hiérarchise pas les contributions par leur auteur",
"Elle ne produit pas de recommandations politiques de son propre chef",
].map((r) => (
<li key={r} className="flex items-start gap-2">
<CheckCircle className="h-3.5 w-3.5 text-green-600 flex-shrink-0 mt-0.5" />
<span>{r}</span>
</li>
))}
</ul>
</div>
</div>
</section>
{/* Données */}
<section className="space-y-6">
<div className="flex items-center gap-3 text-xl font-bold font-serif border-b border-border/50 pb-2">
<Database className="h-5 w-5 text-primary" />
<h2>Données collectées</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full text-sm border-collapse">
<thead>
<tr className="border-b border-border">
<th className="text-left py-2 pr-6 font-mono text-xs uppercase tracking-widest text-muted-foreground">Donnée</th>
<th className="text-left py-2 pr-6 font-mono text-xs uppercase tracking-widest text-muted-foreground">Collectée ?</th>
<th className="text-left py-2 font-mono text-xs uppercase tracking-widest text-muted-foreground">Précisions</th>
</tr>
</thead>
<tbody className="divide-y divide-border/50">
{[
{ donnee: "Texte de la contribution", oui: true, detail: "Conservé en base de données" },
{ donnee: "Pseudonyme", oui: true, detail: "Facultatif, librement choisi par l'utilisateur" },
{ donnee: "Date et heure", oui: true, detail: "Horodatage de la soumission" },
{ donnee: "Résultat du filtre", oui: true, detail: "Acceptée / refusée + motif" },
{ donnee: "Adresse IP", oui: false, detail: "Non conservée" },
{ donnee: "Compte utilisateur", oui: false, detail: "Aucune inscription requise" },
{ donnee: "Cookie de suivi", oui: false, detail: "Aucun tracker, aucune publicité" },
{ donnee: "Données de navigation", oui: false, detail: "Non collectées" },
].map((row) => (
<tr key={row.donnee}>
<td className="py-2.5 pr-6 font-medium">{row.donnee}</td>
<td className="py-2.5 pr-6">
{row.oui
? <span className="text-amber-700 font-semibold">Oui</span>
: <span className="text-green-700 font-semibold">Non</span>
}
</td>
<td className="py-2.5 text-foreground/70">{row.detail}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
{/* Limites */}
<section className="space-y-6">
<div className="flex items-center gap-3 text-xl font-bold font-serif border-b border-border/50 pb-2">
<AlertTriangle className="h-5 w-5 text-primary" />
<h2>Limites du système</h2>
</div>
<div className="space-y-4">
{[
{
titre: "L'IA peut se tromper",
desc: "Le filtre automatique n'est pas infaillible. Une contribution légitime peut être refusée par erreur ; une contribution limite peut passer. Aucun système automatique ne remplace un jugement humain. Si vous estimez qu'un refus est injustifié, vous pouvez reformuler votre contribution.",
},
{
titre: "La synthèse est une interprétation",
desc: "Le résumé produit par l'IA reflète les contributions telles qu'elle les a comprises. Des nuances peuvent être perdues dans l'agrégation. Le texte synthétisé ne doit pas être présenté comme une décision collective formelle, mais comme un aperçu des préoccupations exprimées.",
},
{
titre: "Aucun recours automatique",
desc: "Il n'existe pas encore de mécanisme de contestation d'un refus. Si cette plateforme est déployée dans un cadre institutionnel, il est recommandé d'ajouter un contact humain de recours.",
},
{
titre: "Dépendance à OpenAI",
desc: "Le filtrage et la synthèse dépendent de l'API OpenAI. En cas d'indisponibilité, les soumissions sont refusées par précaution et le service affiche un message d'erreur.",
},
].map((item) => (
<div key={item.titre} className="border-l-4 border-amber-400/60 pl-4 space-y-1">
<p className="font-semibold">{item.titre}</p>
<p className="text-foreground/80 text-sm leading-relaxed">{item.desc}</p>
</div>
))}
</div>
</section>
{/* Modèles */}
<section className="space-y-6">
<div className="flex items-center gap-3 text-xl font-bold font-serif border-b border-border/50 pb-2">
<FileText className="h-5 w-5 text-primary" />
<h2>Récapitulatif technique</h2>
</div>
<div className="bg-muted/40 border border-border/50 p-5">
<table className="w-full text-sm">
<tbody className="divide-y divide-border/50">
{[
{ label: "Backend", val: "Python / Flask" },
{ label: "Base de données", val: "PostgreSQL" },
{ label: "Frontend", val: "React + Vite" },
{ label: "IA de modération", val: "OpenAI GPT-4o mini (défaut)" },
{ label: "IA de synthèse", val: "OpenAI GPT-4o (défaut)" },
{ label: "Données personnelles", val: "Aucune (pseudonyme facultatif uniquement)" },
{ label: "Trackers / publicité", val: "Aucun" },
{ label: "Inscription requise", val: "Non" },
].map((row) => (
<tr key={row.label}>
<td className="py-2 pr-6 font-mono text-xs font-semibold text-muted-foreground uppercase tracking-wider">{row.label}</td>
<td className="py-2 text-foreground/90">{row.val}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
</div>
</div>
</div>
);
}