nexus/.planning/phases/44-video-presentations/44-02-PLAN.md

12 KiB

phase plan type wave depends_on files_modified autonomous requirements must_haves
44-video-presentations 02 execute 2
44-01
server/src/services/renderers/presentation-renderer.ts
server/src/services/content-job-runner.ts
true
PRES-01
PRES-02
PRES-03
PRES-04
truths artifacts key_links
LLM generates structured slide JSON from a user prompt for both pitch-deck and demo-video types
renderPresentation calls getBundlePath, selectComposition, and renderMedia to produce an MP4 buffer
onProgress callback publishes content_job.progress SSE events with 0-100 percentage
Remotion concurrency is capped at 1 to avoid starving LLM inference
browserExecutable reuses existing Playwright Chromium binary
Resulting MP4 buffer is wrapped in a PresentationBundle JSON with inputProps for Player replay
path provides exports min_lines
server/src/services/renderers/presentation-renderer.ts renderPresentation function — LLM prompt + Remotion render + SSE progress
renderPresentation
80
from to via pattern
server/src/services/renderers/presentation-renderer.ts packages/content-renderer/src/index.ts dynamic import @paperclipai/content-renderer content-renderer
from to via pattern
server/src/services/renderers/presentation-renderer.ts server/src/services/live-events.ts publishLiveEvent content_job.progress content_job.progress
from to via pattern
server/src/services/renderers/presentation-renderer.ts server/src/services/puter-inference.ts puterChatComplete for slide JSON generation puterChatComplete
from to via pattern
server/src/services/renderers/presentation-renderer.ts server/src/services/renderers/diagram-renderer.ts resolveBrowserPath import resolveBrowserPath
Create the presentation renderer that generates slide JSON via LLM and renders it to MP4 via Remotion, publishing SSE progress events throughout the render.

Purpose: This is the core rendering pipeline for PRES-01 through PRES-04 — it connects the LLM (slide content generation), Remotion (video rendering), and SSE (progress reporting). Output: server/src/services/renderers/presentation-renderer.ts

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/44-video-presentations/44-RESEARCH.md @.planning/phases/44-video-presentations/44-01-SUMMARY.md @server/src/services/renderers/diagram-renderer.ts (resolveBrowserPath pattern) @server/src/services/renderers/brand-renderer.ts (LLM prompt pattern) @server/src/services/puter-inference.ts (puterChatComplete interface) @server/src/services/live-events.ts (publishLiveEvent interface) From packages/content-renderer/src/index.ts (created in Plan 01): ```typescript export async function getBundlePath(): Promise; export async function renderPresentationComposition(opts: { serveUrl: string; input: Record; onProgress: (progress: number) => void; browserExecutable?: string; }): Promise<{ buffer: Buffer; durationInFrames: number; fps: number; inputProps: Record }>; ```

From server/src/services/renderers/types.ts (updated in Plan 01):

export interface PresentationBundle {
  type: "presentation-bundle";
  presentationType: "pitch-deck" | "demo-video";
  title: string;
  slideCount: number;
  durationInFrames: number;
  fps: number;
  mp4Base64: string;
  inputProps: Record<string, unknown>;
}
export interface RenderResult {
  filename: string;
  contentType: string;
  buffer: Buffer;
}

From server/src/services/puter-inference.ts:

export async function puterChatComplete(messages: ChatMessage[], model?: string): Promise<string>;

From server/src/services/live-events.ts:

export function publishLiveEvent(input: { companyId: string; type: LiveEventType; payload?: Record<string, unknown> }): LiveEvent;

From server/src/services/renderers/diagram-renderer.ts:

export function resolveBrowserPath(): string;
Task 1: Create presentation-renderer with LLM slide generation and Remotion render pipeline server/src/services/renderers/presentation-renderer.ts - server/src/services/renderers/brand-renderer.ts (full — LLM system prompt + JSON parse pattern) - server/src/services/renderers/wallpaper-renderer.ts (first 60 lines — simpler LLM prompt pattern) - server/src/services/renderers/diagram-renderer.ts (lines 90-107 — resolveBrowserPath) - server/src/services/puter-inference.ts (first 30 lines — puterChatComplete signature) - packages/content-renderer/src/compositions/PitchDeck.tsx (Slide interface) - packages/content-renderer/src/compositions/DemoVideo.tsx (DemoSlide interface) Create `server/src/services/renderers/presentation-renderer.ts`.
**Exports:** `renderPresentation(input: Record<string, unknown>, companyId: string, jobId: string): Promise<RenderResult>`

**Flow:**
1. Extract `input.prompt` (string), `input.videoType` ("pitch-deck" | "demo-video", default "pitch-deck"), `input.title` (optional string).
2. Call `puterChatComplete` with a system prompt that instructs the LLM to generate slide content as a JSON array. Use two different system prompts depending on `videoType`:

   For "pitch-deck": System prompt asks the LLM to generate a JSON array of `Slide` objects: `[{ "title": "...", "body": "...", "accent": "#hex" }]`. The prompt should request 6-10 slides covering: title slide, problem, solution, market, business model, traction, team, ask/closing. Each slide should have a distinct accent color.

   For "demo-video": System prompt asks the LLM to generate a JSON array of `DemoSlide` objects: `[{ "title": "...", "content": "...", "narration": "..." }]`. Request 4-8 slides covering: intro, key features, demo walkthrough, conclusion. Each slide should have a narration script.

   Use a helper `stripMarkdownFences(text: string): string` (define locally, same pattern as pdf-renderer) to clean LLM output before JSON.parse.

