Files
Momento/memento-note/components/home-client.tsx
Antigravity 2fd435df6f
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 53s
feat: redesign agents page (architectural-grid style), add image description, fix AI limits, remove dead code
- Redesign agents page with architectural-grid (8) design system:
  rounded-2xl cards, serif headings, motion tabs, dashed templates section
- Replace agent form popup with full-page detail view (SettingsView style)
  with dark planning card, section tooltips, and help button
- Hide advanced mode for slide/excalidraw generators
- Add 'describe images' action to contextual AI assistant
- Add copy button to action/resource preview with HTTP fallback
- Add delete history button to agent run log panel
- Increase AI word limit from 2000 to 5000 (reformulate + transform-markdown)
- Increase max steps slider from 25 to 50
- Fix image description error with clear model compatibility message
- Fix doubled execution count display in agent detail view
- Remove dead files: notes-list-view.tsx, notes-view-toggle.tsx
- Remove 'list' view mode from NotesViewMode type
- Add missing i18n keys (back, configuration, options, copy, cleared)
2026-05-09 17:18:47 +00:00

644 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState, useEffect, useCallback, useRef, useTransition, useMemo } from 'react'
import { useSearchParams, useRouter } from 'next/navigation'
import dynamic from 'next/dynamic'
import { Note } from '@/lib/types'
import { getAllNotes, searchNotes, enableNoteHistory, getNoteById, createNote } from '@/app/actions/notes'
import { NotesMainSection, type NotesViewMode } from '@/components/notes-main-section'
import { NotesEditorialView } from '@/components/notes-editorial-view'
import { MemoryEchoNotification } from '@/components/memory-echo-notification'
import { NotebookSuggestionToast } from '@/components/notebook-suggestion-toast'
import { Button } from '@/components/ui/button'
import { Plus, ArrowUpDown, Search, Sparkles, FileText } from 'lucide-react'
import { useNoteRefresh } from '@/context/NoteRefreshContext'
import { useRefresh } from '@/lib/use-refresh'
import { useReminderCheck } from '@/hooks/use-reminder-check'
import { useAutoLabelSuggestion } from '@/hooks/use-auto-label-suggestion'
import { useNotebooks } from '@/context/notebooks-context'
import { cn } from '@/lib/utils'
import { useLanguage } from '@/lib/i18n'
import { useEditorUI } from '@/context/editor-ui-context'
import { NoteHistoryModal } from '@/components/note-history-modal'
import { toast } from 'sonner'
import { AnimatePresence, motion } from 'motion/react'
type SortOrder = 'newest' | 'oldest' | 'alpha'
const NoteEditor = dynamic(
() => import('@/components/note-editor').then(m => ({ default: m.NoteEditor })),
{ ssr: false }
)
const BatchOrganizationDialog = dynamic(
() => import('@/components/batch-organization-dialog').then(m => ({ default: m.BatchOrganizationDialog })),
{ ssr: false }
)
const AutoLabelSuggestionDialog = dynamic(
() => import('@/components/auto-label-suggestion-dialog').then(m => ({ default: m.AutoLabelSuggestionDialog })),
{ ssr: false }
)
const NotebookSummaryDialog = dynamic(
() => import('@/components/notebook-summary-dialog').then(m => ({ default: m.NotebookSummaryDialog })),
{ ssr: false }
)
type InitialSettings = {
showRecentNotes: boolean
notesViewMode: 'masonry' | 'tabs' | 'list'
noteHistory: boolean
noteHistoryMode: 'manual' | 'auto'
aiAssistantEnabled: boolean
}
interface HomeClientProps {
initialNotes: Note[]
initialSettings: InitialSettings
}
export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
const searchParams = useSearchParams()
const router = useRouter()
const { t } = useLanguage()
const [notes, setNotes] = useState<Note[]>(initialNotes)
const [pinnedNotes, setPinnedNotes] = useState<Note[]>(
initialNotes.filter(n => n.isPinned)
)
const [notesViewMode, setNotesViewMode] = useState<NotesViewMode>(initialSettings.notesViewMode === 'list' ? 'masonry' : initialSettings.notesViewMode)
const [noteHistoryMode] = useState<'manual' | 'auto'>(initialSettings.noteHistoryMode)
const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [notebookSuggestion, setNotebookSuggestion] = useState<{ noteId: string; content: string } | null>(null)
const [batchOrganizationOpen, setBatchOrganizationOpen] = useState(false)
const [historyOpen, setHistoryOpen] = useState(false)
const [historyNote, setHistoryNote] = useState<Note | null>(null)
const [isCreating, startCreating] = useTransition()
const [sortOrder, setSortOrder] = useState<SortOrder>('newest')
const [showSortMenu, setShowSortMenu] = useState(false)
const [showInlineSearch, setShowInlineSearch] = useState(false)
const [inlineSearchQuery, setInlineSearchQuery] = useState('')
const inlineSearchRef = useRef<HTMLInputElement>(null)
const notesRef = useRef(notes)
notesRef.current = notes
const { refreshKey, triggerRefresh } = useNoteRefresh()
const { refreshNotes } = useRefresh()
const { labels, notebooks } = useNotebooks()
const { setControls } = useEditorUI()
const { shouldSuggest: shouldSuggestLabels, notebookId: suggestNotebookId, dismiss: dismissLabelSuggestion } = useAutoLabelSuggestion()
const [autoLabelOpen, setAutoLabelOpen] = useState(false)
const [summaryDialogOpen, setSummaryDialogOpen] = useState(false)
useEffect(() => {
if (shouldSuggestLabels && suggestNotebookId) {
setAutoLabelOpen(true)
}
}, [shouldSuggestLabels, suggestNotebookId])
// Sidebar carnet / inbox: forceList → liste éditoriale + fermer l'éditeur plein écran (comme la ref. architectural-grid)
useEffect(() => {
const forceList = searchParams.get('forceList')
if (forceList !== '1') return
setNotesViewMode(prev => (prev === 'tabs' ? 'masonry' : prev))
setEditingNote(null)
const params = new URLSearchParams(searchParams.toString())
params.delete('forceList')
const newUrl = params.toString() ? `/?${params.toString()}` : '/'
router.replace(newUrl, { scroll: false })
}, [searchParams, router])
const notebookFilter = searchParams.get('notebook')
const handleNoteCreated = useCallback((note: Note) => {
setNotes((prevNotes) => {
const notebookFilter = searchParams.get('notebook')
const labelFilter = searchParams.get('labels')?.split(',').filter(Boolean) || []
const colorFilter = searchParams.get('color')
const search = searchParams.get('search')?.trim() || null
if (notebookFilter && note.notebookId !== notebookFilter) return prevNotes
if (!notebookFilter && note.notebookId) return prevNotes
if (labelFilter.length > 0) {
const noteLabels = note.labels || []
if (!noteLabels.some((label: string) => labelFilter.includes(label))) return prevNotes
}
if (colorFilter) {
const labelNamesWithColor = labels
.filter((label: any) => label.color === colorFilter)
.map((label: any) => label.name)
const noteLabels = note.labels || []
if (!noteLabels.some((label: string) => labelNamesWithColor.includes(label))) return prevNotes
}
if (search) {
router.refresh()
return prevNotes
}
const isPinned = note.isPinned || false
const pinnedNotes = prevNotes.filter(n => n.isPinned)
const unpinnedNotes = prevNotes.filter(n => !n.isPinned)
if (isPinned) {
return [note, ...pinnedNotes, ...unpinnedNotes]
} else {
return [...pinnedNotes, note, ...unpinnedNotes]
}
})
if (!note.notebookId) {
const wordCount = (note.content || '').trim().split(/\s+/).filter(w => w.length > 0).length
if (wordCount >= 20) {
setNotebookSuggestion({ noteId: note.id, content: note.content || '' })
}
}
}, [searchParams, labels, router])
// Always fetch fresh from server — avoids stale state after a save regardless of
// whether the notes list has re-fetched yet.
const handleOpenNoteFresh = useCallback(async (noteId: string, readOnly = false) => {
const note = await getNoteById(noteId)
if (note) setEditingNote({ note, readOnly })
}, [])
const handleAddNote = () => {
startCreating(async () => {
try {
const newNote = await createNote({
content: '',
type: 'richtext',
title: undefined,
notebookId: notebookFilter || undefined,
skipRevalidation: true
})
if (!newNote) return
handleNoteCreated(newNote)
setEditingNote({ note: newNote, readOnly: false })
} catch {
toast.error(t('notes.createFailed') || 'Failed to create note')
}
})
}
const handleOpenHistory = useCallback((note: Note) => {
setHistoryNote(note)
setHistoryOpen(true)
}, [])
const handleEnableHistory = useCallback(async (noteId: string) => {
await enableNoteHistory(noteId)
setNotes((prev) => prev.map((n) => (n.id === noteId ? { ...n, historyEnabled: true } : n)))
setPinnedNotes((prev) => prev.map((n) => (n.id === noteId ? { ...n, historyEnabled: true } : n)))
setEditingNote((prev) => (prev?.note.id === noteId ? { ...prev, note: { ...prev.note, historyEnabled: true } } : prev))
setHistoryNote((prev) => (prev?.id === noteId ? { ...prev, historyEnabled: true } : prev))
}, [])
const handleHistoryRestored = useCallback((restored: Note) => {
setNotes((prev) => prev.map((n) => (n.id === restored.id ? { ...n, ...restored } : n)))
setPinnedNotes((prev) => prev.map((n) => (n.id === restored.id ? { ...n, ...restored } : n)))
setEditingNote((prev) => (prev?.note.id === restored.id ? { ...prev, note: restored } : prev))
setHistoryNote((prev) => (prev?.id === restored.id ? { ...prev, ...restored } : prev))
}, [])
const handleSizeChange = useCallback((noteId: string, size: 'small' | 'medium' | 'large') => {
setNotes(prev => prev.map(n => n.id === noteId ? { ...n, size } : n))
setPinnedNotes(prev => prev.map(n => n.id === noteId ? { ...n, size } : n))
}, [])
useReminderCheck(notes)
// Garder openNote dans l'URL tant que l'éditeur est ouvert → le sidebar peut surligner la note (comme activeNoteId dans la ref.)
useEffect(() => {
const openNoteId = searchParams.get('openNote')
if (!openNoteId) return
let cancelled = false
const run = async () => {
// Always fetch fresh data from DB to avoid showing stale content after a save.
// notesRef.current can be stale if the notes list hasn't re-fetched yet when the
// user closes and re-opens the note quickly after saving.
const note = await getNoteById(openNoteId)
if (cancelled || !note) return
setEditingNote({ note, readOnly: false })
}
run()
return () => {
cancelled = true
}
}, [searchParams])
useEffect(() => {
const handler = (e: Event) => {
const { name } = (e as CustomEvent).detail
if (!name) return
const removeLabel = (note: Note) => {
const currentLabels = note.labels || []
const updated = currentLabels.filter((l) => l.toLowerCase() !== name.toLowerCase())
if (updated.length === currentLabels.length) return note
return { ...note, labels: updated.length > 0 ? updated : null }
}
setNotes((prev) => prev.map(removeLabel))
setPinnedNotes((prev) => prev.map(removeLabel))
}
window.addEventListener('label-deleted', handler)
return () => window.removeEventListener('label-deleted', handler)
}, [])
const prevRefreshKey = useRef(refreshKey)
useEffect(() => {
const search = searchParams.get('search')?.trim() || null
const labelFilter = searchParams.get('labels')?.split(',').filter(Boolean) || []
const colorFilter = searchParams.get('color')
const notebook = searchParams.get('notebook')
const semanticMode = searchParams.get('semantic') === 'true'
const isBackgroundRefresh = refreshKey > prevRefreshKey.current
prevRefreshKey.current = refreshKey
const hasActiveFilter = search || labelFilter.length > 0 || colorFilter
const load = async () => {
if (!isBackgroundRefresh) {
setIsLoading(true)
}
let allNotes = search
? await searchNotes(search, semanticMode, notebook || undefined)
: await getAllNotes(false, notebook || undefined)
const sharedOnly = searchParams.get('shared') === '1'
const remindersOnly = searchParams.get('reminders') === '1'
if (sharedOnly) {
allNotes = allNotes.filter((note: any) => note._isShared)
} else if (remindersOnly) {
allNotes = allNotes.filter((note: any) => note.reminder !== null)
} else if (!notebook && !search) {
allNotes = allNotes.filter((note: any) => !note.notebookId && !note._isShared)
}
if (labelFilter.length > 0) {
allNotes = allNotes.filter((note: any) =>
note.labels?.some((label: string) => labelFilter.includes(label))
)
}
if (colorFilter) {
const labelNamesWithColor = labels
.filter((label: any) => label.color === colorFilter)
.map((label: any) => label.name)
allNotes = allNotes.filter((note: any) =>
note.labels?.some((label: string) => labelNamesWithColor.includes(label))
)
}
setNotes(prev => {
const localSizeMap = new Map(prev.map(n => [n.id, n.size]))
return allNotes.map(n => ({ ...n, size: localSizeMap.get(n.id) ?? n.size }))
})
setPinnedNotes(allNotes.filter((n: any) => n.isPinned))
setIsLoading(false)
}
if (refreshKey > 0 || hasActiveFilter) {
const cancelled = { value: false }
load().then(() => { if (cancelled.value) return })
return () => { cancelled.value = true }
} else {
let filtered = initialNotes
const sharedOnly = searchParams.get('shared') === '1'
const remindersOnly = searchParams.get('reminders') === '1'
if (notebook) {
filtered = initialNotes.filter((n: any) => n.notebookId === notebook && !n._isShared)
} else if (sharedOnly) {
filtered = initialNotes.filter((n: any) => n._isShared)
} else if (remindersOnly) {
filtered = initialNotes.filter((n: any) => n.reminder !== null)
} else {
filtered = initialNotes.filter((n: any) => !n.notebookId && !n._isShared)
}
setNotes(prev => {
const localSizeMap = new Map(prev.map(n => [n.id, n.size]))
return filtered.map(n => ({ ...n, size: localSizeMap.get(n.id) ?? n.size }))
})
setPinnedNotes(filtered.filter(n => n.isPinned))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams, refreshKey])
const currentNotebook = notebooks.find((n: any) => n.id === searchParams.get('notebook'))
useEffect(() => {
setControls({
isTabsMode: notesViewMode === 'tabs',
openNoteComposer: () => handleAddNote(),
})
return () => setControls(null)
}, [notesViewMode, setControls])
// Apply sort order to notes
const sortedNotes = useMemo(() => {
const sorted = [...notes]
if (sortOrder === 'newest') sorted.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
if (sortOrder === 'oldest') sorted.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
if (sortOrder === 'alpha') sorted.sort((a, b) => (a.title || '').localeCompare(b.title || ''))
return sorted
}, [notes, sortOrder])
const sortedPinnedNotes = useMemo(() => {
return sortedNotes.filter(n => n.isPinned)
}, [sortedNotes])
const sortLabels: Record<SortOrder, string> = {
newest: t('sidebar.sortNewest') || 'Plus récentes',
oldest: t('sidebar.sortOldest') || 'Plus anciennes',
alpha: t('sidebar.sortAlpha') || 'A → Z',
}
const isTabs = notesViewMode === 'tabs'
const isEditorialMode = !isTabs
const handleEditorClose = useCallback(() => {
setEditingNote(null)
const params = new URLSearchParams(searchParams.toString())
params.delete('openNote')
const qs = params.toString()
router.replace(qs ? `/?${qs}` : '/', { scroll: false })
// Invalidate notes cache and trigger refresh
refreshNotes(searchParams.get('notebook') || null)
}, [refreshNotes, router, searchParams])
// Called by NoteEditor when a save succeeds — update local state immediately
// so the user sees fresh data if they reopen the note before getAllNotes() completes
const handleNoteSaved = useCallback((savedNote: Note) => {
setNotes(prev => prev.map(n => n.id === savedNote.id ? { ...n, ...savedNote } : n))
setPinnedNotes(prev => prev.map(n => n.id === savedNote.id ? { ...n, ...savedNote } : n))
setEditingNote(prev => prev?.note.id === savedNote.id ? { ...prev, note: savedNote } : prev)
// Refresh sidebar note titles so the new title appears immediately
refreshNotes(savedNote.notebookId || null)
}, [refreshNotes])
return (
<div
className={cn(
'flex w-full min-h-0 flex-1 flex-col',
isTabs ? 'gap-3 py-1' : 'h-full'
)}
>
{editingNote ? (
<NoteEditor
note={editingNote.note}
readOnly={editingNote.readOnly}
onClose={handleEditorClose}
onNoteSaved={handleNoteSaved}
fullPage
/>
) : (
<div className="flex-1 overflow-y-auto min-h-0 bg-memento-paper flex flex-col">
<div className={cn(
'px-12 pt-12 pb-8 flex flex-col gap-6',
isEditorialMode ? 'sticky top-0 bg-memento-paper/90 backdrop-blur-md z-30' : ''
)}>
<div className="flex justify-between items-start">
<h1 className="font-memento-serif text-4xl font-medium tracking-tight text-foreground leading-tight pr-12">
{currentNotebook
? currentNotebook.name
: searchParams.get('shared') === '1'
? (t('sidebar.sharedWithMe') || 'Partagées avec moi')
: searchParams.get('reminders') === '1'
? (t('sidebar.reminders') || 'Rappels')
: t('notes.title')}
</h1>
</div>
<div className="flex items-center justify-between border-b border-foreground/5 pb-4">
<div className="flex items-center gap-6">
<button
onClick={handleAddNote}
disabled={isCreating}
className="flex items-center gap-2 text-[13px] text-foreground font-medium hover:opacity-70 transition-opacity"
>
<Plus size={16} />
<span>{t('notes.newNote') || 'Add Note'}</span>
</button>
{/* Inline search — toggles an input within the toolbar */}
{showInlineSearch ? (
<div className="flex items-center gap-2">
<Search size={14} className="text-muted-foreground shrink-0" />
<input
ref={inlineSearchRef}
autoFocus
type="text"
value={inlineSearchQuery}
onChange={e => {
const q = e.target.value
setInlineSearchQuery(q)
const params = new URLSearchParams(searchParams.toString())
if (q.trim()) {
params.set('search', q)
} else {
params.delete('search')
}
router.push(`/?${params.toString()}`)
}}
onBlur={() => {
if (!inlineSearchQuery) {
setShowInlineSearch(false)
}
}}
onKeyDown={e => {
if (e.key === 'Escape') {
setShowInlineSearch(false)
setInlineSearchQuery('')
const params = new URLSearchParams(searchParams.toString())
params.delete('search')
router.push(`/?${params.toString()}`)
}
}}
placeholder={t('search.placeholder') || 'Rechercher...'}
className="w-48 bg-transparent border-b border-foreground/20 focus:border-foreground outline-none text-[13px] text-foreground placeholder:text-muted-foreground/50 py-0.5 transition-colors"
/>
{inlineSearchQuery && (
<button
onClick={() => {
setShowInlineSearch(false)
setInlineSearchQuery('')
const params = new URLSearchParams(searchParams.toString())
params.delete('search')
router.push(`/?${params.toString()}`)
}}
className="text-muted-foreground hover:text-foreground transition-colors"
>
<span className="text-[11px]">×</span>
</button>
)}
</div>
) : (
<button
onClick={() => {
setShowInlineSearch(true)
setTimeout(() => inlineSearchRef.current?.focus(), 50)
}}
className="flex items-center gap-2 text-[13px] text-foreground font-medium hover:opacity-70 transition-opacity"
>
<Search size={16} />
<span>{t('notes.search') || 'Search'}</span>
</button>
)}
{!searchParams.get('notebook') && searchParams.get('shared') !== '1' && (
<button
onClick={() => setBatchOrganizationOpen(true)}
className="flex items-center gap-2 text-[13px] text-foreground font-medium hover:opacity-70 transition-opacity"
>
<Sparkles size={16} />
<span>{t('notes.reorganize') || 'Réorganiser les notes'}</span>
</button>
)}
</div>
<div className="flex items-center gap-6">
{searchParams.get('notebook') && (
<button
onClick={() => setSummaryDialogOpen(true)}
disabled={!initialSettings.aiAssistantEnabled}
className={cn(
"flex items-center gap-2 text-[13px] font-medium transition-opacity",
initialSettings.aiAssistantEnabled ? "text-foreground hover:opacity-70" : "text-muted-foreground opacity-50 cursor-not-allowed"
)}
title={initialSettings.aiAssistantEnabled ? t('notebook.summary') : "Activez l'Assistant IA dans les paramètres pour résumer"}
>
<FileText size={16} />
<span>{t('notebook.summary') || 'Summarize'}</span>
</button>
)}
<button
onClick={() => setSortOrder(s => s === 'newest' ? 'oldest' : s === 'oldest' ? 'alpha' : 'newest')}
className="flex items-center gap-2 text-[13px] text-foreground font-medium hover:opacity-70 transition-opacity"
>
<ArrowUpDown size={16} />
<span>{sortLabels[sortOrder]}</span>
</button>
</div>
</div>
</div>
<div className="px-12 flex-1 pb-20">
{isLoading ? (
<div className="text-center py-8 text-muted-foreground">{t('general.loading')}</div>
) : isTabs ? (
<NotesMainSection
viewMode={notesViewMode}
notes={sortedNotes}
onEdit={(note, readOnly) => setEditingNote({ note, readOnly })}
onSizeChange={handleSizeChange}
currentNotebookId={searchParams.get('notebook')}
noteHistoryMode={noteHistoryMode}
onOpenHistory={handleOpenHistory}
onEnableHistory={handleEnableHistory}
onNoteCreated={handleNoteCreated}
/>
) : (
<div className="max-w-3xl space-y-16">
{sortedPinnedNotes.length > 0 && (
<div className="mb-6">
<h2 className="text-[10px] font-bold text-muted-foreground tracking-widest uppercase mb-4 px-2">
{t('notes.pinned')}
</h2>
<NotesEditorialView
notes={sortedPinnedNotes}
onOpen={(note: Note, readOnly?: boolean) => handleOpenNoteFresh(note.id, readOnly ?? false)}
notebookName={currentNotebook?.name}
onOpenHistory={handleOpenHistory}
/>
</div>
)}
{sortedNotes.filter((note) => !note.isPinned).length > 0 && (
<NotesEditorialView
notes={sortedNotes.filter((note) => !note.isPinned)}
onOpen={(note, readOnly) => handleOpenNoteFresh(note.id, readOnly ?? false)}
notebookName={currentNotebook?.name}
onOpenHistory={handleOpenHistory}
/>
)}
{notes.length === 0 && (
<div className="h-64 flex flex-col items-center justify-center text-center space-y-4">
<p className="font-memento-serif text-xl italic text-muted-foreground">
{t('notes.emptyState') || 'This notebook is waiting for its first vision.'}
</p>
<button
onClick={handleAddNote}
disabled={isCreating}
className="px-6 py-2 border border-foreground text-[13px] uppercase tracking-[0.2em] hover:bg-foreground hover:text-background transition-all"
>
{t('notes.createFirst') || 'Begin Drawing'}
</button>
</div>
)}
</div>
)}
</div>
<footer className="px-12 py-6 border-t border-foreground/5 text-center mt-auto">
<p className="text-[11px] text-muted-foreground uppercase tracking-[0.2em] font-medium">
Memento &mdash; {new Date().getFullYear()}
</p>
</footer>
</div>
)}
<MemoryEchoNotification onOpenNote={(noteId) => handleOpenNoteFresh(noteId)} />
{notebookSuggestion && (
<NotebookSuggestionToast
noteId={notebookSuggestion.noteId}
noteContent={notebookSuggestion.content}
onDismiss={() => setNotebookSuggestion(null)}
/>
)}
{batchOrganizationOpen && (
<BatchOrganizationDialog
open={batchOrganizationOpen}
onOpenChange={setBatchOrganizationOpen}
onNotesMoved={() => router.refresh()}
/>
)}
{autoLabelOpen && (
<AutoLabelSuggestionDialog
open={autoLabelOpen}
onOpenChange={(open) => {
setAutoLabelOpen(open)
if (!open) dismissLabelSuggestion()
}}
notebookId={suggestNotebookId}
onLabelsCreated={() => router.refresh()}
/>
)}
<NoteHistoryModal
open={historyOpen}
onOpenChange={setHistoryOpen}
note={historyNote}
enabled={!!historyNote?.historyEnabled}
onEnableHistory={async () => { if (historyNote) await handleEnableHistory(historyNote.id) }}
onRestored={handleHistoryRestored}
/>
{searchParams.get('notebook') && (
<NotebookSummaryDialog
open={summaryDialogOpen}
onOpenChange={setSummaryDialogOpen}
notebookId={searchParams.get('notebook')}
notebookName={currentNotebook?.name}
/>
)}
</div>
)
}