feat: add slides generation tool with multiple slide types
- 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:
@@ -29,6 +29,7 @@ import {
|
||||
PinOff,
|
||||
Sparkles,
|
||||
Home,
|
||||
Network,
|
||||
} from 'lucide-react'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
@@ -182,6 +183,7 @@ function SidebarCarnetItem({
|
||||
isExpanded,
|
||||
toggleExpand,
|
||||
hasChildNotebooks,
|
||||
hasActiveDescendant,
|
||||
}: {
|
||||
carnet: { id: string; name: string; initial: string; isPrivate?: boolean }
|
||||
isActive: boolean
|
||||
@@ -201,6 +203,7 @@ function SidebarCarnetItem({
|
||||
isExpanded: boolean
|
||||
toggleExpand: () => void
|
||||
hasChildNotebooks?: boolean
|
||||
hasActiveDescendant?: boolean
|
||||
}) {
|
||||
const { t, language } = useLanguage()
|
||||
const isRtl = language === 'fa' || language === 'ar'
|
||||
@@ -287,6 +290,9 @@ function SidebarCarnetItem({
|
||||
{carnet.name}
|
||||
</span>
|
||||
{carnet.isPrivate && <Lock size={10} className="text-concrete/60 shrink-0" />}
|
||||
{!isActive && hasActiveDescendant && (
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-brand-accent/70 shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 opacity-0 group-hover/item:opacity-100 transition-opacity shrink-0">
|
||||
@@ -424,6 +430,19 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
|
||||
const [renamingNotebook, setRenamingNotebook] = useState<Notebook | null>(null)
|
||||
const [renameValue, setRenameValue] = useState('')
|
||||
const [isDark, setIsDark] = useState(false)
|
||||
const [isMobileOpen, setIsMobileOpen] = useState(false)
|
||||
|
||||
// Écoute l'événement d'ouverture du menu mobile
|
||||
useEffect(() => {
|
||||
const handler = () => setIsMobileOpen(true)
|
||||
window.addEventListener('open-mobile-sidebar', handler)
|
||||
return () => window.removeEventListener('open-mobile-sidebar', handler)
|
||||
}, [])
|
||||
|
||||
// Ferme la sidebar mobile lors d'une navigation
|
||||
useEffect(() => {
|
||||
setIsMobileOpen(false)
|
||||
}, [pathname, searchParams])
|
||||
|
||||
useEffect(() => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'))
|
||||
@@ -631,22 +650,6 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
|
||||
} catch {}
|
||||
}, [])
|
||||
|
||||
// Auto-expand all parent notebooks on first load
|
||||
useEffect(() => {
|
||||
if (orderedNotebooks.length === 0) return
|
||||
const parentIds = new Set<string>()
|
||||
for (const nb of orderedNotebooks) {
|
||||
if (nb.parentId) parentIds.add(nb.parentId)
|
||||
}
|
||||
if (parentIds.size > 0) {
|
||||
setExpandedIds(prev => {
|
||||
const next = new Set(prev)
|
||||
for (const id of parentIds) next.add(id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
}, [orderedNotebooks])
|
||||
|
||||
const togglePin = useCallback((id: string) => {
|
||||
setPinnedIds(prev => {
|
||||
const next = new Set(prev)
|
||||
@@ -730,8 +733,8 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
|
||||
return desc.some(c => currentNotebookId === c.id || hasDescendant(c.id))
|
||||
}
|
||||
const hasActiveDescendant = hasDescendant(notebook.id)
|
||||
// A notebook stays expanded if: manually expanded, has active descendant, OR is pinned
|
||||
const isExpanded = expandedIds.has(notebook.id) || hasActiveDescendant || pinnedIds.has(notebook.id)
|
||||
// A notebook stays expanded if: manually expanded OR is pinned
|
||||
const isExpanded = expandedIds.has(notebook.id) || pinnedIds.has(notebook.id)
|
||||
|
||||
return (
|
||||
<motion.div key={notebook.id}>
|
||||
@@ -784,7 +787,6 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
|
||||
toggleExpand(notebook.id)
|
||||
} else {
|
||||
handleCarnetClick(notebook.id)
|
||||
if (!expandedIds.has(notebook.id)) toggleExpand(notebook.id)
|
||||
}
|
||||
}}
|
||||
onNoteClick={handleNoteClick}
|
||||
@@ -802,6 +804,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
|
||||
isExpanded={isExpanded}
|
||||
toggleExpand={() => toggleExpand(notebook.id)}
|
||||
hasChildNotebooks={children.length > 0}
|
||||
hasActiveDescendant={hasActiveDescendant}
|
||||
/>
|
||||
</div>
|
||||
{isExpanded && renderCarnetTree(notebook.id, level + 1)}
|
||||
@@ -812,10 +815,29 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Overlay mobile */}
|
||||
<AnimatePresence>
|
||||
{isMobileOpen && (
|
||||
<motion.div
|
||||
key="sidebar-backdrop"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="fixed inset-0 bg-black/40 backdrop-blur-sm z-[60] md:hidden"
|
||||
onClick={() => setIsMobileOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<aside
|
||||
className={cn(
|
||||
'hidden h-full min-h-0 w-72 shrink-0 flex-col lg:w-80 md:flex',
|
||||
'border-e border-border/40 bg-white/30 backdrop-blur-md sidebar-shadow dark:border-white/6 dark:bg-[#151515] dark:backdrop-blur-none',
|
||||
// Mobile: fixed overlay, slide in/out
|
||||
'fixed inset-y-0 start-0 z-[70] md:relative md:z-auto',
|
||||
'h-full min-h-0 w-72 lg:w-80 shrink-0 flex flex-col',
|
||||
'transition-transform duration-300 ease-in-out',
|
||||
isMobileOpen ? 'translate-x-0 shadow-2xl' : '-translate-x-full md:translate-x-0',
|
||||
'border-e border-border/40 bg-white/95 md:bg-white/30 backdrop-blur-md sidebar-shadow dark:border-white/6 dark:bg-[#151515] dark:backdrop-blur-none',
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -1145,7 +1167,25 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<div className="my-2 h-px bg-border/20 mx-2" />
|
||||
{/* ── Intelligence section ── */}
|
||||
<div className="pt-3 border-t border-border/20 mx-2 mt-1 space-y-0.5">
|
||||
<p className="text-[9px] font-bold text-muted-ink tracking-[0.2em] uppercase px-1 mb-1 opacity-60">Intelligence</p>
|
||||
<Link
|
||||
href="/graph"
|
||||
className={cn(
|
||||
'w-full flex items-center gap-3 px-3 py-2 text-[12px] transition-all font-medium group rounded-xl',
|
||||
pathname === '/graph'
|
||||
? 'bg-indigo-500/10 text-indigo-500'
|
||||
: 'text-muted-ink hover:text-indigo-500 hover:bg-indigo-500/5'
|
||||
)}
|
||||
>
|
||||
<Network
|
||||
size={14}
|
||||
className={pathname === '/graph' ? 'text-indigo-500' : 'text-muted-ink group-hover:text-indigo-500'}
|
||||
/>
|
||||
<span className="flex-1">Vue en graphe</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
Reference in New Issue
Block a user