feat: add slides generation tool with multiple slide types
Some checks failed
CI / Lint, Test & Build (push) Failing after 17s
CI / Deploy production (on server) (push) Has been skipped

- Add slides.tool.ts with support for title, bullets, chart, stats, table, cards, timeline, quote, comparison, equation, image, summary slide types
- Chart types: bar, horizontal-bar, line, donut, radar
- Integrate with agent executor and canvas system
- Add multilingual support (en/fr)
- Various UI improvements and bug fixes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Antigravity
2026-05-22 17:18:48 +00:00
parent 0f6b9509da
commit 5728452b4a
68 changed files with 6990 additions and 2584 deletions

View File

@@ -3,7 +3,7 @@
import { useState, useCallback, useMemo, useEffect, useRef } from 'react'
import { motion } from 'motion/react'
import { Plus, Bot, Search, LifeBuoy } from 'lucide-react'
import { Plus, Bot, Search, LifeBuoy, Menu } from 'lucide-react'
import { toast } from 'sonner'
import { useLanguage } from '@/lib/i18n'
@@ -218,10 +218,18 @@ export function AgentsPageClient({
/>
) : (
<>
<header className="px-12 pt-12 pb-8 flex flex-col gap-6 sticky top-0 bg-background/80 backdrop-blur-md z-30">
<div className="flex justify-between items-end">
<div className="space-y-1">
<h1 className="font-memento-serif text-4xl font-medium tracking-tight text-foreground">
<header className="px-4 sm:px-8 md:px-12 pt-6 sm:pt-10 md:pt-12 pb-5 sm:pb-6 md:pb-8 flex flex-col gap-4 sm:gap-6 sticky top-0 bg-background/80 backdrop-blur-md z-30">
<div className="flex items-start gap-3 sm:flex-row sm:justify-between sm:items-end">
{/* Hamburger mobile */}
<button
className="md:hidden p-2 -ms-1 text-foreground hover:bg-foreground/5 rounded-lg transition-colors shrink-0 mt-0.5"
onClick={() => window.dispatchEvent(new CustomEvent('open-mobile-sidebar'))}
aria-label="Ouvrir la navigation"
>
<Menu size={22} />
</button>
<div className="space-y-1 flex-1 min-w-0">
<h1 className="font-memento-serif text-2xl sm:text-3xl md:text-4xl font-medium tracking-tight text-foreground">
{t('agents.myAgents')}
</h1>
<p className="text-sm text-muted-foreground font-light">
@@ -246,13 +254,13 @@ export function AgentsPageClient({
</div>
</div>
<div className="flex items-center justify-between gap-8 border-b border-border pt-4">
<div className="flex items-center gap-8">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between sm:gap-8 border-b border-border pt-2 sm:pt-4">
<div className="flex items-center gap-4 sm:gap-8 overflow-x-auto">
{typeFilterOptions.map((opt, i) => (
<button
key={opt.value}
onClick={() => setTypeFilter(opt.value)}
className={`pb-4 text-xs font-bold uppercase tracking-widest transition-all relative ${
className={`pb-4 text-xs font-bold uppercase tracking-widest transition-all relative shrink-0 ${
typeFilter === opt.value ? 'text-foreground' : 'text-muted-foreground hover:text-foreground/60'
}`}
>
@@ -266,7 +274,7 @@ export function AgentsPageClient({
</button>
))}
</div>
<div className="relative pb-4">
<div className="relative pb-4 hidden sm:block">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground/60" />
<input
type="text"
@@ -279,7 +287,7 @@ export function AgentsPageClient({
</div>
</header>
<div className="px-12 flex-1 pb-20 space-y-12">
<div className="px-4 sm:px-8 md:px-12 flex-1 pb-10 sm:pb-16 md:pb-20 space-y-8 sm:space-y-12">
{agents.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 text-center bg-card rounded-2xl border border-border/40 shadow-sm">
<Bot className="w-12 h-12 text-muted-foreground/20 mb-4" />

View File

@@ -0,0 +1,17 @@
'use client'
import dynamic from 'next/dynamic'
// D3 uses browser APIs — must be loaded client-side only
const NoteGraphView = dynamic(
() => import('@/components/note-graph-view').then(m => m.NoteGraphView),
{ ssr: false }
)
export default function GraphPage() {
return (
<div className="h-screen overflow-hidden">
<NoteGraphView />
</div>
)
}

View File

@@ -1,5 +1,6 @@
'use client'
import { Menu } from 'lucide-react'
import { SettingsNav } from '@/components/settings'
export default function SettingsLayout({
@@ -9,21 +10,30 @@ export default function SettingsLayout({
}) {
return (
<div className="flex flex-col h-full bg-[#F2F0E9] dark:bg-dark-paper">
<header className="px-12 pt-20 pb-16 space-y-12 shrink-0">
<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>
<header className="px-4 sm:px-8 md:px-12 pt-8 sm:pt-14 md:pt-20 pb-6 sm:pb-10 md:pb-16 space-y-6 sm:space-y-10 md:space-y-12 shrink-0">
<div className="flex items-start gap-3">
<button
className="md:hidden p-2 -ms-1 text-ink/70 hover:bg-ink/5 rounded-lg transition-colors shrink-0 mt-1"
onClick={() => window.dispatchEvent(new CustomEvent('open-mobile-sidebar'))}
aria-label="Ouvrir la navigation"
>
<Menu size={22} />
</button>
<div>
<h1 className="text-3xl sm:text-5xl md: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 mt-4">
Configuration & Préférences
</p>
</div>
</div>
<SettingsNav />
</header>
<div className="flex-1 overflow-y-auto">
<div className="max-w-5xl mx-auto px-12 py-10 space-y-8">
<div className="max-w-5xl mx-auto px-4 sm:px-8 md:px-12 py-6 sm:py-8 md:py-10 space-y-8">
{children}
</div>
</div>

View File

@@ -10,7 +10,7 @@ export default function SupportPage() {
return (
<div className="container mx-auto py-10 max-w-4xl">
<div className="text-center mb-10">
<h1 className="text-4xl font-bold mb-4">
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold mb-4">
{t('support.title')}
</h1>
<p className="text-muted-foreground text-lg">

View File

@@ -13,6 +13,7 @@ import {
Search,
Clock,
AlertCircle,
Menu,
} from 'lucide-react'
import { Note, Notebook } from '@/lib/types'
import { restoreNote, permanentDeleteNote, emptyTrash } from '@/app/actions/notes'
@@ -139,16 +140,26 @@ export function TrashClient({
return (
<div className="h-full flex flex-col bg-[#F9F8F6] dark:bg-[#1a1a1a]">
<header className="px-12 pt-12 pb-8 flex flex-col gap-6 sticky top-0 bg-[#F9F8F6]/80 dark:bg-[#1a1a1a]/80 backdrop-blur-md z-30 border-b border-border/20">
<div className="flex items-center justify-between">
<div className="space-y-1">
<h1 className="text-4xl font-memento-serif font-medium text-foreground flex items-center gap-4">
{t('sidebar.trash')} <Trash2 size={28} className="text-rose-400 opacity-40" />
</h1>
<p className="text-[10px] text-muted-foreground font-bold uppercase tracking-[0.3em] opacity-60">
{t('trash.autoDelete30')}
</p>
</div>
<header className="px-4 sm:px-8 md:px-12 pt-6 sm:pt-10 md:pt-12 pb-5 sm:pb-6 md:pb-8 flex flex-col gap-4 sm:gap-6 sticky top-0 bg-[#F9F8F6]/80 dark:bg-[#1a1a1a]/80 backdrop-blur-md z-30 border-b border-border/20">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3 min-w-0">
{/* Hamburger mobile */}
<button
className="md:hidden p-2 -ms-1 text-foreground hover:bg-foreground/5 rounded-lg transition-colors shrink-0"
onClick={() => window.dispatchEvent(new CustomEvent('open-mobile-sidebar'))}
aria-label="Ouvrir la navigation"
>
<Menu size={22} />
</button>
<div className="space-y-1 min-w-0">
<h1 className="text-2xl sm:text-3xl md:text-4xl font-memento-serif font-medium text-foreground flex items-center gap-3">
{t('sidebar.trash')} <Trash2 size={28} className="text-rose-400 opacity-40" />
</h1>
<p className="text-[10px] text-muted-foreground font-bold uppercase tracking-[0.3em] opacity-60">
{t('trash.autoDelete30')}
</p>
</div>
</div>{/* end flex items-center gap-3 */}
{items.length > 0 && (
<button
@@ -161,24 +172,24 @@ export function TrashClient({
)}
</div>
<div className="flex items-center gap-6">
<div className="group relative flex-1 max-w-xl">
<div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-6">
<div className="group relative flex-1">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground group-focus-within:text-foreground transition-colors" size={16} />
<input
type="text"
placeholder={t('common.search')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-white dark:bg-white/5 border border-border/40 rounded-2xl pl-12 pr-6 py-4 text-sm outline-none focus:ring-4 ring-foreground/5 transition-all shadow-sm"
className="w-full bg-white dark:bg-white/5 border border-border/40 rounded-2xl pl-12 pr-6 py-3 sm:py-4 text-sm outline-none focus:ring-4 ring-foreground/5 transition-all shadow-sm"
/>
</div>
<div className="flex bg-white dark:bg-white/5 p-1 rounded-2xl border border-border shadow-sm">
<div className="flex bg-white dark:bg-white/5 p-1 rounded-2xl border border-border shadow-sm shrink-0">
{(['all', 'notes', 'notebooks'] as FilterType[]).map(type => (
<button
key={type}
onClick={() => setFilterType(type)}
className={`px-6 py-3 rounded-xl text-[10px] font-bold uppercase tracking-widest transition-all
className={`px-3 sm:px-6 py-2 sm:py-3 rounded-xl text-[10px] font-bold uppercase tracking-widest transition-all
${filterType === type ? 'bg-foreground text-background shadow-lg' : 'text-muted-foreground hover:text-foreground'}`}
>
{type === 'all' ? t('trash.filterAll') : type === 'notes' ? t('nav.notes') : t('nav.notebooks')}
@@ -188,9 +199,9 @@ export function TrashClient({
</div>
</header>
<main className="flex-1 px-12 py-12 overflow-y-auto">
<main className="flex-1 px-4 sm:px-8 md:px-12 py-6 sm:py-8 md:py-12 overflow-y-auto">
{items.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 md:gap-8">
<AnimatePresence mode="popLayout">
{items.map(item => {
const daysLeft = getDaysRemaining(item.deletedAt)
@@ -281,7 +292,7 @@ export function TrashClient({
)}
</main>
<footer className="px-12 py-6 bg-white/50 dark:bg-white/5 border-t border-border flex items-center gap-4">
<footer className="px-4 sm:px-8 md:px-12 py-3 sm:py-4 md:py-6 bg-white/50 dark:bg-white/5 border-t border-border flex items-center gap-3">
<AlertCircle size={14} className="text-muted-foreground" />
<p className="text-[10px] text-muted-foreground font-medium uppercase tracking-widest">
{t('trash.notebookRestoreHint')}