跳到主要内容

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": "实际使用的生成提示词"
}
}
}
}
]
}

注意 outputimageUrl 字段的 "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.jsonagent.skills。确认包含以下条目:

{
"agent": {
"skills": [
// ... 其他 SKILL
{
"name": "image-gen",
"description": "图片生成:根据文字描述生成图片",
"path": "skills/image-gen-skill"
}
]
}
}

验证

在开发者工具中切换到「小程序 AI 编译」模式:

  1. SKILL 列表中出现 image-gen
  2. 点选 generateImage 原子接口,填入参数 {"prompt": "一只橘猫在晒太阳"} 执行
  3. 返回结果中包含 imageUrl(图片地址)
  4. 图片卡片正确渲染,显示加载态和最终图片
  5. 对话模式下输入"画一张小猫咪",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.jsonagent.skills 配置是否正确

参考