Files
Momento/memento-note/app/api/onboarding/seed-demo-notes/route.ts
Antigravity 6b4ed8514f
Some checks failed
CI / Lint, Unit Tests & Build (push) Successful in 5m37s
CI / Deploy production (on server) (push) Has been cancelled
Epic 6: Stories 6-2 (Markdown roundtrip) + 6-3 (Brainstorm PPTX + Canvas)
Story 6-2 — Markdown roundtrip export/import:
- lib/editor/markdown-export.ts: tiptapHTMLToMarkdown, markdownToHTML, looksLikeMarkdown
- lib/editor/markdown-paste-extension.ts: TipTap extension paste Markdown → blocs
- note-editor-toolbar.tsx: export .md + import .md (file picker)
- rich-text-editor.tsx: intégration MarkdownPasteExtension
- 40 tests unitaires markdown-export.test.ts

Story 6-3 — Brainstorm PPTX + Canvas:
- lib/brainstorm/export-pptx.ts: génération PPTX 5 slides (pptxgenjs)
- app/api/brainstorm/[sessionId]/export-pptx/route.ts: route POST protégée
- brainstorm-page.tsx: bouton PPTX, auto-select session, fix emoji, fix router.replace
- wave-canvas.tsx: fitTrigger recentrage, légende bas-droite

Onboarding activation wizard (Story 6-1):
- components/onboarding/: wizard multi-étapes, hints éditeur
- app/api/onboarding/: route PATCH onboarding
- prisma/migrations: champs onboarding user

Locales: 15 langues mises à jour (brainstorm, markdown, onboarding keys)
Sprint: 6-1 done, 6-2 review, 6-3 review

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-29 11:24:56 +00:00

