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

@@ -167,6 +167,11 @@ export async function PUT(
console.error('[HISTORY] Failed to create snapshot from /api/notes/[id] PUT:', snapshotError)
}
// Fire-and-forget: sync [[wikilinks]] in background after content change
if ('content' in updateData) {
syncNoteLinksBackground(id, session.user.id, note.content).catch(() => {})
}
return NextResponse.json({
success: true,
data: parseNote(note)
@@ -180,6 +185,56 @@ export async function PUT(
}
}
/** Background job: parse [[wikilinks]] and sync NoteLink table */
async function syncNoteLinksBackground(noteId: string, userId: string, content: string) {
const WIKILINK_RE = /\[\[([^\]|#]+?)(?:[|#][^\]]+)?\]\]/g
const plain = content.replace(/<[^>]+>/g, ' ')
const wikilinks: { title: string; snippet: string }[] = []
const seen = new Set<string>()
let match: RegExpExecArray | null
WIKILINK_RE.lastIndex = 0
while ((match = WIKILINK_RE.exec(plain)) !== null) {
const title = match[1].trim()
if (!title || seen.has(title.toLowerCase())) continue
seen.add(title.toLowerCase())
const start = Math.max(0, match.index - 50)
const end = Math.min(plain.length, match.index + match[0].length + 50)
const snippet = plain.slice(start, end).replace(/\s+/g, ' ').trim()
wikilinks.push({ title, snippet })
}
const upsertedIds: string[] = []
for (const { title, snippet } of wikilinks) {
let targetNote = await prisma.note.findFirst({
where: { userId, title: { equals: title, mode: 'insensitive' }, trashedAt: null },
select: { id: true },
})
if (!targetNote) {
targetNote = await prisma.note.create({
data: { title, content: '', userId, type: 'richtext', color: 'default', isMarkdown: true, order: 0 },
select: { id: true },
})
}
if (targetNote.id === noteId) continue
await (prisma as any).noteLink.upsert({
where: { sourceNoteId_targetNoteId: { sourceNoteId: noteId, targetNoteId: targetNote.id } },
update: { contextSnippet: snippet.slice(0, 200) },
create: { id: crypto.randomUUID(), sourceNoteId: noteId, targetNoteId: targetNote.id, contextSnippet: snippet.slice(0, 200) },
})
upsertedIds.push(targetNote.id)
}
if (upsertedIds.length > 0) {
await (prisma as any).noteLink.deleteMany({
where: { sourceNoteId: noteId, targetNoteId: { notIn: upsertedIds } },
})
} else {
await (prisma as any).noteLink.deleteMany({ where: { sourceNoteId: noteId } })
}
}
// DELETE /api/notes/[id] - Delete a note
export async function DELETE(
request: NextRequest,