# SERP-1827: AI Creative Generator & Campaign Launch — Implementation Plan

## Context

`docs/TICKETS.md` (SERP-1827) specifies a large feature: generate image/video ad creatives from a product URL, prompt, uploaded images, an existing creative, or a competitor ad (Meta Ad Library), using AI avatars/voices and AI-written scripts/ad copy, then review/approve and launch them into a new or existing campaign.

The codebase is currently a fresh Laravel 13 skeleton — no models beyond `User`, no migrations beyond defaults, no controllers, no frontend framework beyond Blade/Vite/Tailwind, and no AI/ads packages installed (confirmed via exploration). `docs/TASKS.md` shows this ticket sits on top of not-yet-built foundational work (Task 02: normalized cross-platform campaign model, Task 03: platform adapter/MCP interface, Task 13: Meta adapter) — none of which exists yet. This plan therefore also stands up the minimal slice of that foundation needed to launch a creative into a campaign, without attempting the full 5-platform, 954-hour scope of TASKS.md.

**Confirmed decisions (from user):**
- AI SDK: `laravel/ai` (`composer require laravel/ai`) — verified real package via official docs. Provides Agents (text + structured output + tools + human-approval workflows), `Image::of()->generate()`, `Audio::of()->generate()` (TTS), `Transcription`, and `Embeddings`. **It does not do video generation.**
- Video-avatar rendering (talking-avatar video) and Meta Ad Library search are **vendor-specific** capabilities `laravel/ai` doesn't cover — build these behind swappable adapter interfaces, vendor TBD, so the pipeline is fully buildable/testable today with a fake/stub driver.
- Frontend: **Livewire + Alpine** (add `livewire/livewire`; nothing else installed yet).
- Scope: full architecture up front, phased so a thin end-to-end slice ships first (Phase 1 below), later phases add video/avatar/voice/competitor-ads/multi-platform.

---

## Architecture

### New packages
```bash
composer require laravel/ai
composer require livewire/livewire
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan migrate
```
Add provider API keys to `.env` (`OPENAI_API_KEY` at minimum for text+image+audio).

### Data model (new migrations under `database/migrations/`)
Use JSON columns for platform/provider-variable fields instead of a fully generalized EAV schema — pragmatic subset of TASKS.md Task 02, not the full cross-platform abstraction.

- **`avatars`**: `id, name, gender, accent, type[stock|uploaded], source_path, provider_ref, meta json`
- **`voices`**: `id, name, gender, accent, tone, provider, provider_voice_id, sample_path`
- **`creatives`**: `id, user_id, type[image|video], status[queued|generating|processing|completed|failed], source[prompt|product_url|upload|clone|variation|competitor], parent_creative_id nullable (self-ref for clone/variation), variation_group_id nullable (groups a batch requested together), ratio, duration nullable, style nullable, avatar_id nullable FK, voice_id nullable FK, product_url nullable, prompt text nullable, script json nullable, storyboard json nullable, asset_path nullable, thumbnail_path nullable, provider json (which laravel/ai provider + model used), failure_reason nullable, timestamps`
- **`ad_copies`**: `id, creative_id nullable FK, product_url nullable, prompt nullable, primary_text, headline, description, cta, angle, status[draft|approved], timestamps`
- **`competitor_ad_references`**: `id, user_id, platform, external_ad_id, snapshot json, thumbnail_path, search_query, timestamps`
- **`campaigns`**: `id, user_id, name, platform[meta|google|tiktok|taboola|linkedin], objective, budget, currency, status[draft|launched|failed], audience json, locations json, placements json, schedule json, external_campaign_id nullable, meta json, timestamps`
- **`campaign_creatives`** (pivot): `campaign_id FK, creative_id FK, ad_copy_id nullable FK, external_ad_ref nullable, status`

### Provider/adapter contracts (`app/Contracts/`, implementations in `app/Services/`)
This is the key extensibility layer — every external capability sits behind an interface so vendors can be swapped/added later without touching controllers/jobs.

