fix: batch priorités — thème, scroll, wizard, agents, slides, charts, packs
Anti-flash dark (color-scheme + applyDocumentTheme), scroll pages publiques, prompts wizard/slides moins génériques, cron agents rattrapage nextRun null, slides carnet via notebookId, graphiques à 2 crédits + hint, audit dashboard, packs Stripe marqués non configurés sans price ID.
This commit is contained in:
@@ -42,7 +42,8 @@ const LANG_NAMES: Record<string, string> = {
|
||||
function wordTargetsForLevel(level?: string): { min: number; max: number; label: string } {
|
||||
const l = (level || '').toLowerCase()
|
||||
if (/expert|avancé|advanced|fort/.test(l)) {
|
||||
return { min: 400, max: 800, label: 'expert (400–800 words per note)' }
|
||||
// Borné pour éviter timeouts JSON / mode expert qui plantait
|
||||
return { min: 350, max: 700, label: 'expert (350–700 words per note, dense)' }
|
||||
}
|
||||
if (/intermédiaire|intermediate|moyen/.test(l)) {
|
||||
return { min: 300, max: 550, label: 'intermediate (300–550 words per note)' }
|
||||
@@ -240,7 +241,13 @@ Return ONLY:
|
||||
Rules:
|
||||
- Exactly ${count} titles, progressive learning order.
|
||||
- Titles in ${this.langName(lang)} only.
|
||||
- notebookName ≠ full user sentence.`
|
||||
- notebookName ≠ full user sentence (3–8 words, concrete, topic-specific).
|
||||
- FORBIDDEN generic notebook names / chapter patterns (any language):
|
||||
"Fondements", "Foundations", "Maîtrise", "Mastery", "Introduction",
|
||||
"Basics", "Overview", "Getting started", "Chapitre 1", "Chapter 1",
|
||||
"Partie 1", "Bases", "Notions de base", "Synthèse finale" alone.
|
||||
- Prefer specific titles that name the real subject (e.g. topic keywords + angle).
|
||||
- Each title must be unique and non-interchangeable with another topic.`
|
||||
|
||||
const raw = await provider.generateText(prompt)
|
||||
const parsed = this.parseJsonObject(raw)
|
||||
@@ -250,10 +257,41 @@ Rules:
|
||||
while (titles.length < count) {
|
||||
titles.push(lang === 'fr' ? `Chapitre ${titles.length + 1}` : `Chapter ${titles.length + 1}`)
|
||||
}
|
||||
return {
|
||||
notebookName: String(parsed?.notebookName || '').trim().slice(0, 80),
|
||||
titles,
|
||||
let notebookName = String(parsed?.notebookName || '').trim().slice(0, 80)
|
||||
if (this.isGenericNotebookName(notebookName, topic)) {
|
||||
notebookName = this.deriveNotebookName(topic, titles.map((t) => ({ title: t, content: '' })), lang)
|
||||
}
|
||||
const cleanedTitles = titles.map((t, i) =>
|
||||
this.isGenericChapterTitle(t) ? this.fallbackChapterTitle(topic, i, lang) : t,
|
||||
)
|
||||
return {
|
||||
notebookName,
|
||||
titles: cleanedTitles,
|
||||
}
|
||||
}
|
||||
|
||||
private isGenericNotebookName(name: string, topic: string): boolean {
|
||||
const n = (name || '').trim().toLowerCase()
|
||||
if (!n || n.length < 3) return true
|
||||
if (n === topic.trim().toLowerCase() && topic.trim().split(/\s+/).length > 10) return true
|
||||
const banned =
|
||||
/^(fondements?|foundations?|ma[iî]trise|mastery|introduction|bases?|basics?|overview|getting started|notions de base|synth[eè]se|summary)$/i
|
||||
return banned.test(n)
|
||||
}
|
||||
|
||||
private isGenericChapterTitle(title: string): boolean {
|
||||
const t = (title || '').trim()
|
||||
if (!t) return true
|
||||
return /^(chapitre|chapter|partie|part|section)\s*\d+$/i.test(t)
|
||||
|| /^(fondements?|foundations?|ma[iî]trise|introduction|bases?|basics?|overview)$/i.test(t)
|
||||
}
|
||||
|
||||
private fallbackChapterTitle(topic: string, index: number, lang: string): string {
|
||||
const short = topic.trim().slice(0, 48) || (lang === 'fr' ? 'Sujet' : 'Topic')
|
||||
const anglesFr = ['Concepts clés', 'Méthodes', 'Exemples guidés', 'Pièges fréquents', 'Mise en pratique', 'Bilan']
|
||||
const anglesEn = ['Key concepts', 'Methods', 'Guided examples', 'Common pitfalls', 'Practice', 'Recap']
|
||||
const angles = lang === 'fr' ? anglesFr : anglesEn
|
||||
return `${angles[index % angles.length]} — ${short}`
|
||||
}
|
||||
|
||||
private async generateOneNote(
|
||||
|
||||
@@ -229,11 +229,13 @@ Tu produis UNIQUEMENT un outline JSON (pas le corps final des slides).
|
||||
|
||||
Règles (DeckForge + think-cell):
|
||||
- Max ${maxSlides} slides
|
||||
- Headline ASSERTIVE, max 10 mots (pas "Contexte", "Introduction")
|
||||
- keyPoints: 2–5 faits de la note par slide
|
||||
- Headline ASSERTIVE, max 10 mots (INTERDIT: "Contexte", "Introduction", "Présentation", "Overview", "Points clés", "Conclusion" seuls)
|
||||
- keyPoints: 2–5 FAITS CONCRETS tirés de la note (chiffres, noms, formules) — jamais de filler
|
||||
- Chaque slide = UNE idée actionnable (titre = insight, pas libellé de section)
|
||||
- Arc pédagogique pour cours: title → définitions/équations → propriétés → exemples → summary
|
||||
- Types: title | equation | bullets | cards | comparison | timeline | stats | chart | table | quote | summary
|
||||
- Slide 1 = title, dernière = summary
|
||||
- INTERDIT slides vides, listes de 1 mot, ou répéter le titre de la note
|
||||
${mathRule}
|
||||
Réponds en JSON OutlineSchema.`
|
||||
}
|
||||
@@ -242,11 +244,13 @@ Output ONLY outline JSON (not full slide bodies).
|
||||
|
||||
Rules:
|
||||
- Max ${maxSlides} slides
|
||||
- ASSERTIVE headlines, max 10 words
|
||||
- keyPoints: 2–5 facts from the note per slide
|
||||
- ASSERTIVE headlines, max 10 words (FORBIDDEN alone: Context, Introduction, Overview, Key points, Conclusion)
|
||||
- keyPoints: 2–5 CONCRETE facts from the note (numbers, names, formulas) — no filler
|
||||
- One actionable idea per slide (title = insight, not section label)
|
||||
- Pedagogical arc for lessons: title → definitions/equations → properties → examples → summary
|
||||
- Types: title | equation | bullets | cards | comparison | timeline | stats | chart | table | quote | summary
|
||||
- First=title, last=summary
|
||||
- FORBIDDEN empty slides, one-word lists, or repeating the note title alone
|
||||
${mathRule}
|
||||
JSON OutlineSchema only.`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user