Files
Momento/memento-note/lib/brainstorm-collab.ts
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

131 lines
3.7 KiB
TypeScript

import prisma from '@/lib/prisma'
export async function verifyParticipant(
sessionId: string,
userId: string,
requiredRole?: 'host' | 'editor' | 'viewer'
): Promise<{ isParticipant: boolean; role: string }> {
const participant = await prisma.brainstormParticipant.findFirst({
where: { sessionId, userId },
})
if (!participant) {
return { isParticipant: false, role: 'none' }
}
await prisma.brainstormParticipant.update({
where: { id: participant.id },
data: { lastSeenAt: new Date() },
})
if (requiredRole === 'host' && participant.role !== 'host') {
return { isParticipant: false, role: participant.role }
}
if (requiredRole === 'editor' && participant.role === 'viewer') {
return { isParticipant: false, role: participant.role }
}
return { isParticipant: true, role: participant.role }
}
export async function logActivity(
sessionId: string,
action: string,
userId?: string | null,
details?: Record<string, any>
) {
await prisma.brainstormActivity.create({
data: {
sessionId,
userId: userId || null,
action,
details: details ? JSON.stringify(details) : null,
},
})
}
// [UPDATE - SÉCURITÉ] Résoudre l'userId et le périmètre de notes autorisé pour les appels IA.
// - Hôte : accès complet à ses notes (publicNoteIds = null)
// - Invité : restreint aux contextNoteIds publics de la session (publicNoteIds = string[] | [])
export async function resolveAiContextUserId(
sessionId: string,
requestingUserId: string
): Promise<{ aiUserId: string; isGuest: boolean; publicNoteIds: string[] | null }> {
const session = await prisma.brainstormSession.findUnique({
where: { id: sessionId },
select: {
userId: true,
contextNoteIds: true,
},
})
if (!session) throw new Error('Session not found')
const isHost = session.userId === requestingUserId
if (isHost) {
return { aiUserId: requestingUserId, isGuest: false, publicNoteIds: null }
}
// Invité : on restreint aux contextNoteIds déclarés publics par l'hôte
const publicNoteIds: string[] = session.contextNoteIds
? (JSON.parse(session.contextNoteIds) as string[])
: []
return {
aiUserId: session.userId,
isGuest: true,
publicNoteIds: publicNoteIds.length > 0 ? publicNoteIds : [],
}
}
// [UPDATE - SÉCURITÉ] Sanitize les notes injectées dans un prompt IA pour un invité.
// Tronque le contenu et masque les entités nommées (Prénom Nom) avec [Person].
export function sanitizeNotesForGuest(
notes: { id: string; title: string | null; summary: string }[]
): { id: string; title: string; summary: string }[] {
const namedEntityRe = /\b[A-ZÀ-Ü][a-zà-ü]+ [A-ZÀ-Ü][a-zà-ü]+\b/g
return notes.map(n => ({
id: n.id,
title: (n.title || 'Note').replace(namedEntityRe, '[Person]'),
summary: n.summary.slice(0, 80).replace(namedEntityRe, '[Person]') + '…',
}))
}
export async function captureSnapshot(
sessionId: string,
label: string,
activityId?: string
): Promise<void> {
const ideas = await prisma.brainstormIdea.findMany({
where: { sessionId, status: 'active' },
select: {
id: true,
title: true,
waveNumber: true,
positionX: true,
positionY: true,
parentIdeaId: true,
noveltyScore: true,
createdByType: true,
status: true,
},
orderBy: [{ waveNumber: 'asc' }, { createdAt: 'asc' }],
})
const maxStep = await prisma.brainstormSnapshot.findFirst({
where: { sessionId },
orderBy: { step: 'desc' },
select: { step: true },
})
await prisma.brainstormSnapshot.create({
data: {
sessionId,
activityId: activityId || null,
step: (maxStep?.step || 0) + 1,
label,
ideaGraph: JSON.stringify(ideas),
},
})
}