| Contract | MVP implementation | Notes |
|---|---|---|
| `CreativeBriefResolver` | `LaravelAiCreativeBriefResolver` | Uses `laravel/ai` Agent w/ structured output to turn a product URL (fetch+strip HTML via `Illuminate\Http\Client`) or free-text prompt into a normalized `CreativeBrief` DTO (name, benefits, features, images, messaging, CTA) |
| `ImageCreativeGenerator` | `LaravelAiImageGenerator` | Wraps `Laravel\Ai\Image` |
| `ScriptGenerator` | `LaravelAiScriptGenerator` | Wraps Agent structured output → Hook/Problem/Product/Benefits/CTA; supports N-variation generation |
| `VoiceSynthesizer` | `LaravelAiVoiceSynthesizer` | Wraps `Laravel\Ai\Audio`; maps requested gender/accent/tone to nearest supported provider voice, documents the gap where provider doesn't support Indian-accent selection etc. |
| `VideoAvatarProvider` | `NullVideoAvatarProvider` (throws `ProviderNotConfiguredException`, marks creative `failed` with a clear reason) | Real implementation (HeyGen/Synthesia/D-ID/etc.) deferred — interface: `render(Avatar $avatar, Voice $voice, array $script, array $storyboard): VideoRenderResult` |
| `CompetitorAdProvider` | `MetaAdLibraryProvider` (Meta Graph API Ad Library endpoint, public search, cached) | Interface: `search(string $query): Collection<CompetitorAd>` |
| `AdCopyGenerator` | `LaravelAiAdCopyGenerator` | Agent structured output, N variations across fixed angle list (problem/benefit/feature/curiosity/offer-focused) |
| `PlatformAdapter` | `MetaAdapter` (create campaign + attach creative via Meta Marketing API) | Interface: `createCampaign(Campaign $c): string externalId`, `attachCreative(...)`, `publish(...)`; `GoogleAdapter`/`TikTokAdapter`/`TaboolaAdapter`/`LinkedInAdapter` = stub classes implementing the interface but throwing `PlatformNotImplementedException`, so the campaign UI can list all 5 platforms without blocking on every integration |

Bind contracts to implementations in a new `App\Providers\CreativeServiceProvider` (config-driven, e.g. `config('creative.video_provider')`), so swapping a driver later is a config change, not a rewrite.

### Jobs (queue, existing `QUEUE_CONNECTION=database`)
- `GenerateImageCreativeJob` — brief → `ImageCreativeGenerator` → store asset → status `completed`/`failed`
- `GenerateVideoCreativeJob` — brief → `ScriptGenerator` → storyboard (simple deterministic scene template, not AI for v1) → `VoiceSynthesizer` → `VideoAvatarProvider` → status
- `GenerateAdCopyJob` — brief → `AdCopyGenerator` → N `ad_copies` rows
Each job wraps its provider call, catches failures, sets `creatives.status='failed'` + `failure_reason`, and is safely retryable (idempotent — re-running a queued/failed job doesn't duplicate assets, matches section 16's per-creative retry requirement).

### Livewire components (`app/Livewire/`, one Blade wizard flow)
- `CreativeWizard` (multi-step: type → source → options → avatar/voice → quantity/style → summary → generate) — mirrors ticket sections 1,4-15
- `CreativeStatusPoller` — polls job status (sections 16)
- `CreativeReviewGrid` / `CreativeLibrary` — sections 17, 20
- `CompetitorAdSearch` — section 3
- `AdCopyGenerator` (Livewire component name distinct from service class) — section 22
- `CampaignLauncher` (existing-campaign attach flow + new-campaign flow) — sections 18-19

### Routes
All under `routes/web.php` behind `auth` middleware (auth scaffolding is not installed either — Laravel Fortify/Breeze needed; flagged as a prerequisite task, not silently assumed). No `routes/api.php` needed for MVP since Livewire handles interactivity server-side; add API routes later only if a mobile/external client is required.

---

## Build phases

