Files
Momento/memento-note/app/actions/agent-actions.ts
Antigravity 2fd435df6f
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 53s
feat: redesign agents page (architectural-grid style), add image description, fix AI limits, remove dead code
- 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)
2026-05-09 17:18:47 +00:00

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')
}
}