'use client' import React, { useState, useMemo, useRef, useCallback, useEffect } from 'react' import { motion } from 'motion/react' import { ArrowLeft, Plus, Trash2, Globe, FileSearch, FilePlus, FileText, ExternalLink, Brain, ChevronDown, ChevronUp, HelpCircle, Mail, ImageIcon, Presentation, Pencil, Check, Eye, Search, Settings, Clock, Activity, Sparkles, Loader2, BookOpen, LifeBuoy, ListChecks, } from 'lucide-react' import { HierarchicalNotebookSelector } from '@/components/hierarchical-notebook-selector' import { toast } from 'sonner' import { useLanguage } from '@/lib/i18n' import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip' type AgentType = 'scraper' | 'researcher' | 'monitor' | 'custom' | 'slide-generator' | 'excalidraw-generator' | 'task-extractor' function FieldHelp({ tooltip }: { tooltip: string }) { return ( {tooltip} ) } const typeIcons: Record = { scraper: Globe, researcher: Search, monitor: Eye, custom: Settings, 'slide-generator': Presentation, 'excalidraw-generator': Pencil, 'task-extractor': ListChecks, } const TOOL_PRESETS: Record = { scraper: ['web_scrape', 'note_create', 'memory_search'], researcher: ['web_search', 'web_scrape', 'note_search', 'note_create', 'memory_search'], monitor: ['note_search', 'note_read', 'note_create', 'memory_search'], custom: ['memory_search'], 'slide-generator': ['generate_pptx'], 'excalidraw-generator': ['generate_excalidraw'], 'task-extractor': ['note_search', 'note_read', 'task_extract', 'note_create'], } const SLIDE_GENERATOR_THEME_VALUES = [ 'modern_wellness', 'business_authority', 'nature_outdoors', 'vintage_academic', 'soft_creative', 'bohemian', 'vibrant_tech', 'craft_artisan', 'tech_night', 'education_charts', 'forest_eco', 'elegant_fashion', 'art_food', 'luxury_mystery', 'pure_tech_blue', 'coastal_coral', 'vibrant_orange_mint', 'platinum_white_gold', ] as const interface AgentDetailViewProps { agent?: { 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 tools?: string | null maxSteps?: number notifyEmail?: boolean includeImages?: boolean scheduledTime?: string | null scheduledDay?: number | null timezone?: string | null slideTheme?: string | null slideStyle?: string | null lastRun: string | Date | null nextRun?: string | Date | null createdAt: string | Date _count: { actions: number } actions: { id: string; status: string; createdAt: string | Date }[] } | null notebooks: { id: string; name: string; icon?: string | null; parentId?: string | null }[] onSave: (data: FormData) => Promise onBack: () => void onOpenLogs: (agentId: string, agentName: string) => void onOpenHelp: () => void isNew?: boolean } export function AgentDetailView({ agent, notebooks, onSave, onBack, onOpenLogs, onOpenHelp, isNew, }: AgentDetailViewProps) { const { t } = useLanguage() const [name, setName] = useState(agent?.name || '') const [description, setDescription] = useState(agent?.description || '') const [type, setType] = useState((agent?.type as AgentType) || 'scraper') const [role, setRole] = useState(agent?.role || '') const [urls, setUrls] = useState(() => { if (agent?.sourceUrls) { try { return JSON.parse(agent.sourceUrls) } catch { return [''] } } return [''] }) const [sourceNotebookId, setSourceNotebookId] = useState(agent?.sourceNotebookId || '') const [sourceNoteIds, setSourceNoteIds] = useState(() => { if (agent?.sourceNoteIds) { try { return JSON.parse(agent.sourceNoteIds) } catch { return [] } } return [] }) const [noteOptions, setNoteOptions] = useState<{ id: string; title: string }[]>([]) const [targetNotebookId, setTargetNotebookId] = useState(agent?.targetNotebookId || '') const [frequency, setFrequency] = useState(agent?.frequency || 'manual') const [scheduledTime, setScheduledTime] = useState(agent?.scheduledTime || '08:00') const [scheduledDay, setScheduledDay] = useState(agent?.scheduledDay ?? 1) const [timezone] = useState(() => { try { return Intl.DateTimeFormat().resolvedOptions().timeZone } catch { return 'UTC' } }) const [selectedTools, setSelectedTools] = useState(() => { if (agent?.tools) { try { const parsed = JSON.parse(agent.tools) if (parsed.length > 0) return parsed } catch { /* fall through */ } } return TOOL_PRESETS[(agent?.type as AgentType) || 'scraper'] || [] }) const [maxSteps, setMaxSteps] = useState(agent?.maxSteps || 10) const [notifyEmail, setNotifyEmail] = useState(agent?.notifyEmail || false) const [includeImages, setIncludeImages] = useState(agent?.includeImages || false) const [slideTheme, setSlideTheme] = useState(agent?.slideTheme || '') const [slideStyle, setSlideStyle] = useState<'soft' | 'sharp' | 'rounded' | 'pill'>( (agent?.slideStyle as 'soft' | 'sharp' | 'rounded' | 'pill') || 'soft' ) const [excalidrawStyle, setExcalidrawStyle] = useState<'default' | 'austere' | 'sketch-plus'>(() => { if (agent?.slideStyle === 'austere') return 'austere' if (agent?.slideStyle === 'sketch-plus') return 'sketch-plus' return 'default' }) const [excalidrawType, setExcalidrawType] = useState<'auto' | 'architecture-cloud' | 'flowchart' | 'mindmap' | 'org-chart' | 'timeline' | 'process-map'>(() => { const value = (agent?.slideTheme || '').trim() if (value === 'auto' || value === 'architecture-cloud' || value === 'mindmap' || value === 'org-chart' || value === 'timeline' || value === 'process-map') return value return 'flowchart' }) const [isSaving, setIsSaving] = useState(false) const [showAdvanced, setShowAdvanced] = useState(() => { if (agent?.tools) { try { const tools = JSON.parse(agent.tools); if (tools.length > 0) return true } catch { /* */ } } if (agent?.role && agent.role.trim().length > 0) return true return false }) useEffect(() => { if (!sourceNotebookId || (type !== 'slide-generator' && type !== 'excalidraw-generator' && type !== 'monitor')) { setNoteOptions([]) return } fetch(`/api/notes?notebookId=${sourceNotebookId}&limit=50`) .then(r => r.json()) .then(data => { const notes = Array.isArray(data.data) ? data.data : Array.isArray(data) ? data : [] setNoteOptions(notes.map((n: any) => ({ id: n.id, title: n.title || 'Sans titre' }))) }) .catch(() => setNoteOptions([])) }, [sourceNotebookId, type]) const availableTools = useMemo(() => [ { id: 'web_search', icon: Globe, labelKey: 'agents.tools.webSearch' }, { id: 'web_scrape', icon: ExternalLink, labelKey: 'agents.tools.webScrape' }, { id: 'note_search', icon: FileSearch, labelKey: 'agents.tools.noteSearch' }, { id: 'note_read', icon: FileText, labelKey: 'agents.tools.noteRead' }, { id: 'note_create', icon: FilePlus, labelKey: 'agents.tools.noteCreate' }, { id: 'url_fetch', icon: ExternalLink, labelKey: 'agents.tools.urlFetch' }, { id: 'memory_search', icon: Brain, labelKey: 'agents.tools.memorySearch' }, { id: 'generate_pptx', icon: Presentation, labelKey: 'agents.tools.generatePptx' }, { id: 'generate_slides', icon: Presentation, labelKey: 'agents.tools.generateSlides' }, { id: 'generate_excalidraw', icon: Pencil, labelKey: 'agents.tools.generateExcalidraw' }, ], []) const prevTypeRef = useRef(type) if (prevTypeRef.current !== type) { prevTypeRef.current = type setSelectedTools(TOOL_PRESETS[type] || []) setRole('') } const addUrl = () => setUrls([...urls, '']) const removeUrl = (index: number) => setUrls(urls.filter((_, i) => i !== index)) const updateUrl = (index: number, value: string) => { const newUrls = [...urls] newUrls[index] = value setUrls(newUrls) } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!name.trim()) { toast.error(t('agents.form.nameRequired')) return } setIsSaving(true) try { const formData = new FormData() formData.set('name', name.trim()) formData.set('description', description.trim()) formData.set('type', type) formData.set('role', role || t(`agents.defaultRoles.${type}`)) formData.set('frequency', frequency) formData.set('targetNotebookId', targetNotebookId) if (type === 'monitor' || type === 'slide-generator' || type === 'excalidraw-generator') { formData.set('sourceNotebookId', sourceNotebookId) } if (sourceNoteIds.length > 0) { formData.set('sourceNoteIds', JSON.stringify(sourceNoteIds)) } const validUrls = urls.filter(u => u.trim()) if (validUrls.length > 0) { formData.set('sourceUrls', JSON.stringify(validUrls)) } formData.set('tools', JSON.stringify(selectedTools)) formData.set('maxSteps', String(maxSteps)) formData.set('notifyEmail', String(notifyEmail)) formData.set('includeImages', String(includeImages)) formData.set('scheduledTime', scheduledTime) formData.set('scheduledDay', String(scheduledDay)) formData.set('timezone', timezone) if (type === 'slide-generator') { if (slideTheme) formData.set('slideTheme', slideTheme) formData.set('slideStyle', slideStyle) } if (type === 'excalidraw-generator') { formData.set('slideTheme', excalidrawType) formData.set('slideStyle', excalidrawStyle) } await onSave(formData) } catch { toast.error(t('agents.toasts.saveError')) } finally { setIsSaving(false) } } const showSourceNotebook = type === 'monitor' || type === 'slide-generator' || type === 'excalidraw-generator' const Icon = typeIcons[type] || Settings const frequencyLabel = t(`agents.frequencies.${frequency}`) || frequency const successRate = agent?.actions?.length ? Math.round((agent.actions.filter(a => a.status === 'success').length / agent.actions.length) * 100 * 10) / 10 : null const sectionTitleCls = 'text-xs font-bold uppercase tracking-[0.3em] text-muted-foreground' const cardCls = 'bg-card border border-border rounded-3xl overflow-hidden shadow-sm' const labelCls = 'block text-[11px] uppercase tracking-widest font-bold text-muted-foreground' const inputCls = 'w-full bg-muted/50 border border-border rounded-xl px-4 py-3 text-sm outline-none focus:ring-1 focus:ring-foreground/10 focus:border-foreground/20 transition-all text-foreground' const selectCls = 'w-full bg-muted/50 border border-border rounded-xl px-4 py-3 text-sm outline-none focus:ring-1 focus:ring-foreground/10 focus:border-foreground/20 transition-all cursor-pointer font-medium text-foreground appearance-none' return (
{!isNew && agent && ( )}
setName(e.target.value)} className="text-3xl font-memento-serif font-medium text-foreground bg-transparent border-none outline-none placeholder:text-muted-foreground/40 w-full" placeholder={t('agents.form.namePlaceholder')} />
{isNew ? t('agents.newBadge') : `ID: ${agent?.id?.slice(0, 8)}`} {!isNew && agent?.isEnabled && ( {t('agents.actions.toggleOn')} )}
{!isNew && agent && (
{t('agents.status.nextRun')} {t('agents.metadata.executions', { count: agent._count.actions })}
{successRate !== null && (
Succès {successRate}%
)}
)}

