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>
301 lines
13 KiB
TypeScript
301 lines
13 KiB
TypeScript
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 })
|
||
}
|