From 63f747096749ad4f5667f6f2b917108b8ac0ec41 Mon Sep 17 00:00:00 2001 From: cawcenter Date: Mon, 15 Dec 2025 01:56:43 -0500 Subject: [PATCH] feat: content generation UI, test script, and API docs --- CONTENT_GENERATION_API.md | 176 +++++++++++++++++++++ scripts/test-campaign.js | 76 +++++++++ src/lib/queue/config.ts | 4 + src/pages/admin/content-generator.astro | 172 ++++++++++++++++++++ src/pages/api/god/campaigns/create.ts | 2 +- src/pages/api/god/campaigns/launch/[id].ts | 2 +- src/pages/api/god/campaigns/status/[id].ts | 2 +- src/workers/contentGenerator.ts | 2 +- 8 files changed, 432 insertions(+), 4 deletions(-) create mode 100644 CONTENT_GENERATION_API.md create mode 100644 scripts/test-campaign.js create mode 100644 src/pages/admin/content-generator.astro diff --git a/CONTENT_GENERATION_API.md b/CONTENT_GENERATION_API.md new file mode 100644 index 0000000..4161ca3 --- /dev/null +++ b/CONTENT_GENERATION_API.md @@ -0,0 +1,176 @@ +# Content Generation Engine - API Documentation + +## ๐ŸŽฏ Overview + +The Content Generation Engine takes JSON blueprints containing spintax and variables, generates unique combinations via Cartesian expansion, resolves spintax to create unique variations, and produces full 2000-word articles. + +## ๐Ÿ“ก API Endpoints + +### 1. Create Campaign +**POST** `/api/god/campaigns/create` + +Creates a new campaign from a JSON blueprint. + +**Headers:** +``` +X-God-Token: +Content-Type: application/json +``` + +**Request Body:** +```json +{ + "name": "Campaign Name (optional)", + "blueprint": { + "asset_name": "{{CITY}} Solar Revenue", + "deployment_target": "High-Value Funnel", + "variables": { + "STATE": "California", + "CITY": "San Diego|Irvine|Anaheim", + "AVATAR_A": "Solar CEO" + }, + "content": { + "url_path": "{{CITY}}.example.com", + "meta_description": "{Stop|Eliminate} waste in {{CITY}}", + "body": [ + { + "block_type": "Hero", + "content": "

{Title A|Title B}

Content for {{CITY}}

" + } + ] + } + } +} +``` + +**Response:** +```json +{ + "success": true, + "campaignId": "uuid", + "message": "Campaign created. Use /launch/ to generate." +} +``` + +--- + +### 2. Launch Campaign +**POST** `/api/god/campaigns/launch/:id` + +Queues a campaign for content generation. + +**Headers:** +``` +X-God-Token: +``` + +**Response:** +```json +{ + "success": true, + "campaignId": "uuid", + "status": "processing" +} +``` + +--- + +### 3. Check Status +**GET** `/api/god/campaigns/status/:id` + +Get campaign generation status and stats. + +**Headers:** +``` +X-God-Token: +``` + +**Response:** +```json +{ + "campaignId": "uuid", + "name": "Campaign Name", + "status": "completed", + "postsCreated": 15, + "blockUsage": [ + { "block_type": "Hero", "total_uses": 15 } + ] +} +``` + +--- + +## ๐ŸŽจ Blueprint Structure + +### Variables +Pipe-separated values generate Cartesian products: +```json +{ + "CITY": "A|B|C", // 3 values + "STATE": "X|Y" // 2 values + // = 6 total combinations +} +``` + +### Spintax Syntax +``` +{Option A|Option B|Option C} +``` + +### Variable Placeholders +``` +{{VARIABLE_NAME}} +``` + +## ๐Ÿ“Š Usage Tracking + +Every block use and spintax choice is tracked: +- `block_usage_stats` - How many times each block was used +- `spintax_variation_stats` - Which spintax choices were selected +- `variation_registry` - Hash of every unique variation + +## ๐Ÿ”ง Worker Process + +The BullMQ worker (`contentGenerator.ts`): +1. Fetches blueprint from DB +2. Generates Cartesian product of variables +3. For each combination: + - Expands `{{VARIABLES}}` + - Resolves `{spin|tax}` + - Checks uniqueness hash + - Creates post in DB + - Records variation & updates stats + +## ๐Ÿš€ Quick Start + +### Via UI +1. Go to `/admin/content-generator` +2. Paste JSON blueprint +3. Click "Create Campaign" +4. Launch from campaigns list + +### Via API +```bash +# 1. Create +curl -X POST https://spark.jumpstartscaling.com/api/god/campaigns/create \ + -H "X-God-Token: YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d @blueprint.json + +# 2. Launch +curl -X POST https://spark.jumpstartscaling.com/api/god/campaigns/launch/CAMPAIGN_ID \ + -H "X-God-Token: YOUR_TOKEN" + +# 3. Check status +curl https://spark.jumpstartscaling.com/api/god/campaigns/status/CAMPAIGN_ID \ + -H "X-God-Token: YOUR_TOKEN" +``` + +## ๐Ÿ“ฆ Database Schema + +Key tables: +- `campaign_masters` - Stores blueprints +- `content_fragments` - Individual blocks +- `variation_registry` - Unique variations +- `block_usage_stats` - Block usage counts +- `posts` - Final generated content diff --git a/scripts/test-campaign.js b/scripts/test-campaign.js new file mode 100644 index 0000000..6e4342b --- /dev/null +++ b/scripts/test-campaign.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +/** + * CLI Test Runner for Content Generation + * Usage: node scripts/test-campaign.js + */ + +import { pool } from '../src/lib/db.js'; +import { batchQueue } from '../src/lib/queue/config.js'; + +const testBlueprint = { + "asset_name": "{{CITY}} Test Campaign", + "deployment_target": "Test Funnel", + "variables": { + "STATE": "California", + "CITY": "San Diego|Irvine", + "AVATAR_A": "Solar CEO" + }, + "content": { + "url_path": "{{CITY}}.test.com", + "meta_description": "Test campaign for {{CITY}}", + "body": [ + { + "block_type": "Hero", + "content": "

