袁锐钦 · AI编程实

Day 2 搞定了认证和数据库,Picboil 已经是一个能注册、能登录、有数据存储的"半成品"了。
但它还不会动。
洗衣机静静地躺在页面上,像个摆设。用户输入提示词,什么都不会发生。因为没有东西去处理这个输入,没有动画反馈,更没有 AI 在后面接单干活。
Day 3 只干一件事:让这台洗衣机从"摆设"变成"活的机器"。
具体来说三件事:
Day 1 我已经用 CSS 做了一个卡通洗衣机的静态外观。今天要让它动起来。
最核心的动画:滚筒要转。用 CSS @keyframes 就行:
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.drum {
animation: spin 2s linear infinite;
}就这么简单?对,就这么简单。
但有个细节:滚筒转动时里面的内容不能跟着转。你想想真实洗衣机——滚筒在转,但衣服是翻滚着掉下来的,不是贴在筒壁上跟着转圈。
所以用了两层结构:外层转,内层内容用反向动画或者固定定位保持相对稳定。
💡 提示词模板: "创建一个 CSS 洗衣机滚筒动画,要求:①外层圆环持续匀速旋转 ②内部内容区域保持水平不跟随旋转 ③使用 overflow:hidden 裁剪超出部分 ④加入微弱的 3D 透视效果(perspective)增加立体感"
洗衣机运转时要有泡泡从边缘冒出来。这也是纯 CSS:
@keyframes bubble-rise {
0% {
transform: translateY(0) scale(1);
opacity: 0.8;
}
100% {
transform: translateY(-80px) scale(0.3);
opacity: 0;
}
}
.bubble {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
animation: bubble-rise 2s ease-out infinite;
}关键点:每个泡泡的 left 位置、animation-delay、size 都随机化,这样看起来自然不像复制粘贴。AI 帮我生成了 6-8 个泡泡元素,每个参数略有不同。
洗衣机运行时内部灯光要有变化——模拟"正在工作"的状态感:
@keyframes glow-pulse {
0%, 100% { box-shadow: inset 0 0 20px rgba(255, 107, 53, 0.3); }
50% { box-shadow: inset 0 0 40px rgba(78, 205, 196, 0.5); }
}运行中灯光在橙色(主色)和蒸汽蓝(辅助色)之间呼吸切换,告诉用户"我在工作中"。
三个动画加在一起的效果: 滚筒匀速转 → 泡泡不断冒 → 灯光柔和闪。一台活生生的 CSS 洗衣机,零 JavaScript,性能好得离谱。
如果 Day 1 用的是 React Three Fiber 方案,光是加载 Three.js 就要 1MB+。现在整个动画代码不到 3KB。这就是不过度工程化的好处。
动画有了,但洗衣机只是空转——扔进去的东西不会变成图片。需要接一个真正的 AI 图片生成模型。
方案 | 免费额度 | 价格 | 质量 | 接入难度 |
|---|---|---|---|---|
DALL-E 3 | 无 | ~$0.04/张 | 高 | 中 |
Midjourney API | 无 | ~$0.05/张 | 高 | 高 |
Stable Diffusion | 自建 | 电费+GPU | 中 | 高 |
Gemini Imagen 3 | $300 + 500张/天免费 | ~$0.01/张 | 高 | 低 |
MVP 阶段我的优先级很明确:成本低 > 接入简单 > 质量够用。Gemini 三条全中。
$300 的新用户额度意味着开发测试阶段几乎零成本。500 张/天的 Flash Image 免费额度甚至够小规模运营用。
让 AI 帮我装 SDK:
npm install @google/generative-ai然后写一个 API Route 作为后端代理:
// app/api/generate/route.ts
import { GoogleGenerativeAI } from '@google/generative-ai';
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
export async function POST(req: Request) {
const { prompt, size } = await req.json();
const model = genAI.getGenerativeModel({
model: 'imagen-3.0-generate-001', // Imagen 3 高质量模型
});
const result = await model.generateImages({
prompt: prompt,
config: {
numberOfImages: 1,
aspectRatio: size === 'portrait' ? '9:16' : '16:9',
},
});
const imageData = result.generatedImages[0].image.imageBytes;
// 转 base64 返回给前端
return Response.json({
image: `data:image/png;base64,${imageData}`,
});
}💡 提示词模板: "用 Next.js App Router 创建一个 API Route(app/api/generate/route.ts),对接 Google Gemini Imagen 3 API 实现文生图功能。要求:①使用 @google/generative-ai SDK ②接收 prompt 和 size 参数 ③返回 base64 编码的图片 ④加入错误处理(API Key缺失、配额超限、内容过滤)⑤不要把 API Key 暴露到前端代码里"
为什么要用 API Route 做代理? 因为 API Key 绝对不能放到前端代码里。任何人打开浏览器 F12 就能看到你的 Key,然后拿去刷爆你的额度。所有涉及 API Key 的调用都必须走后端。
Picboil 不只有文生图,还有图生图。Gemini 同样支持:
// 图生图:传入参考图片 + 提示词
const result = await model.generateImages({
prompt: 'Transform this image into a cartoon style',
config: {
numberOfImages: 1,
inputConfig: {
imageData: referenceImageBytes, // 用户上传的图片
mimeType: 'image/png',
},
},
});一个 API,两种模式都搞定了。
这是今天最有成就感的部分——把前面做的所有东西串起来。
完整流程是这样的:

