'use client' import { useState, useCallback, useMemo, useEffect, useRef } from 'react' import { motion } from 'motion/react' import { Plus, Bot, Search, LifeBuoy } from 'lucide-react' import { toast } from 'sonner' import { useLanguage } from '@/lib/i18n' import { AgentCard } from '@/components/agents/agent-card' import { AgentDetailView } from '@/components/agents/agent-detail-view' import { AgentTemplates } from '@/components/agents/agent-templates' import { AgentRunLog } from '@/components/agents/agent-run-log' import { AgentHelp } from '@/components/agents/agent-help' import { createAgent, updateAgent, getAgents, } from '@/app/actions/agent-actions' interface Notebook { id: string name: string icon?: string | null } interface AgentItem { id: string name: string description?: string | null type?: string | null role: string sourceUrls?: string | null sourceNotebookId?: string | null sourceNoteIds?: string | null targetNotebookId?: string | null frequency: string isEnabled: boolean lastRun: string | Date | null nextRun?: string | Date | null createdAt: string | Date updatedAt: string | Date tools?: string | null maxSteps?: number notifyEmail?: boolean includeImages?: boolean scheduledTime?: string | null scheduledDay?: number | null timezone?: string | null slideTheme?: string | null slideStyle?: string | null _count: { actions: number } actions: { id: string; status: string; createdAt: string | Date }[] notebook?: { id: string; name: string; icon?: string | null } | null } interface AgentsPageClientProps { agents: AgentItem[] notebooks: Notebook[] } const typeFilterOptions = [ { value: '', labelKey: 'agents.filterAll' }, { value: 'scraper', labelKey: 'agents.types.scraper' }, { value: 'researcher', labelKey: 'agents.types.researcher' }, { value: 'monitor', labelKey: 'agents.types.monitor' }, { value: 'custom', labelKey: 'agents.types.custom' }, ] as const export function AgentsPageClient({ agents: initialAgents, notebooks, }: AgentsPageClientProps) { const { t } = useLanguage() const [agents, setAgents] = useState(initialAgents) const [selectedAgent, setSelectedAgent] = useState(null) const [isNewAgent, setIsNewAgent] = useState(false) const [logAgent, setLogAgent] = useState<{ id: string; name: string } | null>(null) const [showHelp, setShowHelp] = useState(false) const [searchQuery, setSearchQuery] = useState('') const [typeFilter, setTypeFilter] = useState('') const refreshAgents = useCallback(async () => { try { const updated = await getAgents() setAgents(updated) return updated } catch { return null } }, []) const prevActionsRef = useRef>({}) const checkForNewActions = useCallback((updated: AgentItem[]) => { for (const agent of updated) { const lastAction = agent.actions[0] if (!lastAction) continue const prevId = prevActionsRef.current[agent.id] if (prevId === undefined) continue if (prevId !== lastAction.id) { const age = Date.now() - new Date(lastAction.createdAt).getTime() if (age < 5 * 60 * 1000) { if (lastAction.status === 'success') { toast.success(t('agents.toasts.autoRunSuccess', { name: agent.name })) } else if (lastAction.status === 'failure') { toast.error(t('agents.toasts.autoRunError', { name: agent.name })) } } } prevActionsRef.current[agent.id] = lastAction.id } }, [t]) useEffect(() => { for (const agent of initialAgents) { prevActionsRef.current[agent.id] = agent.actions[0]?.id ?? null } const interval = setInterval(async () => { const updated = await refreshAgents() if (updated) checkForNewActions(updated) }, 30000) const onVisible = async () => { if (document.visibilityState === 'visible') { const updated = await refreshAgents() if (updated) checkForNewActions(updated) } } document.addEventListener('visibilitychange', onVisible) return () => { clearInterval(interval) document.removeEventListener('visibilitychange', onVisible) } }, [refreshAgents, checkForNewActions]) const handleToggle = useCallback((id: string, isEnabled: boolean) => { setAgents(prev => prev.map(a => a.id === id ? { ...a, isEnabled } : a)) }, []) const handleCreate = useCallback(() => { setIsNewAgent(true) setSelectedAgent(null) }, []) const handleEdit = useCallback((id: string) => { const agent = agents.find(a => a.id === id) if (agent) { setIsNewAgent(false) setSelectedAgent(agent) } }, [agents]) const handleSave = useCallback(async (formData: FormData) => { const data = { name: formData.get('name') as string, description: (formData.get('description') as string) || undefined, type: formData.get('type') as string, role: formData.get('role') as string, sourceUrls: formData.get('sourceUrls') ? JSON.parse(formData.get('sourceUrls') as string) : undefined, sourceNotebookId: (formData.get('sourceNotebookId') as string) || undefined, sourceNoteIds: formData.get('sourceNoteIds') ? JSON.parse(formData.get('sourceNoteIds') as string) : undefined, targetNotebookId: (formData.get('targetNotebookId') as string) || undefined, frequency: formData.get('frequency') as string, tools: formData.get('tools') ? JSON.parse(formData.get('tools') as string) : undefined, maxSteps: formData.get('maxSteps') ? Number(formData.get('maxSteps')) : undefined, notifyEmail: formData.get('notifyEmail') === 'true', includeImages: formData.get('includeImages') === 'true', scheduledTime: (formData.get('scheduledTime') as string) || undefined, scheduledDay: formData.get('scheduledDay') ? Number(formData.get('scheduledDay')) : undefined, timezone: (formData.get('timezone') as string) || undefined, slideTheme: (formData.get('slideTheme') as string) || undefined, slideStyle: (formData.get('slideStyle') as string) || undefined, } if (selectedAgent && !isNewAgent) { await updateAgent(selectedAgent.id, data) toast.success(t('agents.toasts.updated')) } else { await createAgent(data) toast.success(t('agents.toasts.created')) } setSelectedAgent(null) setIsNewAgent(false) await refreshAgents() }, [selectedAgent, isNewAgent, refreshAgents, t]) const handleBack = useCallback(() => { setSelectedAgent(null) setIsNewAgent(false) }, []) const filteredAgents = useMemo(() => { return agents.filter(agent => { const matchesType = !typeFilter || (agent.type || 'scraper') === typeFilter if (!searchQuery.trim()) return matchesType const q = searchQuery.toLowerCase() const matchesSearch = (agent.name || '').toLowerCase().includes(q) || (agent.description && agent.description.toLowerCase().includes(q)) return matchesType && matchesSearch }) }, [agents, searchQuery, typeFilter]) const existingAgentNames = useMemo(() => agents.map(a => a.name), [agents]) const showDetail = selectedAgent !== null || isNewAgent return ( <> {showDetail ? ( setLogAgent({ id, name })} onOpenHelp={() => setShowHelp(true)} isNew={isNewAgent} /> ) : ( <>

{t('agents.myAgents')}

{t('agents.subtitle')}

{typeFilterOptions.map((opt, i) => ( ))}
setSearchQuery(e.target.value)} placeholder={t('agents.searchPlaceholder')} className="pl-9 pr-4 py-2 text-[13px] bg-card border border-border/50 rounded-lg outline-none focus:border-foreground/20 transition-all placeholder:text-muted-foreground/40 w-48" />
{agents.length === 0 ? (

{t('agents.noAgents')}

{t('agents.noAgentsDescription')}

) : ( <> {filteredAgents.length > 0 ? (
{filteredAgents.map(agent => ( ))}
) : (

{t('agents.noResults')}

)} )}
{t('agents.templates.title')}
)} {logAgent && ( setLogAgent(null)} /> )} {showHelp && ( setShowHelp(false)} /> )} ) }