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:
@@ -10,19 +10,30 @@ import { FusionModal } from '@/components/fusion-modal'
|
||||
import { ReminderDialog } from '@/components/reminder-dialog'
|
||||
import { ContextualAIChat } from '@/components/contextual-ai-chat'
|
||||
import { NoteDocumentInfoPanel } from '@/components/note-document-info-panel'
|
||||
import { format } from 'date-fns'
|
||||
import { fr } from 'date-fns/locale/fr'
|
||||
import { enUS } from 'date-fns/locale/en-US'
|
||||
import { formatAbsoluteDateLocalized } from '@/lib/utils/format-localized-date'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { Note } from '@/lib/types'
|
||||
import { GhostTags } from '@/components/ghost-tags'
|
||||
import { LabelBadge } from '@/components/label-badge'
|
||||
import { NoteAttachments } from '@/components/note-attachments'
|
||||
import { DocumentQAOverlay } from '@/components/document-qa-overlay'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import { useState } from 'react'
|
||||
|
||||
interface NoteEditorFullPageProps {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
const { t, language } = useLanguage()
|
||||
const dateLocale = language === 'fr' ? fr : enUS
|
||||
const { state, actions, note, readOnly, notebooks, fileInputRef, globalLabels } = useNoteEditorContext()
|
||||
const [docQAAttachment, setDocQAAttachment] = useState<{ id: string; fileName: string } | null>(null)
|
||||
const [attachmentsCount, setAttachmentsCount] = useState(0)
|
||||
const [uploadTrigger, setUploadTrigger] = useState(0)
|
||||
|
||||
const notebookName = notebooks.find(nb => nb.id === note.notebookId)?.name || null
|
||||
|
||||
@@ -40,7 +51,7 @@ export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
<div className="flex-1 flex flex-col overflow-y-auto bg-white dark:bg-background">
|
||||
|
||||
{/* TOOLBAR */}
|
||||
<NoteEditorToolbar mode="fullPage" onClose={onClose} />
|
||||
<NoteEditorToolbar mode="fullPage" onClose={onClose} onToggleAttachments={() => setUploadTrigger(v => v + 1)} attachmentsCount={attachmentsCount} />
|
||||
|
||||
{/* BODY — max-w-4xl, responsive px, py-16 */}
|
||||
<div className="max-w-4xl mx-auto w-full px-6 sm:px-12 py-16 space-y-12 min-w-0">
|
||||
@@ -52,7 +63,7 @@ export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
{notebookName && <span style={{ color: 'var(--color-ink)' }}>{notebookName}</span>}
|
||||
{notebookName && <ChevronRight size={10} style={{ color: 'var(--color-concrete)' }} />}
|
||||
<span suppressHydrationWarning style={{ color: 'var(--color-concrete)' }}>
|
||||
{format(new Date(note.contentUpdatedAt), 'MMM d, yyyy')}
|
||||
{formatAbsoluteDateLocalized(new Date(note.contentUpdatedAt), language, 'MMM d, yyyy', dateLocale)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -94,8 +105,15 @@ export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
)}
|
||||
|
||||
{/* Content area — max-w-3xl for wider reading column */}
|
||||
<div className="max-w-3xl mx-auto w-full pb-32">
|
||||
<div className="max-w-3xl mx-auto w-full space-y-8 pb-32">
|
||||
<NoteContentArea />
|
||||
|
||||
<NoteAttachments
|
||||
noteId={note.id}
|
||||
onOpenDocQA={(att) => setDocQAAttachment(att)}
|
||||
onCountChange={setAttachmentsCount}
|
||||
triggerUpload={uploadTrigger}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,7 +142,7 @@ export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
const plain = state.content.replace(/<[^>]+>/g, ' ').trim()
|
||||
const wordCount = plain.split(/\s+/).filter(Boolean).length
|
||||
if (wordCount < 10) {
|
||||
toast.error('Ajoutez au moins 10 mots avant de générer un titre.')
|
||||
toast.error(t('ai.titleGenerationMinWords', { count: wordCount }))
|
||||
return
|
||||
}
|
||||
actions.setIsProcessingAI(true)
|
||||
@@ -139,14 +157,14 @@ export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
const s = data.suggestions?.[0]?.title ?? ''
|
||||
if (s) {
|
||||
actions.setTitle(s)
|
||||
toast.success('Titre généré !')
|
||||
toast.success(t('ai.titleApplied'))
|
||||
} else {
|
||||
toast.error('Impossible de générer un titre.')
|
||||
toast.error(t('ai.titleGenerationFailed'))
|
||||
}
|
||||
} else {
|
||||
toast.error('Erreur lors de la génération du titre.')
|
||||
toast.error(t('ai.titleGenerationError'))
|
||||
}
|
||||
} catch { toast.error('Erreur réseau.') } finally { actions.setIsProcessingAI(false) }
|
||||
} catch { toast.error(t('ai.networkErrorShort')) } finally { actions.setIsProcessingAI(false) }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -166,6 +184,20 @@ export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
</div>
|
||||
|
||||
<input ref={fileInputRef} type="file" accept="image/*" multiple className="hidden" onChange={actions.handleImageUpload} />
|
||||
|
||||
{docQAAttachment && (
|
||||
<DocumentQAOverlay
|
||||
attachment={docQAAttachment}
|
||||
noteId={note.id}
|
||||
noteContent={state.content}
|
||||
onClose={() => setDocQAAttachment(null)}
|
||||
onApplyToNote={(content) => {
|
||||
actions.setPreviousContentForCopilot(state.content)
|
||||
actions.setContent(state.content + '\n\n' + content)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ReminderDialog
|
||||
open={state.showReminderDialog}
|
||||
onOpenChange={actions.setShowReminderDialog}
|
||||
|
||||
Reference in New Issue
Block a user