feat: integrate templates with pipeline creation

- POST /api/pipelines now accepts templateId + customization
- Added createPipelineWithTemplate() function
- Template config stored in pipeline.metadata
- Stage inputs pre-populated from template config
- Schema: added templateId and metadata fields to Pipeline

Usage:
POST /api/pipelines
{
  "topic": "Why cats rule",
  "templateId": "reddit-minecraft",
  "customization": {
    "voice": { "narrator": { "speed": 1.2 } }
  }
}
This commit is contained in:
Chora
2026-02-02 19:56:44 -06:00
parent 831ceeb2aa
commit f9c14c33df
3 changed files with 117 additions and 2 deletions

View File

@@ -13,6 +13,8 @@ model Pipeline {
description String?
status PipelineStatus @default(DRAFT)
currentStage StageName @default(RESEARCH)
templateId String? // Reference to template used
metadata Json? // Template config and customizations
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
stages Stage[]
@@ -20,6 +22,7 @@ model Pipeline {
@@index([status])
@@index([createdAt])
@@index([templateId])
}
model Stage {

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'
import { createPipeline, listPipelines } from '@/lib/pipeline/manager'
import { createPipeline, listPipelines, createPipelineWithTemplate } from '@/lib/pipeline/manager'
import { getVideoTemplate, TemplatePipelineRequestSchema } from '@/lib/templates'
// GET /api/pipelines - List all pipelines
export async function GET(request: NextRequest) {
@@ -27,7 +28,7 @@ export async function GET(request: NextRequest) {
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { topic, description } = body
const { topic, description, templateId, customization, platforms, targetDuration } = body
if (!topic) {
return NextResponse.json(
@@ -36,6 +37,31 @@ export async function POST(request: NextRequest) {
)
}
// If templateId provided, create with template
if (templateId) {
const template = getVideoTemplate(templateId)
if (!template) {
return NextResponse.json(
{ error: `Template not found: ${templateId}` },
{ status: 404 }
)
}
const pipeline = await createPipelineWithTemplate(
topic,
template,
{
description,
customization,
platforms: platforms || template.platforms,
targetDuration,
}
)
return NextResponse.json(pipeline, { status: 201 })
}
// Otherwise create basic pipeline
const pipeline = await createPipeline(topic, description)
return NextResponse.json(pipeline, { status: 201 })

View File

@@ -1,6 +1,7 @@
import prisma from '@/lib/db/client'
import { PipelineStatus, StageStatus, StageName, Pipeline, Stage } from '@prisma/client'
import { STAGE_WEIGHTS } from '@/lib/stages/types'
import { VideoTemplate, TemplateCustomization, Platform } from '@/lib/templates'
// Stage execution order
export const STAGE_ORDER: StageName[] = [
@@ -51,6 +52,91 @@ export async function createPipeline(topic: string, description?: string): Promi
})
}
// Create pipeline with template configuration
export interface TemplatePipelineOptions {
description?: string
customization?: TemplateCustomization
platforms?: Platform[]
targetDuration?: number
}
export async function createPipelineWithTemplate(
topic: string,
template: VideoTemplate,
options: TemplatePipelineOptions = {}
): Promise<Pipeline & { stages: Stage[] }> {
// Merge template with customization
const mergedConfig = {
voice: { ...template.voice, ...options.customization?.voice },
visuals: { ...template.visuals, ...options.customization?.visuals },
audio: { ...template.audio, ...options.customization?.audio },
layout: { ...template.layout, ...options.customization?.layout },
structure: { ...template.structure, ...options.customization?.structure },
platforms: options.platforms || template.platforms,
targetDuration: options.targetDuration,
}
return prisma.pipeline.create({
data: {
topic,
description: options.description,
status: 'DRAFT',
currentStage: 'RESEARCH',
templateId: template.id,
metadata: mergedConfig as any,
stages: {
create: STAGE_ORDER.map(name => ({
name,
status: 'PENDING',
// Store relevant config for each stage
input: getStageInputFromTemplate(name, mergedConfig) as any,
}))
}
},
include: { stages: true }
})
}
// Extract stage-specific config from template
function getStageInputFromTemplate(stageName: StageName, config: any): Record<string, unknown> {
switch (stageName) {
case 'RESEARCH':
return {
structure: config.structure,
targetDuration: config.targetDuration,
}
case 'SCRIPT':
return {
structure: config.structure,
voice: config.voice,
}
case 'VOICE':
return {
voice: config.voice,
}
case 'MUSIC':
return {
audio: config.audio,
}
case 'VISUAL':
return {
visuals: config.visuals,
}
case 'EDITOR':
return {
layout: config.layout,
visuals: config.visuals,
}
case 'PUBLISH':
return {
platforms: config.platforms,
layout: config.layout,
}
default:
return {}
}
}
// Start pipeline execution
export async function startPipeline(pipelineId: string): Promise<Pipeline> {
return prisma.pipeline.update({