'use client' 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' import { Play, Trash2, Loader2, Globe, Search, Eye, Settings, CheckCircle2, XCircle, Clock, Pencil, Activity, Presentation, ListChecks, } from 'lucide-react' import { toast } from 'sonner' import { useLanguage } from '@/lib/i18n' import { getNotebookIcon } from '@/lib/notebook-icon' interface AgentCardProps { agent: { id: string name: string description?: string | null type?: string | null isEnabled: boolean frequency: string lastRun: string | Date | null nextRun?: string | Date | null createdAt: string | Date updatedAt: string | Date _count: { actions: number } actions: { id: string; status: string; createdAt: string | Date }[] notebook?: { id: string; name: string; icon?: string | null } | null } onEdit: (id: string) => void onRefresh: () => void onToggle: (id: string, isEnabled: boolean) => void } const typeConfig: Record = { scraper: { icon: Globe }, researcher: { icon: Search }, monitor: { icon: Eye }, custom: { icon: Settings }, 'slide-generator': { icon: Presentation }, 'excalidraw-generator': { icon: Pencil }, 'task-extractor': { icon: ListChecks }, } const frequencyKeys: Record = { manual: 'agents.frequencies.manual', hourly: 'agents.frequencies.hourly', daily: 'agents.frequencies.daily', weekly: 'agents.frequencies.weekly', monthly: 'agents.frequencies.monthly', } export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps) { const { t, language } = useLanguage() const [isRunning, setIsRunning] = useState(false) const [isDeleting, setIsDeleting] = useState(false) const [isToggling, setIsToggling] = useState(false) const [mounted, setMounted] = useState(false) useEffect(() => { setMounted(true) }, []) const config = typeConfig[agent.type || 'scraper'] || typeConfig.custom const Icon = config.icon const lastAction = agent.actions[0] const dateLocale = language === 'fr' ? fr : enUS const pollRef = useRef | null>(null) 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.error(t('agents.toasts.runError', { error: result.error || t('agents.toasts.runFailed') }), { id: toastId }) setIsRunning(false) return } if (pollRef.current) clearInterval(pollRef.current) const startTime = Date.now() const MAX_POLL_TIME = 10 * 60 * 1000 // 10 minutes pollRef.current = setInterval(async () => { // Safety timeout if (Date.now() - startTime > MAX_POLL_TIME) { if (pollRef.current) clearInterval(pollRef.current) pollRef.current = null setIsRunning(false) toast.error(t('agents.toasts.runError', { error: 'Timeout after 10 minutes' }), { id: toastId, description: '' }) return } try { const res = await fetch(`/api/agents/run-for-note?agentId=${agent.id}`) if (!res.ok) throw new Error('Poll failed') const data = await res.json() if (data.status === 'success') { if (pollRef.current) clearInterval(pollRef.current) pollRef.current = null setIsRunning(false) toast.success(t('agents.toasts.runSuccess', { name: agent.name }), { id: toastId, duration: 6000, description: '' // Clear the loading description }) onRefresh() } else if (data.status === 'failure') { if (pollRef.current) clearInterval(pollRef.current) pollRef.current = null setIsRunning(false) toast.error(t('agents.toasts.runError', { error: data.error || t('agents.toasts.runFailed') }), { id: toastId, description: '' // Clear the loading description }) onRefresh() } } catch (err) { console.error('Polling error:', err) // Keep polling until timeout } }, 3000) } catch { toast.error(t('agents.toasts.runGenericError'), { id: toastId }) setIsRunning(false) } } const handleDelete = async () => { if (!confirm(t('agents.actions.deleteConfirm', { name: agent.name }))) return setIsDeleting(true) try { const { deleteAgent } = await import('@/app/actions/agent-actions') await deleteAgent(agent.id) toast.success(t('agents.toasts.deleted', { name: agent.name })) } catch { toast.error(t('agents.toasts.deleteError')) } finally { setIsDeleting(false) onRefresh() } } const handleToggle = async () => { const newEnabled = !agent.isEnabled setIsToggling(true) onToggle(agent.id, newEnabled) try { const { toggleAgent } = await import('@/app/actions/agent-actions') await toggleAgent(agent.id, newEnabled) toast.success(newEnabled ? t('agents.actions.toggleOn') : t('agents.actions.toggleOff')) } catch { onToggle(agent.id, !newEnabled) toast.error(t('agents.toasts.toggleError')) } finally { setIsToggling(false) } } const nextRunLabel = (() => { if (!agent.isEnabled) return '—' if (agent.frequency === 'manual') return t('agents.frequencies.manual') if (agent.nextRun && new Date(agent.nextRun) > new Date()) { if (!mounted) return '...' return formatDistanceToNow(new Date(agent.nextRun), { addSuffix: true, locale: dateLocale }) } return t(frequencyKeys[agent.frequency] || 'agents.frequencies.manual') })() const statusLabel = lastAction ? lastAction.status === 'success' ? t('agents.status.success') : lastAction.status === 'failure' ? t('agents.status.failure') : lastAction.status === 'running' ? t('agents.status.running') : t('agents.status.pending') : '—' return (
onEdit(agent.id)} >

{agent.name}

{t(`agents.types.${agent.type || 'custom'}`)}

e.stopPropagation()}>
{agent.description && (

{agent.description}

)}
{t(frequencyKeys[agent.frequency] || 'agents.frequencies.manual')} {t('agents.metadata.executions', { count: agent._count.actions })}
{t('agents.status.nextRun')} {nextRunLabel}
{t('agents.status.lastStatus')} {lastAction ? ( {lastAction.status === 'success' && } {lastAction.status === 'failure' && } {lastAction.status === 'running' && } {statusLabel} ) : ( )}
e.stopPropagation()}>
) }