feat: add slides generation tool with multiple slide types
- Add slides.tool.ts with support for title, bullets, chart, stats, table, cards, timeline, quote, comparison, equation, image, summary slide types - Chart types: bar, horizontal-bar, line, donut, radar - Integrate with agent executor and canvas system - Add multilingual support (en/fr) - Various UI improvements and bug fixes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,9 +10,9 @@ const TYPE_DEFAULTS: Record<GenerateType, {
|
||||
maxSteps: number
|
||||
}> = {
|
||||
'slide-generator': {
|
||||
role: 'Crée une présentation PowerPoint professionnelle et visuelle à partir du contenu de la note fournie.',
|
||||
tools: ['note_search', 'note_read', 'generate_pptx'],
|
||||
maxSteps: 8,
|
||||
role: 'Génère une présentation professionnelle à partir du contenu de la note fournie. Appelle generate_slides avec un objet JSON structuré {title, theme, slides:[...]}.',
|
||||
tools: ['note_search', 'note_read', 'generate_slides'],
|
||||
maxSteps: 6,
|
||||
},
|
||||
'excalidraw-generator': {
|
||||
role: 'Génère un diagramme Excalidraw clair et professionnel à partir du contenu de la note fournie.',
|
||||
@@ -30,12 +30,13 @@ export async function POST(req: NextRequest) {
|
||||
const userId = session.user.id
|
||||
|
||||
const body = await req.json()
|
||||
const { noteId, type, theme, style, language } = body as {
|
||||
const { noteId, type, theme, style, language, template } = body as {
|
||||
noteId: string
|
||||
type: GenerateType
|
||||
theme?: string
|
||||
style?: string
|
||||
language?: string
|
||||
template?: string
|
||||
}
|
||||
|
||||
if (!noteId || !type || !TYPE_DEFAULTS[type]) {
|
||||
@@ -56,10 +57,14 @@ export async function POST(req: NextRequest) {
|
||||
let role = defaults.role
|
||||
if (isEn) {
|
||||
if (type === 'slide-generator') {
|
||||
role = 'Create a professional and visual PowerPoint presentation from the provided note content.'
|
||||
const recipeHint = (theme && theme !== 'auto') ? ` Use theme:"${theme}".` : ''
|
||||
role = `Generate a professional presentation from the provided note content.${recipeHint} Call generate_slides with structured JSON {title, theme, slides:[...]}.`
|
||||
} else {
|
||||
role = 'Generate a clear and professional Excalidraw diagram from the provided note content.'
|
||||
}
|
||||
} else if (type === 'slide-generator') {
|
||||
const recipeHint = (theme && theme !== 'auto') ? ` Utilise le thème "${theme}".` : ''
|
||||
role = `Génère une présentation professionnelle à partir du contenu de la note fournie.${recipeHint} Appelle generate_slides avec le JSON structuré {title, theme, slides:[...]}.`
|
||||
}
|
||||
|
||||
const agentName = type === 'slide-generator'
|
||||
@@ -71,13 +76,14 @@ export async function POST(req: NextRequest) {
|
||||
name: agentName,
|
||||
type,
|
||||
role,
|
||||
description: (type === 'slide-generator' && template && template !== 'auto') ? `template:${template}` : undefined,
|
||||
tools: JSON.stringify(defaults.tools),
|
||||
maxSteps: defaults.maxSteps,
|
||||
frequency: 'one-shot',
|
||||
isEnabled: true,
|
||||
sourceNoteIds: JSON.stringify([noteId]),
|
||||
targetNotebookId: note.notebookId ?? undefined,
|
||||
slideTheme: theme ?? 'vibrant_tech',
|
||||
slideTheme: theme ?? 'keynote',
|
||||
slideStyle: style ?? 'soft',
|
||||
userId,
|
||||
},
|
||||
@@ -118,7 +124,7 @@ export async function GET(req: NextRequest) {
|
||||
const action = await prisma.agentAction.findFirst({
|
||||
where: { agentId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: { id: true, status: true, result: true, log: true },
|
||||
select: { id: true, status: true, result: true, log: true, createdAt: true },
|
||||
})
|
||||
|
||||
if (!action) {
|
||||
@@ -126,6 +132,19 @@ export async function GET(req: NextRequest) {
|
||||
return NextResponse.json({ status: 'running' })
|
||||
}
|
||||
|
||||
// Detect stale running actions (killed by process restart or hot-reload)
|
||||
const GENERATION_TIMEOUT_MS = 8 * 60 * 1000 // 8 minutes
|
||||
if (action.status === 'running') {
|
||||
const ageMs = Date.now() - new Date(action.createdAt).getTime()
|
||||
if (ageMs > GENERATION_TIMEOUT_MS) {
|
||||
await prisma.agentAction.update({
|
||||
where: { id: action.id },
|
||||
data: { status: 'failure', log: 'Generation timed out (process restart)' },
|
||||
}).catch(() => { /* best-effort */ })
|
||||
return NextResponse.json({ status: 'failure', actionId: action.id, error: 'La génération a expiré. Veuillez réessayer.' })
|
||||
}
|
||||
}
|
||||
|
||||
// If success, find canvasId from the related canvas (result stores canvas id)
|
||||
let canvasId: string | null = null
|
||||
let noteId: string | null = null
|
||||
|
||||
Reference in New Issue
Block a user