从静态资源到动态合成:掌握 DALL‑E 3 与 Vercel AI SDK 在 Next.js 中的应用
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 秒)的异步特性至关重要。
- Idle(空闲) – UI 等待用户输入。
- Processing(处理中) – 用户点击 “Generate”。RSC 接收请求并启动流。
- Generating(生成中) – AI 模型在服务器上运行。SDK 流式返回表示进度的 token。
- Ready(就绪) – 服务器将图像上传到临时存储(例如 Vercel Blob),并返回签名 URL。
- Display(展示) – React 组件完成 hydration,用
<img>标签替换加载状态。
这与 ReAct Loop(推理 → 行动 → 观察)相呼应。系统推断用户想要视觉内容,调用工具进行行动,并观察流式输出直至资产准备完毕。
实现:使用 Next.js 与 Vercel AI SDK 构建 “厨房”
以下是两个核心文件:
- 服务器动作 – 处理对 OpenAI 的安全 API 调用并管理图像数据。
- 客户端组件 – 管理用户输入并显示结果。
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 的 useCompletion 或 useChat 订阅流。 |
| 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 链接。