From 34a977b5c4b97a514eaa5fe00ad633a5bf8da555 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Tue, 5 May 2026 21:26:35 +0000 Subject: [PATCH] fix: runAgent fire-and-forget + polling sur la page /agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le bouton Play des cartes agents appelait runAgent (Server Action) qui attendait executeAgent (~2-5 min) → spinner bloqué sans résultat. - runAgent retourne immédiatement { success, agentId } - agent-card.tsx lance un polling toutes les 3s sur GET /api/agents/run-for-note?agentId= jusqu'au statut terminal - Toast persistant Sonner pendant la génération, mis à jour au résultat - Cleanup automatique du polling au démontage Co-authored-by: Cursor --- memento-note/app/actions/agent-actions.ts | 38 +++++++++------ memento-note/components/agents/agent-card.tsx | 46 +++++++++++++++---- memento-note/locales/en.json | 2 + memento-note/locales/fr.json | 2 + 4 files changed, 67 insertions(+), 21 deletions(-) diff --git a/memento-note/app/actions/agent-actions.ts b/memento-note/app/actions/agent-actions.ts index 60f9e4d..cd82501 100644 --- a/memento-note/app/actions/agent-actions.ts +++ b/memento-note/app/actions/agent-actions.ts @@ -19,6 +19,7 @@ export async function createAgent(data: { role: string sourceUrls?: string[] sourceNotebookId?: string + sourceNoteIds?: string[] targetNotebookId?: string frequency?: string tools?: string[] @@ -28,6 +29,8 @@ export async function createAgent(data: { scheduledTime?: string scheduledDay?: number timezone?: string + slideTheme?: string + slideStyle?: string }) { const session = await auth() if (!session?.user?.id) { @@ -43,6 +46,7 @@ export async function createAgent(data: { role: data.role, sourceUrls: data.sourceUrls ? JSON.stringify(data.sourceUrls) : null, sourceNotebookId: data.sourceNotebookId || null, + sourceNoteIds: data.sourceNoteIds ? JSON.stringify(data.sourceNoteIds) : null, targetNotebookId: data.targetNotebookId || null, frequency: data.frequency || 'manual', tools: data.tools ? JSON.stringify(data.tools) : '[]', @@ -52,6 +56,8 @@ export async function createAgent(data: { scheduledTime: data.scheduledTime || '08:00', scheduledDay: data.scheduledDay ?? null, timezone: data.timezone || null, + slideTheme: data.slideTheme || null, + slideStyle: data.slideStyle || null, userId: session.user.id, } }) @@ -88,6 +94,7 @@ export async function updateAgent(id: string, data: { role?: string sourceUrls?: string[] sourceNotebookId?: string | null + sourceNoteIds?: string[] | null targetNotebookId?: string | null frequency?: string isEnabled?: boolean @@ -98,6 +105,8 @@ export async function updateAgent(id: string, data: { scheduledTime?: string scheduledDay?: number | null timezone?: string + slideTheme?: string | null + slideStyle?: string | null }) { const session = await auth() if (!session?.user?.id) { @@ -117,6 +126,7 @@ export async function updateAgent(id: string, data: { if (data.role !== undefined) updateData.role = data.role if (data.sourceUrls !== undefined) updateData.sourceUrls = JSON.stringify(data.sourceUrls) if (data.sourceNotebookId !== undefined) updateData.sourceNotebookId = data.sourceNotebookId + if (data.sourceNoteIds !== undefined) updateData.sourceNoteIds = data.sourceNoteIds ? JSON.stringify(data.sourceNoteIds) : null if (data.targetNotebookId !== undefined) updateData.targetNotebookId = data.targetNotebookId if (data.frequency !== undefined) updateData.frequency = data.frequency if (data.isEnabled !== undefined) updateData.isEnabled = data.isEnabled @@ -127,6 +137,8 @@ export async function updateAgent(id: string, data: { if (data.scheduledTime !== undefined) updateData.scheduledTime = data.scheduledTime if (data.scheduledDay !== undefined) updateData.scheduledDay = data.scheduledDay if (data.timezone !== undefined) updateData.timezone = data.timezone + if (data.slideTheme !== undefined) updateData.slideTheme = data.slideTheme + if (data.slideStyle !== undefined) updateData.slideStyle = data.slideStyle // Recalculate nextRun when scheduling fields change const shouldRecalcNextRun = @@ -220,21 +232,21 @@ export async function runAgent(id: string) { if (!session?.user?.id) { throw new Error('Non autorise') } + const userId = session.user.id - try { - const { executeAgent } = await import('@/lib/ai/services/agent-executor.service') - const result = await executeAgent(id, session.user.id) - revalidatePath('/agents') - revalidatePath('/') - return result - } catch (error) { - console.error('Error running agent:', error) - return { - success: false, - actionId: '', - error: error instanceof Error ? error.message : 'Erreur inconnue' - } + // Verify ownership + const agent = await prisma.agent.findUnique({ where: { id }, select: { id: true, userId: true } }) + if (!agent || agent.userId !== userId) { + return { success: false, agentId: id, error: 'Agent introuvable' } } + + // Fire and forget — return immediately so the UI doesn't block + import('@/lib/ai/services/agent-executor.service') + .then(({ executeAgent }) => executeAgent(id, userId)) + .then(() => { /* revalidation is handled client-side via polling */ }) + .catch(err => console.error('[runAgent] Background error:', err)) + + return { success: true, agentId: id, status: 'running' } } // --- History --- diff --git a/memento-note/components/agents/agent-card.tsx b/memento-note/components/agents/agent-card.tsx index e454ade..2e3c2f3 100644 --- a/memento-note/components/agents/agent-card.tsx +++ b/memento-note/components/agents/agent-card.tsx @@ -5,7 +5,7 @@ * Compact card matching the reference design — with a "Next Run / Status" footer. */ -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef } from 'react' import { formatDistanceToNow } from 'date-fns' import { fr } from 'date-fns/locale/fr' import { enUS } from 'date-fns/locale/en-US' @@ -83,21 +83,51 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps const dateLocale = language === 'fr' ? fr : enUS const isNew = Date.now() - new Date(agent.createdAt).getTime() < 5 * 60 * 1000 + const pollRef = useRef | null>(null) + + // Cleanup polling on unmount + useEffect(() => () => { if (pollRef.current) clearInterval(pollRef.current) }, []) + const handleRun = async () => { setIsRunning(true) + const toastId = toast.loading( + t('agents.toasts.running') || `Agent "${agent.name}" en cours…`, + { description: t('agents.toasts.runningDesc') || 'La génération peut prendre quelques minutes.', duration: Infinity } + ) try { const { runAgent } = await import('@/app/actions/agent-actions') const result = await runAgent(agent.id) - if (result.success) { - toast.success(t('agents.toasts.runSuccess', { name: agent.name })) - } else { - toast.error(t('agents.toasts.runError', { error: result.error || t('agents.toasts.runFailed') })) + if (!result.success) { + toast.error(t('agents.toasts.runError', { error: result.error || t('agents.toasts.runFailed') }), { id: toastId }) + setIsRunning(false) + return } + + // Poll status every 3 s until terminal state + if (pollRef.current) clearInterval(pollRef.current) + pollRef.current = setInterval(async () => { + try { + const res = await fetch(`/api/agents/run-for-note?agentId=${agent.id}`) + const data = await res.json() + if (data.status === 'success') { + clearInterval(pollRef.current!) + pollRef.current = null + setIsRunning(false) + toast.success(t('agents.toasts.runSuccess', { name: agent.name }), { id: toastId, duration: 6000 }) + onRefresh() + } else if (data.status === 'failure') { + clearInterval(pollRef.current!) + pollRef.current = null + setIsRunning(false) + toast.error(t('agents.toasts.runError', { error: data.error || t('agents.toasts.runFailed') }), { id: toastId }) + onRefresh() + } + } catch { /* network error — keep polling */ } + }, 3000) + } catch { - toast.error(t('agents.toasts.runGenericError')) - } finally { + toast.error(t('agents.toasts.runGenericError'), { id: toastId }) setIsRunning(false) - onRefresh() } } diff --git a/memento-note/locales/en.json b/memento-note/locales/en.json index 1f29bc9..7118a25 100644 --- a/memento-note/locales/en.json +++ b/memento-note/locales/en.json @@ -1579,6 +1579,8 @@ "updated": "Agent updated", "deleted": "\"{name}\" deleted", "deleteError": "Error deleting", + "running": "Generation in progress…", + "runningDesc": "Generation may take a few minutes. You can navigate freely.", "runSuccess": "\"{name}\" executed successfully", "runError": "Error: {error}", "runFailed": "Execution failed", diff --git a/memento-note/locales/fr.json b/memento-note/locales/fr.json index 385d967..cc7da95 100644 --- a/memento-note/locales/fr.json +++ b/memento-note/locales/fr.json @@ -1657,6 +1657,8 @@ "updated": "Agent mis à jour", "deleted": "\"{name}\" supprimé", "deleteError": "Erreur lors de la suppression", + "running": "Génération en cours…", + "runningDesc": "La génération peut prendre quelques minutes. Vous pouvez naviguer.", "runSuccess": "\"{name}\" exécuté avec succès", "runError": "Erreur : {error}", "runFailed": "Exécution échouée",