Checkpoint: Classification IA automatique à l'insertion RSS, suppression badge IA et bouton Classer avec l'IA dans VeilleDashboard, suppression section Analyse IA dans la boîte de dialogue détail, mise à jour AAPDashboard avec champs IA et affichage catégorie IA en priorité (vue liste et vignettes)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "dab72037",
|
||||
"timestamp": 1780479118639
|
||||
"version": "69e54e0a",
|
||||
"timestamp": 1781255928989
|
||||
}
|
||||
@@ -49,6 +49,10 @@ interface AAPItem {
|
||||
datePublication: Date | null;
|
||||
lien: string | null;
|
||||
importedAt: Date;
|
||||
iaRelevant: boolean | null;
|
||||
iaCategorie: string | null;
|
||||
iaClassifiedBy: "ia" | "rules" | null;
|
||||
iaReason: string | null;
|
||||
}
|
||||
|
||||
const CAT_COLORS: Record<string, string> = {
|
||||
@@ -294,14 +298,14 @@ function AAPListView({ items }: { items: AAPItem[] }) {
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{items.map((item, idx) => (
|
||||
<tr key={item.id} className={cn("hover:bg-muted/30 transition-colors border-l-4", CAT_ACCENT[item.categorie] || "border-l-transparent")}>
|
||||
<tr key={item.id} className={cn("hover:bg-muted/30 transition-colors border-l-4", CAT_ACCENT[item.iaCategorie || item.categorie] || "border-l-transparent")}>
|
||||
<td className="px-4 py-3 text-muted-foreground/50 text-xs">{idx + 1}</td>
|
||||
<td className="px-4 py-3">
|
||||
<p className="font-medium text-foreground line-clamp-2 max-w-sm leading-snug">{item.titre}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<Badge variant="outline" className={cn("text-xs", CAT_COLORS[item.categorie])}>
|
||||
{item.categorie}
|
||||
<Badge variant="outline" className={cn("text-xs", CAT_COLORS[item.iaCategorie || item.categorie])}>
|
||||
{item.iaCategorie || item.categorie}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground text-xs">{item.region || "—"}</td>
|
||||
@@ -330,11 +334,11 @@ function AAPGridView({ items }: { items: AAPItem[] }) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{items.map((item) => (
|
||||
<Card key={item.id} className={cn("group hover:shadow-md transition-all duration-200 border-border overflow-hidden border-l-4", CAT_ACCENT[item.categorie] || "")}>
|
||||
<Card key={item.id} className={cn("group hover:shadow-md transition-all duration-200 border-border overflow-hidden border-l-4", CAT_ACCENT[item.iaCategorie || item.categorie] || "")}>
|
||||
<CardHeader className="pb-2 pt-4 px-4">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<Badge variant="outline" className={cn("text-xs flex-shrink-0", CAT_COLORS[item.categorie])}>
|
||||
{item.categorie}
|
||||
<Badge variant="outline" className={cn("text-xs flex-shrink-0", CAT_COLORS[item.iaCategorie || item.categorie])}>
|
||||
{item.iaCategorie || item.categorie}
|
||||
</Badge>
|
||||
{item.lien && (
|
||||
<a href={item.lien} target="_blank" rel="noopener noreferrer" className="text-muted-foreground hover:text-accent transition-colors flex-shrink-0">
|
||||
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
BookOpen,
|
||||
Trash2,
|
||||
AlertTriangle,
|
||||
Sparkles,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
AlertDialog,
|
||||
@@ -211,46 +210,6 @@ function VeilleDetailDialog({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Analyse IA */}
|
||||
{item.iaClassifiedBy && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Sparkles size={14} className="text-violet-500" />
|
||||
<h3 className="text-sm font-semibold text-foreground">Analyse par l'IA</h3>
|
||||
</div>
|
||||
<div className={cn(
|
||||
"p-4 rounded-lg border space-y-2",
|
||||
item.iaRelevant === false
|
||||
? "bg-orange-50 border-orange-200"
|
||||
: item.iaClassifiedBy === "ia"
|
||||
? "bg-violet-50 border-violet-200"
|
||||
: "bg-muted/20 border-border/50"
|
||||
)}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn(
|
||||
"inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full",
|
||||
item.iaRelevant === false
|
||||
? "bg-orange-100 text-orange-700 border border-orange-300"
|
||||
: item.iaClassifiedBy === "ia"
|
||||
? "bg-violet-100 text-violet-700 border border-violet-300"
|
||||
: "bg-muted text-muted-foreground border border-border"
|
||||
)}>
|
||||
<Sparkles size={10} />
|
||||
{item.iaClassifiedBy === "ia" ? "Classé par l'IA" : "Classé par règles"}
|
||||
</span>
|
||||
{item.iaRelevant === false && (
|
||||
<span className="text-xs text-orange-700 font-medium">Hors périmètre médico-social</span>
|
||||
)}
|
||||
{item.iaRelevant === true && item.iaCategorie && (
|
||||
<span className="text-xs text-violet-700 font-medium">{item.iaCategorie}</span>
|
||||
)}
|
||||
</div>
|
||||
{item.iaReason && (
|
||||
<p className="text-xs text-foreground/70 leading-relaxed italic">"{item.iaReason}"</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Lien externe */}
|
||||
{item.lien && (
|
||||
@@ -278,15 +237,6 @@ export default function VeilleDashboard() {
|
||||
const { user } = useLocalAuth();
|
||||
const isAdmin = user?.role === "admin";
|
||||
const utils = trpc.useUtils();
|
||||
const reclassifyMutation = trpc.veille.reclassifyWithAI.useMutation({
|
||||
onSuccess: (data) => {
|
||||
toast.success(`Classification IA terminée — ${data.processed} article(s) traité(s)${data.errors > 0 ? `, ${data.errors} erreur(s)` : ""}`);
|
||||
utils.veille.list.invalidate();
|
||||
},
|
||||
onError: (err) => {
|
||||
toast.error(`Erreur lors de la classification IA : ${err.message}`);
|
||||
},
|
||||
});
|
||||
const purgeMutation = trpc.veille.purge.useMutation({
|
||||
onSuccess: (data) => {
|
||||
toast.success(`Purge effectuée — ${data.deleted} entrée(s) supprimée(s)`);
|
||||
@@ -369,17 +319,6 @@ export default function VeilleDashboard() {
|
||||
</Button>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 border-primary/50 text-primary hover:bg-primary hover:text-primary-foreground"
|
||||
onClick={() => reclassifyMutation.mutate({ limit: 50 })}
|
||||
disabled={reclassifyMutation.isPending}
|
||||
title="Reclassifier les 50 prochains articles non encore traités par l'IA"
|
||||
>
|
||||
{reclassifyMutation.isPending ? <Loader2 size={15} className="animate-spin" /> : <Sparkles size={15} />}
|
||||
{reclassifyMutation.isPending ? "Classification en cours..." : "Classer avec l'IA"}
|
||||
</Button>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="gap-2 border-destructive/50 text-destructive hover:bg-destructive hover:text-destructive-foreground ml-2">
|
||||
@@ -511,23 +450,7 @@ function VeilleListView({ items, onDetail }: { items: VeilleItem[]; onDetail: (i
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-xs">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-muted-foreground">{item.categorie || "—"}</span>
|
||||
{item.iaClassifiedBy === "ia" && (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-full w-fit",
|
||||
item.iaRelevant === false
|
||||
? "bg-orange-100 text-orange-700 border border-orange-200"
|
||||
: "bg-violet-100 text-violet-700 border border-violet-200"
|
||||
)}
|
||||
title={item.iaReason || "Classé par l'IA"}
|
||||
>
|
||||
<Sparkles size={9} />
|
||||
{item.iaRelevant === false ? "Hors périmètre" : "IA"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-muted-foreground">{item.categorie || "—"}</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground text-xs">{item.niveau || "—"}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground text-xs">{item.territoire || "—"}</td>
|
||||
|
||||
Reference in New Issue
Block a user