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

@@ -95,6 +95,16 @@ async function getParentContext(
return { notes, noteIds }
}
function localeToLanguageName(locale?: string): string {
const map: Record<string, string> = {
en: 'English', fr: 'French', es: 'Spanish', de: 'German',
it: 'Italian', pt: 'Portuguese', nl: 'Dutch', ru: 'Russian',
zh: 'Chinese', ja: 'Japanese', ko: 'Korean', ar: 'Arabic',
fa: 'Farsi', hi: 'Hindi', pl: 'Polish',
}
return map[locale ?? ''] ?? 'English'
}
function buildExpandPromptV2(
parentTitle: string,
parentDesc: string,
@@ -157,7 +167,7 @@ RESPOND ONLY with a valid JSON array of 9 objects:
CRITICAL: Each idea MUST have at least 1 noteRef when notes are provided.
LANGUAGE: You MUST write ALL titles, descriptions, connectionToSeed, and explanation fields in ${locale === 'fr' ? 'French' : locale === 'es' ? 'Spanish' : locale === 'de' ? 'German' : locale === 'it' ? 'Italian' : locale === 'pt' ? 'Portuguese' : locale === 'nl' ? 'Dutch' : locale === 'ru' ? 'Russian' : locale === 'zh' ? 'Chinese' : locale === 'ja' ? 'Japanese' : locale === 'ko' ? 'Korean' : locale === 'ar' ? 'Arabic' : locale === 'fa' ? 'Farsi' : locale === 'hi' ? 'Hindi' : locale === 'pl' ? 'Polish' : 'the same language as the seed idea'}.`
LANGUAGE: You MUST write ALL titles, descriptions, connectionToSeed, and explanation fields in ${localeToLanguageName(locale)}.`
}
export async function POST(

View File

@@ -161,6 +161,7 @@ export async function PATCH(
const updates: Record<string, any> = {}
if (typeof body.isPublic === 'boolean') updates.isPublic = body.isPublic
if (typeof body.guestCanEdit === 'boolean') updates.guestCanEdit = body.guestCanEdit
if (typeof body.seedIdea === 'string' && body.seedIdea.trim()) updates.seedIdea = body.seedIdea.trim()
if (Object.keys(updates).length === 0) {
return NextResponse.json({ error: 'No valid fields to update' }, { status: 400 })

View File

@@ -0,0 +1,63 @@
import { NextRequest, NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
import { auth } from '@/auth'
import { z } from 'zod'
import { verifyParticipant } from '@/lib/brainstorm-collab'
import { emitToSession } from '@/lib/socket-emit'
const starSchema = z.object({
ideaId: z.string().min(1),
})
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ sessionId: string }> }
) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
try {
const { sessionId } = await params
const body = await request.json()
const { ideaId } = starSchema.parse(body)
const brainstormSession = await prisma.brainstormSession.findFirst({
where: { id: sessionId },
})
if (!brainstormSession) {
return NextResponse.json({ error: 'Session not found' }, { status: 404 })
}
const { isParticipant } = await verifyParticipant(sessionId, session.user.id, 'editor')
if (!isParticipant) {
return NextResponse.json({ error: 'No edit permission' }, { status: 403 })
}
const idea = await prisma.brainstormIdea.findFirst({
where: { id: ideaId, sessionId },
})
if (!idea) {
return NextResponse.json({ error: 'Idea not found' }, { status: 404 })
}
const updated = await prisma.brainstormIdea.update({
where: { id: ideaId },
data: { isStarred: !idea.isStarred },
})
await emitToSession(sessionId, 'idea:starred', {
ideaId,
isStarred: updated.isStarred,
userId: session.user.id,
})
return NextResponse.json({ success: true, data: { ideaId, isStarred: updated.isStarred } })
} catch (error) {
console.error('Error starring idea:', error)
return NextResponse.json({ error: 'Failed to star idea' }, { status: 500 })
}
}

View File