{Welcome|Hello} to {{CITY}}

This is a {test|demo} for {{AVATAR_A}}s.

" + } + ] + } +}; + +async function runTest() { + console.log('๐Ÿงช Content Generation Test\n'); + + try { + // 1. Get site ID + const siteResult = await pool.query( + `SELECT id FROM sites WHERE domain = 'spark.jumpstartscaling.com' LIMIT 1` + ); + + if (siteResult.rows.length === 0) { + throw new Error('Admin site not found'); + } + + const siteId = siteResult.rows[0].id; + console.log(`โœ“ Site ID: ${siteId}`); + + // 2. Create campaign + const campaignResult = await pool.query( + `INSERT INTO campaign_masters (site_id, name, blueprint_json, status) + VALUES ($1, $2, $3, 'pending') + RETURNING id`, + [siteId, 'Test Campaign', JSON.stringify(testBlueprint)] + ); + + const campaignId = campaignResult.rows[0].id; + console.log(`โœ“ Campaign created: ${campaignId}`); + + // 3. Queue job + await batchQueue.add('generate_campaign_content', { + campaignId, + campaignName: 'Test Campaign' + }); + + console.log(`โœ“ Job queued for campaign ${campaignId}`); + console.log('\n๐Ÿ“Š Expected output: 2 posts (San Diego, Irvine)'); + console.log('๐Ÿ” Check generation_jobs table for status'); + + } catch (error) { + console.error('โŒ Test failed:', error.message); + process.exit(1); + } finally { + await pool.end(); + process.exit(0); + } +} + +runTest(); diff --git a/src/lib/queue/config.ts b/src/lib/queue/config.ts index bfc3305..96e2662 100644 --- a/src/lib/queue/config.ts +++ b/src/lib/queue/config.ts @@ -59,4 +59,8 @@ export const queues = { cleanup: new Queue('cleanup', queueOptions), }; +// Batch queue for campaign generation +export const batchQueue = new Queue('generate_campaign_content', queueOptions); + export { connection }; +export const redisConnection = connection; diff --git a/src/pages/admin/content-generator.astro b/src/pages/admin/content-generator.astro new file mode 100644 index 0000000..a1b26ee --- /dev/null +++ b/src/pages/admin/content-generator.astro @@ -0,0 +1,172 @@ +--- +import AdminLayout from '../../../layouts/AdminLayout.astro'; +import PageHeader from '../../../components/admin/PageHeader.astro'; +--- + + +
+ + + +
+

๐Ÿ“ Submit Campaign Blueprint

+ +
+
+ + +
+ +
+ + +
+ +
+ + + +
+
+
+ + +
+

โšก Active Campaigns

+
+

No campaigns yet. Create one above.

+
+
+ + +
+

๐Ÿ“Š Generation Stats

+
+
+
Total Campaigns
+
0
+
+
+
Posts Created
+
0
+
+
+
Blocks Used
+
0
+
+
+
Variations
+
0
+
+
+
+
+
+ + diff --git a/src/pages/api/god/campaigns/create.ts b/src/pages/api/god/campaigns/create.ts index d26db9c..c2a834c 100644 --- a/src/pages/api/god/campaigns/create.ts +++ b/src/pages/api/god/campaigns/create.ts @@ -1,6 +1,6 @@ // API Endpoint: POST /api/god/campaigns/create import type { APIRoute } from 'astro'; -import { pool } from '../../../../lib/db/db'; +import { pool } from '../../../../lib/db'; import crypto from 'crypto'; interface CampaignBlueprint { diff --git a/src/pages/api/god/campaigns/launch/[id].ts b/src/pages/api/god/campaigns/launch/[id].ts index d08fe88..1330446 100644 --- a/src/pages/api/god/campaigns/launch/[id].ts +++ b/src/pages/api/god/campaigns/launch/[id].ts @@ -1,6 +1,6 @@ // API Endpoint: POST /api/god/campaigns/launch/[id] import type { APIRoute } from 'astro'; -import { pool } from '../../../../../lib/db/db'; +import { pool } from '../../../../../lib/db'; import { batchQueue } from '../../../../../lib/queue/config'; export const POST: APIRoute = async ({ params, request }) => { diff --git a/src/pages/api/god/campaigns/status/[id].ts b/src/pages/api/god/campaigns/status/[id].ts index aae7329..9393e28 100644 --- a/src/pages/api/god/campaigns/status/[id].ts +++ b/src/pages/api/god/campaigns/status/[id].ts @@ -1,6 +1,6 @@ // API Endpoint: GET /api/god/campaigns/status/[id] import type { APIRoute } from 'astro'; -import { pool } from '../../../../../lib/db/db'; +import { pool } from '../../../../../lib/db'; export const GET: APIRoute = async ({ params, request }) => { try { diff --git a/src/workers/contentGenerator.ts b/src/workers/contentGenerator.ts index bfd9760..0ea509b 100644 --- a/src/workers/contentGenerator.ts +++ b/src/workers/contentGenerator.ts @@ -1,6 +1,6 @@ // BullMQ Worker: Content Generator import { Worker } from 'bullmq'; -import { pool } from '../lib/db/db'; +import { pool } from '../lib/db'; import { redisConnection } from '../lib/queue/config'; import { SpintaxResolver, expandVariables, generateCartesianProduct } from '../lib/spintax/resolver'; import crypto from 'crypto';