fix: nombre slides proportionnel aux vrais mots de la note
Some checks failed
CI / Deploy production (on server) (push) Has been cancelled
CI / Lint, Unit Tests & Build (push) Has been cancelled

- stripNoteMarkdown() : supprime #, **, URLs, métadonnées (Connection to seed,
  Novelty score, Origin...), frontmatter, code blocks
- countNoteWords() : compte les mots sémantiques réels
- slideLimit() : <50 mots → 3 slides, 50-150 → 4, 150-350 → 6, >350 → 8
- Le prompt injecte la règle dynamique avec priorité absolue (⚠️)
- Suppression des 3 occurrences hardcodées '6-12 slides'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Antigravity
2026-05-29 13:09:46 +00:00
parent 212420ec62
commit 6f8121e937

View File

@@ -945,7 +945,7 @@ Appelle generate_slides avec un objet JSON structuré :
12. "summary" → { type:"summary", title:"...", items:["Point clé 1", "Point clé 2", ...] }
═══ RÈGLES ═══
- 6 à 12 slides par présentation
- Nombre de slides : adapté au contenu réel de la note (la règle ⚠️ dans le message utilisateur est ABSOLUE)
- Slide 1 OBLIGATOIREMENT type "title"
- Dernière slide OBLIGATOIREMENT type "summary"
- Au moins 1 slide "chart" ou "stats" si des chiffres existent dans la note
@@ -989,7 +989,7 @@ Call generate_slides with a structured JSON object:
12. "summary" → { type:"summary", title:"...", items:["Key point 1", "Key point 2", ...] }
═══ RULES ═══
- 6 to 12 slides per presentation
- Slide count: adapt to the real content (the ⚠️ rule in the user message is ABSOLUTE)
- Slide 1 MUST be type "title"
- Last slide MUST be type "summary"
- At least 1 "chart" or "stats" slide if numbers exist in the note
@@ -1027,6 +1027,49 @@ You MUST use the task_extract tool. Do NOT respond with text, call the tool dire
},
}
/**
* Strip markdown syntax, URLs, metadata blocks, and frontmatter from note content.
* Returns only the semantic plain text, used to count real words before generating slides.
*/
function stripNoteMarkdown(raw: string): string {
return raw
// Remove frontmatter / YAML blocks
.replace(/^---[\s\S]*?---/m, '')
// Remove markdown headers
.replace(/^#{1,6}\s+/gm, '')
// Remove bold/italic
.replace(/[*_]{1,3}([^*_]+)[*_]{1,3}/g, '$1')
// Remove inline links — keep label only
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
// Remove bare URLs
.replace(/https?:\/\/\S+/g, '')
// Remove metadata lines (Connection to seed, Novelty score, Source brainstorm, Origin, Derived from, etc.)
.replace(/^\*{0,2}(Connection to seed|Novelty score|Source brainstorm|Source note|Origin|Derived from|## Origin)[^:\n]*:?.*$/gim, '')
// Remove horizontal rules
.replace(/^---+$/gm, '')
// Remove blockquote markers
.replace(/^>\s*/gm, '')
// Remove code blocks
.replace(/```[\s\S]*?```/g, '')
.replace(/`[^`]+`/g, '')
// Collapse whitespace
.replace(/\s+/g, ' ')
.trim()
}
/** Count real semantic words in a note */
function countNoteWords(raw: string): number {
return stripNoteMarkdown(raw).split(/\s+/).filter(Boolean).length
}
/** Return max slides based on real word count */
function slideLimit(wordCount: number): { max: number; label: string } {
if (wordCount < 50) return { max: 3, label: '3 slides MAX (note très courte)' }
if (wordCount < 150) return { max: 4, label: '4 slides MAX (note courte)' }
if (wordCount < 350) return { max: 6, label: '6 slides MAX (note moyenne)' }
return { max: 8, label: '8 slides MAX (note longue)' }
}
function extractJsonFromText(text: string): any {
if (!text) return null
@@ -1274,7 +1317,16 @@ async function executeToolUseAgent(
const notesContext = notes.map(n =>
`### ${n.title || untitled} (${n.createdAt.toLocaleDateString(dateLocale)})\n${n.content.substring(0, 2000)}`
).join('\n\n')
// Compute real word count (stripped of markdown/metadata) to determine slide limit
const totalWords = notes.reduce((sum, n) => sum + countNoteWords(n.content || ''), 0)
const { max: maxSlides, label: slideRule } = slideLimit(totalWords)
prompt += `\n\n${lang === 'fr' ? 'Notes source' : 'Source notes'}:\n\n${notesContext}`
prompt += `\n\n${lang === 'fr'
? `⚠️ RÈGLE ABSOLUE : Cette note fait ${totalWords} mots réels (hors markdown/URLs/métadonnées). Tu DOIS générer EXACTEMENT ${slideRule}. Respecter cette limite est PLUS IMPORTANT que la diversité ou la densité.`
: `⚠️ ABSOLUTE RULE: This note has ${totalWords} real words (excluding markdown/URLs/metadata). You MUST generate EXACTLY ${slideRule}. This limit is MORE IMPORTANT than diversity or density.`
}`
}
// ── Executive Template Structure (HTML-compatible) ──
@@ -1317,8 +1369,8 @@ async function executeToolUseAgent(
: `\n\nMANDATORY DENSITY: Every slide must have RICH CONTENT. Minimum 5 items per "bullets" slide (≥15 words each). Minimum 4 cards when using "cards". Minimum 4 data points for "chart". You MUST include at least 1 "chart" slide with REAL data from the notes. If no numbers in notes, create a maturity radar or qualitative comparison scored out of 5.`
prompt += `\n\n${lang === 'fr'
? 'IMPORTANT : Appelle OBLIGATOIREMENT generate_slides avec le JSON structuré {title, theme, slides:[...]}. Ne réponds JAMAIS avec du texte brut. 6-12 slides variées.'
: 'IMPORTANT: You MUST call generate_slides with structured JSON {title, theme, slides:[...]}. NEVER respond with plain text. 6-12 varied slides.'}`
? 'IMPORTANT : Appelle OBLIGATOIREMENT generate_slides avec le JSON structuré {title, theme, slides:[...]}. Ne réponds JAMAIS avec du texte brut. Respecte ABSOLUMENT la limite de slides indiquée.'
: 'IMPORTANT: You MUST call generate_slides with structured JSON {title, theme, slides:[...]}. NEVER respond with plain text. STRICTLY respect the slide count limit indicated.'}`
if (agent.slideTheme && agent.slideTheme !== 'auto') {
prompt += `\n${lang === 'fr'