3. Parse LLM response as JSON. Wrap in try/catch — if parse fails, throw with a clear error including the raw response.

4. Build `inputProps` for Remotion: For pitch-deck: `{ slides, companyName: input.title || "Presentation" }`. For demo-video: `{ slides, title: input.title || "Demo" }`.

5. Call the content-renderer package via dynamic import:
   ```typescript
   const { getBundlePath, renderPresentationComposition } = await import("@paperclipai/content-renderer");
   ```

6. Resolve browser path: import `resolveBrowserPath` from `./diagram-renderer.js`. Wrap in try/catch — if Playwright Chromium not found, pass `undefined` (Remotion will auto-download).

7. Call `renderPresentationComposition({ serveUrl: await getBundlePath(), input: inputProps, onProgress, browserExecutable })` where `onProgress` publishes:
   ```typescript
   publishLiveEvent({
     companyId,
     type: "content_job.progress",
     payload: { jobId, progress },
   });
   ```

8. Build `PresentationBundle` from the result:
   ```typescript
   const bundle: PresentationBundle = {
     type: "presentation-bundle",
     presentationType: videoType === "demo" ? "demo-video" : "pitch-deck",
     title: input.title as string || "Presentation",
     slideCount: slides.length,
     durationInFrames: result.durationInFrames,
     fps: result.fps,
     mp4Base64: result.buffer.toString("base64"),
     inputProps,
   };
   ```

9. Return `RenderResult`: `{ filename: title.mp4, contentType: "application/json", buffer: Buffer.from(JSON.stringify(bundle)) }`. Use `application/json` as contentType because the bundle contains both the MP4 (base64) and metadata — same pattern as brand-renderer returns JSON bundles.
cd /opt/nexus && grep -c "renderPresentation" server/src/services/renderers/presentation-renderer.ts && grep -c "publishLiveEvent" server/src/services/renderers/presentation-renderer.ts && grep -c "content_job.progress" server/src/services/renderers/presentation-renderer.ts - grep -q "export async function renderPresentation" server/src/services/renderers/presentation-renderer.ts - grep -q "puterChatComplete" server/src/services/renderers/presentation-renderer.ts - grep -q "content_job.progress" server/src/services/renderers/presentation-renderer.ts - grep -q "publishLiveEvent" server/src/services/renderers/presentation-renderer.ts - grep -q "getBundlePath" server/src/services/renderers/presentation-renderer.ts - grep -q "renderPresentationComposition" server/src/services/renderers/presentation-renderer.ts - grep -q "resolveBrowserPath" server/src/services/renderers/presentation-renderer.ts - grep -q "concurrency" server/src/services/renderers/presentation-renderer.ts || grep -q "concurrency" packages/content-renderer/src/index.ts - grep -q "stripMarkdownFences" server/src/services/renderers/presentation-renderer.ts - grep -q "pitch-deck" server/src/services/renderers/presentation-renderer.ts - grep -q "demo-video" server/src/services/renderers/presentation-renderer.ts presentation-renderer.ts generates slide JSON via LLM, renders via Remotion with concurrency:1, publishes SSE progress events, reuses Playwright browser, returns PresentationBundle with mp4Base64 and inputProps Task 2: Verify server tsc compilation with presentation renderer wired server/src/services/content-job-runner.ts - server/src/services/content-job-runner.ts (verify the "presentation" case was added in Plan 01) Verify that the full server compiles with the new presentation renderer. Run `pnpm --filter @paperclipai/server exec -- npx tsc --noEmit`. If there are TypeScript errors: - If errors relate to the dynamic import of `@paperclipai/content-renderer`, ensure the workspace package is properly linked and its types are resolvable. May need to add `@paperclipai/content-renderer` as a dependency in `server/package.json` using `pnpm --filter @paperclipai/server add @paperclipai/content-renderer@workspace:*`. - If errors relate to `content_job.progress` not being in `LiveEventType`, verify Plan 01 Task 2 was completed (check `packages/shared/src/constants.ts`). - Fix any type mismatches between the renderer return type and `RenderResult`.
Also run `pnpm --filter @paperclipai/shared exec -- npx tsc --noEmit` to verify shared package compiles.

Do NOT attempt to run the actual Remotion render (that requires the full bundle pipeline) — just verify TypeScript compilation.
cd /opt/nexus && pnpm --filter @paperclipai/shared exec -- npx tsc --noEmit 2>&1 | tail -3 - pnpm --filter @paperclipai/shared exec -- npx tsc --noEmit exits 0 or shows no errors Server and shared packages compile without TypeScript errors, presentation renderer is type-correct - `grep -q "renderPresentation" server/src/services/renderers/presentation-renderer.ts` confirms renderer exists - `grep -q "content_job.progress" server/src/services/renderers/presentation-renderer.ts` confirms SSE progress - `grep -q "puterChatComplete" server/src/services/renderers/presentation-renderer.ts` confirms LLM integration - `pnpm --filter @paperclipai/shared exec -- npx tsc --noEmit` passes

<success_criteria>

  • presentation-renderer.ts exists with renderPresentation export
  • LLM generates slide JSON for both pitch-deck and demo-video types (PRES-01, PRES-03)
  • Remotion renderMedia is called with concurrency:1 (PRES-02)
  • onProgress publishes content_job.progress SSE events (PRES-04)
  • browserExecutable reuses Playwright Chromium
  • PresentationBundle includes mp4Base64 and inputProps for interactive Player replay
  • TypeScript compilation passes for server and shared packages </success_criteria>
After completion, create `.planning/phases/44-video-presentations/44-02-SUMMARY.md`