fix: génération asynchrone (fire-and-forget + polling)
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 23s
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 23s
Le problème : la route POST /api/agents/run-for-note bloquait le thread
HTTP pendant toute la durée de génération (2-5 min), provoquant un
spinner infini sans résultat.
Solution :
- POST retourne immédiatement avec { agentId } et lance executeAgent
en arrière-plan (fire-and-forget, sans await)
- GET /api/agents/run-for-note?agentId= retourne le statut de la
dernière agentAction (running | success | failure) + canvasId/noteId
- Le client poll le statut toutes les 3 secondes jusqu'au résultat,
le toast persistant Sonner se met à jour automatiquement
- Cleanup du polling au démontage du composant
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,6 +21,7 @@ const TYPE_DEFAULTS: Record<GenerateType, {
|
||||
},
|
||||
}
|
||||
|
||||
// ─── POST : kick off generation (fire-and-forget) ──────────────────────────
|
||||
export async function POST(req: NextRequest) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) {
|
||||
@@ -40,7 +41,6 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: 'Paramètres invalides' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Verify note belongs to user
|
||||
const note = await prisma.note.findFirst({
|
||||
where: { id: noteId, userId },
|
||||
select: { id: true, title: true, notebookId: true },
|
||||
@@ -54,7 +54,6 @@ export async function POST(req: NextRequest) {
|
||||
? `Slides — ${(note.title || 'Note').substring(0, 40)}`
|
||||
: `Diagramme — ${(note.title || 'Note').substring(0, 40)}`
|
||||
|
||||
// Create a dedicated one-shot agent for this note
|
||||
const agent = await prisma.agent.create({
|
||||
data: {
|
||||
name: agentName,
|
||||
@@ -72,15 +71,70 @@ export async function POST(req: NextRequest) {
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const { executeAgent } = await import('@/lib/ai/services/agent-executor.service')
|
||||
const result = await executeAgent(agent.id, userId)
|
||||
return NextResponse.json(result)
|
||||
} catch (err) {
|
||||
console.error('[run-for-note] executeAgent error:', err)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: err instanceof Error ? err.message : 'Erreur inconnue' },
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
// ── 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,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user