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
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:
@@ -482,7 +482,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
>
|
||||
{/* Drag Handle - Only visible on mobile/touch devices */}
|
||||
<div
|
||||
className="muuri-drag-handle absolute top-2 left-2 z-20 cursor-grab active:cursor-grabbing p-2 md:hidden"
|
||||
className="muuri-drag-handle absolute top-2 start-2 z-20 cursor-grab active:cursor-grabbing p-2 md:hidden"
|
||||
aria-label={t('notes.dragToReorder') || 'Drag to reorder'}
|
||||
title={t('notes.dragToReorder') || 'Drag to reorder'}
|
||||
>
|
||||
@@ -490,7 +490,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
</div>
|
||||
|
||||
{/* Move to Notebook Dropdown Menu — hidden in trash */}
|
||||
{!isTrashView && <div onClick={(e) => e.stopPropagation()} className="absolute top-2 right-2 z-20">
|
||||
{!isTrashView && <div onClick={(e) => e.stopPropagation()} className="absolute top-2 end-2 z-20">
|
||||
<DropdownMenu open={showNotebookMenu} onOpenChange={setShowNotebookMenu}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
@@ -507,7 +507,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
{t('notebookSuggestion.moveToNotebook')}
|
||||
</div>
|
||||
<DropdownMenuItem onClick={() => handleMoveToNotebook(null)}>
|
||||
<StickyNote className="h-4 w-4 mr-2" />
|
||||
<StickyNote className="h-4 w-4 me-2" />
|
||||
{t('notebookSuggestion.generalNotes')}
|
||||
</DropdownMenuItem>
|
||||
{notebooks.filter(nb => !nb.parentId && !nb.trashedAt).map((notebook: any) => {
|
||||
@@ -523,11 +523,11 @@ export const NoteCard = memo(function NoteCard({
|
||||
<DropdownMenuSubTrigger className="gap-2">
|
||||
<NotebookIcon className="h-4 w-4" />
|
||||
{notebook.name}
|
||||
<ChevronRight className="h-3 w-3 ml-auto opacity-50" />
|
||||
<ChevronRight className="h-3 w-3 ms-auto opacity-50 rtl:rotate-180" />
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem onClick={() => handleMoveToNotebook(notebook.id)}>
|
||||
<NotebookIcon className="h-4 w-4 mr-2" />
|
||||
<NotebookIcon className="h-4 w-4 me-2" />
|
||||
{notebook.name}
|
||||
</DropdownMenuItem>
|
||||
{descendants.map((child: any) => {
|
||||
@@ -544,8 +544,8 @@ export const NoteCard = memo(function NoteCard({
|
||||
})()
|
||||
return (
|
||||
<DropdownMenuItem key={child.id} onClick={() => handleMoveToNotebook(child.id)}>
|
||||
<NotebookIcon className="h-4 w-4 mr-2" />
|
||||
<span className="ml-{depth * 2}">{child.name}</span>
|
||||
<NotebookIcon className="h-4 w-4 me-2" />
|
||||
<span style={{ marginInlineStart: `${depth * 0.5}rem` }}>{child.name}</span>
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
})}
|
||||
@@ -555,14 +555,14 @@ export const NoteCard = memo(function NoteCard({
|
||||
}
|
||||
return (
|
||||
<DropdownMenuItem key={notebook.id} onClick={() => handleMoveToNotebook(notebook.id)}>
|
||||
<NotebookIcon className="h-4 w-4 mr-2" />
|
||||
<NotebookIcon className="h-4 w-4 me-2" />
|
||||
{notebook.name}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
})}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="text-muted-foreground" onSelect={() => onCreateSubNotebook?.()}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
<Plus className="h-4 w-4 me-2" />
|
||||
{t('notebook.createSubNotebook') || 'Nouveau sous-carnet…'}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@@ -575,7 +575,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
size="sm"
|
||||
data-testid="pin-button"
|
||||
className={cn(
|
||||
"absolute top-2 right-12 z-20 h-8 w-8 p-0 rounded-md transition-opacity",
|
||||
"absolute top-2 end-12 z-20 h-8 w-8 p-0 rounded-md transition-opacity",
|
||||
optimisticNote.isPinned ? "opacity-100" : "opacity-0 group-hover:opacity-100"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
@@ -593,7 +593,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
{/* Reminder Icon - Move slightly if pin button is there */}
|
||||
{note.reminder && new Date(note.reminder) > new Date() && (
|
||||
<Bell
|
||||
className="absolute top-3 right-10 h-4 w-4 text-primary"
|
||||
className="absolute top-3 end-10 h-4 w-4 text-primary"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -604,7 +604,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
{t('memoryEcho.fused')}
|
||||
<button
|
||||
onClick={handleRemoveFusedBadge}
|
||||
className="ml-1 opacity-0 group-hover/badge:opacity-100 hover:opacity-100 transition-opacity"
|
||||
className="ms-1 opacity-0 group-hover/badge:opacity-100 hover:opacity-100 transition-opacity"
|
||||
title={t('notes.remove') || 'Remove'}
|
||||
>
|
||||
<Trash2 className="h-2.5 w-2.5" />
|
||||
@@ -614,7 +614,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
|
||||
{/* Title */}
|
||||
{note.title && (
|
||||
<h3 dir="auto" className="text-lg font-heading font-semibold mb-2 pr-20 text-foreground leading-tight tracking-tight flex items-center gap-2">
|
||||
<h3 dir="auto" className="text-lg font-heading font-semibold mb-2 pe-20 text-foreground leading-tight tracking-tight flex items-center gap-2">
|
||||
{(() => {
|
||||
const TypeIcon = NOTE_TYPE_ICONS[note.type] || AlignLeft
|
||||
return <TypeIcon className="h-4 w-4 shrink-0 text-muted-foreground/50" />
|
||||
@@ -653,7 +653,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
handleLeaveShare()
|
||||
}}
|
||||
>
|
||||
<LogOut className="h-3 w-3 mr-1" />
|
||||
<LogOut className="h-3 w-3 me-1" />
|
||||
{t('notes.leaveShare')}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -727,7 +727,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
{owner && (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute bottom-2 left-2 z-20",
|
||||
"absolute bottom-2 start-2 z-20",
|
||||
"w-6 h-6 rounded-full text-white text-[10px] font-semibold flex items-center justify-center",
|
||||
getAvatarColor(owner.name || owner.email || 'Unknown')
|
||||
)}
|
||||
@@ -758,7 +758,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
noteId={note.id}
|
||||
currentReminder={reminderDate}
|
||||
onUpdateReminder={handleUpdateReminder}
|
||||
className="absolute bottom-0 left-0 right-0 p-2 opacity-100 md:opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
className="absolute bottom-0 start-0 end-0 p-2 opacity-100 md:opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -776,7 +776,7 @@ export const NoteCard = memo(function NoteCard({
|
||||
)}
|
||||
|
||||
{/* Connections Badge - Bottom right (spec: amber, absolute) */}
|
||||
<div className="absolute bottom-2 right-2 z-10">
|
||||
<div className="absolute bottom-2 end-2 z-10">
|
||||
<ConnectionsBadge
|
||||
noteId={note.id}
|
||||
onClick={() => {
|
||||
|
||||
Reference in New Issue
Block a user