Recipe 4:封装图片生成
场景
让 AI 能根据用户描述生成图片。用户说「画一张小猫咪」或「生成一张夕阳海滩图」,AI 就输出一张图片,而不是回复一段文字描述。
前置条件
- 完成 Recipe 0 — 有一个可运行的小程序项目
- 项目已开通 AI 开发模式
- 熟悉 SKILL 目录结构
- 基础库版本 ≥ 3.16.1
实现步骤
第 1 步:创建脚手架
npx mp-skills create image-gen
预期输出:
* 创建 Skill: image-gen
ok miniprogram/skills/image-gen-skill/
ok 脚手架已生成
目录结构:
miniprogram/skills/image-gen-skill/
├── SKILL.md 业务描述文件
├── mcp.json 原子接口声明
├── apis/
│ └── generateImage.js 文生图接口实现
├── components/
│ └── image-result-card/ 图片卡片组件
├── seed/ 预览用 Mock 数据
└── index.js SKILL 入口
第 2 步:编写 SKILL.md
SKILL.md 告诉 AI 引擎什么情况下该调用这个 SKILL。编辑 miniprogram/skills/image-gen-skill/SKILL.md:
# 图片生成
可根据用户的描述生成一张图片。
## 触发条件
当用户想要生成或创建一张图片时,例如:
- "画一张小猫咪"
- "生成一张夕阳海滩的图片"
- "帮我画一张山水画"
- "给我创建一张星空图"
## 原子接口
| 接口名称 | 功能描述 |
|---------|---------|
| generateImage | 根据文字描述生成图片,支持选择风格 |
第 3 步:配置 mcp.json
mcp.json 声明 AI 能调用的原子接口及其参数。编辑 miniprogram/skills/image-gen-skill/mcp.json:
{
"tools": [
{
"name": "generateImage",
"description": "根据文字描述生成一张图片",
"input": {
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "图片描述词,用中文描述要生成的内容"
},
"style": {
"type": "string",
"description": "图片风格",
"enum": ["写实摄影", "日系动漫", "水墨国风", "油画", "水彩", "赛博朋克", "3D 渲染"],
"default": "写实摄影"
}
},
"required": ["prompt"]
},
"output": {
"type": "object",
"properties": {
"imageUrl": {
"type": "string",
"description": "生成的图片 URL",
"format": "image"
},
"prompt": {
"type": "string",
"description": "实际使用的生成提示词"
}
}
}
}
]
}
注意
output中imageUrl字段的"format": "image"。这个标记告诉 AI 引擎该字段是图片地址,渲染层会自动展示为图片而非文字。这是图片生成 SKILL 与文本生成 SKILL 的关键区别之一。
第 4 步:实现原子接口
新建 miniprogram/skills/image-gen-skill/apis/generateImage.js:
const { cloud } = require('../_shared');
/**
* 根据文字描述生成图片
* @param {Object} params
* @param {string} params.prompt - 图片描述词
* @param {string} [params.style] - 图片风格
* @returns {Promise<{imageUrl: string, prompt: string}>}
*/
async function generateImage({ prompt, style = '写实摄影' }) {
// 参数校验
if (!prompt || typeof prompt !== 'string') {
throw new Error('prompt 为必填项,且必须是字符串');
}
// 调用云函数,云函数内部通过 AI 网关请求文生图模型
const result = await cloud.callFunction({
name: 'image-gen',
data: {
action: 'generateImage',
prompt,
style,
},
});
if (!result || !result.imageUrl) {
throw new Error('图片生成失败,未返回图片地址');
}
return {
imageUrl: result.imageUrl,
prompt: result.prompt,
};
}
module.exports = { generateImage };
云函数
image-gen的部署不是本文重点。如果使用云开发控制台的 AI 网关,可直接在云函数中对接腾讯混元等文生图模型。
第 5 步:开发原子组件
原子组件负责展示图片。新建 miniprogram/skills/image-gen-skill/components/image-result-card/image-result-card.wxml:
<view class="image-result-card">
<image
class="result-image"
src="{{imageUrl}}"
mode="widthFix"
binderror="onImageError"
bindload="onImageLoad"
/>
<view class="prompt-text" wx:if="{{prompt}}">
{{prompt}}
</view>
<view class="loading-mask" wx:if="{{loading}}">
<text class="loading-text">正在生成图片…</text>
</view>
</view>
image-result-card.wxss:
.image-result-card {
position: relative;
width: 100%;
border-radius: 16rpx;
overflow: hidden;
background: #f5f5f5;
}
.result-image {
width: 100%;
display: block;
}
.prompt-text {
padding: 16rpx 20rpx;
font-size: 26rpx;
color: #666;
background: #fafafa;
}
.loading-mask {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.8);
}
.loading-text {
font-size: 28rpx;
color: #333;
}
image-result-card.js:
Component({
properties: {
imageUrl: {
type: String,
value: '',
},
prompt: {
type: String,
value: '',
},
},
data: {
loading: true,
error: false,
},
methods: {
onImageLoad() {
this.setData({ loading: false });
},
onImageError() {
this.setData({ loading: false, error: true });
console.error('图片加载失败:', this.properties.imageUrl);
},
},
});
组件需要处理 loading 状态和 error 状态。图片生成和加载是一个异步过程,必须给用户明确的反馈。这是图片类 SKILL 与文本类 SKILL 的重要差异——文本几乎是瞬时返回的,图片则需要加载等待。
第 6 步:注册 SKILL
编辑 miniprogram/skills/image-gen-skill/index.js:
const { generateImage } = require('./apis/generateImage');
module.exports = {
name: 'image-gen',
description: '图片生成',
apis: [
{
name: 'generateImage',
handler: generateImage,
},
],
components: {
'image-result-card': './components/image-result-card/image-result-card',
},
};
第 7 步:注册到 app.json
mp-skills create 已自动更新 app.json 的 agent.skills。确认包含以下条目:
{
"agent": {
"skills": [
// ... 其他 SKILL
{
"name": "image-gen",
"description": "图片生成:根据文字描述生成图片",
"path": "skills/image-gen-skill"
}
]
}
}
验证
在开发者工具中切换到「小程序 AI 编译」模式:
- SKILL 列表中出现
image-gen - 点选
generateImage原子接口,填入参数{"prompt": "一只橘猫在晒太阳"}执行 - 返回结果中包含
imageUrl(图片地址) - 图片卡片正确渲染,显示加载态和最终图片
- 对话模式下输入"画一张小猫咪",AI 自动调用
generateImage
完整代码
apis/generateImage.js
const { cloud } = require('../_shared');
async function generateImage({ prompt, style = '写实摄影' }) {
if (!prompt || typeof prompt !== 'string') {
throw new Error('prompt 为必填项,且必须是字符串');
}
const result = await cloud.callFunction({
name: 'image-gen',
data: { action: 'generateImage', prompt, style },
});
if (!result || !result.imageUrl) {
throw new Error('图片生成失败,未返回图片地址');
}
return { imageUrl: result.imageUrl, prompt: result.prompt };
}
module.exports = { generateImage };
components/image-result-card/image-result-card.wxml
<view class="image-result-card">
<image class="result-image" src="{{imageUrl}}" mode="widthFix" binderror="onImageError" bindload="onImageLoad" />
<view class="prompt-text" wx:if="{{prompt}}">{{prompt}}</view>
<view class="loading-mask" wx:if="{{loading}}">
<text class="loading-text">正在生成图片…</text>
</view>
</view>
components/image-result-card/image-result-card.wxss
.image-result-card {
position: relative; width: 100%; border-radius: 16rpx; overflow: hidden; background: #f5f5f5;
}
.result-image { width: 100%; display: block; }
.prompt-text { padding: 16rpx 20rpx; font-size: 26rpx; color: #666; background: #fafafa; }
.loading-mask {
position: absolute; inset: 0; display: flex; align-items: center;
justify-content: center; background: rgba(255,255,255,0.8);
}
.loading-text { font-size: 28rpx; color: #333; }
components/image-result-card/image-result-card.js
Component({
properties: { imageUrl: { type: String, value: '' }, prompt: { type: String, value: '' } },
data: { loading: true, error: false },
methods: {
onImageLoad() { this.setData({ loading: false }); },
onImageError() { this.setData({ loading: false, error: true }); },
},
});
index.js
const { generateImage } = require('./apis/generateImage');
module.exports = {
name: 'image-gen', description: '图片生成',
apis: [{ name: 'generateImage', handler: generateImage }],
components: { 'image-result-card': './components/image-result-card/image-result-card' },
};
与文本生成 SKILL 的关键差异
| 对比项 | 文本生成 | 图片生成 |
|---|---|---|
| 输出类型 | text 字符串 | image URL(format: image) |
| 核心组件 | 无或 text 展示 | 图片卡片(含 loading/error) |
| 加载等待 | 几乎无等待 | 需要 loading 状态 |
| 异步处理 | 简单 | 需处理图片加载失败 |
| 风格控制 | 提示词控制 | style 枚举参数 |
| 数据大小 | 几 KB | 图片 URL(远程加载) |
常见问题
图片显示为空白
- 检查
imageUrl是否以https://开头 - 检查小程序的 downloadFile 域名白名单是否配置
- 检查云函数返回的 URL 是否可公开访问
组件 loading 一直不消失
- 检查
bindload事件是否正确绑定 - 检查图片 URL 是否真实可访问
- 在开发者工具 Network 面板查看图片请求状态
AI 不触发图片生成
- SKILL.md 的触发条件是否写入了用户常用的表达
- 在开发者工具中测试原子接口是否能正常返回
- 检查
app.json中agent.skills配置是否正确