Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 53s
- Redesign agents page with architectural-grid (8) design system: rounded-2xl cards, serif headings, motion tabs, dashed templates section - Replace agent form popup with full-page detail view (SettingsView style) with dark planning card, section tooltips, and help button - Hide advanced mode for slide/excalidraw generators - Add 'describe images' action to contextual AI assistant - Add copy button to action/resource preview with HTTP fallback - Add delete history button to agent run log panel - Increase AI word limit from 2000 to 5000 (reformulate + transform-markdown) - Increase max steps slider from 25 to 50 - Fix image description error with clear model compatibility message - Fix doubled execution count display in agent detail view - Remove dead files: notes-list-view.tsx, notes-view-toggle.tsx - Remove 'list' view mode from NotesViewMode type - Add missing i18n keys (back, configuration, options, copy, cleared)
332 lines
9.9 KiB
TypeScript
332 lines
9.9 KiB
TypeScript
'use server'
|
|
|
|
/**
|
|
* Agent Server Actions
|
|
* CRUD operations for agents and execution triggers.
|
|
*/
|
|
|
|
import { auth } from '@/auth'
|
|
import { prisma } from '@/lib/prisma'
|
|
import { revalidatePath } from 'next/cache'
|
|
import { calculateNextRun } from '@/lib/agents/schedule'
|
|
|
|
// --- CRUD ---
|
|
|
|
export async function createAgent(data: {
|
|
name: string
|
|
description?: string
|
|
type: string
|
|
role: string
|
|
sourceUrls?: string[]
|
|
sourceNotebookId?: string
|
|
sourceNoteIds?: string[]
|
|
targetNotebookId?: string
|
|
frequency?: string
|
|
tools?: string[]
|
|
maxSteps?: number
|
|
notifyEmail?: boolean
|
|
includeImages?: boolean
|
|
scheduledTime?: string
|
|
scheduledDay?: number
|
|
timezone?: string
|
|
slideTheme?: string
|
|
slideStyle?: string
|
|
}) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const agent = await prisma.agent.create({
|
|
data: {
|
|
name: data.name,
|
|
description: data.description,
|
|
type: data.type,
|
|
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) : '[]',
|
|
maxSteps: data.maxSteps || 10,
|
|
notifyEmail: data.notifyEmail || false,
|
|
includeImages: data.includeImages || false,
|
|
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,
|
|
}
|
|
})
|
|
|
|
// Calculate nextRun for scheduled agents
|
|
const freq = data.frequency || 'manual'
|
|
if (freq !== 'manual') {
|
|
const nextRun = calculateNextRun({
|
|
frequency: freq,
|
|
scheduledTime: data.scheduledTime || '08:00',
|
|
scheduledDay: data.scheduledDay,
|
|
timezone: data.timezone,
|
|
})
|
|
if (nextRun) {
|
|
await prisma.agent.update({
|
|
where: { id: agent.id },
|
|
data: { nextRun },
|
|
})
|
|
}
|
|
}
|
|
|
|
revalidatePath('/agents')
|
|
return { success: true, agent }
|
|
} catch (error) {
|
|
console.error('Error creating agent:', error)
|
|
throw new Error('Impossible de creer l\'agent')
|
|
}
|
|
}
|
|
|
|
export async function updateAgent(id: string, data: {
|
|
name?: string
|
|
description?: string
|
|
type?: string
|
|
role?: string
|
|
sourceUrls?: string[]
|
|
sourceNotebookId?: string | null
|
|
sourceNoteIds?: string[] | null
|
|
targetNotebookId?: string | null
|
|
frequency?: string
|
|
isEnabled?: boolean
|
|
tools?: string[]
|
|
maxSteps?: number
|
|
notifyEmail?: boolean
|
|
includeImages?: boolean
|
|
scheduledTime?: string
|
|
scheduledDay?: number | null
|
|
timezone?: string
|
|
slideTheme?: string | null
|
|
slideStyle?: string | null
|
|
}) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const existing = await prisma.agent.findUnique({ where: { id } })
|
|
if (!existing || existing.userId !== session.user.id) {
|
|
throw new Error('Agent non trouve')
|
|
}
|
|
|
|
const updateData: Record<string, unknown> = {}
|
|
if (data.name !== undefined) updateData.name = data.name
|
|
if (data.description !== undefined) updateData.description = data.description
|
|
if (data.type !== undefined) updateData.type = data.type
|
|
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
|
|
if (data.tools !== undefined) updateData.tools = JSON.stringify(data.tools)
|
|
if (data.maxSteps !== undefined) updateData.maxSteps = data.maxSteps
|
|
if (data.notifyEmail !== undefined) updateData.notifyEmail = data.notifyEmail
|
|
if (data.includeImages !== undefined) updateData.includeImages = data.includeImages
|
|
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 =
|
|
data.frequency !== undefined ||
|
|
data.scheduledTime !== undefined ||
|
|
data.scheduledDay !== undefined ||
|
|
data.timezone !== undefined
|
|
|
|
if (shouldRecalcNextRun) {
|
|
const freq = data.frequency || existing.frequency
|
|
if (freq === 'manual') {
|
|
updateData.nextRun = null
|
|
} else {
|
|
const nextRun = calculateNextRun({
|
|
frequency: freq,
|
|
scheduledTime: data.scheduledTime || existing.scheduledTime || '08:00',
|
|
scheduledDay: data.scheduledDay ?? existing.scheduledDay,
|
|
timezone: data.timezone || existing.timezone,
|
|
})
|
|
updateData.nextRun = nextRun
|
|
}
|
|
}
|
|
|
|
const agent = await prisma.agent.update({
|
|
where: { id },
|
|
data: updateData
|
|
})
|
|
|
|
revalidatePath('/agents')
|
|
return { success: true, agent }
|
|
} catch (error) {
|
|
console.error('Error updating agent:', error)
|
|
throw new Error('Impossible de mettre a jour l\'agent')
|
|
}
|
|
}
|
|
|
|
export async function deleteAgent(id: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const existing = await prisma.agent.findUnique({ where: { id } })
|
|
if (!existing || existing.userId !== session.user.id) {
|
|
throw new Error('Agent non trouve')
|
|
}
|
|
|
|
await prisma.agent.delete({ where: { id } })
|
|
revalidatePath('/agents')
|
|
return { success: true }
|
|
} catch (error) {
|
|
console.error('Error deleting agent:', error)
|
|
throw new Error('Impossible de supprimer l\'agent')
|
|
}
|
|
}
|
|
|
|
export async function getAgents() {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const agents = await prisma.agent.findMany({
|
|
where: { userId: session.user.id, NOT: { frequency: 'one-shot' } },
|
|
include: {
|
|
_count: { select: { actions: true } },
|
|
actions: {
|
|
orderBy: { createdAt: 'desc' },
|
|
take: 1,
|
|
},
|
|
notebook: {
|
|
select: { id: true, name: true, icon: true }
|
|
}
|
|
},
|
|
orderBy: { createdAt: 'desc' }
|
|
})
|
|
|
|
return agents
|
|
} catch (error) {
|
|
console.error('Error fetching agents:', error)
|
|
throw new Error('Impossible de charger les agents')
|
|
}
|
|
}
|
|
|
|
// --- Execution ---
|
|
|
|
export async function runAgent(id: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
const userId = session.user.id
|
|
|
|
// 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 ---
|
|
|
|
export async function getAgentActions(agentId: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
// Verify the agent belongs to the user
|
|
const agent = await prisma.agent.findFirst({
|
|
where: { id: agentId, userId: session.user.id },
|
|
select: { id: true }
|
|
})
|
|
if (!agent) throw new Error('Agent non trouve')
|
|
|
|
const actions = await prisma.agentAction.findMany({
|
|
where: { agentId },
|
|
orderBy: { createdAt: 'desc' },
|
|
take: 20,
|
|
select: {
|
|
id: true,
|
|
status: true,
|
|
result: true,
|
|
log: true,
|
|
input: true,
|
|
toolLog: true,
|
|
tokensUsed: true,
|
|
createdAt: true,
|
|
}
|
|
})
|
|
return actions
|
|
} catch (error) {
|
|
console.error('Error fetching agent actions:', error)
|
|
throw new Error('Impossible de charger l\'historique')
|
|
}
|
|
}
|
|
|
|
export async function deleteAgentHistory(agentId: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const agent = await prisma.agent.findFirst({
|
|
where: { id: agentId, userId: session.user.id },
|
|
select: { id: true }
|
|
})
|
|
if (!agent) throw new Error('Agent non trouve')
|
|
|
|
await prisma.agentAction.deleteMany({
|
|
where: { agentId }
|
|
})
|
|
|
|
revalidatePath('/agents')
|
|
return { success: true }
|
|
} catch (error) {
|
|
console.error('Error deleting agent history:', error)
|
|
throw new Error('Impossible de supprimer l\'historique')
|
|
}
|
|
}
|
|
|
|
export async function toggleAgent(id: string, isEnabled: boolean) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const agent = await prisma.agent.update({
|
|
where: { id, userId: session.user.id },
|
|
data: { isEnabled }
|
|
})
|
|
return { success: true, agent }
|
|
} catch (error) {
|
|
console.error('Error toggling agent:', error)
|
|
throw new Error('Impossible de modifier l\'agent')
|
|
}
|
|
}
|