Claude
Skills
Sign in
Back

ai-image-gen

Included with Lifetime
$97 forever

AI image generation with Replicate and fal.ai — text-to-image, image-to-image, inpainting with multiple model support. Use this skill when the user says "add image generation", "setup ai images", "add replicate", "text to image", or "ai image gen".

Image & Video

What this skill does


# AI Image Generation

Multi-provider AI image generation using [Replicate](https://replicate.com) and [fal.ai](https://fal.ai). Supports text-to-image, image-to-image, and inpainting across models like Flux, SDXL, and Ideogram. Generated images are stored permanently via the `storage` skill.

## Prerequisites

- Next.js app with App Router (with `src/` directory)
- `db` skill applied (Drizzle + Postgres)
- `env-config` skill applied
- `auth` skill applied
- `storage` skill applied (for permanent image storage)

## Installation

```bash
bun add replicate @fal-ai/client
```

## Environment Variables

Add to `.env.local`:

```env
# Image Generation Providers
REPLICATE_API_TOKEN=r8_...
FAL_KEY=fal_...
```

### Load from 1Password

If `REPLICATE_API_TOKEN` or `FAL_KEY` are empty or placeholders in `.env.local`, load them from 1Password:

```bash
# Load from 1Password (uses env-from-1password skill)
op item get "Dev Environment" --account=my.1password.com --fields label=notesPlain | tr -d '"' > /tmp/op-env.txt
grep -E "REPLICATE_API_TOKEN|FAL_KEY" /tmp/op-env.txt >> .env.local && rm /tmp/op-env.txt
```

Or use the `/env-from-1password` skill to load all keys at once — it writes `FAL_KEY`, `REPLICATE_API_TOKEN`, and other service keys to `.env.local`.

Add to `env.ts` server schema:

```typescript
REPLICATE_API_TOKEN: z.string().optional(),
FAL_KEY: z.string().optional(),
```

## What Gets Created

```
src/
├── app/
│   └── api/
│       └── ai/
│           └── images/
│               ├── route.ts                # POST generate, GET list generations
│               └── [id]/
│                   └── route.ts            # GET generation status/result
├── lib/
│   ├── ai/
│   │   └── image-gen/
│   │       ├── types.ts                    # ImageGenRequest, ImageGenResult, ModelConfig
│   │       ├── models.ts                   # Model registry: name → provider + model ID + defaults
│   │       ├── providers/
│   │       │   ├── replicate.ts            # Replicate API client
│   │       │   └── fal.ts                  # fal.ai client
│   │       └── generate.ts                 # Unified generate() — picks provider, submits, stores
│   └── db/
│       └── schema/
│           └── generations.ts              # Drizzle schema: generations table
```

## Setup Steps

### Step 1: Create `src/lib/db/schema/generations.ts`

```typescript
import { pgTable, text, integer, timestamp, jsonb, uuid } from "drizzle-orm/pg-core";

export const generations = pgTable("generations", {
  id: uuid("id").primaryKey().defaultRandom(),
  userId: text("user_id").notNull(),
  type: text("type", {
    enum: ["text-to-image", "image-to-image", "inpainting"],
  }).notNull(),
  status: text("status", {
    enum: ["pending", "processing", "completed", "failed"],
  }).notNull().default("pending"),
  provider: text("provider").notNull(),
  model: text("model").notNull(),
  prompt: text("prompt").notNull(),
  negativePrompt: text("negative_prompt"),
  width: integer("width").default(1024),
  height: integer("height").default(1024),
  seed: integer("seed"),
  inputImageUrl: text("input_image_url"),
  maskImageUrl: text("mask_image_url"),
  resultUrl: text("result_url"),
  providerJobId: text("provider_job_id"),
  metadata: jsonb("metadata"),
  error: text("error"),
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
  completedAt: timestamp("completed_at", { withTimezone: true }),
});

export type Generation = typeof generations.$inferSelect;
export type NewGeneration = typeof generations.$inferInsert;
```

### Step 2: Add export to `src/lib/db/schema/index.ts`

```typescript
export * from "./generations";
```

### Step 3: Create `src/lib/ai/image-gen/types.ts`

```typescript
export type ImageProvider = "replicate" | "fal";

export type ImageGenRequest = {
  prompt: string;
  negativePrompt?: string;
  model?: string;
  width?: number;
  height?: number;
  seed?: number;
  inputImageUrl?: string;
  maskImageUrl?: string;
  numOutputs?: number;
  guidanceScale?: number;
};

export type ImageGenResult = {
  url: string;
  seed?: number;
  width: number;
  height: number;
  provider: ImageProvider;
  model: string;
  providerJobId: string;
};

export type ModelConfig = {
  provider: ImageProvider;
  modelId: string;
  name: string;
  description: string;
  defaults: {
    width: number;
    height: number;
    guidanceScale: number;
  };
  capabilities: ("text-to-image" | "image-to-image" | "inpainting")[];
};
```

### Step 4: Create `src/lib/ai/image-gen/models.ts`

```typescript
import type { ModelConfig } from "./types";

export const IMAGE_MODELS: Record<string, ModelConfig> = {
  "flux-schnell": {
    provider: "replicate",
    modelId: "black-forest-labs/flux-schnell",
    name: "Flux Schnell",
    description: "Fast generation, good quality",
    defaults: { width: 1024, height: 1024, guidanceScale: 3.5 },
    capabilities: ["text-to-image"],
  },
  "flux-dev": {
    provider: "replicate",
    modelId: "black-forest-labs/flux-dev",
    name: "Flux Dev",
    description: "High quality, slower",
    defaults: { width: 1024, height: 1024, guidanceScale: 3.5 },
    capabilities: ["text-to-image", "image-to-image"],
  },
  "flux-pro": {
    provider: "fal",
    modelId: "fal-ai/flux-pro/v1.1",
    name: "Flux Pro",
    description: "Best quality Flux model",
    defaults: { width: 1024, height: 1024, guidanceScale: 3.5 },
    capabilities: ["text-to-image", "image-to-image"],
  },
  "sdxl": {
    provider: "replicate",
    modelId: "stability-ai/sdxl:7762fd07cf82c948538e41f63f77d685e02b063e37e496e96eefd46c929f9bdc",
    name: "Stable Diffusion XL",
    description: "Versatile, supports inpainting",
    defaults: { width: 1024, height: 1024, guidanceScale: 7.5 },
    capabilities: ["text-to-image", "image-to-image", "inpainting"],
  },
};

export const DEFAULT_MODEL = "flux-schnell";

export function getModelConfig(modelName?: string): ModelConfig {
  const name = modelName ?? DEFAULT_MODEL;
  const config = IMAGE_MODELS[name];
  if (!config) {
    throw new Error(`Unknown model: ${name}. Available: ${Object.keys(IMAGE_MODELS).join(", ")}`);
  }
  return config;
}
```

### Step 5: Create `src/lib/ai/image-gen/providers/replicate.ts`

```typescript
import Replicate from "replicate";

const replicate = new Replicate();

export async function generateWithReplicate(params: {
  modelId: string;
  prompt: string;
  negativePrompt?: string;
  width?: number;
  height?: number;
  seed?: number;
  inputImageUrl?: string;
  maskImageUrl?: string;
  guidanceScale?: number;
  numOutputs?: number;
}): Promise<{ urls: string[]; predictionId: string }> {
  const input: Record<string, unknown> = {
    prompt: params.prompt,
    width: params.width ?? 1024,
    height: params.height ?? 1024,
    num_outputs: params.numOutputs ?? 1,
  };

  if (params.negativePrompt) input.negative_prompt = params.negativePrompt;
  if (params.seed !== undefined) input.seed = params.seed;
  if (params.guidanceScale !== undefined) input.guidance_scale = params.guidanceScale;
  if (params.inputImageUrl) input.image = params.inputImageUrl;
  if (params.maskImageUrl) input.mask = params.maskImageUrl;

  const prediction = await replicate.predictions.create({
    model: params.modelId as `${string}/${string}`,
    input,
  });

  // Poll for completion
  let result = prediction;
  while (result.status !== "succeeded" && result.status !== "failed") {
    await new Promise((resolve) => setTimeout(resolve, 1000));
    result = await replicate.predictions.get(prediction.id);
  }

  if (result.status === "failed") {
    throw new Error(`Replicate prediction failed: ${result.error}`);
  }

  const output = result.output;
  const urls = Array.isArray(output) ? (output as string[]) : [String(output)];

  return { urls, predictionId: prediction.id };
}
```

### Step 6: Create `src/lib/ai/image-gen/providers/fal.ts`

```typescript
import { fal } from "@fal-ai/client";

export async function generateWithFal(params
Files: 1
Size: 17.5 KB
Complexity: 27/100
Category: Image & Video

Related in Image & Video