从静态资源到动态合成:掌握 DALL‑E 3 与 Vercel AI SDK 在 Next.js 中的应用

发布: (2026年2月23日 GMT+8 04:00)
10 分钟阅读
原文: Dev.to

Source: Dev.to

使用 DALL‑E 3 与 Vercel AI SDK 的生成式 UI

想象一个网页应用,视觉效果不是预先烘焙好、放在 CDN 上的资产,而是实时合成、完美契合用户想象的内容。这不是科幻,而是 生成式 UI 的现实。在本章节中,我们将超越静态图片交付,深入探讨使用 DALL‑E 3 和 Vercel AI SDK 进行动态媒体合成的架构。

为什么要 “流式观察” 而不是 “请求等待”?

如果你曾想过如何在响应迅速的 React 界面中集成高延迟的 AI 生成,那么你来对地方了。我们的目标是将范式从:

  • 请求 & 等待流式 & 观察

从本质上讲,将 DALL‑E 3 集成到 Next.js 应用中 并非 仅仅调用一个 API,而是把网页开发范式从静态资产交付转变为 动态媒体合成

从静态图片到瞬时计算

传统网页(杂货店)生成式 UI(高端餐厅)
资产(图片、CSS、JS)是预先包装好的商品,摆在货架上。图像是 瞬时、带状态的计算过程输出
当顾客要一个苹果时,店员从箱子里挑一个。当用户请求一张图片时,厨师(AI 模型) 接收提示,收集原料(潜在噪声),并开始合成(扩散步骤)。
苹果是静态的;它已经被挑选、清洗并在几小时前上架。菜肴是 即时准备 的,随着生成进度实时流回客户端。

Vercel AI SDK 充当 总厨,在 服务员(客户端 UI)线厨(OpenAI API) 之间管理通信,确保订单被正确处理,并在菜品准备好时立即通知服务员。

图像生成的状态机

当我们调用 generateImage 时,会创建一个在不同阶段之间转换的状态机。这对于处理 DALL‑E 3(可能需要 10‑30 秒)的异步特性至关重要。

  1. Idle(空闲) – UI 等待用户输入。
  2. Processing(处理中) – 用户点击 “Generate”。RSC 接收请求并启动流。
  3. Generating(生成中) – AI 模型在服务器上运行。SDK 流式返回表示进度的 token。
  4. Ready(就绪) – 服务器将图像上传到临时存储(例如 Vercel Blob),并返回签名 URL。
  5. Display(展示) – React 组件完成 hydration,用 <img> 标签替换加载状态。

这与 ReAct Loop(推理 → 行动 → 观察)相呼应。系统推断用户想要视觉内容,调用工具进行行动,并观察流式输出直至资产准备完毕。

实现:使用 Next.js 与 Vercel AI SDK 构建 “厨房”

以下是两个核心文件:

  1. 服务器动作 – 处理对 OpenAI 的安全 API 调用并管理图像数据。
  2. 客户端组件 – 管理用户输入并显示结果。

1️⃣ 服务器动作 (app/actions/generateImage.ts)

// app/actions/generateImage.ts
'use server';

import { generateImage } from '@ai-sdk/openai';
import { openai } from '@ai-sdk/openai'; // Ensure you have the provider installed

export async function generateImageAction(prompt: string) {
  try {
    // 1️⃣ Select the Model
    const model = openai.image('dall-e-3');

    // 2️⃣ Call the AI SDK
    const { image } = await generateImage({
      model,
      prompt,
      size: '1024x1024',
      quality: 'standard',
    });

    if (!image) return { error: 'No image data received.' };

    // 3️⃣ Convert Binary to Base64 for immediate display
    // Note: In a production SaaS (see advanced section), upload to Vercel Blob
    // to avoid payload‑size limits.
    const base64 = Buffer.from(image.uint8Array).toString('base64');
    const dataUrl = `data:image/png;base64,${base64}`;

    return { url: dataUrl };
  } catch (error) {
    console.error('Image generation error:', error);
    return { error: 'Failed to generate image.' };
  }
}

2️⃣ 客户端组件 (app/page.tsx)

// app/page.tsx
'use client';

import { useState } from 'react';
import { generateImageAction } from './actions/generateImage';

