AI Generation
A generic, provider-agnostic generation pipeline lives in src/domains/generation/. Today it ships a Replicate runner; the pipeline executor itself is pure and accepts any RunStep.
Anatomy
src/lib/replicate.ts # root Replicate client (injected; never imported by domains)
src/domains/generation/
├── lib/
│ ├── runner.ts # RunStep type + createReplicateRunner (FileOutput normalization)
│ └── pipeline.ts # executePipeline — sequential, chainAs injection, DI
├── schemas.ts # PipelineStepSchema, GenerateAIInputSchema
└── components/
└── GenerateForm # client form (used on the test page)The generateAI server action
Exposed to any interface (form, MCP tool, REST endpoint).
import { generateAI } from '@/actions/generate'
// Single-step (most common)
await generateAI({
steps: [
{ model: 'black-forest-labs/flux-schnell', input: { prompt: 'a cat' } },
],
paywallConfig: { enableCredits: true, credits: 1 }, // or null for free
})
// Multi-step with chaining
await generateAI({
steps: [
{ model: 'black-forest-labs/flux-schnell', input: { prompt: 'a cat' } },
{ model: 'some/upscaler', input: { scale: 2 }, chainAs: 'image' },
],
paywallConfig: null,
})Returns: { success, results, finalOutput, creditsRemaining }.
chainAs — explicit step chaining
chainAs is the explicit key under which the previous step’s output gets injected into the next step’s input. It replaces any magic {ai_input} convention.
- Model-specific and debuggable:
chainAs: 'image',chainAs: 'init_image',chainAs: 'audio', etc. - Ignored on step 0.
- Lets you compose any pair of Replicate models without a custom adapter per pair.
Paywall integration
The paywallConfig argument flows through the same paywall system used by gated content (see Monetization & Payments):
{ enableCredits: true, credits: N }— require and decrement N credits per generation.{ enableSubscription: true, minimum_tier_rank: 2 }— gate behind a subscription tier.null— free.
If the paywall check fails, generateAI returns { success: false, reason: 'paywall' } without calling Replicate.
On-demand vs. pre-generated
The pipeline supports both modes:
- On-demand — user fills a form (or an agent calls
generateAI), pays the credit cost, and the model runs. Output is returned and (optionally) persisted as arecordsrow. - Pre-generated — a batch job runs the pipeline against a list of seed prompts/keywords overnight, writes outputs to
records, and the/explorebrowse surface presents them at zero per-request cost.
The “AI Asset Library” shape uses both: a seed batch for SEO inventory, plus on-demand generation triggered by users via the credit system.
Next: aigenerator-section-block
A Payload block that stores model config + userInputs field definitions + paywallField. At render time it resolves {variableName} templates from user form input and calls generateAI with resolved steps. Template resolution is the block’s job — the pipeline always receives already-resolved inputs.
Provider-agnostic by design
executePipeline is pure and DI-driven. Adding another provider (OpenAI Images, Anthropic Files, fal.ai, …) is:
- Build a runner:
createMyProviderRunner(client) → RunStep - Register it alongside
createReplicateRunnerinsrc/lib/ - The pipeline doesn’t change.
Reference
- Server action:
src/actions/generate.ts - Replicate client:
src/lib/replicate.ts - Runner factory:
src/domains/generation/lib/runner.ts - Pipeline executor:
src/domains/generation/lib/pipeline.ts - Schemas:
src/domains/generation/schemas.ts