# AI Video Studio — Session Handoff (2026-06-25)

**Branch:** `dev-chen` — **everything below is implemented + tested but NOT committed.** Test DB `petav3_testing`; dev DB migrated. Full Video suite **230 green**; full suite has only the 7 pre-existing unrelated failures (Leads/Membership/Portal/Concierge/Zoom); `npm run build` clean.

Builds on the two plan docs: [`ai-video-chat-draft-plan.md`](ai-video-chat-draft-plan.md), [`ai-video-presenter-and-toggles-plan.md`](ai-video-presenter-and-toggles-plan.md).

## What shipped this session

1. **Chat-to-storyboard (project page).** Empty project opens straight into a guided AI chat: one-question-at-a-time interview, asks for photos, attachments staged in the composer and uploaded on send → project pool, vision summary per photo (cheap text on later turns), produces a readable 方案 (structured scenes), refine in plain language, **就这么定** → commits scenes to a draft → StoryboardStudio (one-way hand-off). Chat uploads are **batch + PDF** (PDF → extracted room photos) and **deduped by content hash** (re-uploading the same file no longer piles up).

2. **Voice & caption toggles.** Whole-video `voiceover_enabled` / `captions_enabled` on the generation; gated in `GenerateVideoJob` + `RepackageVideoJob`; persisted via `updateStoryboard`/`repackage` (+ `VideoGenerationRepository::setOptions`); two switches in StoryboardStudio.

3. **Custom presenter library** (`video_presenters` table, per-user, single-active). Create from a photo+name → vision description → a **synthetic Seedream portrait** is the reference fed to Seedance (real faces are blocked by BytePlus; AI portraits are not). Dedicated "AI Presenters" page nested under the **AI Video** sidebar group. Active presenter's portrait is locked onto the cut at draft/commit and fed as a 2nd reference image (`@Image2`) per scene; falls back to text persona / template when none.

4. **No-presenter "luxe" caption style** (mentor's reference look). Default for no-presenter videos: gold **Playfair serif** headline + white UPPERCASE sub-line (`video_scenes.subtitle` column + `CaptionRenderer` `luxe` variant), English two-tier captions, narrative arc (exterior → rooms → amenities → **price + CTA** closing with `From RM —` placeholder), **silent by default** (voiceover text still written so the toggle works), scene count opened to 12.

5. **"Room-only" button relabeled → "No presenter".**

## Bug fixes this session
- **Seedream 401** — `SeedreamClient::apiKey()` fell back with `config('seedream', config('seedance'))`; Laravel `config($k,$default)` returns null (not the default) for a present-but-null key, so an unset `SEEDREAM_API_KEY` yielded an empty key. Fixed with `?:`. The ARK key + Seedream model are otherwise valid (verified direct ARK 200).
- **502 on Generate AI portrait** — portrait generation was synchronous (Seedream + download + GCS) → php-fpm timeout. Moved to a queued job `GeneratePresenterPortraitJob`; UI shows a spinner and polls (Inertia partial reload) until the new portrait lands, then swaps the image. Removed the lingering flash (polling re-delivered it).
- **Portrait never generated** — the new job defaulted to the **database** queue, but only **Horizon (redis)** workers run. Routed it to `redis-ai`/`ai` (matching the video jobs' `redis-video`/`video`). Cleared 4 stuck DB jobs.

## Outstanding / next session
- **Commit** the whole session to `dev-chen` (still uncommitted); PR to Lee Jie needs Chen's approval.
- **Restart Horizon** after pulling (workers don't hot-reload code) so the AI supervisor has the new job + SeedreamClient fix.
- **Chen e2e + real-run validation** (cost real ARK): (a) luxe video ≈ 90% similar to the reference (gold serif two-tier captions + silent + price/CTA); (b) does Seedance actually lock the synthetic-portrait face across scenes via `@Image2` (the one thing not verifiable from code/docs — may need BytePlus virtual-portrait asset-id if weak).
- **Playfair Display** font downloaded to `resources/video-assets/fonts/PlayfairDisplay.ttf` — must be committed for prod rendering.
- Closing price is a placeholder; edited in StoryboardStudio. Per-scene subtitle editable in SceneCard.
