feat: add slides generation tool with multiple slide types
Some checks failed
CI / Lint, Test & Build (push) Failing after 17s
CI / Deploy production (on server) (push) Has been skipped

- 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:
Antigravity
2026-05-22 17:18:48 +00:00
parent 0f6b9509da
commit 5728452b4a
68 changed files with 6990 additions and 2584 deletions

View File

@@ -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