@@ -0,0 +1,94 @@
import { NextRequest, NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
import { auth } from '@/auth'
import { runLaneWithBillingUser } from '@/lib/ai/provider-for-user'
import { getSystemConfig } from '@/lib/config'
import { verifyParticipant } from '@/lib/brainstorm-collab'
function localeToLanguageName(locale?: string): string {
const map: Record<string, string> = {
en: 'English', fr: 'French', es: 'Spanish', de: 'German',
it: 'Italian', pt: 'Portuguese', nl: 'Dutch', ru: 'Russian',
zh: 'Chinese', ja: 'Japanese', ko: 'Korean', ar: 'Arabic',
fa: 'Farsi', hi: 'Hindi', pl: 'Polish',
}
return map[locale ?? ''] ?? 'English'
}
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ sessionId: string }> }
) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
try {
const { sessionId } = await params
const body = await request.json().catch(() => ({}))
const locale: string = body.locale || 'en'
const brainstormSession = await prisma.brainstormSession.findFirst({
where: { id: sessionId },
include: {
ideas: {
where: { status: { not: 'dismissed' } },
orderBy: [{ waveNumber: 'asc' }, { createdAt: 'asc' }],
},
},
})
if (!brainstormSession) {
return NextResponse.json({ error: 'Session not found' }, { status: 404 })
}
const { isParticipant } = await verifyParticipant(sessionId, session.user.id, 'viewer')
if (!isParticipant) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
}
const ideas = brainstormSession.ideas
if (ideas.length === 0) {
return NextResponse.json({ error: 'No ideas to summarize' }, { status: 400 })
}
const ideasText = ideas
.map((idea, i) => `${i + 1}. [Wave ${idea.waveNumber}] ${idea.title}: ${idea.description}`)
.join('\n')
const starredIdeas = ideas.filter(i => i.isStarred)
const starredText = starredIdeas.length > 0
? `\nStarred/favorite ideas: ${starredIdeas.map(i => i.title).join(', ')}`
: ''
const language = localeToLanguageName(locale)
const prompt = `You are summarizing a brainstorming session. Respond in ${language}.
Seed idea: "${brainstormSession.seedIdea}"
Ideas generated (${ideas.length} total):
${ideasText}${starredText}
Write a concise synthesis (4-6 sentences) that:
1. Captures the main themes and directions explored
2. Highlights the most promising ideas or clusters
3. Suggests a concrete next step
Be direct and action-oriented. No bullet points, just flowing prose.`
const config = await getSystemConfig()
const { result: summary } = await runLaneWithBillingUser(
'tags',
config,
session.user.id,
(provider) => provider.generateText(prompt),
)
return NextResponse.json({ success: true, data: { summary: summary.trim() } })
} catch (error) {
console.error('Error summarizing brainstorm:', error)
return NextResponse.json({ error: 'Failed to summarize' }, { status: 500 })
}
}

View File

@@ -109,6 +109,16 @@ Respond ONLY with a valid JSON array of objects:
}
}
function localeToLanguageName(locale?: string): string {
const map: Record<string, string> = {
en: 'English', fr: 'French', es: 'Spanish', de: 'German',
it: 'Italian', pt: 'Portuguese', nl: 'Dutch', ru: 'Russian',
zh: 'Chinese', ja: 'Japanese', ko: 'Korean', ar: 'Arabic',
fa: 'Farsi', hi: 'Hindi', pl: 'Polish',
}
return map[locale ?? ''] ?? 'English'
}
function buildPromptV2(seedIdea: string, classifiedNotes: ClassifiedNote[], locale?: string): string {
const supportNotes = classifiedNotes.filter(n => n.category === 'SUPPORT')
const tensionNotes = classifiedNotes.filter(n => n.category === 'TENSION')
@@ -168,7 +178,7 @@ RESPOND ONLY with a valid JSON array of 9 objects:
CRITICAL: Each idea MUST have at least 1 noteRef. Only use null noteId if genuinely no note connects to that idea.
LANGUAGE: You MUST write ALL titles, descriptions, connectionToSeed, and explanation fields in ${locale === 'fr' ? 'French' : locale === 'es' ? 'Spanish' : locale === 'de' ? 'German' : locale === 'it' ? 'Italian' : locale === 'pt' ? 'Portuguese' : locale === 'nl' ? 'Dutch' : locale === 'ru' ? 'Russian' : locale === 'zh' ? 'Chinese' : locale === 'ja' ? 'Japanese' : locale === 'ko' ? 'Korean' : locale === 'ar' ? 'Arabic' : locale === 'fa' ? 'Farsi' : locale === 'hi' ? 'Hindi' : locale === 'pl' ? 'Polish' : 'the same language as the seed idea'}.`
LANGUAGE: You MUST write ALL titles, descriptions, connectionToSeed, and explanation fields in ${localeToLanguageName(locale)}.`
}
function buildFallbackIdeas(classifiedNotes: ClassifiedNote[]): any[] {