fix: nombre slides proportionnel aux vrais mots de la note
- 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:
@@ -945,7 +945,7 @@ Appelle generate_slides avec un objet JSON structuré :
|
|||||||
12. "summary" → { type:"summary", title:"...", items:["Point clé 1", "Point clé 2", ...] }
|
12. "summary" → { type:"summary", title:"...", items:["Point clé 1", "Point clé 2", ...] }
|
||||||
|
|
||||||
═══ RÈGLES ═══
|
═══ 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"
|
- Slide 1 OBLIGATOIREMENT type "title"
|
||||||
- Dernière slide OBLIGATOIREMENT type "summary"
|
- Dernière slide OBLIGATOIREMENT type "summary"
|
||||||
- Au moins 1 slide "chart" ou "stats" si des chiffres existent dans la note
|
- 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", ...] }
|
12. "summary" → { type:"summary", title:"...", items:["Key point 1", "Key point 2", ...] }
|
||||||
|
|
||||||
═══ RULES ═══
|
═══ 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"
|
- Slide 1 MUST be type "title"
|
||||||
- Last slide MUST be type "summary"
|
- Last slide MUST be type "summary"
|
||||||
- At least 1 "chart" or "stats" slide if numbers exist in the note
|
- 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 {
|
function extractJsonFromText(text: string): any {
|
||||||
if (!text) return null
|
if (!text) return null
|
||||||
|
|
||||||
@@ -1274,7 +1317,16 @@ async function executeToolUseAgent(
|
|||||||
const notesContext = notes.map(n =>
|
const notesContext = notes.map(n =>
|
||||||
`### ${n.title || untitled} (${n.createdAt.toLocaleDateString(dateLocale)})\n${n.content.substring(0, 2000)}`
|
`### ${n.title || untitled} (${n.createdAt.toLocaleDateString(dateLocale)})\n${n.content.substring(0, 2000)}`
|
||||||
).join('\n\n')
|
).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' ? '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) ──
|
// ── 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.`
|
: `\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'
|
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 : 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. 6-12 varied slides.'}`
|
: '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') {
|
if (agent.slideTheme && agent.slideTheme !== 'auto') {
|
||||||
prompt += `\n${lang === 'fr'
|
prompt += `\n${lang === 'fr'
|
||||||
|
|||||||
Reference in New Issue
Block a user