Files
Momento/memento-note/docs/brainstorm-documentation.md
Antigravity 1fcea6ed7d
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 7s
feat: brainstorm sessions, PDF document Q&A, embedding fixes, and UI improvements
- 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
2026-05-14 17:43:21 +00:00

25 KiB
Raw Permalink Blame History

Brainstorm — Documentation Complète

Vue d'ensemble

Le brainstorm est un outil de génération d'idées assistée par IA basé sur un graphe radial D3. L'utilisateur saisit une "graine" (seed idea), l'IA génère 9 idées en 3 vagues (Variations, Analogies, Disruptions), et l'utilisateur peut approfondir, dismiss, convertir en note, ou ajouter ses propres idées. Le tout en temps-réel via Socket.io.


Architecture

[Browser] ←→ [Next.js API Routes] ←→ [PostgreSQL]
    ↕                   ↕
[Socket.io Client] ←→ [Socket.io Server :3002]
  • Frontend : React + D3.js + React Query + Socket.io Client
  • Backend : Next.js App Router + Prisma + OpenAI
  • Temps réel : Socket.io sur le port 3002

Modèles de données (Prisma)

BrainstormSession

Champ Type Description
id String (cuid) PK
seedIdea String L'idée graine saisie par l'utilisateur
sourceNoteId String? FK vers la note source (optionnel)
contextNoteIds String? JSON string des IDs des notes de contexte
exportedNoteId String? FK vers la note exportée
userId String FK vers le propriétaire
inviteToken String? Token d'invitation unique
inviteExpiry DateTime? Date d'expiration du token
status String "active" par défaut
Relations ideas[], participants[], activities[], shares[]

BrainstormIdea

Champ Type Description
id String (cuid) PK
sessionId String FK vers la session
waveNumber Int 1, 2 ou 3
title String Titre de l'idée
description String Description détaillée
connectionToSeed String? Lien avec l'idée graine
noveltyScore Int? Score de nouveauté (0-100)
parentIdeaId String? FK vers l'idée parent (auto-référence)
convertedToNoteId String? FK vers la note créée par conversion
relatedNoteIds String? JSON string des IDs des notes liées
status String "active", "dismissed", "converted"
positionX Float? Position X sur le canvas
positionY Float? Position Y sur le canvas
createdBy String? FK vers l'utilisateur créateur
createdByType String "ai" ou "human"
Relations session, parentIdea?, children[], noteRefs[], creator?

BrainstormNoteRef

Champ Type Description
id String (cuid) PK
ideaId String FK vers l'idée
noteId String? FK vers la note (peut être null)
relation String "derived_from", "opposes", "extends", "synthesizes", "transposes", "none_found"
explanation String Pourquoi cette note est liée
verdict String "unresolved", "accepted", "dismissed"

BrainstormParticipant

Champ Type Description
sessionId + userId Unique Un user = un rôle par session
role String "host", "editor", "viewer"
joinedAt, lastSeenAt DateTime Présence

BrainstormActivity

Champ Type Description
action String "wave_generated", "manual_idea", "idea_dismissed", "idea_converted", "joined", "invite_created"
details String? JSON stringifié

BrainstormShare

Champ Type Description
sessionId + userId Unique Un partage par couple session/user
sharedBy String FK vers l'envoyeur
status String "pending", "accepted", "declined", "removed"
permission String "editor", "viewer"

Pages et URLs

URL Description
/brainstorm Page principale du brainstorm
/brainstorm?session=<id> Ouvrir une session spécifique
/brainstorm?seed=<text> Auto-créer un brainstorm avec ce seed
/brainstorm?sourceNoteId=<id> Créer depuis une note
/brainstorm?invite=<token> Rejoindre via token d'invitation

API Routes

POST /api/brainstorm — Créer un brainstorm