301 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import prisma from '@/lib/prisma'
import { embeddingService } from '@/lib/ai/services/embedding.service'
import { upsertNoteEmbedding } from '@/lib/embeddings'
type DemoNote = { title: string; content: string }
const DEMO_NOTES: Record<string, DemoNote[]> = {
fr: [
{
title: 'Réunion Q3 — Stratégie produit',
content: `<h2>Réunion Q3 — Stratégie produit</h2>
<p>Points clés abordés lors de la réunion trimestrielle :</p>
<ul>
<li><strong>Roadmap</strong> : priorité aux features de monétisation (Stripe, BYOK) avant les agents autonomes</li>
<li><strong>KPI Activation</strong> : objectif 40% d'utilisateurs actifs dans les 7 jours suivant l'inscription</li>
<li><strong>Acquisition PLG</strong> : brainstorm partageable = viral loop principale</li>
<li><strong>Pricing</strong> : Pro à 9,90 €/mois validé avec 3 personas cibles</li>
</ul>
<p>Prochaine étape : finaliser l'onboarding wizard pour atteindre le taux d'activation cible.</p>`,
},
{
title: 'Idées de projets secondaires',
content: `<h2>Idées de projets secondaires</h2>
<p>Liste de projets à explorer dans les prochains mois :</p>
<ul>
<li>📱 <strong>App de suivi d'habitudes</strong> — gamification, streaks, rappels intelligents</li>
<li>🎙️ <strong>Podcast "Tech Indépendant"</strong> — interviews de freelances tech, conseils pratiques</li>
<li>🤖 <strong>Bot Telegram de veille</strong> — scraping RSS + résumé IA chaque matin</li>
<li>📚 <strong>Newsletter hebdomadaire</strong> — curation de ressources pour développeurs</li>
<li>🎮 <strong>Jeu de stratégie minimaliste</strong> — WebGL, logique pure, pas de monétisation</li>
</ul>
<p>Critères de sélection : impact fort, temps minimal, apprentissage technique.</p>`,
},
{
title: 'Livres à lire — Recommandations',
content: `<h2>Livres à lire — Recommandations</h2>
<h3>Productivité & Pensée</h3>
<ul>
<li><strong>Building a Second Brain</strong> — Tiago Forte · Système de notes PKM, méthode CODE</li>
<li><strong>Deep Work</strong> — Cal Newport · Concentration profonde dans un monde de distractions</li>
<li><strong>Atomic Habits</strong> — James Clear · Les habitudes comme système de progression</li>
</ul>
<h3>Technique</h3>
<ul>
<li><strong>Designing Data-Intensive Applications</strong> — Martin Kleppmann · Architecture systèmes</li>
<li><strong>The Pragmatic Programmer</strong> — Hunt & Thomas · Bonnes pratiques développement</li>
</ul>
<p>Prochaine lecture : Building a Second Brain (lien avec Momento évident).</p>`,
},
{
title: 'Notes de formation React — Hooks avancés',
content: `<h2>Notes de formation React — Hooks avancés</h2>
<h3>useCallback vs useMemo</h3>
<ul>
<li><strong>useCallback</strong> : mémoïse une <em>fonction</em>, utile pour éviter re-renders des enfants</li>
<li><strong>useMemo</strong> : mémoïse une <em>valeur calculée</em>, utile pour calculs coûteux</li>
<li>⚠️ Ne pas abuser — le coût de la mémoïsation peut dépasser le gain</li>
</ul>
<h3>useReducer</h3>
<p>Préférer <code>useReducer</code> à <code>useState</code> quand :</p>
<ul>
<li>L'état a plusieurs sous-valeurs liées</li>
<li>La prochaine valeur dépend de la précédente</li>
<li>La logique de transition est complexe</li>
</ul>
<h3>Pattern : Context + useReducer</h3>
<p>Combine Context API avec useReducer pour un state management léger sans Redux.</p>`,
},
{
title: 'Objectifs personnels 2025',
content: `<h2>Objectifs personnels 2025</h2>
<h3>🎯 Professionnel</h3>
<ul>
<li>Lancer un produit SaaS avec les premiers utilisateurs payants</li>
<li>Contribuer à un projet open source significatif</li>
<li>Maîtriser l'architecture de systèmes distribués</li>
</ul>
<h3>📚 Apprentissage</h3>
<ul>
<li>Lire 12 livres techniques (1 par mois)</li>
<li>Compléter une formation en machine learning appliqué</li>
<li>Améliorer le niveau en algorithmique (LeetCode medium)</li>
</ul>
<h3>🌱 Personnel</h3>
<ul>
<li>Sport : 3x par semaine minimum</li>
<li>Méditation : 10 minutes par jour (habitude matinale)</li>
<li>Voyager dans 2 nouveaux pays</li>
</ul>
<p>Revue mensuelle le 1er de chaque mois pour ajuster les priorités.</p>`,
},
],
en: [
{
title: 'Q3 Meeting — Product Strategy',
content: `<h2>Q3 Meeting — Product Strategy</h2>
<p>Key points from the quarterly review:</p>
<ul>
<li><strong>Roadmap</strong>: monetization features first (Stripe, BYOK) before autonomous agents</li>
<li><strong>Activation KPI</strong>: target 40% active users within 7 days of signup</li>
<li><strong>PLG Acquisition</strong>: shareable brainstorm canvas = main viral loop</li>
<li><strong>Pricing</strong>: Pro at $9.90/month validated with 3 target personas</li>
</ul>
<p>Next step: finalize onboarding wizard to reach activation target.</p>`,
},
{
title: 'Side Project Ideas',
content: `<h2>Side Project Ideas</h2>
<p>Projects to explore in the coming months:</p>
<ul>
<li>📱 <strong>Habit tracking app</strong> — gamification, streaks, smart reminders</li>
<li>🎙️ <strong>Tech podcast</strong> — interviews with indie developers, practical advice</li>
<li>🤖 <strong>Telegram news bot</strong> — RSS scraping + AI summary every morning</li>
<li>📚 <strong>Weekly newsletter</strong> — curated resources for developers</li>
<li>🎮 <strong>Minimalist strategy game</strong> — WebGL, pure logic, no monetization</li>
</ul>
<p>Selection criteria: high impact, minimal time, technical learning.</p>`,
},
{
title: 'Books to Read — Recommendations',
content: `<h2>Books to Read — Recommendations</h2>
<h3>Productivity & Thinking</h3>
<ul>
<li><strong>Building a Second Brain</strong> — Tiago Forte · PKM note-taking system, CODE method</li>
<li><strong>Deep Work</strong> — Cal Newport · Deep focus in a distracted world</li>
<li><strong>Atomic Habits</strong> — James Clear · Habits as a system for progress</li>
</ul>
<h3>Technical</h3>
<ul>
<li><strong>Designing Data-Intensive Applications</strong> — Martin Kleppmann · Systems architecture</li>
<li><strong>The Pragmatic Programmer</strong> — Hunt & Thomas · Development best practices</li>
</ul>
<p>Next read: Building a Second Brain (obvious connection to Momento).</p>`,
},
{
title: 'React Training Notes — Advanced Hooks',
content: `<h2>React Training Notes — Advanced Hooks</h2>
<h3>useCallback vs useMemo</h3>
<ul>
<li><strong>useCallback</strong>: memoizes a <em>function</em>, useful to prevent child re-renders</li>
<li><strong>useMemo</strong>: memoizes a <em>computed value</em>, useful for expensive calculations</li>
<li>⚠️ Don't overuse — memoization cost can exceed the gain</li>
</ul>
<h3>useReducer</h3>
<p>Prefer <code>useReducer</code> over <code>useState</code> when:</p>
<ul>
<li>State has multiple related sub-values</li>
<li>Next state depends on previous state</li>
<li>Transition logic is complex</li>
</ul>`,
},
{
title: 'Personal Goals 2025',
content: `<h2>Personal Goals 2025</h2>
<h3>🎯 Professional</h3>
<ul>
<li>Launch a SaaS product with first paying users</li>
<li>Contribute to a significant open source project</li>
<li>Master distributed systems architecture</li>
</ul>
<h3>📚 Learning</h3>
<ul>
<li>Read 12 technical books (1 per month)</li>
<li>Complete an applied machine learning course</li>
<li>Improve algorithmic skills (LeetCode medium)</li>
</ul>
<h3>🌱 Personal</h3>
<ul>
<li>Exercise: 3x per week minimum</li>
<li>Meditation: 10 minutes per day (morning habit)</li>
<li>Travel to 2 new countries</li>
</ul>`,
},
],
fa: [
{
title: 'جلسه Q3 — استراتژی محصول',
content: `<h2>جلسه Q3 — استراتژی محصول</h2>
<p>نکات کلیدی جلسه فصلی:</p>
<ul>
<li><strong>نقشه راه</strong>: اول ویژگی‌های پولی‌سازی (Stripe، BYOK) و سپس عامل‌های خودمختار</li>
<li><strong>KPI فعال‌سازی</strong>: هدف ۴۰٪ کاربران فعال در ۷ روز اول</li>
<li><strong>رشد محصول‌محور</strong>: اشتراک‌گذاری بوم طوفان فکری = حلقه ویروسی اصلی</li>
</ul>
<p>قدم بعدی: نهایی کردن ویزارد آنبوردینگ برای رسیدن به هدف فعال‌سازی.</p>`,
},
{
title: 'ایده‌های پروژه‌های جانبی',
content: `<h2>ایده‌های پروژه‌های جانبی</h2>
<ul>
<li>📱 <strong>اپلیکیشن پیگیری عادت</strong> — گیمیفیکیشن، استریک، یادآوری هوشمند</li>
<li>🎙️ <strong>پادکست فناوری</strong> — مصاحبه با توسعه‌دهندگان مستقل</li>
<li>🤖 <strong>ربات تلگرام اخبار</strong> — خلاصه‌سازی روزانه با هوش مصنوعی</li>
</ul>`,
},
{
title: 'کتاب‌های پیشنهادی',
content: `<h2>کتاب‌های پیشنهادی</h2>
<ul>
<li><strong>ساختن مغز دوم</strong> — تیاگو فورتی · سیستم یادداشت‌برداری PKM</li>
<li><strong>کار عمیق</strong> — کال نیوپورت · تمرکز عمیق در دنیای پر از حواس‌پرتی</li>
<li><strong>عادت‌های اتمی</strong> — جیمز کلیر · عادت‌ها به عنوان سیستم پیشرفت</li>
</ul>`,
},
{
title: 'یادداشت‌های آموزش React',
content: `<h2>یادداشت‌های آموزش React</h2>
<h3>useCallback و useMemo</h3>
<ul>
<li><strong>useCallback</strong>: یک تابع را حفظ می‌کند</li>
<li><strong>useMemo</strong>: یک مقدار محاسبه‌شده را حفظ می‌کند</li>
</ul>`,
},
{
title: 'اهداف شخصی ۲۰۲۵',
content: `<h2>اهداف شخصی ۲۰۲۵</h2>
<h3>🎯 حرفه‌ای</h3>
<ul>
<li>راه‌اندازی یک محصول SaaS با اولین کاربران پرداخت‌کننده</li>
<li>مشارکت در یک پروژه متن‌باز مهم</li>
</ul>
<h3>📚 یادگیری</h3>
<ul>
<li>خواندن ۱۲ کتاب فناوری (یکی در ماه)</li>
<li>تکمیل یک دوره یادگیری ماشین کاربردی</li>
</ul>`,
},
],
}
function getNotesForLocale(locale: string): DemoNote[] {
const lang = locale.split('-')[0].toLowerCase()
return DEMO_NOTES[lang] ?? DEMO_NOTES.en
}
/** Wraps a promise with a timeout — rejects after `ms` milliseconds. */
function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms)
)
return Promise.race([promise, timeout])
}
export async function POST(req: NextRequest) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const userId = session.user.id
let locale = 'en'
try {
const body = await req.json().catch(() => ({}))
if (body?.locale) locale = body.locale
} catch { /* ignore */ }
// Idempotency check — if any demo notes already exist, return them (handles partial creation too)
const existing = await prisma.note.findMany({
where: { userId, isDemo: true, trashedAt: null },
select: { id: true, title: true },
})
if (existing.length > 0) {
return NextResponse.json({ created: false, notes: existing, message: 'Demo notes already exist' })
}
const demoNotes = getNotesForLocale(locale)
const created: { id: string; title: string | null }[] = []
for (const demo of demoNotes) {
const note = await prisma.note.create({
data: {
userId,
title: demo.title,
content: demo.content,
isMarkdown: true,
isDemo: true,
type: 'richtext',
color: 'default',
},
})
created.push({ id: note.id, title: note.title })
// Synchronous embedding generation so semantic search works immediately (6s timeout per note)
try {
const { embedding } = await withTimeout(
embeddingService.generateNoteEmbedding(demo.title, demo.content),
6000
)
if (embedding) {
await withTimeout(upsertNoteEmbedding(note.id, embedding), 3000)
}
} catch (e) {
console.error('[ONBOARDING] Embedding failed for demo note:', note.id, e)
}
}
return NextResponse.json({ created: true, notes: created })
}