教程:构建您的第一个应用
在本教程中,您将构建一个 字数统计器 小程序,它读取活动聊天历史,按角色(用户 vs 助手)统计字数,并显示统计数据。最后,您将了解如何创建、测试、打包和发布小程序。
您将学到什么:
- 创建
manifest.json - 使用
window.aisSDK 编写应用 HTML - 使用
ais.chat.getHistory()读取消息 - 使用
ais.storage跨会话持久化数据 - 使用侧载进行本地测试
- 打包为
.ais包 - 发布到社区注册表
所需时间: 约 15 分钟。
第一步:创建清单
每个小程序都需要一个 manifest.json,描述应用、其权限和入口点。创建一个新目录并添加此文件:
mkdir word-counter
cd word-counter
创建 manifest.json:
{
"name": "word-counter",
"version": "1.0.0",
"abi": 1,
"type": "mini-program",
"title": "Word Counter",
"description": "Count words in your chat history by role",
"author": { "name": "Your Name" },
"entry": "index.html",
"base_url": "https://localhost:8080/",
"permissions": ["storage", "chat:read", "ui:toast"],
"keywords": ["statistics", "utility"]
}
关键字段:
| 字段 | 值 | 原因 |
|---|---|---|
name | word-counter | 唯一标识符(小写,仅连字符) |
abi | 1 | 必需 —— 匹配当前平台 ABI |
type | mini-program | 告诉平台这是沙箱 iframe 应用 |
entry | index.html | 要加载的 HTML 文件 |
base_url | https://localhost:8080/ | 资产托管位置(生产环境需更新) |
permissions | ["storage", "chat:read", "ui:toast"] | 我们需要读取聊天并保存统计 |
storage 权限始终自动授予,但明确列出它是好的做法,以便用户知道您的应用存储数据。
第二步:创建 HTML
在同一目录中创建 index.html。这是您的整个应用 —— HTML、CSS 和 JavaScript 在一个文件中。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family:
system-ui,
-apple-system,
sans-serif;
padding: 20px;
color: #e0e0e0;
background: #1a1a2e;
min-height: 100vh;
}
h1 {
font-size: 22px;
margin-bottom: 16px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
margin-bottom: 20px;
}
.stat-card {
background: #16213e;
border: 1px solid #333;
border-radius: 8px;
padding: 16px;
text-align: center;
}
.stat-value {
font-size: 32px;
font-weight: 700;
color: #58a6ff;
font-variant-numeric: tabular-nums;
}
.stat-label {
font-size: 14px;
color: #888;
margin-top: 4px;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
min-height: 48px;
padding: 10px 20px;
font-size: 16px;
border: 1px solid #444;
border-radius: 6px;
background: #2a2a4e;
color: #e0e0e0;
cursor: pointer;
}
button:hover {
background: #3a3a5e;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: #1a73e8;
border-color: #1a73e8;
}
.btn-primary:hover {
background: #1557b0;
}
.btn-close {
background: none;
border-color: #666;
color: #888;
}
#last-updated {
margin-top: 16px;
font-size: 14px;
color: #666;
}
</style>
</head>
<body>
<h1>字数统计器</h1>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="total-words">--</div>
<div class="stat-label">总字数</div>
</div>
<div class="stat-card">
<div class="stat-value" id="user-words">--</div>
<div class="stat-label">您的字数</div>
</div>
<div class="stat-card">
<div class="stat-value" id="ai-words">--</div>
<div class="stat-label">AI 字数</div>
</div>
<div class="stat-card">
<div class="stat-value" id="msg-count">--</div>
<div class="stat-label">消息数</div>
</div>
</div>
<div class="actions">
<button class="btn-primary" id="analyze">分析聊天</button>
<button class="btn-close" id="close">关闭</button>
</div>
<div id="last-updated"></div>
<script>
// 辅助函数:统计字符串中的字数
function countWords(text) {
if (!text || typeof text !== "string") return 0;
return text.trim().split(/\s+/).filter(Boolean).length;
}
// 格式化数字,添加逗号 (1234 -> "1,234")
function fmt(n) {
return n.toLocaleString();
}
ais.ready(async function () {
// 设置面板标题
ais.ui.setTitle("字数统计器");
// 尝试恢复上次保存的统计
var saved = await ais.storage.get("last-stats");
if (saved) {
showStats(saved);
}
// 分析按钮
document
.getElementById("analyze")
.addEventListener("click", async function () {
this.disabled = true;
this.textContent = "分析中...";
try {
// 从聊天历史获取最多 500 条消息
var messages = await ais.chat.getHistory(500);
var userWords = 0;
var aiWords = 0;
var msgCount = messages.length;
for (var i = 0; i < messages.length; i++) {
var msg = messages[i];
var words = countWords(msg.content);
if (msg.role === "user") {
userWords += words;
} else if (msg.role === "assistant") {
aiWords += words;
}
}
var stats = {
total: userWords + aiWords,
user: userWords,
ai: aiWords,
messages: msgCount,
timestamp: Date.now(),
};
showStats(stats);
// 保存统计以供下次使用
await ais.storage.set("last-stats", stats);
ais.ui.toast("已分析 " + msgCount + " 条消息!");
} catch (err) {
ais.ui.toast("错误: " + err.message);
}
this.disabled = false;
this.textContent = "分析聊天";
});
// 关闭按钮
document.getElementById("close").addEventListener("click", function () {
ais.close();
});
});
function showStats(stats) {
document.getElementById("total-words").textContent = fmt(stats.total);
document.getElementById("user-words").textContent = fmt(stats.user);
document.getElementById("ai-words").textContent = fmt(stats.ai);
document.getElementById("msg-count").textContent = fmt(stats.messages);
if (stats.timestamp) {
var date = new Date(stats.timestamp);
document.getElementById("last-updated").textContent =
"上次分析: " + date.toLocaleString();
}
}
</script>
</body>
</html>
代码说明
ais.ready()-- 在运行任何逻辑之前等待 SDK 桥接连接。ais.storage.get('last-stats')-- 恢复之前保存的统计,以便用户启动时立即看到数据。ais.chat.getHistory(500)-- 从活动对话获取最多 500 条消息。- 字数统计 -- 遍历消息,按空白分割内容并按角色统计。
ais.storage.set('last-stats', stats)-- 持久化结果以供下次使用。ais.ui.toast()-- 分析完成时显示通知。ais.close()-- 用户点击关闭时返回聊天视图。
第三步:本地测试
您需要本地 HTTP 服务器来提供清单和 HTML 文件。使用您喜欢的任何工具:
# Python 3
cd word-counter
python3 -m http.server 8080
# 或 Node.js
npx serve -p 8080
# 或 PHP
php -S localhost:8080
现在在平台中安装应用:
- 打开 aiscouncil.net 并登录
- 点击左侧边栏中的应用图标
- 在侧载部分,粘贴:
http://localhost:8080/manifest.json - 点击安装
- 查看权限(storage, chat:read, ui:toast)并点击允许
- 点击已安装应用卡片上的打开
对于最快的开发循环,使用 HTML 上传而不是 URL 侧载。直接上传您的 index.html —— 无需服务器。平台会自动创建合成清单。每次进行更改时可以卸载并重新上传。
故障排除
| 问题 | 解决方案 |
|---|---|
| "Failed to fetch manifest" | 确保本地服务器正在运行并提供 CORS 头。尝试 python3 -m http.server 8080,它提供 CORS 安全服务。 |
| 应用显示空白白页 | 检查浏览器控制台的错误。最常见的问题是在 ais.ready() 之前调用 ais.* 方法。 |
| "PermissionDenied: chat:read" | 您的清单在权限数组中不包含 chat:read。更新清单并重新安装。 |
| 代码更改后应用不更新 | 先卸载应用(点击卡片上的 X 按钮),然后重新安装。入口 HTML 在安装时缓存。 |
第四步:添加一些润色
让我们添加一个功能:随着新消息到达实时统计字数。
在 ais.ready() 回调中关闭按钮处理程序之后添加此代码:
// 订阅新消息以进行实时计数
ais.chat.onMessage(function (msg) {
// 重新读取保存的统计并添加新消息的字数
ais.storage.get("last-stats").then(function (stats) {
if (!stats) return;
var words = countWords(msg.content);
if (msg.role === "user") stats.user += words;
else if (msg.role === "assistant") stats.ai += words;
stats.total = stats.user + stats.ai;
stats.messages++;
stats.timestamp = Date.now();
showStats(stats);
ais.storage.set("last-stats", stats);
});
});
现在计数器会随着用户聊天实时更新,无需再次点击"分析"。
第五步:打包为 .ais 包
.ais 包是包含清单和所有应用文件的 ZIP 存档。平台提取 ZIP,读取清单,并将所有资产(CSS、JS、图片)内联到入口 HTML 中。
对于像我们这样的单文件应用,包很简单:
cd word-counter
zip -r ../word-counter.ais manifest.json index.html
这会在父目录中创建 word-counter.ais。
测试包
- 在平台中,转到应用并点击上传应用
- 选择
word-counter.ais - 查看权限并批准
- 应用从包中安装,所有资产已内联
包是自包含的。用户不需要对原始 base_url 的网络访问 —— 所有内容在安装时内联。这使得包非常适合离线分发和分享。
多文件包
如果您的应用有单独的 CSS、JavaScript 或图片文件,将它们全部包含在 ZIP 中:
zip -r ../word-counter.ais manifest.json index.html style.css app.js icon.png
平台自动内联:
<link rel="stylesheet" href="style.css">变为<style>...</style><script src="app.js"></script>变为<script>...</script><img src="icon.png">变为<img src="data:image/png;base64,...">
第六步:发布到注册表
一旦您的应用准备好供他人使用,将其发布到社区注册表。
1. 托管您的文件
将清单和入口 HTML 上传到公共 CDN。GitHub Pages 免费且简单:
# 在您的 GitHub 仓库中(例如 github.com/yourname/word-counter)
# 将 manifest.json 和 index.html 推送到 main 分支
# 在仓库设置中启用 GitHub Pages(源:main 分支,根目录)
您的文件将在以下位置可用:
https://yourname.github.io/word-counter/manifest.jsonhttps://yourname.github.io/word-counter/index.html
更新清单中的 base_url 以匹配:
"base_url": "https://yourname.github.io/word-counter/"
2. Fork aiscouncil 仓库
转到 github.com/nicholasgasior/bcz 并点击 Fork。
3. 添加您的包条目
编辑 registry/packages.json 并在 packages 数组中添加条目:
{
"name": "word-counter",
"type": "mini-program",
"version": "1.0.0",
"manifest": "https://yourname.github.io/word-counter/manifest.json",
"tier": "community",
"category": "utilities",
"description": "Count words in your chat history by role",
"icon": "https://yourname.github.io/word-counter/icon.png",
"added": "2026-02-19",
"price": 0,
"currency": "USD",
"seller": null
}
4. 验证
运行验证脚本检查您的条目:
python3 registry/validate.py packages
如果验证通过,您就准备好提交了。
5. 提交拉取请求
将更改推送到您的 fork 并向主仓库创建 PR。如果自动验证通过,PR 可以合并,您的应用将出现在平台的应用商店部分。
请参阅发布到注册表了解定价、卖家设置和验证级别的完整详情。
技巧和最佳实践
设计
- 深色主题默认 -- 大多数平台用户使用深色模式。为深色背景(
#1a1a2e或类似)设计,使用浅色文本(#e0e0e0)。 - 48px 最小触摸目标 -- 按钮和交互元素应至少 48px 高,以便无障碍和 VLM 友好交互。
- 14px 最小字体大小 -- 所有文本必须至少 14px 以便阅读。
- 响应式布局 -- 应用面板宽度可变。使用 CSS Grid 或 Flexbox 配合
auto-fit进行适配。
性能
- 在存储中缓存结果 -- 使用
ais.storage保存计算结果。启动时恢复它们,以便用户立即看到数据。 - 限制聊天历史请求 --
ais.chat.getHistory(500)通常足够。避免请求无限制历史。 - 无轮询 -- 使用
ais.chat.onMessage()进行实时更新,而不是重复调用getHistory。
安全
- 请求最小权限 -- 只列出您的应用实际使用的权限。更少的权限意味着更多用户会信任并安装您的应用。
- 验证所有输入 -- 来自
ais.chat.getHistory()的数据包含用户生成的内容。插入 DOM 前进行清理。 - 不要存储敏感数据 --
ais.storage未加密。永远不要存储密码、令牌或 API 密钥(除非您的应用有secrets:sync并明确处理凭证传输)。
兼容性
- 检查
ais.platform.abi-- 如果您的应用依赖特定 SDK 功能,检查 ABI 版本并在平台较旧时显示有用的消息。 - 在 try/catch 中包装 SDK 调用 -- 权限错误和平台版本差异可能导致拒绝。优雅处理它们。
- 使用 hello-world 示例测试 -- 平台附带一个示例小程序,您可以作为参考。