Story 6-4/6-5: Chat with PDF (done) + PPTX watermark PLG
Story 6-4 — Chat with PDF: - Feature déjà implémentée (document-qa-overlay.tsx, ingestion, search) - Marquée done dans sprint-status Story 6-5 — PPTX Export Watermark (PLG viral loop): - lib/brainstorm/export-pptx.ts: addWatermark + withWatermark helpers - lib/ai/tools/pptx.tool.ts: même pattern monkey-patch addSlide - Watermark 'memento-note.com' 7pt gris bas-droite sur chaque slide - Zéro modification des 14+ fonctions de slides existantes - 174 tests passent, aucune erreur TS Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
50
docs/6-5-pptx-export-watermark.md
Normal file
50
docs/6-5-pptx-export-watermark.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Story 6-5 — PPTX Export Watermark (PLG Viral Loop)
|
||||||
|
|
||||||
|
**Epic:** 6 — Croissance & Activation (PLG)
|
||||||
|
**Status:** done
|
||||||
|
|
||||||
|
## User Story
|
||||||
|
|
||||||
|
**En tant qu'** utilisateur qui partage une présentation exportée depuis Memento,
|
||||||
|
**Je veux** que chaque slide porte discrètement la mention "memento-note.com",
|
||||||
|
**Afin que** les destinataires découvrent l'outil et s'inscrivent (boucle virale PLG).
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [x] **AC-1** : Chaque slide du PPTX brainstorm contient "memento-note.com" en bas-droite
|
||||||
|
- [x] **AC-2** : Chaque slide du PPTX IA (generate_pptx) contient "memento-note.com" en bas-droite
|
||||||
|
- [x] **AC-3** : Le watermark est subtil (7pt, gris clair `#B8B0A8`, italique) — visible mais non intrusif
|
||||||
|
- [x] **AC-4** : Zéro régression sur la mise en page existante des slides
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [x] T1 — Ajouter helper `addWatermark(slide)` + `withWatermark(pres)` dans `lib/brainstorm/export-pptx.ts`
|
||||||
|
- [x] T2 — Appliquer `withWatermark` dans `generateBrainstormPptx` (monkey-patch `pres.addSlide`)
|
||||||
|
- [x] T3 — Ajouter helper `addWatermark(slide)` + `withWatermark(pres)` dans `lib/ai/tools/pptx.tool.ts`
|
||||||
|
- [x] T4 — Appliquer `withWatermark` dans `buildPresentation` (monkey-patch `pres.addSlide`)
|
||||||
|
- [x] T5 — Vérifier TypeScript + 174 tests unitaires passent
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
- `memento-note/lib/brainstorm/export-pptx.ts` — helpers `addWatermark` + `withWatermark`, monkey-patch dans `generateBrainstormPptx`
|
||||||
|
- `memento-note/lib/ai/tools/pptx.tool.ts` — helpers `addWatermark` + `withWatermark`, monkey-patch dans `buildPresentation`
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
**Pattern monkey-patch :**
|
||||||
|
```ts
|
||||||
|
function withWatermark(pres: PptxGenJSModule): PptxGenJSModule {
|
||||||
|
const original = pres.addSlide.bind(pres)
|
||||||
|
;(pres as any).addSlide = (...args: any[]) => {
|
||||||
|
const slide = original(...args)
|
||||||
|
addWatermark(slide)
|
||||||
|
return slide
|
||||||
|
}
|
||||||
|
return pres
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Ce pattern garantit que le watermark est ajouté sur **toutes** les slides sans modifier les 14+ fonctions de construction de slides.
|
||||||
|
|
||||||
|
**Design watermark :**
|
||||||
|
- Position : `x: 7.0, y: 5.35, w: 2.7, h: 0.2` (bas-droite, slide 10"×5.63")
|
||||||
|
- Style : 7pt Arial, couleur `B8B0A8`, italique, alignement droite
|
||||||
@@ -66,8 +66,8 @@ development_status:
|
|||||||
6-1-onboarding-activation: done # story-onboarding-activation.md
|
6-1-onboarding-activation: done # story-onboarding-activation.md
|
||||||
6-2-markdown-roundtrip: review # brief-markdown-roundtrip.md
|
6-2-markdown-roundtrip: review # brief-markdown-roundtrip.md
|
||||||
6-3-brainstorm-canvas-finalize: review # story: 6-3-brainstorm-canvas-finalize.md
|
6-3-brainstorm-canvas-finalize: review # story: 6-3-brainstorm-canvas-finalize.md
|
||||||
6-4-chat-with-pdf: backlog
|
6-4-chat-with-pdf: done # already implemented: document-qa-overlay.tsx + document-ingestion + document-search tool
|
||||||
6-5-pptx-export-watermark: backlog
|
6-5-pptx-export-watermark: done # story: 6-5-pptx-export-watermark.md
|
||||||
epic-6-retrospective: optional
|
epic-6-retrospective: optional
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,26 @@ const SHAPE_RECT = 'rect' as const
|
|||||||
const SHAPE_ROUND_RECT = 'roundRect' as const
|
const SHAPE_ROUND_RECT = 'roundRect' as const
|
||||||
const SHAPE_OVAL = 'ellipse' as const
|
const SHAPE_OVAL = 'ellipse' as const
|
||||||
|
|
||||||
|
/** PLG viral watermark injected on every slide automatically */
|
||||||
|
function addWatermark(slide: any) {
|
||||||
|
slide.addText('memento-note.com', {
|
||||||
|
x: 7.0, y: 5.35, w: 2.7, h: 0.2,
|
||||||
|
fontSize: 7, fontFace: 'Arial', color: 'B8B0A8',
|
||||||
|
align: 'right', italic: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wrap pres.addSlide so every new slide gets the Momento watermark automatically */
|
||||||
|
function withWatermark(pres: PptxGenJSModule): PptxGenJSModule {
|
||||||
|
const original = pres.addSlide.bind(pres)
|
||||||
|
;(pres as any).addSlide = (...args: any[]) => {
|
||||||
|
const slide = original(...args)
|
||||||
|
addWatermark(slide)
|
||||||
|
return slide
|
||||||
|
}
|
||||||
|
return pres
|
||||||
|
}
|
||||||
|
|
||||||
function addBadge(s: any, num: number, accent: string) {
|
function addBadge(s: any, num: number, accent: string) {
|
||||||
s.addShape(SHAPE_OVAL, {
|
s.addShape(SHAPE_OVAL, {
|
||||||
x: 9.3, y: 5.1, w: 0.4, h: 0.4,
|
x: 9.3, y: 5.1, w: 0.4, h: 0.4,
|
||||||
@@ -978,7 +998,7 @@ async function buildPresentation(spec: PresentationSpec): Promise<PptxGenJSModul
|
|||||||
const style = STYLES[spec.style || 'soft'] || STYLES.soft!
|
const style = STYLES[spec.style || 'soft'] || STYLES.soft!
|
||||||
|
|
||||||
const PptxGenJS = await getPptxGenClass()
|
const PptxGenJS = await getPptxGenClass()
|
||||||
const pres = new PptxGenJS()
|
const pres = withWatermark(new PptxGenJS())
|
||||||
pres.title = spec.title
|
pres.title = spec.title
|
||||||
pres.author = 'Momento'
|
pres.author = 'Momento'
|
||||||
pres.subject = spec.title
|
pres.subject = spec.title
|
||||||
|
|||||||
@@ -66,6 +66,29 @@ function truncate(text: string, max: number): string {
|
|||||||
return text.length > max ? text.slice(0, max - 1) + '…' : text
|
return text.length > max ? text.slice(0, max - 1) + '…' : text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PLG viral watermark — added to every slide automatically via addSlide monkey-patch.
|
||||||
|
* Subtle branding in bottom-right corner to drive organic acquisition.
|
||||||
|
*/
|
||||||
|
function addWatermark(slide: any) {
|
||||||
|
slide.addText('memento-note.com', {
|
||||||
|
x: 7.0, y: 5.35, w: 2.7, h: 0.2,
|
||||||
|
fontSize: 7, fontFace: 'Arial', color: 'B8B0A8',
|
||||||
|
align: 'right', italic: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wrap pres.addSlide so every new slide gets the Momento watermark automatically */
|
||||||
|
function withWatermark(pres: PptxGenJSModule): PptxGenJSModule {
|
||||||
|
const original = pres.addSlide.bind(pres)
|
||||||
|
;(pres as any).addSlide = (...args: any[]) => {
|
||||||
|
const slide = original(...args)
|
||||||
|
addWatermark(slide)
|
||||||
|
return slide
|
||||||
|
}
|
||||||
|
return pres
|
||||||
|
}
|
||||||
|
|
||||||
// Add a consistent slide background
|
// Add a consistent slide background
|
||||||
function addBg(slide: any) {
|
function addBg(slide: any) {
|
||||||
slide.background = { color: T.bg }
|
slide.background = { color: T.bg }
|
||||||
@@ -279,7 +302,7 @@ function buildSummarySlide(pres: PptxGenJSModule, session: SessionLike, stats: {
|
|||||||
|
|
||||||
export async function generateBrainstormPptx(session: SessionLike): Promise<{ buffer: Buffer; filename: string }> {
|
export async function generateBrainstormPptx(session: SessionLike): Promise<{ buffer: Buffer; filename: string }> {
|
||||||
const PptxGenJS = await getPptxGenClass()
|
const PptxGenJS = await getPptxGenClass()
|
||||||
const pres = new PptxGenJS()
|
const pres = withWatermark(new PptxGenJS())
|
||||||
|
|
||||||
pres.layout = 'LAYOUT_WIDE'
|
pres.layout = 'LAYOUT_WIDE'
|
||||||
pres.author = 'Momento'
|
pres.author = 'Momento'
|
||||||
|
|||||||
Reference in New Issue
Block a user