Input : { seedIdea, sourceNoteId?, contextNoteIds?, locale? } Processus :

  1. Si pas de contextNoteIds → embedding du seed → recherche des 8 notes les plus proches
  2. LLM classe chaque note : SUPPORT, TENSION, ou EXTENSION
  3. LLM génère 9 idées en 3 vagues :
    • Wave 1 (Variations) : 3 idées, dont au moins 1 de SUPPORT, 1 répondant à TENSION
    • Wave 2 (Analogies) : 3 idées, dont au moins 1 d'EXTENSION, 1 transposition de pattern
    • Wave 3 (Disruptions) : 3 idées, dont au moins 1 inversion de SUPPORT, 1 synthèse de contradictions
  4. Chaque idée a : wave, title, description, connectionToSeed, noveltyScore, noteRefs[]
  5. Positionnement radial : angle = (idx % 3) * (2π/3) + (wave-1) * 0.5, radius = wave * 150
  6. Crée session + host participant + log activity + 9 idées avec noteRefs Retour : Session complète + { support, tension, extension } counts

GET /api/brainstorm — Lister les sessions de l'utilisateur

Retour : BrainstormSessionListItem[] (id, seedIdea, totalIdeas, activeIdeas, dates)

GET /api/brainstorm/shared — Sessions partagées acceptées

Processus : Fetch shares where userId + accepted, upsert participant si manquant Retour : Même format que la liste + _isShared: true

GET /api/brainstorm/[sessionId] — Détail d'une session

Accès : Propriétaire OU participant OU share accepted Retour : Session complète avec ideas (ordonnées par wave/date), noteRefs, creator, sourceNote, exportedNote

DELETE /api/brainstorm/[sessionId] — Supprimer une session

Accès : Propriétaire uniquement

POST /api/brainstorm/[sessionId]/expand — Approfondir une idée

Accès : Propriétaire uniquement Input : { ideaId, locale? } Processus :

  1. Charge les noteRefs de l'idée parent + 5 notes via embedding
  2. LLM génère 9 sous-idées en 3 vagues relatives au parent
  3. Positionnement radial autour du parent
  4. Crée les idées avec parentIdeaId = source idea Retour : Session mise à jour

POST /api/brainstorm/[sessionId]/manual-idea — Ajouter une idée manuelle

Accès : Participant avec rôle editor Input : { title, description?, parentIdeaId?, locale? } Processus :

  1. Wave = min(parent.wave + 1, 3) si parent, sinon 1
  2. Crée l'idée avec createdBy = userId, createdByType = 'human'
  3. Auto-link : embedding → 3 notes les plus proches → BrainstormNoteRef avec relation "extends"
  4. Enrichissement IA (non-bloquant) : LLM polit le titre, enrichit la description, set noveltyScore. Respecte la locale (IMPORTANT: You MUST write ALL text in ${lang}) Retour : Session mise à jour (201)

POST /api/brainstorm/[sessionId]/dismiss — Rejeter une idée

Accès : Participant avec rôle editor Input : { ideaId } Processus : Transaction → status = "dismissed" + tous noteRefs verdict = "dismissed"

POST /api/brainstorm/[sessionId]/convert — Convertir en note

Accès : Propriétaire uniquement Input : { ideaId } Processus :

  1. Crée une Note avec :
    • Titre = titre de l'idée
    • Contenu = markdown formaté (description, connection, novelty, source brainstorm, noteRefs avec relations)
    • Labels = ['brainstorm', 'idée']
    • Même notebook que la note source
  2. Transaction : status = "converted", convertedToNoteId = note.id, noteRefs verdict = "accepted", tag les notes référencées avec "brainstorm-fruitful" Retour : Note créée (201)

POST /api/brainstorm/[sessionId]/finalize — Finaliser la session

Accès : Propriétaire uniquement Processus :

  • Pour chaque note référencée : si tous les refs sont dismissed → tag "brainstorm-dry"
  • Compte notesEnriched (≥1 accepted) et notesMarkedDry (all dismissed) Retour : { notesSolicited, notesEnriched, notesMarkedDry }

POST /api/brainstorm/[sessionId]/export — Exporter en note

Accès : Propriétaire uniquement Processus :

  • Génère un markdown complet : header, summary, sections par wave, notes sollicitées, notes converties
  • Crée une Note avec labels ['brainstorm', 'export']
  • Update session.exportedNoteId Retour : Note créée (201)