**Phase 1 — Foundation (must land first)** — ✅ done
1. ✅ `composer require laravel/ai livewire/livewire`; published config/migrations. (Installed via `laravel/breeze --dev` + `breeze:install livewire`, which pinned `livewire/livewire` to `^3.6.4` + added `livewire/volt`.)
2. ✅ Auth scaffolding (Breeze + Livewire + Volt) — login/register/password-reset/email-verification routes and Volt pages in place.
3. ✅ All migrations + Eloquent models (`Creative`, `AdCopy`, `Avatar`, `Voice`, `Campaign`, `CampaignCreative`, `CompetitorAdReference`) with relationships.
4. ✅ Contracts (`app/Contracts/*`) + `CreativeServiceProvider` bindings; implemented `LaravelAiCreativeBriefResolver`, `LaravelAiImageGenerator`, `LaravelAiScriptGenerator`, `LaravelAiVoiceSynthesizer`, `LaravelAiAdCopyGenerator`, `NullVideoAvatarProvider`, `NullCompetitorAdProvider`, `MetaAdapter` (create-campaign/attach-creative/publish, gated on `META_ACCESS_TOKEN`/`META_AD_ACCOUNT_ID`), stub adapters for Google/TikTok/Taboola/LinkedIn via `UnimplementedPlatformAdapter` + `PlatformAdapterFactory`. All bindings verified resolving in `config/creative.php`.
5. ✅ `GenerateImageCreativeJob`, `GenerateAdCopyJob` — idempotent, catch-and-record `failure_reason`, covered by `tests/Feature/GenerateImageCreativeJobTest.php` (happy path + failure path, using `laravel/ai`'s `Image::fake()`/`StructuredAnonymousAgent::fake()`). Queue worker uses existing `QUEUE_CONNECTION=database`.

Also fixed a pre-existing test-env bug found along the way: `phpunit.xml` now overrides `APP_URL=http://localhost` — the real `.env`'s `APP_URL` includes the shared-hosting subpath, which broke every HTTP feature test's relative-path resolution (unrelated to this ticket, but was silently failing 24 of 28 tests before this).

**Phase 2 — Thin end-to-end MVP slice**
6. `CreativeWizard` Livewire flow for **image creatives only**, sourced from prompt or product URL (sections 1A/1B, 4, 5, 11, 15, 16, 17).
7. `AdCopyGenerator` component (section 22).
8. `CampaignLauncher`: create-new-campaign flow + attach-to-existing-campaign flow, launching via `MetaAdapter` only (sections 18, 19).
9. `CreativeLibrary` listing (section 20).
→ **Milestone: a user can type a prompt or paste a URL, get an AI image + AI ad copy, and launch it as a real Meta campaign.**

**Phase 3 — Clone/variation + competitor ads**
10. Clone/Create Variation for existing creatives (section 2).
11. `MetaAdLibraryProvider` + `CompetitorAdSearch` + swap-based variation (section 3).

**Phase 4 — Video/avatar/voice**
12. Avatar management (stock seed set + upload) and Voice management (section 7, 8) using `VoiceSynthesizer` (real) — audio-only is achievable now via `laravel/ai` `Audio`.
13. Storyboard generation (section 14) and `GenerateVideoCreativeJob` wired to `NullVideoAvatarProvider` initially (fails gracefully with a clear "video provider not configured" message) — swap in a real vendor adapter (HeyGen/Synthesia/D-ID) here once chosen, with zero changes to jobs/UI.

**Phase 5 — Multi-platform**
14. Replace stub `GoogleAdapter`/`TikTokAdapter`/`TaboolaAdapter`/`LinkedInAdapter` with real implementations (this is where TASKS.md Task 03/14-17 land) — out of this ticket's critical path but the interface is ready.

---

## Verification
- **Phase 1:** `php artisan migrate:fresh`, `php artisan tinker` to manually instantiate each service and confirm bindings resolve; unit test each `*Generator`/`*Adapter` against a faked `laravel/ai` response (package ships testing utilities per its docs).
- **Phase 2:** Feature test: submit a prompt via `CreativeWizard`, assert a queued job dispatches, run it, assert `creatives.status = completed` and an asset file exists in `storage/app/public`; feature test the full launch flow asserting a `campaigns` row + `campaign_creatives` pivot row is created and `MetaAdapter::createCampaign` was called (mock the Meta HTTP client, don't hit the real API in CI).
- Manually run `php artisan serve`, walk the wizard in-browser end to end (prompt → image → ad copy → launch to a test Meta ad account), confirm generation status UI transitions Queued→Generating→Completed and a failure (e.g. bad `.env` key) surfaces as `Failed` with a retry button, not a silent hang.
