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:
@@ -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(
|
||||
|
||||
@@ -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 })
|
||||
|
||||
63
memento-note/app/api/brainstorm/[sessionId]/star/route.ts
Normal file
63
memento-note/app/api/brainstorm/[sessionId]/star/route.ts
Normal 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 })
|
||||
}
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
@@ -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[] {
|
||||
|
||||
Reference in New Issue
Block a user