Files
ClaudeMarketing/app/api/campaigns/[id]/launch/route.ts
T
Trey t 807dfc539b feat: add asset preferences, video research, and Remotion ad assets
- Add thumbs-down feedback modal and preference API endpoint
- Add AI UGC video platforms research doc
- Add ReflectAd Remotion composition with public flow assets
- Add gemini-ad-designer and poster-ad-designer pipeline skills
- Add research_reflect_v1.1 pipeline script

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 20:28:07 -05:00

92 lines
2.7 KiB
TypeScript

import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { buildCampaignPrompt, launchPipeline, type AppConfig } from "@/lib/claude";
import path from "path";
import { mkdirSync } from "fs";
export async function POST(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await auth();
if (!session) return new Response("Unauthorized", { status: 401 });
const { id } = await params;
const campaign = await prisma.campaign.findUnique({
where: { id },
include: { app: true },
});
if (!campaign) {
return Response.json({ error: "Not found" }, { status: 404 });
}
if (campaign.status !== "draft") {
return Response.json(
{ error: "Campaign is not in draft status" },
{ status: 400 }
);
}
// Build AppConfig from the campaign's app
let appConfig: AppConfig | undefined;
if (campaign.app) {
const app = campaign.app;
appConfig = {
name: app.name,
slug: app.slug,
primaryColor: app.primaryColor,
accentColor: app.accentColor,
darkBg: app.darkBg,
assetsDir: `apps/${app.slug}`,
brandIdentity: app.brandIdentity,
productInfo: app.productInfo,
platformGuidelines: app.platformGuidelines,
stylePreferences: app.stylePreferences,
};
}
const config = campaign.config ? JSON.parse(campaign.config) : {};
const pipelineRoot =
process.env.PIPELINE_ROOT || path.join(process.cwd(), "pipeline");
const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, "");
const taskName = campaign.name.replace(/\s+/g, "_").toLowerCase();
const outputPath = `outputs/${taskName}_${dateStr}`;
// Create output directories
const dirs = ["ads", "scripts", "video", "copy"];
for (const dir of dirs) {
mkdirSync(path.join(pipelineRoot, outputPath, dir), { recursive: true });
}
const prompt = buildCampaignPrompt(
{
name: campaign.name,
platforms: JSON.parse(campaign.platforms),
goal: config.goal || "brand awareness",
keyMessage: config.keyMessage || campaign.name,
socialProof: config.socialProof,
targetAudience: config.targetAudience,
visualDirection: config.visualDirection,
competitorApps: config.competitorApps,
variations: config.variations,
useTrendReport: config.useTrendReport,
screenshots: config.screenshots,
},
appConfig
);
await prisma.campaign.update({
where: { id },
data: { prompt, outputPath },
});
// Launch pipeline asynchronously — don't await
launchPipeline(id, prompt, pipelineRoot, appConfig).catch((err) =>
console.error(`Pipeline failed for campaign ${id}:`, err)
);
return Response.json({ status: "launched", outputPath });
}