cleanup: remove unused components to trigger deployment
Some checks failed
Deploy to Production / Build and Deploy (push) Has been cancelled

This commit is contained in:
Antigravity
2026-05-10 13:25:06 +00:00
parent d85851c979
commit 280852914e
131 changed files with 15037 additions and 470 deletions

View File

@@ -0,0 +1,357 @@
import React from 'react';
import {
Sparkles,
ChevronRight,
MessageSquare,
FileCode,
Globe,
Send,
Scissors,
Zap,
Languages,
Layout,
ArrowRightLeft,
BookOpen,
History
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
import { AITab, AITone, Note } from '../types';
interface AISidebarProps {
isOpen: boolean;
setIsOpen: (open: boolean) => void;
activeNote: Note | undefined;
aiTab: AITab;
setAiTab: (tab: AITab) => void;
selectedTone: AITone;
setSelectedTone: (tone: AITone) => void;
}
export const AISidebar: React.FC<AISidebarProps> = ({
isOpen,
setIsOpen,
activeNote,
aiTab,
setAiTab,
selectedTone,
setSelectedTone
}) => {
return (
<AnimatePresence>
{isOpen && (
<motion.aside
initial={{ x: 400, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: 400, opacity: 0 }}
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
className="w-[400px] border-l border-border bg-white shadow-2xl flex flex-col z-50 shrink-0 relative"
>
<div className="p-6 border-b border-border space-y-2">
<div className="flex items-center justify-between">
<h3 className="flex items-center gap-2 font-serif text-xl font-medium text-ink">
<Sparkles size={18} className="text-ochre" />
IA Assistant
</h3>
<button
onClick={() => setIsOpen(false)}
className="p-1 hover:bg-slate-100 rounded-full transition-colors text-muted-ink"
>
<ChevronRight size={20} />
</button>
</div>
<p className="text-[11px] text-muted-ink uppercase tracking-wider font-medium opacity-60 truncate">
"{activeNote?.title}"
</p>
</div>
<div className="flex border-b border-border px-2">
{(['discussion', 'actions', 'resources'] as AITab[]).map((tab) => (
<button
key={tab}
onClick={() => setAiTab(tab)}
className={`flex-1 py-3 text-[10px] uppercase tracking-[0.2em] font-bold transition-all relative
${aiTab === tab ? 'text-manganese' : 'text-muted-ink hover:text-ink/60'}`}
>
{tab}
{aiTab === tab && (
<motion.div
layoutId="activeTab"
className="absolute bottom-0 left-0 right-0 h-0.5 bg-ochre"
/>
)}
</button>
))}
</div>
<div className="flex-1 overflow-y-auto p-6 custom-scrollbar">
<AnimatePresence mode="wait">
{aiTab === 'discussion' && (
<motion.div
key="discussion"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="space-y-8"
>
<div className="h-64 flex flex-col items-center justify-center text-center space-y-4 text-muted-ink/40">
<div className="w-16 h-16 rounded-full border border-dashed border-muted-ink/20 flex items-center justify-center">
<MessageSquare size={24} />
</div>
<p className="text-xs font-serif italic leading-relaxed px-8">Posez une question à l'Assistant pour commencer.</p>
</div>
<div className="space-y-4">
<div className="space-y-3">
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-muted-ink">Contexte</label>
<div className="w-full p-3 bg-glass border border-border rounded-lg text-xs flex items-center justify-between cursor-pointer hover:bg-white/40 transition-colors backdrop-blur-sm">
<div className="flex items-center gap-2">
<FileCode size={14} className="text-muted-ink" />
<span>Cette note</span>
</div>
<ChevronRight size={14} className="rotate-90 text-muted-ink" />
</div>
</div>
<div className="space-y-3">
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-muted-ink">Ton d'écriture</label>
<div className="grid grid-cols-2 gap-2">
{(['Professional', 'Creative', 'Academic', 'Casual'] as AITone[]).map((tone) => (
<button
key={tone}
onClick={() => setSelectedTone(tone)}
className={`p-3 rounded-xl border text-[11px] font-medium transition-all
${selectedTone === tone ? 'bg-manganese text-paper border-manganese shadow-lg shadow-manganese/10' : 'bg-glass border-border text-muted-ink hover:border-ink/20'}`}
>
{tone.toUpperCase().substring(0, 3)}
</button>
))}
</div>
</div>
</div>
</motion.div>
)}
{aiTab === 'actions' && (
<motion.div
key="actions"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="space-y-8"
>
<div className="space-y-8">
<div>
<div className="flex items-center gap-2 mb-4">
<div className="h-px flex-1 bg-border/40" />
<h4 className="text-[10px] uppercase tracking-[0.25em] font-bold text-muted-ink whitespace-nowrap">Transformations</h4>
<div className="h-px flex-1 bg-border/40" />
</div>
<div className="grid grid-cols-2 gap-2">
{[
{ icon: <Sparkles size={14} />, label: 'Clarifier', color: 'ochre' },
{ icon: <Scissors size={14} />, label: 'Raccourcir', color: 'rust' },
{ icon: <Zap size={14} />, label: 'Améliorer', color: 'sage' },
{ icon: <Languages size={14} />, label: 'Traduire', color: 'slate' },
].map((action, i) => (
<button
key={i}
className="flex flex-col items-center gap-3 p-4 bg-glass border border-border rounded-xl transition-all group hover:border-ink/20"
>
<div className={`p-2 rounded-lg bg-slate-50 dark:bg-white/10 transition-colors group-hover:bg-manganese group-hover:text-paper shadow-sm text-ink/60`}>
{action.icon}
</div>
<span className="text-[10px] font-bold text-ink/80 uppercase tracking-widest">{action.label}</span>
</button>
))}
<button className="col-span-2 flex items-center justify-center gap-3 py-3 px-4 bg-glass border border-border rounded-xl text-[10px] font-bold text-ink/80 hover:bg-slate-50 dark:hover:bg-white/10 transition-colors hover:border-ink/20 uppercase tracking-widest">
<FileCode size={14} className="text-muted-ink" />
Convertir en Markdown
</button>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center gap-2 mb-2">
<div className="h-px flex-1 bg-border/40" />
<h4 className="text-[10px] uppercase tracking-[0.25em] font-bold text-muted-ink whitespace-nowrap">Generation Tools</h4>
<div className="h-px flex-1 bg-border/40" />
</div>
<div className="group relative p-6 rounded-2xl bg-white border border-border hover:border-blueprint/30 transition-all duration-500 overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<Layout size={80} className="text-blueprint" />
</div>
<div className="relative space-y-5">
<div className="flex items-center gap-3">
<div className="p-2 bg-slate-50 rounded-lg text-blueprint">
<Layout size={18} />
</div>
<div className="space-y-0.5">
<h5 className="text-sm font-bold text-ink leading-none">Présentation</h5>
<p className="text-[10px] text-muted-ink uppercase tracking-tight">Convertir en slides interactives</p>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<span className="text-[9px] uppercase tracking-[0.2em] font-bold text-muted-ink/60 px-1">Thème</span>
<select className="w-full bg-glass dark:bg-black/20 border border-border rounded-lg px-2 py-2 text-xs outline-none focus:ring-1 ring-blueprint/10 transition-all cursor-pointer">
<option>Architectural Mono</option>
<option>Vibrant Tech</option>
<option>Minimal Silk</option>
</select>
</div>
<div className="space-y-1.5">
<span className="text-[9px] uppercase tracking-[0.2em] font-bold text-muted-ink/60 px-1">Style</span>
<select className="w-full bg-glass dark:bg-black/20 border border-border rounded-lg px-2 py-2 text-xs outline-none focus:ring-1 ring-blueprint/10 transition-all cursor-pointer">
<option>Professional</option>
<option>Creative</option>
<option>Brutalist</option>
</select>
</div>
</div>
<button className="w-full py-3.5 bg-blueprint text-paper rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-blueprint/20 uppercase tracking-[0.2em]">
Générer
<ArrowRightLeft size={14} className="opacity-60" />
</button>
</div>
</div>
<div className="group relative p-6 rounded-2xl bg-white border border-border hover:border-sage/30 transition-all duration-500 overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<BookOpen size={80} className="text-sage" />
</div>
<div className="relative space-y-5">
<div className="flex items-center gap-3">
<div className="p-2 bg-slate-50 rounded-lg text-sage">
<BookOpen size={18} />
</div>
<div className="space-y-0.5">
<h5 className="text-sm font-bold text-ink leading-none">Diagramme</h5>
<p className="text-[10px] text-muted-ink uppercase tracking-tight">Visualisation de structure</p>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<span className="text-[9px] uppercase tracking-[0.2em] font-bold text-muted-ink/60 px-1">Type</span>
<select className="w-full bg-glass dark:bg-black/20 border border-border rounded-lg px-2 py-2 text-xs outline-none focus:ring-1 ring-sage/10 transition-all cursor-pointer">
<option>Logic Flow</option>
<option>Mind Map</option>
<option>Hierarchy</option>
</select>
</div>
<div className="space-y-1.5">
<span className="text-[9px] uppercase tracking-[0.2em] font-bold text-muted-ink/60 px-1">Style</span>
<select className="w-full bg-glass dark:bg-black/20 border border-border rounded-lg px-2 py-2 text-xs outline-none focus:ring-1 ring-sage/10 transition-all cursor-pointer">
<option>Draft</option>
<option>Polished</option>
<option>Handwritten</option>
</select>
</div>
</div>
<button className="w-full py-3.5 bg-sage text-paper rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-sage/20 uppercase tracking-[0.2em]">
Tracer
<ArrowRightLeft size={14} className="opacity-60" />
</button>
</div>
</div>
</div>
<div className="flex flex-col items-center gap-2 opacity-20 py-4">
<History size={16} />
<span className="text-[10px] font-bold uppercase tracking-widest whitespace-nowrap">Auto-Save Enabled</span>
</div>
</div>
</motion.div>
)}
{aiTab === 'resources' && (
<motion.div
key="resources"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="space-y-8"
>
<div className="space-y-6">
<div className="space-y-2">
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-muted-ink">URL (Optionnel)</label>
<div className="relative">
<input type="text" placeholder="https://..." className="w-full bg-glass border border-border rounded-lg pl-3 pr-10 py-3 text-xs outline-none focus:border-blueprint transition-colors" />
<Globe size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-ink/40" />
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-muted-ink">Texte de la ressource</label>
<textarea
rows={8}
placeholder="Collez votre texte ici (markdown, HTML, texte brut...)"
className="w-full bg-glass border border-border rounded-lg p-4 text-xs outline-none focus:border-blueprint transition-colors resize-none leading-relaxed"
/>
</div>
<div className="space-y-3">
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-muted-ink">Mode d'intégration</label>
<div className="grid grid-cols-3 gap-2">
{[
{ id: 'replace', label: 'Remplacer', sub: 'Direct, sans IA' },
{ id: 'append', label: 'Compléter', sub: 'Ajoute sans réécrire' },
{ id: 'merge', label: 'Fusionner', sub: 'Réécrit et intègre' },
].map((mode) => (
<button key={mode.id} className={`flex flex-col items-center justify-center p-3 rounded-lg border transition-all text-center ${mode.id === 'append' ? 'bg-sage/10 border-sage/50 ring-1 ring-sage/10' : 'bg-white border-border hover:bg-slate-50'}`}>
<span className={`text-[11px] font-bold ${mode.id === 'append' ? 'text-sage' : 'text-ink'}`}>{mode.label}</span>
<span className="text-[8px] text-muted-ink opacity-60 leading-tight mt-1 font-medium">{mode.sub}</span>
</button>
))}
</div>
</div>
<button className="w-full py-4 bg-blueprint text-white rounded-xl text-sm font-bold flex items-center justify-center gap-3 hover:opacity-90 transition-opacity shadow-lg shadow-blueprint/20">
<Sparkles size={18} />
Générer l'aperçu
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
<AnimatePresence>
{aiTab === 'discussion' && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
className="p-6 bg-white border-t border-border"
>
<div className="relative">
<textarea
rows={3}
placeholder="Posez une question sur cette note..."
className="w-full bg-glass backdrop-blur-sm border border-border rounded-2xl p-4 pr-12 text-sm outline-none focus:border-blueprint transition-colors resize-none leading-relaxed font-light"
/>
<div className="absolute right-3 bottom-3 flex gap-2">
<button className="p-2 text-muted-ink hover:text-ink rounded-lg transition-colors">
<Globe size={16} />
</button>
<button className="p-2 bg-blueprint text-white rounded-lg transition-transform hover:scale-105 active:scale-95 shadow-lg shadow-blueprint/10">
<Send size={16} />
</button>
</div>
</div>
<p className="text-[9px] text-muted-ink text-center mt-3 uppercase tracking-widest font-bold opacity-30 italic">Maj+Entrée = nouvelle ligne</p>
</motion.div>
)}
</AnimatePresence>
</motion.aside>
)}
</AnimatePresence>
);
};

View File

@@ -0,0 +1,347 @@
import React from 'react';
import {
Plus,
ArrowLeft,
Clock,
Activity,
Trash2,
Edit3,
Play,
Eye,
Microscope,
Globe,
Layers,
Zap,
BookOpen,
Sparkles
} from 'lucide-react';
import { motion } from 'motion/react';
interface AgentsViewProps {
selectedAgentId: string | null;
setSelectedAgentId: (id: string | null) => void;
}
export const AgentsView: React.FC<AgentsViewProps> = ({
selectedAgentId,
setSelectedAgentId
}) => {
return (
<div className="h-full flex flex-col overflow-y-auto custom-scrollbar">
{!selectedAgentId ? (
<>
<header className="px-12 pt-12 pb-8 flex flex-col gap-6 sticky top-0 bg-paper/80 backdrop-blur-md z-30">
<div className="flex justify-between items-end">
<div className="space-y-1">
<h1 className="text-4xl font-serif font-medium tracking-tight text-ink">Mes Agents</h1>
<p className="text-sm text-muted-ink font-light">Automatisez vos tâches de veille et de recherche.</p>
</div>
<button className="px-6 py-2.5 bg-ink text-paper text-sm font-medium rounded-xl hover:opacity-90 transition-all flex items-center gap-3 shadow-lg shadow-ink/10">
<Plus size={18} />
Nouvel Agent
</button>
</div>
<div className="flex items-center gap-8 border-b border-ink/5 pt-4">
{['Tous', 'Veilleur', 'Chercheur', 'Surveillant', 'Personnalisé'].map((tag, i) => (
<button key={i} className={`pb-4 text-xs font-bold uppercase tracking-widest transition-all relative ${i === 0 ? 'text-ink' : 'text-muted-ink hover:text-ink/60'}`}>
{tag}
{i === 0 && <motion.div layoutId="activeAgentTag" className="absolute bottom-0 left-0 right-0 h-0.5 bg-ink" />}
</button>
))}
</div>
</header>
<div className="px-12 flex-1 pb-20 space-y-12">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{[
{ id: 'a1', icon: <Eye size={20} className="text-amber-600" />, title: 'Surveillant de Notes', status: 'Réussi', type: 'SURVEILLANT', meta: 'Hebdomadaire • 6 exéc.', desc: 'Analyse les notes récentes dun carnet et suggère des compléments, références et liens.' },
{ id: 'a2', icon: <Microscope size={20} className="text-indigo-600" />, title: 'Chercheur de Sujet', status: 'Réussi', type: 'CHERCHEUR', meta: 'Hebdomadaire • 14 exéc.', desc: 'Recherche des informations approfondies sur les derniers modèles de Deepseek et voir lavis des utilisateurs.' },
{ id: 'a3', icon: <Globe size={20} className="text-emerald-600" />, title: 'Veille IA', status: 'Réussi', type: 'VEILLEUR', meta: 'Quotidien • 20 exéc.', desc: 'Scrape les flux RSS de 6 sites IA (The Verge, TechCrunch...) et génère un résumé.' },
].map((agent, i) => (
<div
key={i}
onClick={() => setSelectedAgentId(agent.id)}
className="bg-white dark:bg-white/5 border border-border rounded-2xl p-6 space-y-6 hover:border-ink/20 transition-all group cursor-pointer shadow-sm relative overflow-hidden"
>
<div className="flex items-start justify-between">
<div className="flex items-center gap-4">
<div className="p-3 bg-slate-50 dark:bg-white/10 rounded-xl group-hover:bg-ink group-hover:text-paper transition-all">
{agent.icon}
</div>
<div className="space-y-1">
<h4 className="text-[13px] font-bold text-ink">{agent.title}</h4>
<p className="text-[10px] font-bold uppercase tracking-widest text-muted-ink opacity-60">{agent.type}</p>
</div>
</div>
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
<label className="relative inline-flex items-center cursor-pointer">
<input type="checkbox" className="sr-only peer" defaultChecked />
<div className="w-8 h-4 bg-gray-200 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-3 after:w-3 after:transition-all peer-checked:bg-emerald-500"></div>
</label>
</div>
</div>
<p className="text-xs text-muted-ink leading-relaxed line-clamp-3">
{agent.desc}
</p>
<div className="space-y-3">
<div className="flex items-center justify-between text-[10px] text-muted-ink font-medium">
<div className="flex items-center gap-4">
<span className="flex items-center gap-1"><Clock size={10} /> {agent.meta.split('•')[0]}</span>
<span>{agent.meta.split('•')[1]}</span>
</div>
</div>
<div className="flex items-center justify-between text-[10px] text-muted-ink font-medium">
<div className="flex items-center gap-2">
<span className="uppercase tracking-tight">Prochaine exécution</span>
<span className="text-ink">Hebdomadaire</span>
</div>
<div className="flex items-center gap-2">
<span className="uppercase tracking-tight">Dernier statut</span>
<span className="text-emerald-600 flex items-center gap-1"><Activity size={8} /> {agent.status}</span>
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-2 border-t border-border pt-4">
<button className="py-2 border border-border rounded-lg hover:bg-slate-50 dark:hover:bg-white/5 flex items-center justify-center transition-colors text-muted-ink hover:text-ink"><Edit3 size={14} /> <span className="ml-2 text-[10px] font-bold uppercase">Modifier</span></button>
<button
onClick={(e) => { e.stopPropagation(); }}
className="py-2 border border-border rounded-lg hover:bg-slate-50 dark:hover:bg-white/5 flex items-center justify-center transition-colors text-muted-ink hover:text-ink"
>
<Play size={14} className="fill-current" />
</button>
<button
onClick={(e) => { e.stopPropagation(); }}
className="py-2 border border-border rounded-lg hover:bg-rose-50 hover:text-rose-600 hover:border-rose-100 flex items-center justify-center transition-colors text-muted-ink"
>
<Trash2 size={14} />
</button>
</div>
</div>
))}
</div>
<div className="space-y-8">
<div className="flex items-center gap-4">
<h5 className="text-[10px] font-bold uppercase tracking-[0.3em] text-muted-ink whitespace-nowrap">Modèles</h5>
<div className="h-px w-full bg-border/40" />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{[
{ title: 'Veille IA', desc: 'Scrape les flux RSS de 6 sites IA et génère un résumé hebdomadaire.', icon: <Globe size={18} /> },
{ title: 'Veille Tech', desc: 'Crée un résumé quotidien des news Hacker News et Product Hunt.', icon: <Zap size={18} /> },
{ title: 'Veille Dev', desc: 'Surveille les repos GitHub pour détecter les nouvelles releases.', icon: <Layers size={18} /> },
].map((model, i) => (
<div key={i} className="bg-white/40 dark:bg-white/5 border border-dashed border-border rounded-2xl p-6 group cursor-pointer hover:bg-white dark:hover:bg-white/10 hover:border-ink/20 transition-all">
<div className="w-8 h-8 rounded-lg bg-slate-50 dark:bg-white/10 flex items-center justify-center text-muted-ink group-hover:bg-ink group-hover:text-paper mb-4 transition-all">
{model.icon}
</div>
<h4 className="text-[13px] font-bold text-ink mb-2">{model.title}</h4>
<p className="text-xs text-muted-ink leading-relaxed mb-4">{model.desc}</p>
<button className="text-[11px] font-bold uppercase tracking-widest text-ink hover:opacity-60 transition-opacity flex items-center gap-2">
<Plus size={14} /> Installer
</button>
</div>
))}
</div>
</div>
</div>
</>
) : (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="flex-1 flex flex-col"
>
<header className="px-12 py-10 border-b border-border bg-white dark:bg-paper backdrop-blur-md sticky top-0 z-30">
<div className="flex items-center justify-between max-w-5xl mx-auto">
<button
onClick={() => setSelectedAgentId(null)}
className="flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-muted-ink hover:text-ink transition-colors"
>
<ArrowLeft size={16} />
Retour
</button>
<div className="flex items-center gap-4">
<button className="px-5 py-2 text-xs font-bold uppercase tracking-widest border border-border rounded-xl hover:bg-slate-50 dark:hover:bg-white/5 transition-all">
Logs
</button>
<button className="px-6 py-2 bg-ink text-paper text-xs font-bold uppercase tracking-widest rounded-xl hover:opacity-90 transition-all shadow-lg shadow-ink/10">
Enregistrer
</button>
</div>
</div>
</header>
<div className="flex-1 px-12 py-16 max-w-5xl mx-auto w-full space-y-16">
<section className="space-y-8">
<div className="flex items-end justify-between">
<div className="space-y-4">
<div className="flex items-center gap-3">
<div className="p-4 bg-ink text-paper rounded-2xl shadow-xl shadow-ink/10">
{selectedAgentId === 'a1' ? <Eye size={32} /> : selectedAgentId === 'a2' ? <Microscope size={32} /> : <Globe size={32} />}
</div>
<div className="space-y-1">
<h2 className="text-3xl font-serif font-medium text-ink">
{selectedAgentId === 'a1' ? 'Surveillant de Notes' : selectedAgentId === 'a2' ? 'Chercheur de Sujet' : 'Veille IA'}
</h2>
<div className="flex items-center gap-3">
<span className="text-[10px] font-bold uppercase tracking-widest px-2 py-1 bg-slate-100 dark:bg-white/10 text-muted-ink rounded-md">ID: {selectedAgentId}</span>
<span className="text-[10px] font-bold uppercase tracking-widest px-2 py-1 bg-emerald-50 dark:bg-emerald-500/10 text-emerald-600 rounded-md">Actif</span>
</div>
</div>
</div>
</div>
<div className="flex items-center gap-8 text-[12px] uppercase tracking-[0.2em] font-bold text-muted-ink">
<div className="flex flex-col items-end gap-1">
<span className="opacity-40">Dernière exéc.</span>
<span className="text-ink">Il y a 2h</span>
</div>
<div className="flex flex-col items-end gap-1">
<span className="opacity-40">Succès</span>
<span className="text-emerald-600 tracking-normal">98.4%</span>
</div>
</div>
</div>
</section>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
<div className="lg:col-span-2 space-y-12">
<section className="space-y-6">
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-muted-ink">Configuration Logique</h3>
<div className="bg-white dark:bg-white/5 border border-border rounded-3xl overflow-hidden shadow-sm">
<div className="p-8 space-y-8">
<div className="space-y-4">
<label className="block text-[11px] uppercase tracking-widest font-bold text-muted-ink">Source de données</label>
<div className="grid grid-cols-2 gap-4">
<div className="p-4 rounded-2xl border-2 border-ink bg-slate-50 dark:bg-white/10 flex items-center justify-between group cursor-pointer transition-all">
<div className="flex items-center gap-3">
<BookOpen size={18} className="text-ink" />
<span className="text-sm font-medium text-ink">Carnets Externes</span>
</div>
<div className="w-5 h-5 rounded-full border-4 border-ink flex items-center justify-center">
<div className="w-2 h-2 bg-ink rounded-full" />
</div>
</div>
<div className="p-4 rounded-2xl border border-border hover:border-ink/20 flex items-center justify-between group cursor-pointer transition-all">
<div className="flex items-center gap-3">
<Globe size={18} className="text-muted-ink" />
<span className="text-sm font-medium text-muted-ink">Web (Flux RSS/SEO)</span>
</div>
<div className="w-5 h-5 rounded-full border border-border" />
</div>
</div>
</div>
<div className="space-y-4">
<label className="block text-[11px] uppercase tracking-widest font-bold text-muted-ink">Modèle d'Intelligence</label>
<select className="w-full bg-slate-50 dark:bg-black/20 border border-border rounded-xl px-4 py-4 text-sm outline-none focus:ring-2 ring-ink/5 transition-all cursor-pointer font-medium text-ink">
<option>Gemini 2.0 Flash (Optimisé)</option>
<option>Gemini 1.5 Pro (Analytique)</option>
<option>Claude 3.5 Sonnet (Via API)</option>
</select>
</div>
<div className="space-y-4">
<label className="block text-[11px] uppercase tracking-widest font-bold text-muted-ink">Instruction Système (Prompt)</label>
<textarea
rows={6}
className="w-full bg-slate-50 dark:bg-black/20 border border-border rounded-2xl p-6 text-sm outline-none focus:ring-2 ring-ink/5 transition-all font-light leading-relaxed resize-none text-ink"
defaultValue="Tu es un assistant spécialisé dans l'analyse de notes architecturales. Ta mission est de scanner les nouveaux carnets, extraire les concepts de matérialité et suggérer des références historiques pertinentes."
/>
</div>
</div>
</div>
</section>
<section className="space-y-6">
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-muted-ink">Actions de Sortie</h3>
<div className="grid grid-cols-2 gap-4">
<div className="p-6 bg-white dark:bg-white/5 border border-border rounded-2xl space-y-4 hover:border-ink/20 transition-all cursor-pointer group">
<div className="w-10 h-10 rounded-xl bg-slate-50 dark:bg-white/10 flex items-center justify-center text-muted-ink group-hover:bg-ink group-hover:text-paper transition-all">
<Plus size={18} />
</div>
<div className="space-y-1">
<h4 className="text-sm font-bold text-ink leading-tight">Nouvelle Note</h4>
<p className="text-[10px] text-muted-ink font-medium uppercase tracking-tight">Créer un brouillon auto.</p>
</div>
</div>
<div className="p-6 bg-white dark:bg-white/5 border border-border rounded-2xl space-y-4 hover:border-ink/20 transition-all cursor-pointer group opacity-40 border-dashed">
<div className="w-10 h-10 rounded-xl border border-dashed border-border flex items-center justify-center text-muted-ink group-hover:bg-ink group-hover:text-paper transition-all">
<Sparkles size={18} />
</div>
<div className="space-y-1">
<h4 className="text-sm font-bold text-ink leading-tight">Webhook API</h4>
<p className="text-[10px] text-muted-ink font-medium uppercase tracking-tight">Ajouter une destination</p>
</div>
</div>
</div>
</section>
</div>
<div className="space-y-10">
<section className="space-y-6">
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-muted-ink">Planification</h3>
<div className="bg-ink rounded-3xl p-8 space-y-8 text-paper shadow-2xl shadow-ink/20 relative overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-10">
<Clock size={100} />
</div>
<div className="relative space-y-6">
<div className="space-y-2">
<span className="text-[10px] uppercase tracking-widest font-bold opacity-60">Fréquence</span>
<div className="text-2xl font-serif italic text-paper">Hebdomadaire</div>
</div>
<div className="h-px bg-white/10" />
<div className="space-y-4">
<div className="flex items-center justify-between text-xs">
<span className="opacity-60">Lundi</span>
<span className="font-bold">09:00</span>
</div>
<div className="flex items-center justify-between text-xs">
<span className="opacity-60">Jeudi</span>
<span className="font-bold">14:30</span>
</div>
</div>
<button className="w-full py-3 bg-white text-ink text-xs font-bold uppercase tracking-widest rounded-xl hover:opacity-90 transition-all">
Modifier le planning
</button>
</div>
</div>
</section>
<section className="space-y-6">
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-muted-ink">Notifications</h3>
<div className="space-y-3">
{[
{ label: 'Rapport d\'exécution', enabled: true },
{ label: 'Erreurs critiques', enabled: true },
{ label: 'Modifications de notes', enabled: false },
].map((item, i) => (
<div key={i} className="flex items-center justify-between p-4 bg-slate-50 dark:bg-white/10 rounded-2xl border border-border/50">
<span className="text-xs font-medium text-ink">{item.label}</span>
<label className="relative inline-flex items-center cursor-pointer">
<input type="checkbox" className="sr-only peer" defaultChecked={item.enabled} />
<div className="w-8 h-4 bg-gray-200 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-3 after:w-3 after:transition-all peer-checked:bg-[#75B2D6]"></div>
</label>
</div>
))}
</div>
</section>
<section className="pt-6">
<button className="w-full py-4 border border-rose-200 text-rose-600 bg-rose-50/50 dark:bg-rose-500/10 rounded-2xl text-xs font-bold uppercase tracking-widest hover:bg-rose-100 transition-colors flex items-center justify-center gap-3">
<Trash2 size={16} />
Supprimer l'agent
</button>
</section>
</div>
</div>
</div>
</motion.div>
)}
</div>
);
};

View File

@@ -0,0 +1,388 @@
import React from 'react';
import {
Plus,
Search,
Share2,
Pin,
ChevronRight,
ArrowLeft,
MoreVertical,
Sparkles,
Tag as TagIcon,
X,
BookOpen
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
import { Note, Carnet, Tag } from '../types';
interface NotebooksViewProps {
activeNoteId: string | null;
activeCarnet: Carnet | undefined;
filteredNotes: Note[];
activeNote: Note | undefined;
setActiveNoteId: (id: string | null) => void;
togglePin: (id: string) => void;
setShowNewNoteModal: (show: boolean) => void;
isAISidebarOpen: boolean;
setIsAISidebarOpen: (open: boolean) => void;
selectedTagIds: string[];
setSelectedTagIds: (ids: string[]) => void;
allNotes: Note[];
activeCarnetId: string;
setShowNewCarnetModal: (show: boolean, parentId?: string) => void;
}
export const NotebooksView: React.FC<NotebooksViewProps> = ({
activeNoteId,
activeCarnet,
filteredNotes,
activeNote,
setActiveNoteId,
togglePin,
setShowNewNoteModal,
isAISidebarOpen,
setIsAISidebarOpen,
selectedTagIds,
setSelectedTagIds,
allNotes,
activeCarnetId,
setShowNewCarnetModal
}) => {
const [isTagsExpanded, setIsTagsExpanded] = React.useState(false);
const [tagSearchQuery, setTagSearchQuery] = React.useState('');
const availableTags = React.useMemo(() => {
const carnetNotes = allNotes.filter(n => n.carnetId === activeCarnetId);
const tagsMap = new Map<string, Tag>();
carnetNotes.forEach(note => {
note.tags?.forEach(tag => {
tagsMap.set(tag.id, tag);
});
});
return Array.from(tagsMap.values()).sort((a, b) => {
// AI tags first, then alphabetical
if (a.type === 'ai' && b.type !== 'ai') return -1;
if (a.type !== 'ai' && b.type === 'ai') return 1;
return a.label.localeCompare(b.label);
});
}, [allNotes, activeCarnetId]);
const visibleTags = React.useMemo(() => {
let filtered = availableTags;
if (tagSearchQuery) {
filtered = availableTags.filter(t =>
t.label.toLowerCase().includes(tagSearchQuery.toLowerCase())
);
} else if (!isTagsExpanded) {
filtered = availableTags.slice(0, 10);
// Ensure selected tags are always visible even if not in the first 10
selectedTagIds.forEach(id => {
if (!filtered.find(t => t.id === id)) {
const tag = availableTags.find(t => t.id === id);
if (tag) filtered.push(tag);
}
});
}
return filtered;
}, [availableTags, isTagsExpanded, tagSearchQuery, selectedTagIds]);
const toggleTag = (tagId: string) => {
if (selectedTagIds.includes(tagId)) {
setSelectedTagIds(selectedTagIds.filter(id => id !== tagId));
} else {
setSelectedTagIds([...selectedTagIds, tagId]);
}
};
if (!activeNoteId) {
return (
<div className="h-full flex flex-col overflow-y-auto">
<header className="px-12 pt-12 pb-8 flex flex-col gap-6 sticky top-0 bg-paper/80 backdrop-blur-md z-30">
<div className="flex justify-between items-start">
<h1 className="text-4xl font-serif font-medium tracking-tight text-ink leading-tight pr-12">
{activeCarnet?.name} {filteredNotes[0]?.date || 'Oct 26'}
</h1>
</div>
<div className="flex items-center justify-between border-b border-ink/5 pb-4">
<div className="flex items-center gap-6">
<button
onClick={() => setShowNewNoteModal(true)}
className="flex items-center gap-2 text-[13px] text-ink font-medium hover:opacity-70 transition-opacity"
>
<Plus size={16} />
<span>Add Note</span>
</button>
<button
onClick={() => setShowNewCarnetModal(true, activeCarnetId)}
className="flex items-center gap-2 text-[13px] text-concrete font-medium hover:text-ink transition-all"
>
<BookOpen size={16} />
<span>New Sub-Carnet</span>
</button>
<button className="flex items-center gap-2 text-[13px] text-ink font-medium hover:opacity-70 transition-opacity">
<Search size={16} />
<span>Search</span>
</button>
</div>
<button className="flex items-center gap-2 text-[13px] text-ink font-medium hover:opacity-70 transition-opacity">
<Share2 size={16} />
<span>Share</span>
</button>
</div>
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 text-[10px] font-bold uppercase tracking-[0.2em] text-concrete">
<TagIcon size={12} />
<span>Filter by Tags</span>
{selectedTagIds.length > 0 && (
<span className="bg-blueprint/10 text-blueprint px-2 py-0.5 rounded-full text-[9px] lowercase tracking-normal">
{selectedTagIds.length} active
</span>
)}
</div>
{availableTags.length > 10 && (
<div className="relative group">
<input
type="text"
placeholder="Search tags..."
className="bg-transparent border-b border-border/40 text-[10px] outline-none focus:border-blueprint/40 py-1 px-2 w-32 transition-all focus:w-48 placeholder:text-concrete/40"
onChange={(e) => setTagSearchQuery(e.target.value)}
/>
</div>
)}
</div>
<div className="flex flex-wrap gap-2 items-center min-h-[32px]">
<AnimatePresence mode="popLayout">
{visibleTags.map(tag => {
const isActive = selectedTagIds.includes(tag.id);
return (
<motion.button
layout
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
key={tag.id}
onClick={() => toggleTag(tag.id)}
className={`px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-wider transition-all border flex items-center gap-2
${isActive
? 'bg-ink text-paper border-ink shadow-lg shadow-ink/10'
: 'bg-white/40 border-border text-concrete hover:border-concrete/40 hover:bg-white/60'}`}
>
{tag.type === 'ai' && (
<Sparkles
size={10}
className={isActive ? 'text-blueprint' : 'text-blueprint/60'}
/>
)}
{tag.label}
{isActive && <X size={10} />}
</motion.button>
);
})}
</AnimatePresence>
{availableTags.length > 10 && !tagSearchQuery && (
<button
onClick={() => setIsTagsExpanded(!isTagsExpanded)}
className="px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider text-concrete/60 hover:text-ink transition-colors border border-dashed border-border rounded-full"
>
{isTagsExpanded ? 'Show less' : `+ ${availableTags.length - 10} more`}
</button>
)}
{selectedTagIds.length > 0 && (
<button
onClick={() => setSelectedTagIds([])}
className="px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider text-rust hover:underline ml-auto"
>
Clear all
</button>
)}
</div>
</div>
</header>
<div className="px-12 flex-1 pb-20">
<div className="max-w-3xl space-y-16">
{filteredNotes.map((note, index) => (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 * index, duration: 0.8 }}
key={note.id}
className="space-y-4 group cursor-pointer relative"
onClick={() => setActiveNoteId(note.id)}
>
<h2 className="text-2xl font-serif font-medium text-ink flex items-center justify-between">
<span className="flex items-center gap-3">
{note.isPinned && <Pin size={18} className="text-amber-500 fill-amber-500" />}
{note.title}
</span>
<div className="flex items-center gap-2">
<button
onClick={(e) => {
e.stopPropagation();
togglePin(note.id);
}}
className={`p-2 rounded-full transition-all ${note.isPinned ? 'text-amber-600 bg-amber-50' : 'opacity-0 group-hover:opacity-40 hover:bg-slate-100 text-ink'}`}
>
<Pin size={16} />
</button>
<button className="opacity-0 group-hover:opacity-40 transition-opacity">
<ChevronRight size={20} />
</button>
</div>
</h2>
<div className="flex flex-col md:flex-row gap-8 items-start">
<div className="w-full md:w-56 aspect-[4/3] bg-white/50 dark:bg-white/5 border border-border overflow-hidden rounded shadow-sm flex-shrink-0">
<img
src={note.imageUrl}
alt={note.title}
className="w-full h-full object-cover mix-blend-multiply opacity-80 grayscale contrast-125 hover:grayscale-0 hover:opacity-100 transition-all duration-500"
referrerPolicy="no-referrer"
/>
</div>
<div className="space-y-3">
<div className="flex flex-wrap gap-2 mb-2">
{note.tags?.map(tag => (
<div
key={tag.id}
className={`px-2 py-0.5 rounded text-[9px] font-bold uppercase tracking-wider border flex items-center gap-1.5
${tag.type === 'ai'
? 'bg-blueprint/5 border-blueprint/20 text-blueprint'
: 'bg-concrete/5 border-border text-concrete'}`}
>
{tag.type === 'ai' && <Sparkles size={8} />}
{tag.label}
</div>
))}
</div>
<p className="text-[14px] leading-relaxed text-ink/80 font-light max-w-lg line-clamp-4">
{note.content}
</p>
<span className="text-[11px] text-muted-ink uppercase tracking-widest font-medium">Read more</span>
</div>
</div>
</motion.div>
))}
{filteredNotes.length === 0 && (
<div className="h-64 flex flex-col items-center justify-center text-center space-y-4">
<p className="font-serif text-xl italic text-muted-ink">This notebook is waiting for its first vision.</p>
<button
onClick={() => setShowNewNoteModal(true)}
className="px-6 py-2 border border-ink text-[13px] uppercase tracking-[0.2em] hover:bg-ink hover:text-paper transition-all"
>
Begin Drawing
</button>
</div>
)}
</div>
</div>
<footer className="px-12 py-6 border-t border-ink/5 text-center mt-auto">
<p className="text-[11px] text-muted-ink uppercase tracking-[0.2em] font-medium">
&copy; 2024 Architectural Grid. All rights reserved.
</p>
</footer>
</div>
);
}
return (
<div className="h-full flex overflow-hidden transition-all duration-500">
<div className="flex-1 flex flex-col overflow-y-auto bg-white dark:bg-paper">
<div className="px-12 py-8 flex items-center justify-between sticky top-0 bg-white/90 dark:bg-paper/90 backdrop-blur-sm z-40 border-b border-border">
<button
onClick={() => setActiveNoteId(null)}
className="flex items-center gap-2 text-ink hover:opacity-60 transition-opacity"
>
<ArrowLeft size={18} />
<span className="text-sm font-medium">Back to collection</span>
</button>
<div className="flex items-center gap-4">
<button
onClick={() => togglePin(activeNoteId!)}
className={`p-2 rounded-full transition-all ${activeNote?.isPinned ? 'text-amber-600 bg-amber-50 dark:bg-ochre/10' : 'text-muted-ink hover:text-ink'}`}
title={activeNote?.isPinned ? "Unpin note" : "Pin note"}
>
<Pin size={18} className={activeNote?.isPinned ? 'fill-amber-600' : ''} />
</button>
<button
onClick={() => setIsAISidebarOpen(!isAISidebarOpen)}
className={`flex items-center gap-2 px-3 py-1.5 rounded-full border transition-all duration-300
${isAISidebarOpen ? 'bg-ink text-paper border-ink' : 'border-border text-ink hover:bg-white/50 dark:hover:bg-white/5'}`}
>
<Sparkles size={16} />
<span className="text-xs font-medium">AI Assistant</span>
</button>
<button className="p-2 text-muted-ink hover:text-ink transition-colors">
<Share2 size={18} />
</button>
<button className="p-2 text-muted-ink hover:text-ink transition-colors">
<MoreVertical size={18} />
</button>
</div>
</div>
<div className="max-w-4xl mx-auto w-full px-12 py-16 space-y-12">
<div className="space-y-4">
<div className="flex items-center gap-3 text-[12px] text-muted-ink uppercase tracking-[.25em] font-bold">
<span>{activeCarnet?.name}</span>
<ChevronRight size={10} />
<span>{activeNote?.date}</span>
</div>
<h1 className="text-5xl md:text-6xl font-serif font-bold text-ink leading-tight">
{activeNote?.title}
</h1>
<div className="flex flex-wrap gap-2 pt-2">
{activeNote?.tags?.map(tag => (
<div
key={tag.id}
className={`px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest border flex items-center gap-2
${tag.type === 'ai'
? 'bg-blueprint/5 border-blueprint/20 text-blueprint'
: 'bg-paper border-border text-concrete'}`}
>
{tag.type === 'ai' && <Sparkles size={12} />}
{tag.label}
{tag.type === 'ai' && (
<div className="w-1.5 h-1.5 rounded-full bg-blueprint animate-pulse" />
)}
</div>
))}
</div>
</div>
<div className="aspect-[16/9] w-full bg-slate-100 dark:bg-white/5 rounded-xl overflow-hidden shadow-xl">
<img
src={activeNote?.imageUrl}
alt={activeNote?.title}
className="w-full h-full object-cover grayscale contrast-110"
referrerPolicy="no-referrer"
/>
</div>
<div className="max-w-2xl mx-auto space-y-8 pb-32">
<p className="text-xl md:text-2xl font-serif leading-relaxed text-ink italic">
{activeNote?.content.split('.')[0]}.
</p>
<div className="h-px bg-border w-32" />
<p className="text-lg leading-relaxed text-ink/80 font-light space-y-4 text-justify whitespace-pre-line">
{activeNote?.content}
{activeNote?.id.startsWith('n-') && (
<>
<br /><br />
Architectural grids serve as the invisible scaffolding upon which spatial experiences are constructed. Beyond mere structural repetition, they facilitate a rhythmic dialogue between materiality and void. In this exploration, we examine how light fractures these rigid boundaries, creating a dynamic interplay that evolves with the passage of time.
</>
)}
</p>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,66 @@
import React from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { SettingsTab } from '../types';
import { SettingsHeader } from './settings/SettingsHeader';
import { GeneralTab } from './settings/GeneralTab';
import { AITab } from './settings/AITab';
import { AppearanceTab } from './settings/AppearanceTab';
interface SettingsViewProps {
activeSettingsTab: SettingsTab;
setActiveSettingsTab: (tab: SettingsTab) => void;
}
export const SettingsView: React.FC<SettingsViewProps> = ({
activeSettingsTab,
setActiveSettingsTab
}) => {
return (
<div className="h-full flex flex-col bg-paper dark:bg-dark-paper overflow-y-auto custom-scrollbar relative font-sans">
<div className="absolute inset-0 opacity-[0.04] pointer-events-none grainy-bg mix-blend-multiply dark:mix-blend-overlay" />
<div className="relative z-10 flex flex-col min-h-full">
<SettingsHeader
activeTab={activeSettingsTab}
setActiveTab={setActiveSettingsTab}
/>
<div className="flex-1 px-12 pb-24 h-full">
<div className="max-w-6xl mx-auto">
<AnimatePresence mode="wait">
{activeSettingsTab === 'general' && (
<GeneralTab key="general" />
)}
{activeSettingsTab === 'ai' && (
<AITab key="ai" />
)}
{activeSettingsTab === 'appearance' && (
<AppearanceTab key="appearance" />
)}
{['profile', 'data', 'mcp', 'about'].includes(activeSettingsTab) && (
<motion.div
key="placeholder"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="h-[50vh] flex flex-col items-center justify-center border border-dashed border-border rounded-[32px] space-y-6 bg-white/20 dark:bg-white/5"
>
<div className="w-16 h-16 rounded-3xl border border-dashed border-concrete/20 flex items-center justify-center text-concrete/40 bg-paper/50">
<span className="text-2xl font-serif italic text-concrete">?</span>
</div>
<div className="text-center space-y-1">
<p className="text-ink font-bold text-sm tracking-tight">Section en développement</p>
<p className="text-concrete italic text-[11px] font-light">Le module {activeSettingsTab} sera disponible prochainement.</p>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,350 @@
import React from 'react';
import {
Plus,
Archive,
Settings,
ChevronRight,
BookOpen,
Bot,
Microscope,
Activity,
Pin,
Moon,
Sun,
Bell,
Lock
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
import { NavigationView, Carnet, Note } from '../types';
interface NoteLinkProps {
note: Note;
isActive: boolean;
onClick: () => void;
}
const NoteLink: React.FC<NoteLinkProps> = ({ note, isActive, onClick }) => (
<motion.button
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
onClick={onClick}
className={`w-full flex items-center gap-2 pl-12 pr-4 py-2 text-[12px] transition-colors rounded-lg
${isActive ? 'bg-white/50 dark:bg-white/10 text-ink font-medium' : 'text-muted-ink hover:text-ink hover:bg-white/30'}`}
>
<div className="flex items-center gap-2 flex-1 truncate">
<div className={`w-1.5 h-1.5 rounded-full shrink-0 ${isActive ? 'bg-ink' : 'bg-transparent border border-muted-ink/30'}`} />
<span className="truncate">{note.title}</span>
</div>
{note.isPinned && <Pin size={10} className="text-amber-500 shrink-0" />}
</motion.button>
);
interface SidebarItemProps {
carnet: Carnet;
isActive: boolean;
notes: Note[];
activeNoteId: string | null;
onCarnetClick: () => void;
onNoteClick: (noteId: string) => void;
onAddSubCarnet: () => void;
children?: React.ReactNode;
level: number;
isExpanded: boolean;
toggleExpand: () => void;
}
const SidebarItem: React.FC<SidebarItemProps> = ({
carnet,
isActive,
notes,
activeNoteId,
onCarnetClick,
onNoteClick,
onAddSubCarnet,
children,
level,
isExpanded,
toggleExpand
}) => {
const hasChildren = React.Children.count(children) > 0;
return (
<div className="space-y-0.5">
<div
className="flex items-center group relative h-10"
style={{ paddingLeft: `${level * 12}px` }}
>
{/* Hierarchy Guide Line */}
{level > 0 && (
<div className="absolute left-[8px] top-[-10px] bottom-1/2 w-px bg-border/40" />
)}
{level > 0 && (
<div className="absolute left-[8px] top-1/2 w-[8px] h-px bg-border/40" />
)}
<div className="flex-1 flex items-center gap-1">
{hasChildren ? (
<button
onClick={(e) => {
e.stopPropagation();
toggleExpand();
}}
className="p-1 hover:bg-ink/5 dark:hover:bg-white/5 rounded-md transition-colors text-muted-ink"
>
<motion.div animate={{ rotate: isExpanded ? 90 : 0 }} transition={{ duration: 0.2 }}>
<ChevronRight size={14} />
</motion.div>
</button>
) : (
<div className="w-6" /> // Spacer for alignment
)}
<motion.button
whileHover={{ x: 2 }}
onClick={onCarnetClick}
className={`flex-1 flex items-center gap-2.5 px-2 py-1.5 rounded-lg transition-all duration-300
${isActive ? 'bg-white shadow-sm border border-border/40 dark:bg-white/10' : 'hover:bg-white/40 dark:hover:bg-white/5'}`}
>
<div className={`w-6 h-6 rounded-md flex items-center justify-center text-[10px] font-bold border transition-all
${isActive ? 'bg-ink text-paper border-ink' : 'bg-paper dark:bg-white/10 text-concrete border-border dark:border-white/10'}`}>
{carnet.initial}
</div>
<div className="flex-1 text-left flex items-center gap-2">
<span className={`text-[12px] font-medium transition-colors truncate ${isActive ? 'text-ink' : 'text-muted-ink hover:text-ink'}`}>
{carnet.name}
</span>
{carnet.isPrivate && <Lock size={10} className="text-concrete/60 shrink-0" />}
</div>
<div className="flex items-center gap-1.5">
<button
onClick={(e) => {
e.stopPropagation();
onAddSubCarnet();
}}
className="opacity-0 group-hover:opacity-100 p-1 hover:bg-ink/10 dark:hover:bg-white/10 rounded-md transition-all text-concrete hover:text-ink"
title="Add sub-carnet"
>
<Plus size={10} />
</button>
{notes.length > 0 && (
<span className="text-[9px] font-bold text-concrete/40 px-1.5 border border-border/40 rounded-full group-hover:text-concrete transition-colors">
{notes.length}
</span>
)}
</div>
</motion.button>
</div>
</div>
<AnimatePresence initial={false}>
{(isExpanded || (isActive && !hasChildren)) && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: [0.23, 1, 0.32, 1] }}
className="overflow-hidden"
>
<div className="relative" style={{ marginLeft: `${(level + 1) * 12 + 10}px` }}>
{/* Vertical line for nested content */}
<div className="absolute left-[-6px] top-0 bottom-4 w-px bg-border/30" />
<div className="space-y-1 py-1">
{children}
{isActive && !hasChildren && notes.map(note => (
<NoteLink
key={note.id}
note={note}
isActive={activeNoteId === note.id}
onClick={() => onNoteClick(note.id)}
/>
))}
{isActive && !hasChildren && notes.length === 0 && (
<p className="pl-8 py-2 text-[10px] italic text-concrete/40 font-light">
No notes found
</p>
)}
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
};
interface SidebarProps {
activeView: NavigationView;
isDarkMode: boolean;
setIsDarkMode: (val: boolean) => void;
setActiveView: (view: NavigationView) => void;
carnets: Carnet[];
notes: Note[];
activeCarnetId: string;
activeNoteId: string | null;
setActiveCarnetId: (id: string) => void;
setActiveNoteId: (id: string | null) => void;
setShowNewCarnetModal: (show: boolean, parentId?: string) => void;
}
export const Sidebar: React.FC<SidebarProps> = ({
activeView,
isDarkMode,
setIsDarkMode,
setActiveView,
carnets,
notes,
activeCarnetId,
activeNoteId,
setActiveCarnetId,
setActiveNoteId,
setShowNewCarnetModal
}) => {
const [expandedIds, setExpandedIds] = React.useState<Set<string>>(new Set(['4'])); // Default expand Research
const toggleExpand = (id: string) => {
const newSet = new Set(expandedIds);
if (newSet.has(id)) newSet.delete(id);
else newSet.add(id);
setExpandedIds(newSet);
};
const renderCarnetTree = (parentId: string | undefined = undefined, level: number = 0) => {
return carnets
.filter(c => c.parentId === parentId)
.map(carnet => (
<SidebarItem
key={carnet.id}
carnet={carnet}
isActive={activeCarnetId === carnet.id}
notes={notes.filter(n => n.carnetId === carnet.id)}
activeNoteId={activeNoteId}
level={level}
isExpanded={expandedIds.has(carnet.id)}
toggleExpand={() => toggleExpand(carnet.id)}
onAddSubCarnet={() => {
if (!expandedIds.has(carnet.id)) toggleExpand(carnet.id);
setShowNewCarnetModal(true, carnet.id);
}}
onCarnetClick={() => {
setActiveCarnetId(carnet.id);
setActiveNoteId(null);
// Auto expand when clicking
if (!expandedIds.has(carnet.id)) toggleExpand(carnet.id);
}}
onNoteClick={(id) => {
setActiveCarnetId(carnet.id);
setActiveNoteId(id);
}}
>
{renderCarnetTree(carnet.id, level + 1)}
</SidebarItem>
));
};
return (
<aside className="w-80 bg-white/30 dark:bg-[#151515] backdrop-blur-md border-r border-border p-6 flex flex-col z-20 shrink-0 transition-colors duration-500">
<div className="mb-10 flex items-center justify-between">
<div className="w-10 h-10 rounded-full bg-slate-200 dark:bg-white/10 border border-border flex items-center justify-center text-ink font-serif text-lg shadow-sm">
A
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setIsDarkMode(!isDarkMode)}
className="p-2 text-muted-ink hover:text-ink transition-all bg-white/50 dark:bg-white/10 rounded-full border border-border dark:border-white/10"
>
{isDarkMode ? <Sun size={14} /> : <Moon size={14} />}
</button>
<button className="p-2 text-muted-ink hover:text-ink transition-all relative group bg-white/50 dark:bg-white/10 rounded-full border border-border dark:border-white/10">
<Bell size={14} />
<span className="absolute -top-1 -right-1 w-4 h-4 bg-rose-500 text-white text-[9px] font-bold flex items-center justify-center rounded-full border border-white shadow-sm">
3
</span>
</button>
<div className="flex bg-white/50 dark:bg-white/10 p-1 rounded-full border border-border dark:border-white/10 transition-all">
<button
onClick={() => setActiveView('notebooks')}
className={`p-1.5 rounded-full transition-all ${activeView === 'notebooks' ? 'bg-ink text-paper shadow-sm' : 'text-muted-ink hover:text-ink'}`}
title="Carnets"
>
<BookOpen size={14} />
</button>
<button
onClick={() => setActiveView('agents')}
className={`p-1.5 rounded-full transition-all ${activeView === 'agents' ? 'bg-ink text-paper shadow-sm' : 'text-muted-ink hover:text-ink'}`}
title="Agents"
>
<Bot size={14} />
</button>
</div>
</div>
</div>
<div className="flex-1 overflow-y-auto space-y-8 -mx-2 px-2 py-4 custom-scrollbar">
{activeView === 'notebooks' ? (
<div className="space-y-6">
<div className="flex items-center justify-between px-4">
<p className="text-[10px] font-bold text-concrete tracking-[0.2em] uppercase">
Architecture Grid
</p>
<button
onClick={() => setShowNewCarnetModal(true)}
className="p-1 hover:bg-paper dark:hover:bg-white/5 rounded-md text-concrete hover:text-ink transition-colors"
title="New Carnet"
>
<Plus size={14} />
</button>
</div>
<nav className="space-y-0.5">
{renderCarnetTree()}
</nav>
</div>
) : activeView === 'agents' ? (
<div>
<p className="text-[10px] font-bold text-muted-ink tracking-widest uppercase mb-4 px-4">
Intelligence OS
</p>
<div className="space-y-1">
{[
{ id: 'a1', name: 'Mes Agents', icon: <Bot size={16} /> },
{ id: 'a2', name: 'Le Lab AI', icon: <Microscope size={16} /> },
{ id: 'a3', name: 'Activités', icon: <Activity size={16} /> },
].map(item => (
<button
key={item.id}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 group
${item.id === 'a1' ? 'active-nav-item' : 'text-muted-ink hover:bg-white/40 dark:hover:bg-white/5 hover:text-ink'}`}
>
<div className={`w-8 h-8 rounded-full flex items-center justify-center border transition-colors
${item.id === 'a1' ? 'bg-ink text-paper border-ink' : 'bg-white/60 dark:bg-white/10 border-border dark:border-white/10 group-hover:border-ink/20'}`}>
{item.icon}
</div>
<span className="text-[13px] font-medium">{item.name}</span>
</button>
))}
</div>
</div>
) : null}
</div>
<div className="pt-6 border-t border-border space-y-4">
<button className="flex items-center gap-3 px-4 text-[13px] text-muted-ink hover:text-ink transition-colors font-medium group">
<Archive size={16} className="text-muted-ink group-hover:text-ink" />
<span>Archive</span>
</button>
<button
onClick={() => setActiveView('settings')}
className={`flex items-center gap-3 px-4 text-[13px] transition-colors font-medium group ${activeView === 'settings' ? 'text-ink' : 'text-muted-ink hover:text-ink'}`}
>
<Settings size={16} className={activeView === 'settings' ? 'text-ink' : 'text-muted-ink group-hover:text-ink'} />
<span>Settings</span>
</button>
</div>
</aside>
);
};

View File

@@ -0,0 +1,152 @@
import React from 'react';
import { Sparkles, Edit3, MessageCircle, Languages, Tag, History, FlaskConical } from 'lucide-react';
import { motion } from 'motion/react';
const AISettingCard = ({ icon, title, description, defaultChecked = false }: any) => (
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-2xl p-6 flex items-center justify-between group hover:shadow-xl hover:shadow-blueprint/5 transition-all duration-300">
<div className="flex items-center gap-5">
<div className="p-3 bg-paper dark:bg-blueprint/10 rounded-2xl text-blueprint group-hover:bg-blueprint group-hover:text-white group-hover:scale-110 transition-all duration-300 border border-blueprint/20">
{icon}
</div>
<div className="space-y-1">
<h4 className="text-[13px] font-bold text-ink">{title}</h4>
<p className="text-[10px] text-muted-ink leading-relaxed pr-4 line-clamp-2">{description}</p>
</div>
</div>
<label className="relative inline-flex items-center cursor-pointer shrink-0 ml-4">
<input type="checkbox" className="sr-only peer" defaultChecked={defaultChecked} />
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-blueprint"></div>
</label>
</div>
);
export const AITab: React.FC = () => {
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="space-y-16 pb-20"
>
<div className="space-y-10">
<h3 className="text-[10px] font-bold uppercase tracking-[0.4em] text-muted-ink opacity-60">Configurez vos fonctionnalités IA et préférences</h3>
<div className="space-y-6">
<h4 className="text-sm font-bold text-ink border-b border-border/40 pb-4">Fonctionnalités IA</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
<AISettingCard
icon={<Edit3 size={18} />}
title="Suggestions de titre"
description="Suggérer des titres pour les notes sans titre après 50+ mots"
defaultChecked
/>
<AISettingCard
icon={<Sparkles size={18} />}
title="IA Note"
description="Active le bouton de chat IA et les outils d'amélioration du texte"
defaultChecked
/>
<AISettingCard
icon={<MessageCircle size={18} />}
title="💡 J'ai remarqué quelque chose..."
description="Aperçu quotidien de vos notes"
defaultChecked
/>
<AISettingCard
icon={<Languages size={18} />}
title="Détection de langue"
description="Détecte automatiquement la langue de vos notes"
defaultChecked
/>
<AISettingCard
icon={<Tag size={18} />}
title="Suggestion des labels"
description="Suggère et applique des étiquettes automatiquement à vos notes"
defaultChecked
/>
<AISettingCard
icon={<History size={18} />}
title="Historique des notes"
description="Active les snapshots de versions et la restauration depuis History"
defaultChecked
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 pt-6">
{/* Fréquence */}
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-2xl p-8 space-y-10">
<div className="space-y-1.5 text-left text-blueprint">
<h4 className="text-sm font-bold">Fréquence</h4>
<p className="text-[10px] opacity-60 uppercase tracking-wider font-semibold">Fréquence d'analyse des connexions</p>
</div>
<div className="space-y-6">
<label className="flex items-center gap-4 cursor-pointer group">
<input type="radio" name="freq" className="sr-only peer" defaultChecked />
<div className="w-5 h-5 rounded-full border-2 border-border peer-checked:border-blueprint flex items-center justify-center p-0.5 transition-all">
<div className="w-full h-full rounded-full bg-transparent peer-checked:bg-blueprint" />
</div>
<span className="text-sm font-medium text-ink group-hover:opacity-70 transition-opacity">Quotidienne</span>
</label>
<label className="flex items-center gap-4 cursor-pointer group">
<input type="radio" name="freq" className="sr-only peer" />
<div className="w-5 h-5 rounded-full border-2 border-border peer-checked:border-blueprint flex items-center justify-center p-0.5 transition-all">
<div className="w-full h-full rounded-full bg-transparent peer-checked:bg-blueprint" />
</div>
<span className="text-sm font-medium text-ink group-hover:opacity-70 transition-opacity">Hebdomadaire</span>
</label>
</div>
</div>
{/* Mode d'historique */}
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-2xl p-8 space-y-10">
<div className="space-y-1.5 text-left text-blueprint">
<h4 className="text-sm font-bold">Mode d'historique</h4>
<p className="text-[10px] opacity-60 uppercase tracking-wider font-semibold">Gestion des snapshots</p>
</div>
<div className="space-y-6">
<label className="flex items-center gap-4 cursor-pointer group">
<input type="radio" name="hist" className="sr-only peer" defaultChecked />
<div className="w-5 h-5 rounded-full border-2 border-border peer-checked:border-blueprint flex items-center justify-center p-0.5 transition-all">
<div className="w-full h-full rounded-full bg-transparent peer-checked:bg-blueprint" />
</div>
<div className="space-y-0.5">
<p className="text-sm font-bold text-ink">Manuel (bouton commit)</p>
<p className="text-[10px] text-muted-ink">Créer des snapshots manuellement</p>
</div>
</label>
<label className="flex items-center gap-4 cursor-pointer group">
<input type="radio" name="hist" className="sr-only peer" />
<div className="w-5 h-5 rounded-full border-2 border-border peer-checked:border-blueprint flex items-center justify-center p-0.5 transition-all">
<div className="w-full h-full rounded-full bg-transparent peer-checked:bg-blueprint" />
</div>
<div className="space-y-0.5">
<p className="text-sm font-bold text-ink">Automatique (intelligent)</p>
<p className="text-[10px] text-muted-ink">Snapshots automatiques avec détection</p>
</div>
</label>
</div>
</div>
</div>
{/* Mode Démo */}
<div className="bg-ochre/5 dark:bg-ochre/10 border border-ochre/20 rounded-2xl p-8 flex items-center justify-between group transition-all duration-300 hover:bg-ochre/10">
<div className="flex items-center gap-6">
<div className="p-3 bg-paper dark:bg-ochre/20 rounded-2xl text-ochre border border-ochre/30">
<FlaskConical size={20} />
</div>
<div className="space-y-1.5 text-left">
<h4 className="text-sm font-bold text-ink flex items-center gap-3">
🧪 Mode Démo
</h4>
<p className="text-[11px] text-muted-ink leading-relaxed font-medium">Accélère Memory Echo pour les tests. Les connexions apparaissent instantanément.</p>
</div>
</div>
<label className="relative inline-flex items-center cursor-pointer shrink-0 ml-4">
<input type="checkbox" className="sr-only peer" />
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-ochre"></div>
</label>
</div>
</div>
</motion.div>
);
};

View File

@@ -0,0 +1,85 @@
import React from 'react';
import { Palette, Type, LayoutGrid, Maximize } from 'lucide-react';
import { motion } from 'motion/react';
const AppearanceSelect = ({ icon, title, description, options, defaultValue }: any) => (
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-2xl p-8 space-y-8 group transition-all duration-300 hover:shadow-xl hover:shadow-slate/5">
<div className="flex items-center gap-5">
<div className="p-3 bg-paper dark:bg-white/10 rounded-2xl text-slate border border-border group-hover:scale-110 transition-transform duration-300">
{icon}
</div>
<div className="space-y-0.5 text-left">
<h4 className="text-base font-bold text-ink">{title}</h4>
<p className="text-[11px] text-concrete leading-tight">{description}</p>
</div>
</div>
<div className="relative group/select">
<select
defaultValue={defaultValue}
className="w-full bg-white/50 dark:bg-black/40 border border-border rounded-xl px-5 py-4 text-sm outline-none focus:ring-1 ring-slate/20 appearance-none cursor-pointer text-ink font-bold transition-all hover:bg-white dark:hover:bg-black/60"
>
{options.map((opt: string) => (
<option key={opt}>{opt}</option>
))}
</select>
<div className="absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none text-concrete group-hover/select:text-slate transition-colors">
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 1L5 5L9 1" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
</div>
</div>
);
export const AppearanceTab: React.FC = () => {
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="space-y-16 pb-20"
>
<div className="space-y-10">
<h3 className="text-[10px] font-bold uppercase tracking-[0.4em] text-concrete opacity-60">Personnaliser l'apparence de l'application</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<AppearanceSelect
icon={<Palette size={20} />}
title="Thème"
description="Sélectionner le mode visuel"
options={['Clair', 'Sombre', 'Système']}
defaultValue="Clair"
/>
<AppearanceSelect
icon={<Type size={20} />}
title="Taille de la police"
description="Ajustez la lisibilité globale de l'interface"
options={['Petite', 'Moyenne', 'Grande']}
defaultValue="Moyenne"
/>
<AppearanceSelect
icon={<Type size={20} />}
title="Famille de polices"
description="La typographie définit l'âme de l'application"
options={['Inter', 'JetBrains Mono', 'Public Sans', 'Outfit']}
defaultValue="JetBrains Mono"
/>
<AppearanceSelect
icon={<LayoutGrid size={20} />}
title="Affichage des notes"
description="Gestion visuelle de la grille de composition"
options={['Cartes (grille)', 'Liste', 'Tableau']}
defaultValue="Cartes (grille)"
/>
<AppearanceSelect
icon={<Maximize size={20} />}
title="Taille des notes"
description="Structure de la mise en page des éléments"
options={['Taille uniforme', 'Variable (Masonry)']}
defaultValue="Taille uniforme"
/>
</div>
</div>
</motion.div>
);
};

View File

@@ -0,0 +1,82 @@
import React from 'react';
import { Globe, Bell } from 'lucide-react';
import { motion } from 'motion/react';
export const GeneralTab: React.FC = () => {
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="space-y-12"
>
<div className="space-y-4">
<h3 className="text-[10px] font-bold uppercase tracking-[0.3em] text-concrete">Paramètres généraux de l'application</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Langue */}
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-xl p-8 space-y-6">
<div className="flex items-center gap-5">
<div className="p-3 bg-paper dark:bg-white/10 rounded-2xl text-slate border border-border">
<Globe size={18} />
</div>
<div className="space-y-0.5">
<h4 className="text-base font-bold text-ink">Langue</h4>
<p className="text-[11px] text-concrete">Sélectionner une langue</p>
</div>
</div>
<div className="relative group">
<select className="w-full bg-white/50 dark:bg-black/40 border border-border rounded-xl px-5 py-3.5 text-sm outline-none focus:ring-1 ring-blueprint/20 appearance-none cursor-pointer transition-all hover:bg-white dark:hover:bg-black/60 text-ink font-medium">
<option>Français</option>
<option>English</option>
<option>Español</option>
</select>
<div className="absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none opacity-40 text-concrete">
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 1L5 5L9 1" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
</div>
</div>
{/* Notifications */}
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-xl p-8 space-y-6">
<div className="flex items-center gap-5">
<div className="p-3 bg-paper dark:bg-white/10 rounded-2xl text-slate border border-border">
<Bell size={18} />
</div>
<div className="space-y-0.5">
<h4 className="text-base font-bold text-ink">Notifications</h4>
<p className="text-[11px] text-concrete">Gérez vos préférences de notifications</p>
</div>
</div>
<div className="space-y-6 divide-y divide-border/40 text-left">
<div className="flex items-center justify-between pt-0">
<div className="space-y-1">
<p className="text-xs font-bold text-ink">Notifications par email</p>
<p className="text-[10px] text-concrete leading-relaxed">Recevoir des notifications importantes par email</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input type="checkbox" className="sr-only peer" />
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-slate"></div>
</label>
</div>
<div className="flex items-center justify-between pt-6">
<div className="space-y-1">
<p className="text-xs font-bold text-ink">Notifications bureau</p>
<p className="text-[10px] text-concrete leading-relaxed">Recevoir des notifications dans votre navigateur</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input type="checkbox" className="sr-only peer" defaultChecked />
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-slate"></div>
</label>
</div>
</div>
</div>
</div>
</div>
</motion.div>
);
};

View File

@@ -0,0 +1,51 @@
import React from 'react';
import { Settings, Sparkles, Palette, User, Database, Code, Info } from 'lucide-react';
import { motion } from 'motion/react';
import { SettingsTab } from '../../types';
interface SettingsHeaderProps {
activeTab: SettingsTab;
setActiveTab: (tab: SettingsTab) => void;
}
export const SettingsHeader: React.FC<SettingsHeaderProps> = ({ activeTab, setActiveTab }) => {
const tabs = [
{ id: 'general', label: 'Paramètres généraux', icon: <Settings size={14} /> },
{ id: 'ai', label: 'Paramètres IA', icon: <Sparkles size={14} /> },
{ id: 'appearance', label: 'Apparence', icon: <Palette size={14} /> },
{ id: 'profile', label: 'Profil', icon: <User size={14} /> },
{ id: 'data', label: 'Gestion des données', icon: <Database size={14} /> },
{ id: 'mcp', label: 'Paramètres MCP', icon: <Code size={14} /> },
{ id: 'about', label: 'À propos', icon: <Info size={14} /> },
];
return (
<header className="px-12 pt-20 pb-16 space-y-12">
<div className="space-y-4">
<h1 className="text-[64px] font-serif text-ink tracking-tight leading-none italic font-medium">Paramètres</h1>
<p className="text-[10px] font-bold uppercase tracking-[0.4em] text-concrete opacity-60">Configuration & Préférences</p>
</div>
<nav className="flex items-center gap-1 border-b border-border/40 pb-px">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as SettingsTab)}
className={`flex items-center gap-2.5 px-6 py-5 text-[10px] font-bold uppercase tracking-[0.18em] transition-all relative whitespace-nowrap
${activeTab === tab.id ? 'text-ink' : 'text-concrete hover:text-ink/60'}`}
>
<span className={activeTab === tab.id ? 'text-ink' : 'text-concrete'}>{tab.icon}</span>
{tab.label}
{activeTab === tab.id && (
<motion.div
layoutId="activeSettingsTabLine"
className="absolute bottom-0 left-0 right-0 h-0.5 bg-ink"
transition={{ type: 'spring', bounce: 0.1, duration: 0.8 }}
/>
)}
</button>
))}
</nav>
</header>
);
};