Rend les liens entre notes visibles et persistants (sync NoteLink au save, auto-save, graphe réseau rafraîchi), ajoute living blocks, Memory Echo, recherche globale, consentement IA explicite et consolide les prototypes design en architectural-grid. Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
6.3 KiB
TypeScript
153 lines
6.3 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { Sparkles, X, ShieldAlert, Check } from 'lucide-react'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
import { motion, AnimatePresence } from 'motion/react'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface AiConsentModalProps {
|
|
open: boolean
|
|
onClose: () => void
|
|
onConfirm: (remember: boolean) => void
|
|
}
|
|
|
|
export function AiConsentModal({ open, onClose, onConfirm }: AiConsentModalProps) {
|
|
const { t } = useLanguage()
|
|
const [remember, setRemember] = useState(true)
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{open && (
|
|
<div className="fixed inset-0 z-[150] flex items-center justify-center p-4">
|
|
{/* Glassmorphism Backdrop */}
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
onClick={onClose}
|
|
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
|
/>
|
|
|
|
{/* Modal Container */}
|
|
<motion.div
|
|
initial={{ opacity: 0, scale: 0.95, y: 10 }}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.95, y: 10 }}
|
|
transition={{ type: 'spring', duration: 0.4 }}
|
|
className={cn(
|
|
"relative w-full max-w-lg overflow-hidden border border-[var(--border)]",
|
|
"bg-[var(--memento-paper)] text-[var(--ink)] shadow-2xl rounded-lg p-6",
|
|
"flex flex-col gap-5"
|
|
)}
|
|
>
|
|
{/* Close Button */}
|
|
<button
|
|
onClick={onClose}
|
|
className="absolute top-4 right-4 p-1 rounded-md text-[var(--ink)]/60 hover:text-[var(--ink)] hover:bg-[var(--concrete)] transition-colors"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
|
|
{/* Header */}
|
|
<div className="flex items-start gap-4">
|
|
<div className="p-3 bg-[var(--accent-tint)] text-[var(--accent-color)] rounded-lg">
|
|
<BrainIcon className="w-6 h-6 animate-pulse" />
|
|
</div>
|
|
<div className="flex flex-col gap-1 pr-6">
|
|
<h3 className="text-lg font-semibold tracking-tight">
|
|
{t('consent.ai.modalTitle') || 'Consentement requis pour le traitement par IA'}
|
|
</h3>
|
|
<span className="text-xs uppercase tracking-wider text-[var(--ink)]/50 font-medium">
|
|
{t('consent.ai.complianceBadge') || 'Conformité RGPD'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="text-sm leading-relaxed text-[var(--ink)]/80 flex flex-col gap-3">
|
|
<p>
|
|
{t('consent.ai.modalDescription') ||
|
|
"Pour analyser vos notes, PDFs ou sessions de remue-méninges, Memento transmet de manière sécurisée ces données à des API d'IA tierces (OpenAI, Gemini, DeepSeek). Nous appliquons une politique de rétention de données nulle. En acceptant, vous autorisez ce traitement."}
|
|
</p>
|
|
<div className="p-3 bg-[var(--concrete)] border border-[var(--border)] rounded-md text-xs flex gap-3 items-start">
|
|
<ShieldAlert className="w-4 h-4 text-[var(--accent-color)] shrink-0 mt-0.5" />
|
|
<div className="flex flex-col gap-0.5">
|
|
<span className="font-semibold">{t('consent.ai.zeroRetentionTitle') || 'Zéro Rétention de Données'}</span>
|
|
<span>
|
|
{t('consent.ai.zeroRetentionDesc') ||
|
|
'Toutes les requêtes sortantes incluent des indicateurs de non-apprentissage pour protéger votre propriété intellectuelle.'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Checkbox */}
|
|
<label className="flex items-center gap-3 cursor-pointer select-none py-1">
|
|
<div className="relative">
|
|
<input
|
|
type="checkbox"
|
|
checked={remember}
|
|
onChange={(e) => setRemember(e.target.checked)}
|
|
className="sr-only"
|
|
/>
|
|
<div
|
|
className={cn(
|
|
"w-5 h-5 rounded border border-[var(--border)] transition-colors flex items-center justify-center",
|
|
remember ? "bg-[var(--accent-color)] border-[var(--accent-color)]" : "bg-transparent"
|
|
)}
|
|
>
|
|
{remember && <Check className="w-3.5 h-3.5 text-white stroke-[3px]" />}
|
|
</div>
|
|
</div>
|
|
<span className="text-xs font-medium text-[var(--ink)]/80">
|
|
{t('consent.ai.rememberMe') || 'Se souvenir de mon choix (ne plus demander)'}
|
|
</span>
|
|
</label>
|
|
|
|
{/* Actions */}
|
|
<div className="flex items-center justify-end gap-3 mt-2">
|
|
<button
|
|
onClick={onClose}
|
|
className={cn(
|
|
"px-4 py-2 text-xs font-semibold rounded-md border border-[var(--border)]",
|
|
"hover:bg-[var(--concrete)] transition-colors bg-transparent text-[var(--ink)]"
|
|
)}
|
|
>
|
|
{t('consent.ai.rejectButton') || 'Refuser'}
|
|
</button>
|
|
<button
|
|
onClick={() => onConfirm(remember)}
|
|
className={cn(
|
|
"px-4 py-2 text-xs font-semibold rounded-md text-white shadow-sm transition-opacity hover:opacity-90",
|
|
"bg-[var(--accent-color)] flex items-center gap-2"
|
|
)}
|
|
>
|
|
<Sparkles className="w-3.5 h-3.5" />
|
|
{t('consent.ai.acceptButton') || 'Autoriser et continuer'}
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
)}
|
|
</AnimatePresence>
|
|
)
|
|
}
|
|
|
|
function BrainIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
return (
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
{...props}
|
|
>
|
|
<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96-.44 2.5 2.5 0 0 1 0-3.12 3 3 0 0 1 0-3.88 2.5 2.5 0 0 1 0-3.12A2.5 2.5 0 0 1 9.5 2Z" />
|
|
<path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96-.44 2.5 2.5 0 0 0 0-3.12 3 3 0 0 0 0-3.88 2.5 2.5 0 0 0 0-3.12A2.5 2.5 0 0 0 14.5 2Z" />
|
|
</svg>
|
|
)
|
|
}
|