export default function ImageGeneratorPage() {
  const [prompt, setPrompt] = useState('');
  const [imageUrl, setImageUrl] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setError(null);
    setImageUrl(null);
    setIsLoading(true);

    // Call the Server Action
    const result = await generateImageAction(prompt);

    if (result.error) {
      setError(result.error);
    } else if (result.url) {
      setImageUrl(result.url);
    }

    setIsLoading(false);
  };

  return (
    <div>
      <h2>Generative Image App</h2>

      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={prompt}
          onChange={e => setPrompt(e.target.value)}
          placeholder="Describe the image you want..."
          disabled={isLoading}
          style={{ width: '100%', padding: '0.5rem', marginBottom: '1rem' }}
        />
        <button type="submit" disabled={isLoading}>
          {isLoading ? 'Generating...' : 'Generate Image'}
        </button>
      </form>

      {error && <p style={{ color: 'red' }}>Error: {error}</p>}

      {imageUrl && (
        <div style={{ marginTop: '1rem' }}>
          <img src={imageUrl} alt={prompt} style={{ maxWidth: '100%' }} />
        </div>
      )}
    </div>
  );
}

Source:

接下来做什么?

  • 流式 Token – 用流式接口替代简单的 await generateImage,以显示进度条或中间预览。
  • 持久化存储 – 将最终图像上传到 Vercel Blob(或其他对象存储),并提供签名 URL,以保持负载轻量。
  • 安全性 – 将 API 密钥严格保存在服务器端,绝不要在客户端包中暴露它们。
  • 高级 UI – 与 React Server Components 结合使用,在图像流入时预渲染占位符。

TL;DR

将图像视为 实时计算 而非静态文件。
使用 Vercel AI SDK 编排 安全、流式 的工作流:从提示 → AI 生成 → 客户端展示。
利用清晰的状态机(Idle → Processing → Generating → Ready → Display)保持 UI 响应迅速、用户友好。

祝烹饪愉快! 🍳🚀

Source:

使用服务器发送事件 (SSE) 与 Vercel Blob 生成图像

// Example component (simplified)
export default function ImageResult({ imageUrl }) {
  return (
    <div>
      {imageUrl ? (
        <img src={imageUrl} alt="Generated" />
      ) : (
        <>Loading…</>
      )}
    </div>
  );
}

为什么不返回巨大的 Base64 字符串?

在真实的 SaaS 应用中,从 Server Action 发送庞大的 Base64 负载是有风险的:

  • 负载限制 – 可能超过请求/响应大小。
  • 超时 – 长时间的生成可能触及 Vercel 的 10 秒限制。
  • 用户体验 – 在整个图像传输完成之前,UI 会卡住。

推荐架构:“营销资产工作室”

步骤描述
1️⃣ 客户端调用 Server Action 并使用 Vercel AI SDK 的 useCompletionuseChat 订阅流。
2️⃣ 服务器不等待完整图像 就开始生成,流式发送状态更新(例如 “已收到提示…”, “DALL‑E 3 正在处理…”, “上传中…”)。
3️⃣ 服务器一旦从 OpenAI 收到字节,就将它们上传到 Vercel Blob(或其他对象存储)。
4️⃣ 服务器将最终的 Blob URL 流式返回给客户端。
5️⃣ 客户端收到 URL 后,用实际图像替换加载骨架。

结果: UI 永不冻结。用户在后台进行 20 秒生成时,会看到进度指示器(真实的或模拟的)。

常见陷阱与解决方案

问题症状解决方案
Vercel 超时请求在 10 秒后返回 408 错误。将 Serverless Function 超时时间调高(Pro 计划)将工作转移到后台队列(例如 Vercel QStash)。
负载过大Server Action 静默失败或抛出错误。绝不返回 Base64。将在 Action 中把图像上传至存储(Vercel Blob、S3 等),并仅返回 URL
缺少 API 密钥"Invalid API Key" 错误。确保在 .env.local 中定义 OPENAI_API_KEY绝不将密钥提交到源码控制。
Async/Await 不匹配客户端收到 [object Promise]Server Actions 必须是 async,客户端必须 await 结果(或使用 SDK 的流式钩子)。

概念转变:从静态资产到动态合成

  • 旧模型: “杂货店” – 按需获取静态文件。
  • 新模型: “餐厅厨房” – 图像作为计算管道的一部分 实时生成

通过利用:

  • Vercel AI SDK(流式完成 & 聊天)
  • React Server Components(安全的服务器端逻辑)
  • Vercel Blob(对象存储)

我们将图像视为不是预先存在的文件,而是 有状态输出,客户端可以订阅。

要点

  • 生成式 UI = 订阅一个过程,而不仅仅是获取数据。
  • 对于原型,返回简单的 Base64 即可。
  • 对于生产级 SaaS,使用 SSE 流 + Blob 存储 来保持 UI 响应并使架构可扩展。

进一步阅读

  • The Modern Stack: Building Generative UI with Next.js, Vercel AI SDK, and React Server Components – 全面的路线图。
  • AI 与 JavaScript & TypeScript 系列的 Amazon 链接。
0 浏览
Back to Blog

相关文章

阅读更多 »