feat: editor improvements and architectural grid prototype

Multiple feature additions and improvements across the application:

- NextGen Editor: drag handles, smart paste, block actions
- Structured views: Kanban and table layouts for notes
- Architectural Grid: new brainstorming/agent interface prototype
- Flashcards: SM-2 revision algorithm with AI generation
- MCP server: robustness improvements
- Graph/PDF chat: fix click propagation and copy behavior
- Various UI/UX enhancements and bug fixes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Antigravity
2026-05-27 19:45:15 +00:00
parent 2de66a863d
commit f46654f574
99 changed files with 29948 additions and 919 deletions

View File

@@ -0,0 +1,396 @@
import React from 'react';
import {
Plus,
ArrowLeft,
Clock,
Activity,
Trash2,
Edit3,
Play,
Eye,
Microscope,
Globe,
Layers,
Zap,
BookOpen,
Sparkles,
ChevronDown,
Info,
Check,
ClipboardCheck,
ListTodo,
ArrowRight,
Loader2,
Menu
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
import { Carnet, Note } from '../types';
import { HierarchicalCarnetSelector } from './HierarchicalCarnetSelector';
import { extractActionItems } from '../services/geminiService';
import { v4 as uuidv4 } from 'uuid';
interface AgentsViewProps {
selectedAgentId: string | null;
setSelectedAgentId: (id: string | null) => void;
carnets: Carnet[];
notes: Note[];
onAddNote?: (note: Note) => void;
onOpenSidebar?: () => void;
}
export const AgentsView: React.FC<AgentsViewProps> = ({
selectedAgentId,
setSelectedAgentId,
carnets,
notes,
onAddNote,
onOpenSidebar
}) => {
const [selectedCarnetForAgent, setSelectedCarnetForAgent] = React.useState<string | null>('4');
const [agentType, setAgentType] = React.useState<'Surveillant' | 'Personnalisé' | 'Slides' | 'Diagramme' | 'Tasks'>('Tasks');
const [isRunningAgent, setIsRunningAgent] = React.useState(false);
const [agentResult, setAgentResult] = React.useState<string | null>(null);
const handleRunTaskAgent = async () => {
setIsRunningAgent(true);
setAgentResult(null);
// Get notes from carnet
const filteredNotes = notes.filter(n => n.carnetId === selectedCarnetForAgent && !n.isDeleted);
try {
const result = await extractActionItems(filteredNotes);
setAgentResult(result);
} catch (e) {
console.error(e);
} finally {
setIsRunningAgent(false);
}
};
const handleSaveResult = () => {
if (!agentResult || !onAddNote) return;
const carnetName = carnets.find(c => c.id === selectedCarnetForAgent)?.name || 'General';
const newNote: Note = {
id: uuidv4(),
carnetId: selectedCarnetForAgent || '1',
title: `Consolidated Actions: ${carnetName}`,
date: new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).format(new Date()),
content: agentResult,
imageUrl: 'https://images.unsplash.com/photo-1484480974693-6ca0a78fb36b?auto=format&fit=crop&q=80&w=800&h=600',
tags: [{ id: 't-tasks', label: 'Action Items', type: 'ai' }]
};
onAddNote(newNote);
alert('Note de tâches créée avec succès !');
setSelectedAgentId(null);
};
return (
<div className="h-full flex flex-col overflow-y-auto custom-scrollbar bg-[#F9F8F6] dark:bg-dark-paper space-y-12">
{!selectedAgentId ? (
<>
<header className="px-6 sm: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 lg:items-end gap-4">
<div className="flex items-center gap-4">
<button
onClick={onOpenSidebar}
className="lg:hidden p-2 -ml-2 text-ink hover:bg-black/5 rounded-lg transition-colors"
>
<Menu size={20} />
</button>
<div className="space-y-1">
<h1 className="text-3xl sm: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>
</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é.' },
{ id: 'a4', icon: <ClipboardCheck size={20} className="text-rose-500" />, title: 'Action Miner', status: 'Inactif', type: 'TASKS', meta: 'À la demande', desc: 'Scan vos notes pour extraire automatiquement les tâches, assignés et deadlines.' },
].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-24">
<section className="space-y-12">
<div className="text-center space-y-4">
<p className="text-[10px] font-bold uppercase tracking-[0.4em] text-concrete">Sélectionnez le type d'agent</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 max-w-4xl mx-auto">
{[
{ id: 'Surveillant', icon: <Eye size={18} />, label: 'Surveillant', desc: 'Surveille un carnet et analyse les notes' },
{ id: 'Tasks', icon: <ListTodo size={18} />, label: 'Action Items', desc: 'Extrait les tâches et deadlines' },
{ id: 'Slides', icon: <Layers size={18} />, label: 'Slides', desc: 'Crée une présentation PowerPoint à partir de notes' },
{ id: 'Diagramme', icon: <Zap size={18} />, label: 'Diagramme', desc: 'Crée un diagramme Excalidraw à partir de notes' },
].map((type) => (
<button
key={type.id}
onClick={() => setAgentType(type.id as any)}
className={`p-6 rounded-2xl border-2 transition-all flex flex-col items-center gap-3 text-center group relative
${agentType === type.id ? 'border-accent bg-white shadow-xl shadow-accent/10' : 'border-border bg-white/50 hover:bg-white'}`}
>
<div className={`p-3 rounded-xl transition-all ${agentType === type.id ? 'bg-accent text-white' : 'bg-slate-50 text-concrete group-hover:text-ink'}`}>
{type.icon}
</div>
<div className="space-y-1">
<p className="text-[13px] font-bold text-ink">{type.label}</p>
<p className="text-[10px] text-muted-ink leading-tight">{type.desc}</p>
</div>
<div className={`absolute top-4 right-4 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all
${agentType === type.id ? 'border-accent' : 'border-border opacity-20'}`}>
{agentType === type.id && <div className="w-2 h-2 bg-accent rounded-full" />}
</div>
</button>
))}
</div>
</div>
</section>
<section className="space-y-10">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-[0.3em] text-concrete">
CONFIGURATION <Info size={12} className="opacity-40" />
</div>
<button className="flex items-center gap-2 px-6 py-2 border-2 border-rose-100 bg-rose-50 rounded-xl text-rose-500 text-[11px] font-bold uppercase tracking-widest hover:bg-rose-100 transition-colors">
<Trash2 size={14} /> Supprimer
</button>
</div>
<div className="bg-white dark:bg-white/5 border border-border/60 rounded-[32px] p-12 space-y-12 shadow-sm">
<div className="space-y-6">
<div className="flex items-center gap-2">
<label className="text-[11px] font-bold uppercase tracking-widest text-concrete">DESCRIPTION (OPTIONEL)</label>
<Info size={12} className="text-concrete/40" />
</div>
<textarea
className="w-full bg-slate-50 dark:bg-black/20 border border-border/40 rounded-2xl p-6 text-sm outline-none focus:ring-4 ring-accent/5 focus:border-accent/40 transition-all font-light leading-relaxed resize-none text-ink"
placeholder="Décrivez brièvement le rôle de cet agent..."
defaultValue="Lit une note et génère un diagramme visuel dans le Lab Excalidraw."
/>
</div>
<div className="space-y-6">
<div className="flex items-center gap-2">
<label className="text-[11px] font-bold uppercase tracking-widest text-concrete">CARNET À SURVEILLER</label>
<Info size={12} className="text-concrete/40" />
</div>
<HierarchicalCarnetSelector
carnets={carnets}
selectedId={selectedCarnetForAgent}
onSelect={setSelectedCarnetForAgent}
/>
</div>
<div className="space-y-6">
<div className="flex items-center gap-2">
<label className="text-[11px] font-bold uppercase tracking-widest text-concrete">NOTES À ANALYSER</label>
<Info size={12} className="text-concrete/40" />
</div>
<div className="bg-slate-50 dark:bg-black/20 border border-border/40 rounded-2xl overflow-hidden divide-y divide-border/20">
{[
'Résumé du conteneur LXC devSandbox',
'Connexion SSH sans mot de passe à devSandbox',
'Gateway token (blank to generate)',
'Procédure d\'accès à openclaw',
'Derniers commits du repo Momento'
].map((note, i) => (
<label key={i} className="flex items-center gap-4 px-6 py-4 cursor-pointer hover:bg-white/50 transition-colors group">
<div className={`w-5 h-5 rounded border transition-all flex items-center justify-center
${i === 0 ? 'bg-accent border-accent text-white' : 'bg-white border-border group-hover:border-accent/40'}`}>
{i === 0 && <Check size={12} />}
</div>
<input type="checkbox" className="hidden" defaultChecked={i === 0} />
<span className={`text-[13px] transition-colors ${i === 0 ? 'font-medium text-ink' : 'text-muted-ink'}`}>{note}</span>
</label>
))}
</div>
<p className="text-[10px] text-concrete/60 italic font-medium">{1} note(s) sélectionnée(s)</p>
</div>
<div className="space-y-8">
<div className="flex items-center gap-2">
<label className="text-[11px] font-bold uppercase tracking-widest text-concrete">TYPE DE DIAGRAMME</label>
</div>
<div className="grid grid-cols-2 md:grid-cols-2 gap-3">
{[
'Auto (détection métier)', 'Flowchart (processus)',
'Mindmap (idées)', 'Organigramme (équipes)',
'Timeline / roadmap', 'Process map (opérations)',
'Architecture cloud (zones/RG)'
].map((type, i) => (
<button key={i} className={`px-6 py-4 rounded-xl border text-[13px] text-left transition-all
${i === 0 ? 'border-ink bg-slate-50 font-bold text-ink ring-2 ring-ink/5' : 'border-border text-concrete hover:border-concrete/40 hover:bg-slate-50/50'}`}>
{type}
</button>
))}
</div>
</div>
<div className="pt-8 border-t border-border flex flex-col items-center gap-6">
{agentResult ? (
<div className="w-full space-y-4">
<div className="flex items-center justify-between">
<h5 className="text-[10px] font-bold uppercase tracking-widest text-concrete">Résultat de l'extraction</h5>
<button onClick={handleSaveResult} className="text-accent hover:underline text-[10px] font-bold uppercase tracking-widest flex items-center gap-1">
<ArrowRight size={12} /> Sauvegarder dans une note
</button>
</div>
<div className="bg-slate-50 dark:bg-black/20 p-6 rounded-2xl border border-accent/20 font-serif text-sm text-ink leading-relaxed whitespace-pre-wrap max-h-96 overflow-y-auto">
{agentResult}
</div>
</div>
) : (
<button
onClick={handleRunTaskAgent}
disabled={isRunningAgent}
className="px-12 py-4 bg-accent text-white rounded-2xl text-sm font-bold uppercase tracking-[0.2em] hover:opacity-90 transition-all shadow-xl shadow-accent/20 flex items-center gap-4 disabled:opacity-50"
>
{isRunningAgent ? <Loader2 size={18} className="animate-spin" /> : <Play size={18} fill="currentColor" />}
Lancer l'extraction d'actions
</button>
)}
<p className="text-[10px] text-concrete/60 italic">Cet agent analysera toutes les notes du carnet sélectionné.</p>
</div>
</div>
</section>
</div>
</motion.div>
)}
</div>
);
};