feat: brainstorm sessions, PDF document Q&A, embedding fixes, and UI improvements
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 7s

- Add brainstorm feature with collaborative canvas, AI idea generation, live cursors, playback, and export
- Add PDF upload/extraction/ingestion pipeline with pgvector document search (RAG)
- Add document Q&A overlay with streaming chat and PDF preview
- Add note attachments UI with status polling, grid layout, and auto-scroll
- Add task extraction AI tool and agent executor improvements
- Fix NoteEmbedding missing updatedAt column, re-index 66 notes with 1536-dim embeddings
- Fix brainstorm 'Create Note' button: add success toast and redirect to created note
- Fix memory echo notification infinite polling
- Fix chat route to always include document_search tool
- Add brainstorm i18n keys across all 14 locales
- Add socket server for real-time brainstorm collaboration
- Add hierarchical notebook selector and organize notebook dialog improvements
- Add sidebar brainstorm section with session management
- Update prisma schema with brainstorm tables, attachments, and document chunks
This commit is contained in:
Antigravity
2026-05-14 17:43:21 +00:00
parent 195e845f0a
commit 1fcea6ed7d
228 changed files with 57656 additions and 1059 deletions

View File

@@ -25,6 +25,10 @@ import { deleteNote, toggleArchive, togglePin, updateNote } from '@/app/actions/
import { ReminderDialog } from '@/components/reminder-dialog'
import { useNotebooks } from '@/context/notebooks-context'
import { toast } from 'sonner'
import { fr } from 'date-fns/locale/fr'
import { enUS } from 'date-fns/locale/en-US'
import { formatAbsoluteDateLocalized } from '@/lib/utils/format-localized-date'
import { cn } from '@/lib/utils'
type NotesEditorialViewProps = {
notes: Note[]
@@ -33,13 +37,16 @@ type NotesEditorialViewProps = {
onOpenHistory?: (note: Note) => void
}
function formatNoteDate(date: Date | string): string {
function formatNoteDate(date: Date | string, language: string): string {
const d = typeof date === 'string' ? new Date(date) : date
return d.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}).toUpperCase()
const locale = language === 'fr' ? fr : enUS
if (language === 'fa') {
return formatAbsoluteDateLocalized(d, language, 'd MMM yyyy', locale)
}
const month = d.toLocaleDateString('en-US', { month: 'short', timeZone: 'UTC' })
const day = d.getUTCDate()
const year = d.getUTCFullYear()
return `${month.toUpperCase()} ${day}, ${year}`
}
function EditorialNoteMenu({ note, onOpen, onOpenHistory }: {
@@ -113,27 +120,27 @@ function EditorialNoteMenu({ note, onOpen, onOpenHistory }: {
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
<DropdownMenuItem onClick={e => { e.stopPropagation(); onOpen(note) }}>
<Pencil className="h-4 w-4 mr-2 text-foreground/50" />
<Pencil className="h-4 w-4 me-2 text-foreground/50" />
{t('notes.open') || 'Ouvrir'}
</DropdownMenuItem>
<DropdownMenuItem onClick={handlePin}>
<Pin className="h-4 w-4 mr-2 text-foreground/50" />
<Pin className="h-4 w-4 me-2 text-foreground/50" />
{note.isPinned ? (t('notes.unpin') || 'Désépingler') : (t('notes.pin') || 'Épingler')}
</DropdownMenuItem>
<DropdownMenuItem onClick={handleArchive}>
<Archive className="h-4 w-4 mr-2 text-foreground/50" />
<Archive className="h-4 w-4 me-2 text-foreground/50" />
{note.isArchived ? (t('notes.unarchive') || 'Désarchiver') : (t('notes.archive') || 'Archiver')}
</DropdownMenuItem>
{onOpenHistory && (
<DropdownMenuItem onClick={e => { e.stopPropagation(); onOpenHistory(note) }}>
<History className="h-4 w-4 mr-2 text-foreground/50" />
<History className="h-4 w-4 me-2 text-foreground/50" />
{t('notes.history') || 'Historique'}
</DropdownMenuItem>
)}
{/* Rappel */}
<DropdownMenuItem onClick={e => { e.stopPropagation(); setShowReminder(true) }}>
<Bell className="h-4 w-4 mr-2 text-foreground/50" />
<Bell className="h-4 w-4 me-2 text-foreground/50" />
{note.reminder
? (t('reminder.changeReminder') || 'Modifier le rappel')
: (t('reminder.setReminder') || 'Définir un rappel')}
@@ -142,17 +149,17 @@ function EditorialNoteMenu({ note, onOpen, onOpenHistory }: {
{/* Déplacer vers un carnet */}
<DropdownMenuSub>
<DropdownMenuSubTrigger onClick={e => e.stopPropagation()}>
<FolderOpen className="h-4 w-4 mr-2 text-foreground/50" />
<FolderOpen className="h-4 w-4 me-2 text-foreground/50" />
{t('notebookSuggestion.moveToNotebook') || 'Déplacer vers…'}
</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="w-52">
<DropdownMenuItem onClick={e => { e.stopPropagation(); handleMoveToNotebook(null) }}>
<span className="w-4 h-4 rounded-full bg-foreground text-background flex items-center justify-center text-[9px] font-semibold mr-2 shrink-0">N</span>
<span className="w-4 h-4 rounded-full bg-foreground text-background flex items-center justify-center text-[9px] font-semibold me-2 shrink-0">N</span>
{t('notebookSuggestion.generalNotes') || 'Notes générales'}
</DropdownMenuItem>
{notebooks.map((nb: any) => (
<DropdownMenuItem key={nb.id} onClick={e => { e.stopPropagation(); handleMoveToNotebook(nb.id) }}>
<span className="w-4 h-4 rounded-full bg-foreground text-background flex items-center justify-center text-[9px] font-semibold mr-2 shrink-0">{nb.name.charAt(0).toUpperCase()}</span>
<span className="w-4 h-4 rounded-full bg-foreground text-background flex items-center justify-center text-[9px] font-semibold me-2 shrink-0">{nb.name.charAt(0).toUpperCase()}</span>
{nb.name}
</DropdownMenuItem>
))}
@@ -161,7 +168,7 @@ function EditorialNoteMenu({ note, onOpen, onOpenHistory }: {
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleDelete} className="text-destructive focus:text-destructive focus:bg-destructive/10">
<Trash2 className="h-4 w-4 mr-2" />
<Trash2 className="h-4 w-4 me-2" />
{t('notes.delete') || 'Supprimer'}
</DropdownMenuItem>
</DropdownMenuContent>
@@ -252,7 +259,7 @@ function EditorialThumbnail({
type="button"
aria-label={t('notes.generateIllustration') || 'Générer une illustration IA'}
title={t('notes.generateIllustration') || 'Générer une illustration IA'}
className="absolute bottom-2 right-2 flex h-9 w-9 items-center justify-center rounded-full border border-border bg-background/95 text-foreground shadow-card-rest backdrop-blur-sm transition-colors hover:bg-accent z-10 opacity-0 group-hover/thumb:opacity-100 md:opacity-100 focus-visible:opacity-100"
className="absolute bottom-2 end-2 flex h-9 w-9 items-center justify-center rounded-full border border-border bg-background/95 text-foreground shadow-card-rest backdrop-blur-sm transition-colors hover:bg-accent z-10 opacity-0 group-hover/thumb:opacity-100 md:opacity-100 focus-visible:opacity-100"
onClick={handleGenerateSvg}
disabled={busy}
>
@@ -333,7 +340,7 @@ export function NotesEditorialView({
notebookName,
onOpenHistory,
}: NotesEditorialViewProps) {
const { t } = useLanguage()
const { t, language } = useLanguage()
const { data: session } = useSession()
const { data: allLabels } = useLabelsQuery()
const [aiIllustrationEnabled, setAiIllustrationEnabled] = useState(false)
@@ -354,7 +361,8 @@ export function NotesEditorialView({
{notes.map((note: Note, index: number) => {
const title = getNoteDisplayTitle(note, t('notes.untitled') || 'Untitled')
const excerpt = getNotePlainExcerpt(note)
const dateStr = formatNoteDate(note.createdAt)
const dateStr = formatNoteDate(note.createdAt, language)
const editorialRtl = language === 'fa' || language === 'ar'
return (
<motion.article
@@ -365,14 +373,31 @@ export function NotesEditorialView({
className="space-y-4 group cursor-pointer relative pb-8"
onClick={() => onOpen(note)}
>
{/* Date / breadcrumb */}
<div className="note-date-badge">
{notebookName ? `${notebookName}${dateStr}` : dateStr}
{/* Date / breadcrumb — isolated bidi so Latin notebook name + Jalali date dont reorder wrongly */}
<div
className={cn('note-date-badge', editorialRtl && 'note-date-badge--locale-rtl')}
dir={editorialRtl ? 'rtl' : 'ltr'}
>
{notebookName ? (
<>
<bdi className={cn(editorialRtl && 'uppercase tracking-[0.2em]')}>{notebookName}</bdi>
<span className="mx-1.5 select-none text-muted-foreground/80" aria-hidden>
</span>
<bdi dir="rtl" lang={editorialRtl ? 'fa' : undefined}>
{dateStr}
</bdi>
</>
) : (
<bdi dir={editorialRtl ? 'rtl' : 'ltr'} lang={editorialRtl ? 'fa' : undefined}>
{dateStr}
</bdi>
)}
</div>
{/* Actions menu — absolutely positioned at top-right */}
<div
className="absolute top-0 right-0 opacity-0 group-hover:opacity-100 transition-opacity"
className="absolute top-0 end-0 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={e => e.stopPropagation()}
>
<EditorialNoteMenu note={note} onOpen={onOpen} onOpenHistory={onOpenHistory} />