Files
Momento/memento-note/app/api/agents/run-for-note/route.ts
Antigravity 4f950740eb
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 23s
feat: options de génération (thème/style/type) + masquer agents one-shot
- Les agents créés par run-for-note ont frequency:'one-shot' et sont
  filtrés de la page /agents (NOT frequency:'one-shot' dans getAgents)
- Panneau slides : sélecteurs Thème (11 palettes) + Style (sharp/soft/rounded/pill)
- Panneau diagramme : sélecteurs Type (auto/flowchart/mindmap/timeline/
  org-chart/architecture/process-map) + Style (sketchy/austere/sketch+)
- Les valeurs sélectionnées sont transmises à l'API et injectées dans le prompt

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 21:45:57 +00:00

141 lines
4.6 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
type GenerateType = 'slide-generator' | 'excalidraw-generator'
const TYPE_DEFAULTS: Record<GenerateType, {
role: string
tools: string[]
maxSteps: number
}> = {
'slide-generator': {
role: 'Crée une présentation PowerPoint professionnelle et visuelle à partir du contenu de la note fournie.',
tools: JSON.stringify(['note_search', 'note_read', 'generate_pptx']),
maxSteps: 8,
},
'excalidraw-generator': {
role: 'Génère un diagramme Excalidraw clair et professionnel à partir du contenu de la note fournie.',
tools: JSON.stringify(['note_search', 'note_read', 'generate_excalidraw']),
maxSteps: 6,
},
}
// ─── POST : kick off generation (fire-and-forget) ──────────────────────────
export async function POST(req: NextRequest) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 })
}
const userId = session.user.id
const body = await req.json()
const { noteId, type, theme, style } = body as {
noteId: string
type: GenerateType
theme?: string
style?: string
}
if (!noteId || !type || !TYPE_DEFAULTS[type]) {
return NextResponse.json({ error: 'Paramètres invalides' }, { status: 400 })
}
const note = await prisma.note.findFirst({
where: { id: noteId, userId },
select: { id: true, title: true, notebookId: true },
})
if (!note) {
return NextResponse.json({ error: 'Note introuvable' }, { status: 404 })
}
const defaults = TYPE_DEFAULTS[type]
const agentName = type === 'slide-generator'
? `Slides — ${(note.title || 'Note').substring(0, 40)}`
: `Diagramme — ${(note.title || 'Note').substring(0, 40)}`
const agent = await prisma.agent.create({
data: {
name: agentName,
type,
role: defaults.role,
tools: defaults.tools,
maxSteps: defaults.maxSteps,
frequency: 'one-shot',
isEnabled: true,
sourceNoteIds: JSON.stringify([noteId]),
targetNotebookId: note.notebookId ?? undefined,
slideTheme: theme ?? 'vibrant_tech',
slideStyle: style ?? 'soft',
userId,
},
})
// ── Fire and forget — do NOT await so the HTTP response returns immediately ──
// In Node.js / Docker self-hosted, the process keeps running after response.
import('@/lib/ai/services/agent-executor.service')
.then(({ executeAgent }) => executeAgent(agent.id, userId))
.catch(err => console.error('[run-for-note] Background agent error:', err))
return NextResponse.json({ success: true, agentId: agent.id, status: 'running' })
}
// ─── GET : poll current agent status ──────────────────────────────────────
export async function GET(req: NextRequest) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 })
}
const userId = session.user.id
const agentId = req.nextUrl.searchParams.get('agentId')
if (!agentId) {
return NextResponse.json({ error: 'agentId manquant' }, { status: 400 })
}
// Verify ownership
const agent = await prisma.agent.findFirst({
where: { id: agentId, userId },
select: { id: true },
})
if (!agent) {
return NextResponse.json({ error: 'Agent introuvable' }, { status: 404 })
}
// Return latest action for this agent
const action = await prisma.agentAction.findFirst({
where: { agentId },
orderBy: { createdAt: 'desc' },
select: { id: true, status: true, result: true, log: true },
})
if (!action) {
// Action not created yet (race condition in first poll) — still running
return NextResponse.json({ status: 'running' })
}
// If success, find canvasId from the related canvas (result stores canvas id)
let canvasId: string | null = null
let noteId: string | null = null
if (action.status === 'success' && action.result) {
// result field: the executor stores canvas.id or note.id
const canvas = await prisma.canvas.findFirst({
where: { id: action.result },
select: { id: true },
})
if (canvas) {
canvasId = canvas.id
} else {
noteId = action.result
}
}
return NextResponse.json({
status: action.status, // 'running' | 'success' | 'failure'
actionId: action.id,
canvasId,
noteId,
error: action.status === 'failure' ? (action.log || 'Erreur inconnue') : undefined,
})
}