POST /api/brainstorm/[sessionId]/update-position — Sauvegarder la position d'un nœud

Input : { ideaId, positionX, positionY }

POST /api/brainstorm/[sessionId]/invite — Créer une invitation

Accès : Host uniquement Input : { role, expiresInHours?, email? }

  • Si email fourni : trouve l'utilisateur, crée une Notification, retourne { mode: 'email' }
  • Sinon : génère un token, retourne { mode: 'link', inviteUrl }

POST /api/brainstorm/join — Rejoindre via token

Input : { token } Processus : Vérifie token + expiry → crée BrainstormParticipant → notifie le owner

GET /api/brainstorm/[sessionId]/activity — Feed d'activité

Retour : 50 dernières activités avec user info


Server Actions (app/actions/brainstorm.ts)

createBrainstormShare(sessionId, recipientEmail, permission?)

Partage par email (même pattern que NoteShare).

  1. Vérifie que le user est propriétaire
  2. Cherche le recipient par email
  3. Gère les cas existants :
    • accepted → retourne { message: 'already_shared' } (succès info)
    • pending → retourne { message: 'already_pending' } (succès info)
    • declined/removed → ré-invite (remet à pending)
  4. Sinon → crée un BrainstormShare avec status pending

respondToBrainstormShare(shareId, action)

Accepter ou refuser un partage.

  • Accept → upsert BrainstormParticipant (idempotent)
  • Decline → update status

getPendingBrainstormShares()

Retourne les shares pending pour l'utilisateur courant (avec session + sharer info).

getAcceptedBrainstormShares()

Retourne les shares accepted (pour afficher dans la sidebar).

removeBrainstormShare(sessionId)

Passe le share en status "removed".


Interface utilisateur — Page Brainstorm

Layout global

┌──────────────────────────────────────────────────────────────┐
│ HEADER (fixed, border-bottom, backdrop-blur)                 │
│ [Wind icon] Waves of Thought                                  │
│ "Unfold dimensions of potentiality"                          │
│ [__________ input seed idea __________] [+]                  │
│ • • • AI is harvesting seeds of thought...                   │
├──────────────────────────────────┬─────────────┬────┐        │
│                                  │             │    │        │
│         CANVAS (D3)              │   DETAIL    │ S  │        │
│                                  │   PANEL     │ I  │        │
│   ○ ring 1                       │  (400px)    │ D  │        │
│     ○ ring 2                     │             │ E  │        │
│       ○ ring 3                   │             │ B  │        │
│                                  │             │ A  │        │
│   [toolbar flottant en bas]      │             │ R  │        │
│                                  │             │    │        │
├──────────────────────────────────┴─────────────┴────┘        │
│ [Activity Feed panel] (slide depuis la droite)               │
│ [BrainstormShareDialog] (modal)                              │
│ [Impact Toast] (fixed bottom center)                         │
└──────────────────────────────────────────────────────────────┘

Header

  • Icône : <Wind> dans un carré orange qui tourne pendant la génération
  • Titre : "Waves of Thought" (serif)
  • Sous-titre : "Unfold dimensions of potentiality" (muted)
  • Input seed : Grand champ serif italic avec glow gradient au focus
  • Bouton submit : <Plus> icône, positionné à droite dans l'input, disabled si vide ou en génération
  • Indicateur de génération : 3 points orange pulsants + texte "AI is harvesting seeds of thought..."

Canvas D3 (WaveCanvas)

