快速开始
5 分钟创建你的第一个 TiansheAI 插件
1. 创建项目结构
my-plugin/
├── manifest.json # 插件清单(必需)
├── index.js # 入口文件(必需)
└── views/ # 自定义页面(可选)
└── index.html
2. 编写 manifest.json
{
"id": "my_first_plugin",
"name": "我的第一个插件",
"version": "1.0.0",
"author": "Your Name",
"description": "这是一个示例插件",
"icon": "P",
"main": "index.js",
"dataTables": [
{
"code": "tasks",
"name": "任务列表",
"columns": [
{
"name": "任务名称",
"type": "VARCHAR",
"fieldType": "text",
"nullable": false
},
{
"name": "状态",
"type": "VARCHAR",
"fieldType": "select",
"nullable": false,
"metadata": {
"options": ["待处理", "进行中", "已完成"]
}
}
]
}
],
"permissions": {
"database": true,
"browser": true
}
}
3. 编写插件代码
module.exports = {
// 插件激活时调用
async activate(context) {
console.log('插件已激活:', context.manifest.name);
// 注册命令
context.registerCommand('process-task', async (params, helpers) => {
const { rowId, rowData } = params;
// 更新任务状态
const tableId = helpers.plugin.getDataTableId('tasks');
await helpers.database.updateRow(tableId, rowId, {
'状态': '进行中'
});
// 执行业务逻辑...
await helpers.utils.sleep(1000);
// 更新为完成
await helpers.database.updateRow(tableId, rowId, {
'状态': '已完成'
});
return { success: true };
});
},
// 插件停用时调用
async deactivate() {
console.log('插件已停用');
}
};
4. 安装插件
在 TiansheAI 应用中:
- 打开「插件管理」页面
- 点击「导入插件」
- 选择你的插件目录或 .tsai 压缩包
- 等待安装完成后即可使用
项目结构
my-plugin/
├── manifest.json # 插件清单(必需)
├── index.js # 入口文件(必需)
├── lib/ # 工具库(可选)
│ ├── utils.js
│ └── api-client.js
├── views/ # 自定义页面(可选)
│ ├── index.html
│ ├── settings.html
│ └── app.js
├── assets/ # 静态资源(可选)
│ └── icon.png
└── extensions/ # 浏览器扩展(可选)
└── content.js
Manifest 配置
manifest.json 是插件的核心配置文件,定义了插件的基本信息、数据表、UI 扩展等。
基础字段
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
id |
string | 是 | 插件唯一标识,只能包含字母、数字、下划线 |
name |
string | 是 | 插件显示名称 |
version |
string | 是 | 语义化版本号 (例: 1.0.0) |
author |
string | 是 | 作者名称 |
main |
string | 是 | 入口文件路径 (相对路径) |
description |
string | 否 | 插件描述 |
icon |
string | 否 | 图标 (emoji 或图片路径) |
category |
string | 否 | 分类 (如: 电商、工具、数据采集) |
完整示例
{
"id": "doudian_auto_publish",
"name": "抖店自动上货",
"version": "1.0.0",
"author": "TiansheAI Team",
"description": "自动化抖店商品发布",
"icon": "E",
"category": "电商",
"main": "index.js",
"dataTables": [
{
"code": "stores",
"name": "店铺配置",
"columns": [
{
"name": "店铺名称",
"type": "VARCHAR",
"fieldType": "text",
"nullable": false
},
{
"name": "登录状态",
"type": "VARCHAR",
"fieldType": "select",
"nullable": false,
"metadata": {
"options": ["未登录", "已登录", "已过期"]
}
}
]
}
],
"configuration": {
"properties": {
"publish_interval": {
"type": "number",
"title": "发布间隔(秒)",
"default": 5,
"minimum": 1,
"maximum": 60
}
}
},
"contributes": {
"toolbarButtons": [
{
"id": "publish-selected",
"label": "发布选中",
"icon": "upload",
"command": "publish",
"requiresSelection": true
}
]
},
"permissions": {
"browser": true,
"network": true,
"database": true
}
}
生命周期
插件的生命周期从导入开始,经过加载、激活、运行、停用,最终卸载。
导入
解压、验证清单
加载
创建数据表、加载模块
激活
执行 activate()
运行
处理命令和事件
停用
执行 deactivate()
生命周期钩子
module.exports = {
// 插件激活 - 模块加载后自动调用
// 用于初始化、注册命令和 API
async activate(context) {
console.log('插件激活');
// 注册命令
context.registerCommand('my-command', handler);
// 暴露 API 给其他插件
context.exposeAPI('getData', async () => {
return { data: 'hello' };
});
},
// 插件停用 - 卸载前调用
// 用于清理资源、取消订阅
async deactivate() {
console.log('插件停用');
// 清理定时器、关闭连接等
},
// 插件启动 - 用户点击"启动"按钮时调用
// 用于开始业务逻辑
async onStart(helpers) {
console.log('插件启动');
// 开始采集、监控等
},
// 插件停止 - 用户点击"停止"按钮时调用
// 用于暂停业务逻辑
async onStop(helpers) {
console.log('插件停止');
// 暂停任务
},
// 命令映射
commands: {
'custom-action': async (params, helpers) => {
// 处理命令
}
}
};
PluginContext
PluginContext 是每个插件的专属上下文,在 activate() 钩子中传入。
属性
| 属性 | 类型 | 说明 |
|---|---|---|
manifest |
JSPluginManifest | 插件清单对象 |
plugin |
PluginInfo | 插件基本信息 |
dataTables |
DataTableInfo[] | 数据表列表 |
方法
// 注册命令
context.registerCommand('command-id', async (params, helpers) => {
// 处理命令
});
// 获取数据表
const table = context.getDataTable('code');
// 配置管理
await context.getConfiguration('key');
await context.setConfiguration('key', value);
// 数据存储(key-value)
await context.setData('key', value);
const data = await context.getData('key');
await context.deleteData('key');
// 暴露 API 给其他插件
context.exposeAPI('apiName', async (...args) => {
return result;
});
// 获取暴露的 API
const api = context.getExposedAPI('apiName');
PluginHelpers
PluginHelpers 提供 20+ 个命名空间的 API,是插件与系统交互的主要接口。
命名空间列表
database
数据库查询、插入、更新、删除
browser
浏览器创建、导航、交互
network
HTTP 请求、Webhook
ui
通知、模态窗口、消息提示
storage
配置、持久化数据
utils
验证、格式化、ID 生成
ai
本地 LLM 推理、对话
openai
OpenAI API 调用
scheduler
Cron、定时执行
taskQueue
并发控制、批量任务
crypto
AES、SHA、HMAC 加密
plugin
插件信息、数据表访问
命令系统
命令是插件响应用户操作的主要方式,可以从工具栏按钮、行按钮或其他插件触发。
注册命令
async activate(context) {
context.registerCommand('publish', async (params, helpers) => {
// params 包含:
// - rowId: 行 ID
// - rowData: 行数据
// - selectedRows: 选中的多行数据
// - datasetId: 数据表 ID
// - count: 选中行数
const { rowId, rowData } = params;
// 执行业务逻辑
await doSomething(rowData);
// 返回结果
return { success: true, message: '处理完成' };
});
}
命令参数
| 参数 | 类型 | 说明 |
|---|---|---|
rowId |
number | 当前行的 ID |
rowData |
object | 当前行的完整数据 |
selectedRows |
object[] | 选中的多行数据数组 |
datasetId |
string | 当前数据表的 ID |
count |
number | 选中的行数 |
数据表定义
在 manifest.json 的 dataTables 字段中定义插件的数据表结构。
列类型
| type (DuckDB) | fieldType (UI) | 说明 |
|---|---|---|
VARCHAR |
text |
单行文本输入 |
TEXT |
textarea |
多行文本输入 |
INTEGER |
number |
整数输入 |
DOUBLE |
number |
浮点数输入 |
VARCHAR |
select |
下拉选择 (需配置 options) |
TIMESTAMP |
datetime |
日期时间选择器 |
BOOLEAN |
checkbox |
复选框 |
完整示例
{
"dataTables": [
{
"code": "products",
"name": "商品列表",
"description": "待发布的商品信息",
"hidden": false,
"columns": [
{
"name": "商品名称",
"type": "VARCHAR",
"fieldType": "text",
"nullable": false
},
{
"name": "价格",
"type": "DOUBLE",
"fieldType": "number",
"nullable": false
},
{
"name": "状态",
"type": "VARCHAR",
"fieldType": "select",
"nullable": false,
"metadata": {
"options": ["待发布", "发布中", "已发布", "失败"]
}
},
{
"name": "描述",
"type": "TEXT",
"fieldType": "textarea",
"nullable": true,
"metadata": {
"placeholder": "输入商品描述..."
}
},
{
"name": "创建时间",
"type": "TIMESTAMP",
"fieldType": "datetime",
"nullable": true
}
]
}
]
}
隐藏表
设置 "hidden": true 可以创建仅供插件内部使用的数据表,不会在 UI 中显示。
数据库操作
通过 helpers.database 命名空间操作数据表。
查询数据
// 获取数据表 ID
const tableId = helpers.plugin.getDataTableId('products');
// 查询所有数据
const all = await helpers.database.query(tableId);
// 条件查询
const pending = await helpers.database.query(
tableId,
`SELECT * FROM data WHERE "状态" = '待发布'`
);
// 获取行数
const count = await helpers.database.getCount(tableId);
// 条件计数
const pendingCount = await helpers.database.getCount(
tableId,
`"状态" = '待发布'`
);
// 获取表结构
const schema = await helpers.database.getSchema(tableId);
插入数据
// 插入单条
const rowId = await helpers.database.insert(tableId, {
'商品名称': '示例商品',
'价格': 99.9,
'状态': '待发布'
});
// 批量插入
const count = await helpers.database.insertBatch(tableId, [
{ '商品名称': '商品1', '价格': 100, '状态': '待发布' },
{ '商品名称': '商品2', '价格': 200, '状态': '待发布' }
]);
更新数据
// 更新单行
await helpers.database.updateRow(tableId, rowId, {
'状态': '已发布',
'发布时间': new Date().toISOString()
});
// 批量更新
const affected = await helpers.database.updateBatch(
tableId,
`"状态" = '待发布'`,
{ '状态': '发布中' }
);
删除数据
// 删除单行
await helpers.database.deleteRow(tableId, rowId);
// 批量删除
const deleted = await helpers.database.deleteBatch(
tableId,
`"状态" = '已完成'`
);
持久化存储
通过 helpers.storage 命名空间管理配置和持久化数据。
// 获取配置 (从 manifest.configuration 定义)
const interval = await helpers.storage.getConfig('publish_interval');
// 设置配置
await helpers.storage.setConfig('publish_interval', 10);
// 存储任意数据
await helpers.storage.setData('lastRunTime', Date.now());
await helpers.storage.setData('cache', { key: 'value' });
// 获取数据
const lastRun = await helpers.storage.getData('lastRunTime');
// 删除数据
await helpers.storage.deleteData('cache');
browser - 浏览器控制
创建和控制浏览器实例,实现网页自动化。
// 创建浏览器实例
const browser = await helpers.browser.create({
headless: false, // 显示浏览器窗口
partition: 'shop-1', // 会话隔离
proxy: 'http://...', // 代理设置
userAgent: '...' // 自定义 UA
});
try {
// 导航
await browser.goto('https://example.com');
// 等待元素
await browser.waitForSelector('.login-form');
// 点击
await browser.click('#login-btn');
// 输入文本
await browser.type('#username', 'user@example.com');
// 获取文本
const title = await browser.getText('h1');
// 获取属性
const href = await browser.getAttribute('a', 'href');
// 执行 JavaScript
const result = await browser.evaluate(() => {
return document.title;
});
// 截图
const screenshot = await browser.screenshot();
// 等待
await browser.waitForNavigation();
await browser.waitForTimeout(1000);
} finally {
// 关闭浏览器
await browser.close();
}
选择器操作
// 获取多个元素
const items = await browser.querySelectorAll('.product-item');
// 检查元素存在
const exists = await browser.exists('#modal');
// 获取元素数量
const count = await browser.count('.item');
// 滚动到元素
await browser.scrollIntoView('#footer');
// 选择下拉框
await browser.selectOption('#category', 'electronics');
// 上传文件
await browser.uploadFile('#file-input', '/path/to/file.jpg');
network - 网络请求
发送 HTTP 请求,支持 GET、POST、PUT、DELETE 等方法。
// GET 请求
const data = await helpers.network.get('https://api.example.com/data');
// POST 请求
const result = await helpers.network.post('https://api.example.com/submit', {
body: { name: 'test', value: 123 },
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token...'
}
});
// PUT 请求
await helpers.network.put('https://api.example.com/item/1', {
body: { status: 'updated' }
});
// DELETE 请求
await helpers.network.delete('https://api.example.com/item/1');
// 通用请求
const response = await helpers.network.request({
url: 'https://api.example.com/data',
method: 'POST',
headers: { 'X-Custom': 'value' },
body: { data: 'test' },
timeout: 30000
});
ui - 用户界面
显示通知、对话框等 UI 元素。
// 显示通知
helpers.ui.notify('操作成功', 'success');
helpers.ui.notify('发生错误', 'error');
helpers.ui.notify('请注意', 'warning');
helpers.ui.notify('提示信息', 'info');
// 显示确认对话框
const confirmed = await helpers.ui.confirm('确定要删除吗?');
if (confirmed) {
// 执行删除
}
// 显示输入对话框
const input = await helpers.ui.prompt('请输入名称:');
// 获取当前数据集信息
const currentDataset = helpers.ui.getCurrentDataset();
ai - AI 能力
调用本地 LLM 或 OpenAI API。
// 本地 AI 对话
const response = await helpers.ai.chat([
{ role: 'system', content: '你是一个有帮助的助手' },
{ role: 'user', content: '帮我总结这段文字...' }
]);
// OpenAI API
const result = await helpers.openai.chat({
model: 'gpt-4',
messages: [
{ role: 'user', content: '你好' }
],
temperature: 0.7
});
// 创建 Embedding
const embedding = await helpers.openai.createEmbedding({
model: 'text-embedding-ada-002',
input: '要转换的文本'
});
scheduler - 定时任务
创建定时任务和 Cron 表达式任务。
// 延迟执行
const taskId = await helpers.scheduler.delay(5000, async () => {
console.log('5 秒后执行');
});
// 间隔执行
const intervalId = await helpers.scheduler.interval(60000, async () => {
console.log('每 60 秒执行一次');
});
// Cron 表达式
const cronId = await helpers.scheduler.cron('0 */5 * * * *', async () => {
console.log('每 5 分钟执行一次');
});
// 取消任务
await helpers.scheduler.cancel(taskId);
crypto - 加密工具
AES 加密、哈希计算、HMAC 签名。
// AES 加密
const encrypted = await helpers.crypto.encrypt('敏感数据', 'secret-key');
// AES 解密
const decrypted = await helpers.crypto.decrypt(encrypted, 'secret-key');
// 计算哈希
const sha256 = await helpers.crypto.hash('data', 'sha256');
const md5 = await helpers.crypto.hash('data', 'md5');
// HMAC 签名
const signature = await helpers.crypto.hmac('data', 'secret', 'sha256');
taskQueue - 任务队列
管理并发任务,支持限流和批量处理。
// 创建任务队列
const queue = await helpers.taskQueue.create({
concurrency: 3, // 并发数
interval: 1000, // 间隔 (ms)
timeout: 30000 // 超时 (ms)
});
// 添加任务
const items = [1, 2, 3, 4, 5];
for (const item of items) {
queue.add(async () => {
await processItem(item);
});
}
// 等待所有任务完成
await queue.onIdle();
// 或者监听进度
queue.on('completed', (result) => {
console.log('任务完成:', result);
});
queue.on('error', (error) => {
console.error('任务失败:', error);
});
// 启动队列
await queue.start();
工具栏按钮
在数据表工具栏添加自定义按钮。
{
"contributes": {
"toolbarButtons": [
{
"id": "publish-selected",
"label": "发布选中",
"icon": "upload",
"command": "publish",
"confirmMessage": "确定要发布选中的商品吗?",
"requiresSelection": true,
"minSelection": 1,
"maxSelection": 100,
"appliesTo": {
"mode": "plugin-tables",
"tableFilter": ["products"]
},
"parameterMapping": {
"datasetId": "$datasetId",
"selectedRows": "$selectedRows",
"count": "$count"
}
}
]
}
}
配置说明
| 字段 | 说明 |
|---|---|
requiresSelection |
是否需要选中行才能点击 |
minSelection |
最少选中行数 |
maxSelection |
最多选中行数 |
appliesTo.mode |
all / plugin-tables / specific |
confirmMessage |
点击前的确认提示 |
自定义页面
创建自定义 HTML 页面,支持弹窗、嵌入和侧边栏模式。
{
"contributes": {
"customPages": [
{
"id": "settings-page",
"title": "插件设置",
"source": {
"type": "local",
"path": "views/settings.html"
},
"displayMode": "popup",
"width": 600,
"height": 400
}
]
}
}
页面通信
<!DOCTYPE html>
<html>
<head>
<script src="app.js"></script>
</head>
<body>
<button onclick="saveSettings()">保存</button>
<script>
// 调用插件命令
async function saveSettings() {
const result = await window.tianshe.executeCommand('save-settings', {
theme: 'dark'
});
console.log(result);
}
// 获取数据
async function loadData() {
const data = await window.tianshe.getData('settings');
console.log(data);
}
</script>
</body>
</html>
Activity Bar 视图
在左侧 Activity Bar 添加插件入口。
{
"contributes": {
"activityBarView": {
"id": "plugin-main",
"title": "我的插件",
"icon": "M",
"enabled": true,
"order": 100,
"source": {
"type": "local",
"path": "views/index.html"
},
"lifecycle": {
"strategy": "keep-alive",
"keepBrowserViewsOnHide": false
},
"isolation": {
"pagePartition": "persist:my-plugin",
"enableNodeIntegration": false,
"contextIsolation": true
}
}
}
}
跨插件通信
默认隔离的安全架构,需要显式声明权限才能进行跨插件调用。
暴露 API
// index.js
async activate(context) {
// 暴露 API
context.exposeAPI('getStores', async (filter) => {
const tableId = helpers.plugin.getDataTableId('stores');
return await helpers.database.query(tableId);
});
}
// manifest.json
{
"crossPlugin": {
"exposedAPIs": ["getStores"],
"allowedCallers": ["*"] // 或指定插件 ID
}
}
调用 API
// index.js
async activate(context) {
// 调用其他插件的 API
const stores = await context.callExposedAPI(
'provider-plugin-id',
'getStores',
[{ status: 'active' }]
);
}
// manifest.json
{
"crossPlugin": {
"canCall": ["provider-plugin-id"]
}
}
热重载开发
开发模式下自动监听文件变化并重新加载插件。
启用开发模式
- 导入插件时选择目录(非 .tsai 压缩包)
- 勾选「开发模式」选项
- 修改源代码后自动重载
开发模式特点:
- 使用符号链接而非复制文件
- 修改代码后自动重载
- 1 秒防抖避免频繁重载
- 保持数据表数据不丢失
字节码保护
将 JavaScript 编译为字节码,保护源代码。
# 编译为字节码
npm run plugin:compile-bytecode
# 加密代码
npm run plugin:encrypt-code
# 生成密钥对
npm run plugin:generate-keypair
# 签名代码
npm run plugin:sign-code
# 打包受保护的插件
npm run plugin:pack-protected
完整示例
一个功能完整的数据采集插件示例。
{
"id": "data_collector",
"name": "数据采集器",
"version": "1.0.0",
"author": "Developer",
"description": "自动采集网页数据",
"icon": "C",
"main": "index.js",
"dataTables": [
{
"code": "tasks",
"name": "采集任务",
"columns": [
{ "name": "URL", "type": "TEXT", "fieldType": "textarea", "nullable": false },
{ "name": "状态", "type": "VARCHAR", "fieldType": "select", "nullable": false,
"metadata": { "options": ["待采集", "采集中", "已完成", "失败"] } },
{ "name": "结果", "type": "TEXT", "fieldType": "textarea", "nullable": true }
]
}
],
"configuration": {
"properties": {
"concurrency": { "type": "number", "title": "并发数", "default": 3 },
"timeout": { "type": "number", "title": "超时(秒)", "default": 30 }
}
},
"contributes": {
"toolbarButtons": [
{
"id": "start-collect",
"label": "开始采集",
"icon": "play",
"command": "collect",
"requiresSelection": true
}
]
},
"permissions": {
"browser": true,
"database": true
}
}
module.exports = {
async activate(context) {
console.log('数据采集器已激活');
context.registerCommand('collect', async (params, helpers) => {
const { selectedRows } = params;
const tableId = helpers.plugin.getDataTableId('tasks');
// 获取配置
const concurrency = await helpers.storage.getConfig('concurrency') || 3;
const timeout = await helpers.storage.getConfig('timeout') || 30;
// 创建任务队列
const queue = await helpers.taskQueue.create({
concurrency,
timeout: timeout * 1000
});
// 添加采集任务
for (const row of selectedRows) {
queue.add(async () => {
const rowId = row.rowid;
// 更新状态为采集中
await helpers.database.updateRow(tableId, rowId, {
'状态': '采集中'
});
try {
// 创建浏览器
const browser = await helpers.browser.create({ headless: true });
try {
await browser.goto(row.URL);
await browser.waitForSelector('body');
// 提取数据
const title = await browser.getText('h1') || '';
const content = await browser.getText('article') || '';
// 保存结果
await helpers.database.updateRow(tableId, rowId, {
'状态': '已完成',
'结果': JSON.stringify({ title, content })
});
} finally {
await browser.close();
}
} catch (error) {
await helpers.database.updateRow(tableId, rowId, {
'状态': '失败',
'结果': error.message
});
}
});
}
// 等待完成
await queue.onIdle();
helpers.ui.notify(`采集完成,共处理 ${selectedRows.length} 条`, 'success');
return { success: true };
});
},
async deactivate() {
console.log('数据采集器已停用');
}
};