Files
Momento/memento-note/app/api/brainstorm/[sessionId]/export/route.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

195 lines
6.7 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
import { auth } from '@/auth'
import { verifyParticipant } from '@/lib/brainstorm-collab'
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ sessionId: string }> }
) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
try {
const { sessionId } = await params
const brainstormSession = await prisma.brainstormSession.findFirst({
where: {
id: sessionId,
OR: [
{ userId: session.user.id },
{ participants: { some: { userId: session.user.id } } },
],
},
include: {
ideas: {
orderBy: [{ waveNumber: 'asc' }, { createdAt: 'asc' }],
include: {
noteRefs: {
include: {
note: { select: { id: true, title: true } },
},
},
},
},
},
})
if (!brainstormSession) {
return NextResponse.json({ error: 'Session not found' }, { status: 404 })
}
if (brainstormSession.exportedNoteId) {
const existingNote = await prisma.note.findUnique({
where: { id: brainstormSession.exportedNoteId },
})
if (existingNote) {
return NextResponse.json({ success: true, data: existingNote })
}
}
const waveLabels: Record<number, string> = {
1: '🔄 Variations',
2: '🔗 Analogies',
3: '💥 Disruptions',
}
const activeIdeas = brainstormSession.ideas.filter(i => i.status !== 'dismissed')
const convertedIdeas = brainstormSession.ideas.filter(i => i.status === 'converted')
const dismissedCount = brainstormSession.ideas.filter(i => i.status === 'dismissed').length
let markdown = `# Brainstorm: ${brainstormSession.seedIdea}\n\n`
markdown += `> Generated on ${brainstormSession.createdAt.toLocaleDateString()}\n\n`
markdown += `---\n\n`
markdown += `**Summary**: ${activeIdeas.length} active ideas, ${convertedIdeas.length} converted to notes, ${dismissedCount} dismissed.\n\n`
for (let wave = 1; wave <= 3; wave++) {
const waveIdeas = activeIdeas.filter(i => i.waveNumber === wave)
if (waveIdeas.length === 0) continue
markdown += `## ${waveLabels[wave] || `Wave ${wave}`}\n\n`
for (const idea of waveIdeas) {
const statusIcon = idea.status === 'converted' ? '✅' : idea.status === 'dismissed' ? '❌' : '💡'
markdown += `### ${statusIcon} ${idea.title}\n`
markdown += `${idea.description}\n\n`
markdown += `- **Connection**: ${idea.connectionToSeed || 'N/A'}\n`
markdown += `- **Novelty**: ${idea.noveltyScore || 'N/A'}/10\n`
const validRefs = (idea.noteRefs || []).filter(r => r.noteId && r.note)
if (validRefs.length > 0) {
markdown += `- **Origin**:\n`
for (const ref of validRefs) {
const relLabel = {
derived_from: 'Derived from',
opposes: 'Opposes',
extends: 'Extends',
synthesizes: 'Synthesizes',
transposes: 'Transposes',
}[ref.relation] || 'Related to'
markdown += ` - ${relLabel} [${ref.note?.title || 'Untitled'}](note:${ref.noteId}): ${ref.explanation}\n`
}
}
if (idea.convertedToNoteId) {
markdown += `- **→ Converted to note**: ${idea.convertedToNoteId}\n`
}
if (idea.parentIdeaId) {
const parent = brainstormSession.ideas.find(i => i.id === idea.parentIdeaId)
if (parent) {
markdown += `- **Parent idea**: ${parent.title}\n`
}
}
markdown += `\n`
}
}
const allReferencedNoteIds = new Set<string>()
for (const idea of brainstormSession.ideas) {
for (const ref of idea.noteRefs || []) {
if (ref.noteId) allReferencedNoteIds.add(ref.noteId)
}
}
if (allReferencedNoteIds.size > 0) {
markdown += `---\n\n## Notes sollicitées\n\n`
const refNotes = await prisma.note.findMany({
where: { id: { in: Array.from(allReferencedNoteIds) } },
select: { id: true, title: true },
})
for (const note of refNotes) {
const refsForNote = brainstormSession.ideas.flatMap(i =>
(i.noteRefs || []).filter(r => r.noteId === note.id)
)
const accepted = refsForNote.filter(r => r.verdict === 'accepted').length
const dismissed = refsForNote.filter(r => r.verdict === 'dismissed').length
const verdictStr = accepted > 0 ? `${accepted} idea(s) accepted` : dismissed > 0 ? `❌ all dismissed` : '⏳ unresolved'
markdown += `- [${note.title || 'Untitled'}](note:${note.id}) — ${verdictStr}\n`
}
markdown += `\n`
}
if (convertedIdeas.length > 0) {
markdown += `## Converted Notes\n\n`
const convertedNoteIds = convertedIdeas
.map(i => i.convertedToNoteId)
.filter(Boolean) as string[]
if (convertedNoteIds.length > 0) {
const notes = await prisma.note.findMany({
where: { id: { in: convertedNoteIds } },
select: { id: true, title: true },
})
for (const note of notes) {
markdown += `- [${note.title || 'Untitled'}](note:${note.id})\n`
}
}
}
const notebookName = brainstormSession.seedIdea.length > 40
? brainstormSession.seedIdea.substring(0, 40).trim() + '…'
: brainstormSession.seedIdea
let notebook = await prisma.notebook.findFirst({
where: { userId: session.user.id, name: notebookName, trashedAt: null },
})
if (!notebook) {
const notebookCount = await prisma.notebook.count({ where: { userId: session.user.id, trashedAt: null } })
notebook = await prisma.notebook.create({
data: {
userId: session.user.id,
name: notebookName,
order: notebookCount,
icon: 'wind',
},
})
}
const note = await prisma.note.create({
data: {
userId: session.user.id,
title: `Synthèse: ${brainstormSession.seedIdea.slice(0, 50)}`,
content: markdown,
type: 'markdown',
labels: JSON.stringify(['brainstorm', 'export']),
notebookId: notebook.id,
},
})
await prisma.brainstormSession.update({
where: { id: sessionId },
data: { exportedNoteId: note.id },
})
return NextResponse.json({ success: true, data: { ...note, _notebookName: notebookName } }, { status: 201 })
} catch (error) {
console.error('Error exporting brainstorm:', error)
return NextResponse.json(
{ error: 'Failed to export brainstorm' },
{ status: 500 }
)
}
}