2ab8af64d4
Variation spawner: select an image asset → spawn N variations that keep the same emotional pattern/visual style but explore different hook angles. Runs a focused ad-creative-designer agent with the original as a Gemini reference image. New assets link back via parentAssetId. Content repurposing: resize an image to all other platform dimensions using Sharp (cover crop). Captions are re-toned for the target platform via Claude CLI. No external APIs needed — fully local. - Add parentAssetId self-relation to Asset model - lib/repurpose.ts: Sharp resize, platform format mapping, caption re-toning - lib/variations.ts: asset DNA extraction, variation prompt builder, mini-pipeline - API routes: /api/assets/[id]/repurpose (GET formats, POST resize) - API routes: /api/assets/[id]/variations (GET existing, POST spawn) - Repurpose modal: checkbox list of target formats - Variation modal: count picker, async launch - Asset card: Repurpose + Variations buttons on image assets - Asset lineage: "Derived from" shown on child assets Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
import { auth } from "@/lib/auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { getActiveAppId } from "@/lib/active-app";
|
|
|
|
export async function GET(request: Request) {
|
|
const session = await auth();
|
|
if (!session) return new Response("Unauthorized", { status: 401 });
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const campaignId = searchParams.get("campaignId");
|
|
const type = searchParams.get("type");
|
|
const platform = searchParams.get("platform");
|
|
const status = searchParams.get("status");
|
|
const search = searchParams.get("search");
|
|
|
|
const appId = await getActiveAppId();
|
|
|
|
const where: Record<string, unknown> = {};
|
|
if (campaignId) where.campaignId = campaignId;
|
|
if (type && type !== "all") where.type = type;
|
|
if (platform && platform !== "all") where.platform = platform;
|
|
if (status && status !== "all") where.status = status;
|
|
if (search) {
|
|
where.OR = [
|
|
{ fileName: { contains: search } },
|
|
{ metadata: { contains: search } },
|
|
];
|
|
}
|
|
|
|
// Filter by active app's campaigns
|
|
if (appId) {
|
|
where.campaign = { ...((where.campaign as object) || {}), appId };
|
|
}
|
|
|
|
const assets = await prisma.asset.findMany({
|
|
where,
|
|
orderBy: { createdAt: "desc" },
|
|
include: {
|
|
campaign: { select: { name: true } },
|
|
parentAsset: { select: { id: true, fileName: true } },
|
|
},
|
|
});
|
|
|
|
return Response.json(assets);
|
|
}
|