Éléments visuels

  • Fond : #F8F7F2 avec grille de points (20px)
  • 3 anneaux : rayons 200, 400, 600 — traits gris pointillés, opacité 0.5
  • Nœud racine (seed) :
    • Cercle noir (#141414), rayon 40
    • Texte "SEED" en blanc, 10px bold
    • Label de l'idée graine en serif italic 18px, positionné à y=80
  • Nœuds idée :
    • Cercle blanc avec bordure colorée par wave (orange/blue/violet)
    • Rayon 28 (18 si dismissed)
    • Texte : titre tronqué à 18 caractères, positionné sous le cercle
    • Badges :
      • Converti : fond vert + checkmark ✓
      • Notes liées : emoji 📎 en haut-gauche
      • Créé par humain : cercle bleu avec initiale en haut-droite
      • Créé par IA : symbole ✦ violet en haut-droite
      • Dismissed : opacité 0.3
  • Liens :
    • Wave (racine → idée) : gris pointillé, 1.5px
    • Parent (idée → sous-idée) : jaune solid, 2px

Forces D3

  • forceLink : distance = wave × 200 (wave links), 180 (parent links)
  • forceManyBody : strength -800 (répulsion)
  • forceRadial : rayon = wave × 200, strength 0.8
  • forceCollide : radius + 30

Interactions

Action Effet
Clic sur nœud Sélectionne l'idée → ouvre le panneau détail
Double-clic sur nœud Ouvre l'éditeur inline pour créer un enfant
Double-clic sur canvas Ouvre l'éditeur inline pour créer une idée racine
Drag d'un nœud Déplace le nœud, fixe la position, émet idea:moved au socket
Zoom/Pan d3.zoom, échelle [0.1, 5], centré avec scale 0.8

Éditeur inline (double-clic)

Apparaît sous le point de clic, carte flottante (260px) :

  • En-tête : icône "+" bleue + label "Réponse" ou "Nouvelle idée"
  • Input serif avec fond subtil
  • Raccourcis : ↵ enregistrer · esc annuler
  • Si enfant : label "→ enfant"
  • Triangle pointeur sous la carte
  • Enter → appelle onCreateIdea({ title, parentIdeaId, x, y })
  • Escape → ferme

Stabilité du canvas

  • Le useEffect D3 ne se redéclenche QUE quand sessionId ou ideasKey (string sérialisée) changent
  • Tous les callbacks (onNodeSelect, onPositionUpdate, onCreateIdea) sont passés via refs (onNodeSelectRef.current) pour éviter les re-renders
  • Les remote moves (socket) mettent à jour directement les nœuds D3 sans re-render React

Toolbar flottante (bas du canvas)

Pilule animée qui apparaît quand une session est active :

Élément Style Action
Wave 1/2/3 3 points colorés (orange/blue/violet) Légende visuelle
Avatars Cercles colorés empilés (max 4, "+N") Présence des utilisateurs connectés
Exporter Texte orange Appelle exportBrainstorm puis finalizeBrainstorm
Inviter Texte émeraude, icône <UserPlus> Ouvre BrainstormShareDialog
Activité Icône <Activity>, muted/orange si actif Toggle le panneau Activity Feed
Supprimer Icône poubelle, muted → rose au hover Supprime la session

Panneau détail (droite, 400px, slide animé)

Apparaît quand une idée est sélectionnée :

Section Contenu
Badge wave Pilule colorée (Wave 1 orange / Wave 2 blue / Wave 3 violet)
Note créée Badge vert si convertie
Fermer Bouton chevron droite
Titre 3xl serif bold
Score de nouveauté <Zap> + nombre
Créateur Humain = cercle bleu + initiale, IA = spark violet + "IA"
Description Texte de l'idée
Connexion au seed Carte slate, citation italic
Origine de l'idée Cartes noteRef avec badge de relation (emerald=positive, rose=opposes, amber=autre), bouton "View"
Bouton Approfondir <Wind> + "Deepen", bordure dashed, hover orange → expandIdea
Bouton Créer Note <FileText> + "Create Note", bordure dashed, hover émeraude → convertIdea
Bouton Non pertinent Texte muted, hover rose → dismissIdea

Sidebar sessions (bande droite, 64px)

  • Icône <History> en haut
  • Liste scrollable de boutons circulaires (40px) avec la première lettre du seed
    • Actif : fond sombre, texte blanc, scale 110, shadow
    • Partagé : fond orange clair, texte orange
    • Normal : fond blanc, texte muted
  • Filet décoratif en bas

Activity Feed (panneau coulissant, 320px)

Slide depuis la droite avec animation spring :

  • En-tête : icône + "Activity" + bouton fermer
  • État vide : "No activity yet" italic
  • Items : icône par type d'action, username bold, label d'action, titre idée (tronqué 30 chars), temps relatif (1m/1h/1d)
  • Types : manual_idea (ampoule bleue), wave_generated (éclair orange), joined (user+ vert), idea_dismissed (X rose), invite_created (user+ violet)

BrainstormShareDialog (modal)

  • Trigger : bouton "Inviter" de la toolbar
  • Header : icône orange + "Partager le brainstorm" + seed idea tronqué
  • Champ email avec label "Adresse email"
  • Bouton "Partager" orange avec <UserPlus>
  • Messages de feedback :
    • Succès (vert + ✓) : "Invitation envoyée!" / "Invitation renvoyée!"
    • Info (ambre + ⚠) : "Cette personne a déjà accès" / "Invitation déjà en attente"
    • Erreur (rose + ⚠) : "No account found with this email" etc.
  • Note : "La personne recevra une notification pour accepter ou refuser."

Impact Toast (fixed bottom center)

Apparaît après Export + Finalize :

  • "X note(s) enriched / X note(s) marked dry"
  • Disparaît après 4 secondes

Sidebar gauche (app principale)

Section Brainstorms (SidebarBrainstorms)

  • Vue vide : Icône <Sparkles> orange + "Aucune session" + "Démarrer →"
  • Loading : 3 skeletons pulsants
  • Liste (max 10) :
    • Sessions possédées : Cercle orange + <Sparkles> + seed idea + count idées + date + bouton supprimer (rose au hover)
    • Sessions partagées : Cercle bleu + <Sparkles> + seed idea + badge "partagé" + count idées + date (pas de bouton supprimer)
  • Clic → navigate vers /brainstorm?session={id}

Navigation

  • Bouton "brainstorms" dans le toggle de vues (icône <Sparkles> orange, highlight orange quand actif)

Notifications (cloche)

Partages brainstorm pending

  • Section dédiée entre les reminders et les share requests de notes
  • Avatar orange gradient avec initiale de l'envoyeur
  • Icône <Wind> + label "BRAINSTORM" orange
  • Nom de l'envoyeur + "invited you to a brainstorm" + aperçu du seed (tronqué 35 chars)
  • Boutons Accepter (orange) / Refuser

Notifications système

  • brainstorm_invite : icône <Wind> émeraude
  • brainstorm_joined : icône <Wind> bleue

Temps réel (Socket.io)

Événements client → serveur

Event Data Quand
cursor:move { x, y } ou null Mouvement souris sur le canvas
idea:moved { ideaId, positionX, positionY, userId } Fin de drag d'un nœud
activity:new { action, userId, userName, details } Action utilisateur

Événements serveur → client

Event Data Effet
presence:update PresenceUser[] Mise à jour de la liste des utilisateurs connectés
cursor:update { userId, cursor: {x,y} } Déplacement du curseur d'un autre user
activity:new ActivityEvent Nouvelle activité dans le feed
idea:moved { ideaId, positionX, positionY } Déplacement d'un nœud par un autre user
idea:added Placeholder (non utilisé côté client)
idea:dismissed Placeholder (non utilisé côté client)

Flux de déplacement en temps réel

  1. User A drag un nœud → fin de drag → handlePositionUpdate → émet idea:moved + POST API (persist)
  2. Socket server → broadcast idea:moved aux autres dans la room
  3. User B → useBrainstormSocket callback → setRemoteMove(...) (avec compteur séquentiel)
  4. WaveCanvas → useEffect([remoteMove]) → fixe fx/fy du nœud cible → 30 ticks de simulation → update positions liens/nœuds directement dans D3

Curseurs live

  • Chaque user a un curseur coloré (SVG arrow + nom) affiché sur le canvas des autres
  • 12 couleurs assignées en round-robin
  • useCursorTracking attache un listener mousemove au container et émet cursor:move

Ghost Cursor IA

  • Quand une vague IA est en génération, un curseur violet animé apparaît
  • Se déplace aléatoirement dans un rayon de 150-350px du centre
  • Pulsation violette + badge "AI ✦"

Flux de partage

Par email (server action — principal)

Propriétaire → Dialog email → createBrainstormShare()
  → Cherche recipient par email
  → Crée BrainstormShare (pending)
  → Destinataire voit dans NotificationPanel (cloche)
  → Accept → Upsert BrainstormParticipant
  → Decline → Update status
  → Apparaît dans sidebar (bleu, badge "partagé")

Par lien (API invite — secondaire)

Propriétaire → invite route → génère token → copie URL
  → Destinataire ouvre URL avec ?invite=<token>
  → join route → vérifie token + expiry
  → Crée BrainstormParticipant
  → Notifie le owner

Hooks React Query

Hook Type Query Key API
useBrainstormSessions() Query ['brainstorm', 'sessions'] GET /api/brainstorm
useSharedBrainstormSessions() Query ['brainstorm', 'shared-sessions'] GET /api/brainstorm/shared
useBrainstormSession(id) Query ['brainstorm', 'session', id] GET /api/brainstorm/{id}
useCreateBrainstorm() Mutation POST /api/brainstorm
useExpandIdea(id) Mutation POST /api/brainstorm/{id}/expand
useDismissIdea(id) Mutation POST /api/brainstorm/{id}/dismiss
useConvertIdea(id) Mutation POST /api/brainstorm/{id}/convert
useExportBrainstorm(id) Mutation POST /api/brainstorm/{id}/export
useFinalizeBrainstorm(id) Mutation POST /api/brainstorm/{id}/finalize
useDeleteBrainstorm() Mutation DELETE /api/brainstorm/{id}
useAddManualIdea(id) Mutation POST /api/brainstorm/{id}/manual-idea
useBrainstormActivity(id) Query GET /api/brainstorm/{id}/activity (refetch 10s)

Fichiers sources

Fichier Rôle
components/brainstorm/brainstorm-page.tsx Page principale complète
components/brainstorm/wave-canvas.tsx Canvas D3 (noeuds, liens, interactions)
components/brainstorm/brainstorm-share-dialog.tsx Modal de partage par email
components/brainstorm/activity-feed.tsx Panneau d'activité
components/brainstorm/ghost-cursor.tsx Curseur IA animé
components/brainstorm/live-cursors.tsx Curseurs des autres users + avatars
hooks/use-brainstorm.ts Tous les hooks React Query
hooks/use-brainstorm-socket.ts Hook Socket.io
app/actions/brainstorm.ts Server actions (partage)
app/api/brainstorm/route.ts POST create + GET list
app/api/brainstorm/[sessionId]/route.ts GET/DELETE session
app/api/brainstorm/[sessionId]/expand/route.ts POST approfondir
app/api/brainstorm/[sessionId]/manual-idea/route.ts POST idée manuelle
app/api/brainstorm/[sessionId]/dismiss/route.ts POST rejeter
app/api/brainstorm/[sessionId]/convert/route.ts POST convertir en note
app/api/brainstorm/[sessionId]/finalize/route.ts POST finaliser
app/api/brainstorm/[sessionId]/export/route.ts POST exporter
app/api/brainstorm/[sessionId]/update-position/route.ts POST position
app/api/brainstorm/[sessionId]/invite/route.ts POST invitation par lien
app/api/brainstorm/[sessionId]/activity/route.ts GET activités
app/api/brainstorm/join/route.ts POST rejoindre par token
app/api/brainstorm/shared/route.ts GET sessions partagées
socket-server.ts Serveur Socket.io
types/brainstorm.ts Types TypeScript
lib/brainstorm-collab.ts verifyParticipant + logActivity
prisma/schema.prisma Modèles DB (lignes 454-572)
components/notification-panel.tsx Notifications partage brainstorm
components/sidebar.tsx SidebarBrainstorms (lignes 85-169)

Composants obsolètes (existent mais non utilisés)

Fichier Note
components/brainstorm/manual-idea-dialog.tsx Remplacé par l'éditeur inline du canvas
components/brainstorm/invite-dialog.tsx Remplacé par BrainstormShareDialog
components/brainstorm/brainstorm-create-dialog.tsx Remplacé par l'input inline du header
components/brainstorm/brainstorm-canvas.tsx Ancienne implémentation avec react-force-graph-2d, remplacé par WaveCanvas (D3 direct)