12 KiB
12 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 44-video-presentations | 02 | execute | 2 |
|
|
true |
|
|
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>