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:
@@ -11,6 +11,7 @@ import {
|
||||
type OrganizationPlan,
|
||||
} from '@/app/actions/organize-notebook'
|
||||
import { toast } from 'sonner'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
|
||||
interface OrganizeNotebookDialogProps {
|
||||
open: boolean
|
||||
@@ -29,6 +30,8 @@ export function OrganizeNotebookDialog({
|
||||
notebookName,
|
||||
onDone,
|
||||
}: OrganizeNotebookDialogProps) {
|
||||
const { t, language } = useLanguage()
|
||||
const organizePanelSlide = language === 'fa' || language === 'ar' ? -60 : 60
|
||||
const [step, setStep] = useState<Step>('idle')
|
||||
const [plan, setPlan] = useState<OrganizationPlan | null>(null)
|
||||
const [editableGroups, setEditableGroups] = useState<OrganizationGroup[]>([])
|
||||
@@ -44,7 +47,7 @@ export function OrganizeNotebookDialog({
|
||||
const res = await analyzeNotebookForOrganization(notebookId)
|
||||
|
||||
if (!res.success || !res.plan) {
|
||||
setError(res.error ?? 'Erreur inconnue')
|
||||
setError(res.error ?? t('organizeNotebook.unknownError'))
|
||||
setStep('idle')
|
||||
return
|
||||
}
|
||||
@@ -94,14 +97,14 @@ export function OrganizeNotebookDialog({
|
||||
const res = await executeNotebookOrganization(finalPlan)
|
||||
|
||||
if (!res.success) {
|
||||
setError(res.error ?? 'Erreur inconnue')
|
||||
setError(res.error ?? t('organizeNotebook.unknownError'))
|
||||
setStep('preview')
|
||||
return
|
||||
}
|
||||
|
||||
setResult({ created: res.created, moved: res.moved })
|
||||
setStep('done')
|
||||
toast.success(`Carnet organisé — ${res.created} sous-carnet(s) créé(s), ${res.moved} note(s) déplacée(s)`)
|
||||
toast.success(t('organizeNotebook.toastSuccess', { created: res.created, moved: res.moved }))
|
||||
onDone?.()
|
||||
}, [plan, editableGroups, onDone])
|
||||
|
||||
@@ -138,11 +141,11 @@ export function OrganizeNotebookDialog({
|
||||
|
||||
{/* Panel */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 60 }}
|
||||
initial={{ opacity: 0, x: organizePanelSlide }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: 60 }}
|
||||
exit={{ opacity: 0, x: organizePanelSlide }}
|
||||
transition={{ type: 'spring', stiffness: 280, damping: 30 }}
|
||||
className="fixed right-0 top-0 bottom-0 z-50 w-[460px] bg-card border-l border-border shadow-2xl flex flex-col"
|
||||
className="fixed end-0 top-0 bottom-0 z-50 w-[460px] bg-card border-s border-border shadow-2xl flex flex-col"
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
@@ -152,7 +155,7 @@ export function OrganizeNotebookDialog({
|
||||
<Sparkles size={16} className="text-blueprint" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-[13px] font-semibold text-ink">Organiser le carnet</h2>
|
||||
<h2 className="text-[13px] font-semibold text-ink">{t('organizeNotebook.title')}</h2>
|
||||
<p className="text-[11px] text-muted-ink truncate max-w-[240px]">{notebookName}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,14 +189,10 @@ export function OrganizeNotebookDialog({
|
||||
)}
|
||||
<div className="space-y-3">
|
||||
<p className="text-[13px] text-ink leading-relaxed">
|
||||
L'IA va analyser les notes de ce carnet et vous proposer un plan de réorganisation en sous-carnets thématiques.
|
||||
{t('organizeNotebook.intro')}
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{[
|
||||
'Regroupement par sujet ou thème',
|
||||
'Création de sous-carnets manquants',
|
||||
'Aperçu complet avant modification',
|
||||
].map(item => (
|
||||
{[t('organizeNotebook.bulletThemes'), t('organizeNotebook.bulletSubfolders'), t('organizeNotebook.bulletPreview')].map(item => (
|
||||
<li key={item} className="flex items-center gap-2 text-[12px] text-muted-ink">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-blueprint shrink-0" />
|
||||
{item}
|
||||
@@ -224,8 +223,8 @@ export function OrganizeNotebookDialog({
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center space-y-1.5">
|
||||
<p className="text-[14px] font-medium text-ink">Analyse en cours…</p>
|
||||
<p className="text-[12px] text-muted-ink">L'IA lit vos notes et identifie les thèmes</p>
|
||||
<p className="text-[14px] font-medium text-ink">{t('organizeNotebook.analyzingTitle')}</p>
|
||||
<p className="text-[12px] text-muted-ink">{t('organizeNotebook.analyzingSubtitle')}</p>
|
||||
</div>
|
||||
<div className="flex gap-1.5">
|
||||
{[0, 1, 2].map(i => (
|
||||
@@ -253,7 +252,11 @@ export function OrganizeNotebookDialog({
|
||||
<div className="flex items-center gap-3 p-3 rounded-xl bg-blueprint/5 border border-blueprint/20">
|
||||
<Sparkles size={12} className="text-blueprint shrink-0" />
|
||||
<p className="text-[11px] text-blueprint font-medium">
|
||||
{editableGroups.length} groupe(s) · {totalNotes} note(s) · {newSubNbs} nouveau(x) sous-carnet(s)
|
||||
{t('organizeNotebook.previewSummary', {
|
||||
groups: editableGroups.length,
|
||||
notes: totalNotes,
|
||||
newSubs: newSubNbs,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -290,7 +293,7 @@ export function OrganizeNotebookDialog({
|
||||
/>
|
||||
{group.isNew && (
|
||||
<span className="px-1.5 py-0.5 rounded text-[9px] font-bold uppercase tracking-wider bg-blueprint/10 text-blueprint shrink-0">
|
||||
Nouveau
|
||||
{t('organizeNotebook.badgeNew')}
|
||||
</span>
|
||||
)}
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
@@ -329,7 +332,7 @@ export function OrganizeNotebookDialog({
|
||||
<Check size={10} className="text-blueprint" />
|
||||
</div>
|
||||
<span className="text-[11px] text-muted-ink truncate group-hover:text-ink transition-colors">
|
||||
{note.title || 'Note sans titre'}
|
||||
{note.title || t('organizeNotebook.untitledNote')}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -341,7 +344,7 @@ export function OrganizeNotebookDialog({
|
||||
{/* Collapsed count */}
|
||||
{!expandedGroups.has(idx) && (
|
||||
<div className="px-4 pb-2.5 text-[11px] text-muted-ink/60">
|
||||
{group.notes.length} note(s)
|
||||
{t('organizeNotebook.notesInGroup', { count: group.notes.length })}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
@@ -360,8 +363,8 @@ export function OrganizeNotebookDialog({
|
||||
>
|
||||
<Loader2 size={32} className="text-blueprint animate-spin" />
|
||||
<div className="text-center space-y-1">
|
||||
<p className="text-[14px] font-medium text-ink">Organisation en cours…</p>
|
||||
<p className="text-[12px] text-muted-ink">Création des sous-carnets et déplacement des notes</p>
|
||||
<p className="text-[14px] font-medium text-ink">{t('organizeNotebook.executingTitle')}</p>
|
||||
<p className="text-[12px] text-muted-ink">{t('organizeNotebook.executingSubtitle')}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -378,10 +381,10 @@ export function OrganizeNotebookDialog({
|
||||
<CheckCircle2 size={32} className="text-emerald-500" />
|
||||
</div>
|
||||
<div className="text-center space-y-1.5">
|
||||
<p className="text-[15px] font-semibold text-ink">Carnet organisé !</p>
|
||||
<p className="text-[15px] font-semibold text-ink">{t('organizeNotebook.doneTitle')}</p>
|
||||
{result && (
|
||||
<p className="text-[12px] text-muted-ink">
|
||||
{result.created} sous-carnet(s) créé(s) · {result.moved} note(s) déplacée(s)
|
||||
{t('organizeNotebook.doneStats', { created: result.created, moved: result.moved })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -399,7 +402,7 @@ export function OrganizeNotebookDialog({
|
||||
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-ink text-paper text-[13px] font-semibold hover:opacity-85 transition-opacity"
|
||||
>
|
||||
<Sparkles size={14} />
|
||||
Analyser avec l'IA
|
||||
{t('organizeNotebook.analyzeButton')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -409,7 +412,7 @@ export function OrganizeNotebookDialog({
|
||||
onClick={() => { setStep('idle'); setError(null) }}
|
||||
className="flex-1 px-4 py-2.5 rounded-xl border border-border text-[13px] font-medium text-muted-ink hover:text-ink transition-colors"
|
||||
>
|
||||
Recommencer
|
||||
{t('organizeNotebook.restart')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleExecute}
|
||||
@@ -422,7 +425,7 @@ export function OrganizeNotebookDialog({
|
||||
)}
|
||||
>
|
||||
<Check size={14} />
|
||||
Valider
|
||||
{t('organizeNotebook.confirm')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -432,7 +435,7 @@ export function OrganizeNotebookDialog({
|
||||
onClick={handleClose}
|
||||
className="w-full px-4 py-2.5 rounded-xl border border-border text-[13px] font-medium text-muted-ink hover:text-ink transition-colors"
|
||||
>
|
||||
Fermer
|
||||
{t('organizeNotebook.closeButton')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user