Bỏ qua để vào nội dung chính
Build a DALL-E 3 Image Generator App With Next.js

Build a DALL-E 3 Image Generator App With Next.js

Bởi Mia Thompson
15 thg 3, 20266 phút đọc

DALL-E 3 produces stunning images from text prompts. Build a polished image generation app with prompt history, style presets, and image gallery using Next.js and the OpenAI API.

What We're Building

A full-stack image generator with: a prompt input with style presets, image generation via DALL-E 3, a gallery of generated images stored in Postgres, and download functionality. This is a practical starting point for any AI-powered image feature.

The Generation API Route

// app/api/generate-image/route.ts
import OpenAI from 'openai';
import { db } from '@/lib/db';

const openai = new OpenAI();

export async function POST(request: Request) {
  const { prompt, style, size } = await request.json();

  // Enhance prompt with style modifier
  const styleModifiers: Record<string, string> = {
    photorealistic: 'photorealistic, high detail, 8K resolution',
    illustration: 'digital illustration, vibrant colors, clean lines',
    '3d-render': '3D render, cinema 4D, octane render, soft lighting',
    watercolor: 'watercolor painting, soft washes, artistic',
  };

  const enhancedPrompt = style
    ? `${prompt}. Style: ${styleModifiers[style] ?? style}`
    : prompt;

  const response = await openai.images.generate({
    model: 'dall-e-3',
    prompt: enhancedPrompt,
    n: 1,
    size: size ?? '1024x1024',
    quality: 'hd',
    response_format: 'url',
  });

  const imageUrl = response.data[0].url!;
  const revisedPrompt = response.data[0].revised_prompt;

  // Store in database
  const saved = await db.generatedImage.create({
    data: {
      prompt,
      revisedPrompt,
      imageUrl,
      style,
      size: size ?? '1024x1024',
    },
  });

  return Response.json({ id: saved.id, imageUrl, revisedPrompt });
}

The Image Generator UI

'use client';
import { useState } from 'react';

const STYLES = [
  { id: 'none', label: 'No Style' },
  { id: 'photorealistic', label: 'Photo' },
  { id: 'illustration', label: 'Illustration' },
  { id: '3d-render', label: '3D Render' },
  { id: 'watercolor', label: 'Watercolor' },
];

export function ImageGenerator() {
  const [prompt, setPrompt] = useState('');
  const [style, setStyle] = useState('none');
  const [imageUrl, setImageUrl] = useState('');
  const [revisedPrompt, setRevisedPrompt] = useState('');
  const [loading, setLoading] = useState(false);

  async function generate() {
    if (!prompt.trim()) return;
    setLoading(true);
    setImageUrl('');
    
    const res = await fetch('/api/generate-image', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ prompt, style: style === 'none' ? null : style }),
    });
    
    const data = await res.json();
    setImageUrl(data.imageUrl);
    setRevisedPrompt(data.revisedPrompt);
    setLoading(false);
  }

  return (
    <div className="max-w-2xl mx-auto p-6">
      <h1 className="text-2xl font-bold mb-6">AI Image Generator</h1>
      
      <textarea
        value={prompt}
        onChange={e => setPrompt(e.target.value)}
        placeholder="Describe the image you want to create..."
        className="w-full p-3 border rounded-lg h-24 resize-none"
      />
      
      <div className="flex gap-2 my-3">
        {STYLES.map(s => (
          <button
            key={s.id}
            onClick={() => setStyle(s.id)}
            className={`px-3 py-1 rounded-full text-sm border transition-colors ${
              style === s.id ? 'bg-blue-600 text-white border-blue-600' : 'hover:bg-gray-50'
            }`}
          >
            {s.label}
          </button>
        ))}
      </div>
      
      <button
        onClick={generate}
        disabled={loading || !prompt.trim()}
        className="w-full py-3 bg-blue-600 text-white rounded-lg font-semibold disabled:opacity-50"
      >
        {loading ? 'Generating... (30-60s)' : 'Generate Image'}
      </button>
      
      {loading && (
        <div className="mt-6 aspect-square bg-gray-100 rounded-xl animate-pulse flex items-center justify-center">
          <p className="text-gray-400">DALL-E 3 is painting your image...</p>
        </div>
      )}
      
      {imageUrl && !loading && (
        <div className="mt-6">
          <img src={imageUrl} alt={prompt} className="w-full rounded-xl shadow-lg" />
          {revisedPrompt && (
            <p className="mt-3 text-sm text-gray-500">
              <strong>Revised prompt:</strong> {revisedPrompt}
            </p>
          )}
          <a
            href={imageUrl}
            download="generated.png"
            className="mt-3 inline-block px-4 py-2 bg-gray-800 text-white rounded text-sm"
          >
            Download
          </a>
        </div>
      )}
    </div>
  );
}

Key DALL-E 3 Tips

  • DALL-E 3 rewrites your prompt for better results — always log revised_prompt
  • Use quality: "hd" for production; it's twice the cost but significantly better
  • 1024x1792 and 1792x1024 are great for portrait/landscape use cases
  • DALL-E 3 costs $0.040 per standard image, $0.080 per HD — factor this into your pricing

Không spam, hủy đăng ký bất kỳ lúc nào.

Bài viết liên quan