{t('agents.form.agentType')}

{[ { value: 'researcher' as AgentType, labelKey: 'agents.types.researcher', descKey: 'agents.typeDescriptions.researcher', icon: Search }, { value: 'scraper' as AgentType, labelKey: 'agents.types.scraper', descKey: 'agents.typeDescriptions.scraper', icon: Globe }, { value: 'monitor' as AgentType, labelKey: 'agents.types.monitor', descKey: 'agents.typeDescriptions.monitor', icon: Eye }, { value: 'custom' as AgentType, labelKey: 'agents.types.custom', descKey: 'agents.typeDescriptions.custom', icon: Settings }, { value: 'slide-generator' as AgentType, labelKey: 'agents.types.slideGenerator', descKey: 'agents.typeDescriptions.slideGenerator', icon: Presentation }, { value: 'excalidraw-generator' as AgentType, labelKey: 'agents.types.excalidrawGenerator', descKey: 'agents.typeDescriptions.excalidrawGenerator', icon: Pencil }, { value: 'task-extractor' as AgentType, labelKey: 'agents.types.taskExtractor', descKey: 'agents.typeDescriptions.taskExtractor', icon: ListChecks }, ].map(at => { const TypeIcon = at.icon return ( ) })}

{t('agents.form.configuration')}

{type === 'researcher' ? (
setDescription(e.target.value)} className={inputCls} placeholder={t('agents.form.researchTopicPlaceholder')} />
) : (
setDescription(e.target.value)} className={inputCls} placeholder={t('agents.form.descriptionPlaceholder')} />
)} {(type === 'scraper' || type === 'custom') && (
{urls.map((url, i) => (
updateUrl(i, e.target.value)} className={inputCls} placeholder="https://example.com" /> {urls.length > 1 && ( )}
))}
)} {showSourceNotebook && (
!nb.trashedAt)} selectedId={sourceNotebookId || null} onSelect={(id) => { setSourceNotebookId(id); setSourceNoteIds([]) }} placeholder={t('agents.form.selectNotebook') || 'Select notebook...'} />
)} {(type === 'slide-generator' || type === 'excalidraw-generator') && sourceNotebookId && noteOptions.length > 0 && (
{noteOptions.map(note => { const isSelected = sourceNoteIds.includes(note.id) return ( ) })}
{sourceNoteIds.length > 0 && (

{t('agents.form.notesSelected', { count: sourceNoteIds.length })}

)}
)} {type === 'slide-generator' && ( <>
{(['soft', 'sharp', 'rounded', 'pill'] as const).map(s => ( ))}
)} {type === 'excalidraw-generator' && ( <>
{([ { id: 'auto', labelKey: 'agents.form.excalidrawDiagramTypeAuto' }, { id: 'flowchart', labelKey: 'agents.form.excalidrawDiagramTypeFlowchart' }, { id: 'mindmap', labelKey: 'agents.form.excalidrawDiagramTypeMindmap' }, { id: 'org-chart', labelKey: 'agents.form.excalidrawDiagramTypeOrgChart' }, { id: 'timeline', labelKey: 'agents.form.excalidrawDiagramTypeTimeline' }, { id: 'process-map', labelKey: 'agents.form.excalidrawDiagramTypeProcessMap' }, { id: 'architecture-cloud', labelKey: 'agents.form.excalidrawDiagramTypeArchitectureCloud' }, ] as const).map(opt => ( ))}
{([ { id: 'default', labelKey: 'agents.form.excalidrawDiagramStyleDefault' }, { id: 'sketch-plus', labelKey: 'agents.form.excalidrawDiagramStyleSketchPlus' }, { id: 'austere', labelKey: 'agents.form.excalidrawDiagramStyleAustere' }, ] as const).map(opt => ( ))}
)} {type !== 'slide-generator' && type !== 'excalidraw-generator' && (
!nb.trashedAt)} selectedId={targetNotebookId || null} onSelect={(id) => setTargetNotebookId(id)} placeholder={t('agents.form.inbox') || 'Inbox'} />
)}