① 输入阶段
用户在便签上输入提示词(比如 "a cute cat wearing a spacesuit"),点击 "Boil it" 按钮。
② 投递动画
便签纸卷起 → 飞向洗衣机投料口 → 洗衣机盖子关上。这段用 Framer Motion 做,大约 800ms 的动画时长。
③ "洗涤"动画
盖子关上后触发 CSS 动画序列:
这段动画故意设为 3-4 秒。为什么?因为如果 API 调用太快(有时候 Flash Image 1秒就返回),用户会觉得"这就完了?"——没有仪式感。稍微等一下,让用户觉得 AI 在认真"煮"他的图。
④ API 调用
动画播放到后半段时,前端悄悄发起 fetch('/api/generate') 请求。用户还在看泡泡,后端已经在调 Gemini 了。
⑤ 结果弹出
API 返回图片后:
整个链路一气呵成,从点击到看到结果大约 5-8 秒。
其中 3-4 秒是动画(仪式感),2-4 秒是真正的 AI 生成时间。用户感知到的不是"等待",而是一场完整的体验。
这就是 Picboil 和其他 AI 图片工具最大的区别:别人是"输入→等待加载→出图",我们是"输入→一场小演出→出图"。等待本身也可以是产品的一部分。
用户生成过图片之后,得有地方看。这个功能用 Prisma 查询就能搞定:
// 查询当前用户的生成历史
const history = await prisma.task.findMany({
where: { userId: currentUser.id },
orderBy: { createdAt: 'desc' },
take: 20,
include: {
images: true, // 关联生成的图片
},
});Day 2 设计数据库的时候已经建好了 tasks 表和 images 表,这里直接用。
展示区放在页面下方,卡片式布局,每张图显示缩略图、提示词摘要、生成时间。点击可以放大查看。
这个功能没什么技术难点,但对用户体验很重要——用户回来能看到自己之前的作品,才会有"这是我的工具"的感觉,而不是一个一次性用完就走的网页。
现象: 生成的历史图片用 base64 存储和展示,3-4 张之后页面明显卡顿。
原因: 一张 Imagen 3 生成的图片 base64 编码后大概 2-3MB。直接内嵌在 HTML 里或通过 API 返回,体积太大。
怎么修:
改用 Cloudflare R2 对象存储。图片生成后上传到 R2,只返回一个 URL 给前端。R2 的读取请求几乎免费,而且自带 CDN 加速。
// 生成图片 → 上传 R2 → 返回 URL
const buffer = Buffer.from(imageData, 'base64');
await r2.put(`images/${taskId}.png`, buffer);
return { url: `https://r2.picboil.ai/images/${taskId}.png` };MVP 阶段先用本地文件系统存着,上线前再迁移到 R2。先跑通逻辑,再优化性能。
💡 提示词技巧: 让 AI 做图片相关功能时,加上"图片必须使用对象存储(如 AWS S3 / Cloudflare R2),禁止使用 base64 内嵌或 localStorage 存储。返回图片 URL 而非图片数据。"
教训:MVP 先跑通,但要知道哪里以后要改。 base64 能用但不代表应该用在生产环境。
现象: 输入 "a stylish girl in coffee shop" 这种完全正常的提示词,偶尔被 Gemini 的安全过滤器拦截,返回错误。
原因: Google 的安全策略比较保守,某些词组合会触发误判。这不是 bug,是策略问题。
怎么修:
// 简化的降级逻辑
async function generateWithFallback(prompt) {
try {
return await generateWithImagen3(prompt); // 主力模型
} catch (error) {
if (isSafetyFilterError(error)) {
return await generateWithFlash(prompt); // 降级重试
}
throw error;
}
}教训:AI 模型的安全策略是不可控的外部因素。 你能做的是优雅地处理失败,而不是试图绕过它。好的错误体验 = 好的产品体验。
现象: 有时候 API 返回很快(< 2秒),但动画还没播完,图片就已经出来了——打断了动画节奏。或者反过来,API 很慢(> 10秒),动画播完了图片还没来,用户盯着静止的洗衣机发呆。
原因: 动画时长固定,API 耗时不固定,两者没协调好。
怎么修:
用一个状态机管理整个过程:
type MachineState = 'idle' | 'animating' | 'generating' | 'done' | 'error';
// 动画开始 → 同时发起 API 请求(不等动画结束)
// API 先回来 → 等动画播到合适节点再展示结果
// 动画先完 → 显示 "Almost ready..." 等待文字
const [state, setState] = useState<MachineState>('idle');
const handleGenerate = async () => {
setState('animating'); // 开始动画
const apiPromise = fetch('/api/generate'); // 同时请求
// 等动画播到 70% 左右
await minDelay(apiPromise, 3000);
setState('done'); // 展示结果
};核心思路:动画和 API 并行跑,谁慢等谁,但至少保证 3 秒的最短体验时间。
💡 提示词模板: "实现一个异步状态机,管理'点击→动画→API调用→结果展示'的完整流程。要求:①动画和 API 并行执行 ②设置最小体验时长(如 3秒)③API 快于动画时等动画播到 80% 再展示 ④API 慢于动画时显示过渡态 ⑤处理好取消和重复点击的场景"
✅ CSS 洗衣机动画完善(滚筒转动 + 泡泡效果 + 灯光脉冲) ✅ Gemini Imagen 3 API 接入(文生图 + 图生图) ✅ 完整交互链路跑通(输入 → 投递 → 洗涤动画 → AI 生成 → 结果弹出) ✅ 生成历史功能(Prisma 查询 + 卡片式展示) ✅ 对象存储方案确定(Cloudflare R2,MVP 阶段先本地) ✅ 安全过滤降级策略(主模型拦截自动降级 Flash)
项目 | 花费 |
|---|---|
时间 | 约 6 小时 |
金钱 | $0(Gemini 免费额度) |
新增依赖 | @google/generative-ai |
修复 Bug 数 | 3 个 |
新增动画 | 3 组(spin / bubble / glow) |
Day 1 教会我"不要过度工程化",Day 2 教会我"AI 写的代码不能直接信",Day 3 教会我的是——
产品的灵魂不在技术栈有多牛,而在"感受"的设计上。
同样的 Gemini API,别人做成一个输入框+加载圈+出图,我做成了"便签飞进去→洗衣机转起来→泡泡冒出来→图片跳出来"。
技术上没有任何高深的地方。CSS 动画是基础语法,API 调用是标准 CRUD。但用户感受到的东西完全不同。
做出海站、做产品,功能可以被复制(你也接 Gemini 我也接 Gemini),但这种"用起来的感觉"很难被复制。
这也是我为什么花那么多时间在动画和交互链路上——因为这才是用户记住你的原因。
三天下来,Picboil 已经是一个能完整走通"注册→登录→输入→生成→查看历史"流程的最小可用产品了:
✅ 项目骨架(Next.js 15 + TypeScript + Tailwind) ✅ CSS 卡通洗衣机(替代原 3D 方案) ✅ 认证系统(邮箱 + Google OAuth) ✅ 数据库(4 张表 + 软删除) ✅ AI 图片生成(Gemini Imagen 3 + Flash 降级) ✅ 完整交互链路(动画 + API + 状态机) ✅ 生成历史
🔲 下一步 Day 4:
三天完成了 MVP 的核心功能,接下来就是包装和上线了。
如果你在做 AI 辅助开发,或者对这些踩坑经历有共鸣,欢迎交流。
一个人走得快,一群人走得远。💪