fix: batch priorités — thème, scroll, wizard, agents, slides, charts, packs
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m15s
CI / Deploy production (on server) (push) Has been skipped

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:
Antigravity
2026-07-16 20:48:55 +00:00
parent 556a0b2f3f
commit a77f3ffb6e
16 changed files with 240 additions and 39 deletions

View File

@@ -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 (400800 words per note)' }
// Borné pour éviter timeouts JSON / mode expert qui plantait
return { min: 350, max: 700, label: 'expert (350700 words per note, dense)' }
}
if (/intermédiaire|intermediate|moyen/.test(l)) {
return { min: 300, max: 550, label: 'intermediate (300550 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 (38 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(

View File

@@ -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: 25 faits de la note par slide
- Headline ASSERTIVE, max 10 mots (INTERDIT: "Contexte", "Introduction", "Présentation", "Overview", "Points clés", "Conclusion" seuls)
- keyPoints: 25 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: 25 facts from the note per slide
- ASSERTIVE headlines, max 10 words (FORBIDDEN alone: Context, Introduction, Overview, Key points, Conclusion)
- keyPoints: 25 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.`
}