feat: hierarchical notebook system - trash, selectors, breadcrumb, sidebar tree
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m9s

- Schema: soft delete with trashedAt on Notebook model
- API: PATCH/GET notebooks support trashedAt filtering with cascade
- Sidebar: recursive tree rendering with collapse/expand, visual guides, hover actions
- HierarchicalNotebookSelector: portal-based dropdown with search, breadcrumbs, dropUp support
- AI chat: context selector with Toutes mes notes + notebook selector
- Agent detail: flat selects replaced with HierarchicalNotebookSelector
- Breadcrumb: notebook path display on home page
- Trash view: card grid with countdown, restore/permanent delete
- CSS: design tokens (ink, paper, blueprint, concrete, etc.)
- Types: parentId, trashedAt added to Notebook interface
This commit is contained in:
Antigravity
2026-05-10 10:52:26 +00:00
parent 539c72cf6d
commit 916fb78dfb
20 changed files with 1319 additions and 391 deletions

View File

@@ -11,7 +11,7 @@ import { useLanguage } from '@/lib/i18n'
import { MarkdownContent } from '@/components/markdown-content'
import { useWebSearchAvailable } from '@/hooks/use-web-search-available'
import { useNotebooks } from '@/context/notebooks-context'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { HierarchicalNotebookSelector } from '@/components/hierarchical-notebook-selector'
import { toast } from 'sonner'
import { createConversation } from '@/app/actions/chat-actions'
@@ -352,29 +352,39 @@ export function AIChat({ showFloatingTrigger = true }: { showFloatingTrigger?: b
{/* Input Area & Tone Controls (Only in Chat tab) */}
<div className={cn("p-4 border-t border-border/40 bg-background shrink-0", activeTab !== 'chat' && "hidden")}>
{/* Context Scope */}
<div className="mb-3">
<span className="text-[9px] font-bold uppercase tracking-widest text-muted-foreground block mb-1.5 ml-1">{t('ai.discussionContextLabel')}</span>
<Select value={chatScope} onValueChange={setChatScope}>
<SelectTrigger className="h-8 text-xs bg-background border-border/60">
<SelectValue placeholder={t('ai.selectNotebook')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">
<div className="flex items-center gap-2">
<Layers className="h-4 w-4 text-muted-foreground" />
<span>{t('ai.allMyNotes')}</span>
</div>
</SelectItem>
{notebooks && notebooks.length > 0 && notebooks.map(nb => (
<SelectItem key={nb.id} value={nb.id}>
<div className="flex items-center gap-2">
<BookOpen className="h-4 w-4 text-muted-foreground" />
<span>{nb.name}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
<div className="mb-3 space-y-2">
<span className="text-[9px] font-bold uppercase tracking-widest text-muted-foreground block ml-1">Source du Contexte</span>
<button
onClick={() => setChatScope('all')}
className={cn(
'w-full p-2.5 border rounded-lg text-xs flex items-center justify-between transition-all',
chatScope === 'all' ? 'bg-blueprint/10 border-blueprint/30' : 'bg-card border-border hover:border-foreground/20'
)}
>
<div className="flex items-center gap-2">
<Layers className="h-3.5 w-3.5 text-blueprint/60" />
<span className={cn('font-medium', chatScope === 'all' ? 'text-blueprint' : 'text-foreground/60')}>
{t('ai.allMyNotes') || 'Toutes mes notes'}
</span>
</div>
{chatScope === 'all' && (
<span className="text-[8px] bg-blueprint/10 text-blueprint px-1.5 py-0.5 rounded uppercase font-bold">Auto</span>
)}
</button>
<div className="flex items-center gap-2 px-2">
<div className="h-px flex-1 bg-border/40" />
<span className="text-[9px] font-bold text-muted-foreground uppercase tracking-widest">+ Carnet</span>
<div className="h-px flex-1 bg-border/40" />
</div>
<HierarchicalNotebookSelector
notebooks={notebooks.filter(nb => !nb.trashedAt)}
selectedId={chatScope !== 'all' ? chatScope : null}
onSelect={(id) => setChatScope(id)}
placeholder={t('ai.selectNotebook') || 'Inclure un carnet...'}
dropUp
/>
</div>
{/* Tone Selection */}