release: v0.17.0
GitHub Pages / build (push) Canceled after 0s
GitHub Pages / deploy (push) Canceled after 0s
Docker image / meta (push) Successful in 24s
Docs Docker image / meta (push) Successful in 7s
Docker image / build (amd64, linux/amd64, ubuntu-latest) (push) Failing after 59s
Docs Docker image / build (amd64, linux/amd64, ubuntu-latest) (push) Failing after 30s
Docker image / merge (push) Canceled after 0s
Docs Docker image / merge (push) Canceled after 0s
Docker image / build (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
Docs Docker image / build (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s

This commit is contained in:
Qiufeng
2026-08-19 22:09:49 +08:00
parent 9414048f9d
commit 84e9e7bff5
469 changed files with 19997 additions and 39561 deletions
+14 -36
View File
@@ -1,10 +1,10 @@
# Infinite Canvas 画布节点插件
# MiragenFlow 画布节点插件
给画布扩展自定义节点。每个插件是一个**独立目录**,用 **TypeScript** 编写,自带 `package.json` / `build.mjs` / `src/index.tsx` / `dist/`,互不耦合,可单独构建、发布、升级。
本目录保留画布节点的源码级内部扩展能力。每个扩展是一个独立 TypeScript 目录,自带 `package.json` / `build.mjs` / `src/index.tsx` / `dist/`,可单独开发和构建。
内置节点只有文本、图片、视频、音频、生成配置、组六种;其余节点(Markdown、SVG、HTML、3D 全景、便利贴……)都是插件。
内置节点包括文本、图片、音频、生成配置和组。Markdown、SVG、HTML、3D 全景和便利贴目录是 SDK 与内部集成示例。
作者只写节点 UI 与逻辑,**类型、JSX、宿主 React、构建全部由 [`@infinite-canvas/plugin-sdk`](./sdk/README.md) 提供**,写 TSX 全程有代码提示;产物仍是宿主加载器现有契约的 ESM(React external、宿主单例)。
作者只写节点 UI 与逻辑,类型、JSX、宿主 React 桥接和构建由 [`@miragenflow/plugin-sdk`](./sdk/README.md) 提供。当前产品不包含远程插件市场、注册表、自动发现或 URL 安装入口;需要上线的扩展应作为应用源码的一部分接入并随产品构建。
## 目录约定
@@ -21,55 +21,37 @@ plugins/canvas/
svg/ html/ panorama/ sticky-note/ ...
```
## 快速开始
## 创建内部扩展
```bash
cp -r plugins/canvas/template plugins/canvas/my-plugin
cd plugins/canvas/my-plugin
# 改 package.json 的 name;改 src/index.tsx 里的 id / nodes[].type
npm install
npm run dev # watch 构建,产物同步到 web/public/plugins/my-plugin.js
npm run dev # watch 构建 dist/my-plugin.js
npm run typecheck # tsc --noEmit,类型自检
```
产物名取**目录名**,复制后记得把目录改成插件名。
## 构建 / 发布 / 升级
## 构建
```bash
cd plugins/canvas/<name>
npm install
npm run build # → dist/<name>.js,并同步到 web/public/plugins/<name>.js
npm run dev # watch,改动自动构建并同步
npm run build # → dist/<name>.js
npm run dev # watch,改动自动构建
```
把 `dist/<name>.js` 托管到任意静态地址(CDN、GitHub Raw、对象存储),用户在画布「节点插件」管理器填该 URL 安装。升级时重新构建覆盖同一 URL,用户点「更新」即可。
## 官方插件注册表
本项目官方插件由 CI 集中构建后发布到孤儿分支 `plugins-dist`(**构建产物不进 git**),画布「节点插件」面板顶部的**官方插件**区经 jsDelivr 从该分支远程拉取并一键安装;第三方插件仍走下方「第三方插件」的 JS URL 安装。构建脚本与发布说明见 [`registry/`](./registry/README.md);清单地址可用 `VITE_PLUGIN_REGISTRY_URL` 覆盖成自建来源。
## 本地开发
`npm run dev` 起 watch,产物会同步到 `web/public/plugins/<name>.js`。此后有两种方式在画布里用到它:
**方式一(推荐):自动发现。** 画布启动时会扫描 `web/public/plugins/` 下的插件,自动加入「节点插件」管理器列表,**默认关闭**;打开开关即启用。无需手动填 URL,启用时会按文件重新拉取,配合 watch 改完刷新即最新。
**方式二:`VITE_DEV_PLUGINS`。** 在 `web/.env.local` 声明(逗号分隔多个),这些插件每次刷新页面都**重新拉取并直接激活**(不缓存、不落库、无开关):
```env
VITE_DEV_PLUGINS=/plugins/markdown.js,/plugins/svg.js
```
再起画布 `web`(`npm run dev`)。流程即:改 `src/index.tsx` → watch 自动构建 → 刷新画布看到最新效果,无需反复安装。
`buildPlugin` 只生成 `dist/` 产物,不会写入 `web/public/` 或生成远程清单。示例扩展默认不在产品界面中出现;商业构建需要在应用源码中显式注册准备启用的节点定义。
## 用 SDK 写插件
默认导出 `definePlugin({...})`(对象形式,**无需再 `const { React } = runtime`**):
```tsx
import { definePlugin, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
import { definePlugin, useState } from "@miragenflow/plugin-sdk";
import type { CanvasNodeContentProps } from "@miragenflow/plugin-sdk";
function Content({ ctx }: CanvasNodeContentProps) {
const [n, setN] = useState(0);
@@ -106,7 +88,7 @@ SDK 导出的 hooks(`useState/useEffect/useMemo/useRef/...`)运行时转发宿
showInCreateMenu?: boolean; // 默认 true
hasSourceHandle?: boolean; // 右侧输出连接点,默认 true
keepAspectRatio?: (node) => boolean;
resource?: (node) => { kind: "text"|"image"|"video"|"audio", text?, url? } | null; // 作为上游输入被消费时输出什么
resource?: (node) => { kind: "text"|"image"|"audio", text?, url? } | null; // 作为上游输入被消费时输出什么
Content: ({ ctx }) => ReactNode; // 节点主体渲染
Panel?: ({ ctx, onClose }) => ReactNode; // 可选:节点下方面板
toolbar?: (ctx) => Array<{ id, title, label, icon, onClick, danger? }>; // 追加到 hover 工具栏
@@ -153,11 +135,7 @@ ctx.applyOps([
- **CSS**:写独立 `.css`,`import css from "./styles.css"` 拿到字符串(esbuild `text` loader),放到 `css` 字段自动注入/清理;`src/env.d.ts` 声明 `*.css`。参考 `markdown/`。
- **HTML**:HTML 节点把 HTML 字符串塞进 sandbox iframe 的 `srcDoc`,自带 `<style>`,不需要插件级 CSS。参考 `html/`。
## 兼容说明
加载器仍接受**默认导出为工厂函数** `(runtime) => CanvasPlugin`(用 `runtime.React`)或**普通对象**;老的 JS 插件无需改动即可运行。SDK 的 automatic JSX 让新插件走对象形式,更简洁。
## 注意
- 插件代码会在画布页面内**直接执行**,可访问浏览器本地数据(含 AI API Key)。发布前请自审,用户也只应安装可信来源。
- 扩展代码会在画布页面内直接执行,可以访问浏览器本地数据(含 AI API Key),接入商业构建前需要完成代码审查。
- 交互控件记得 `onMouseDown={(e) => e.stopPropagation()}`(避免触发节点拖拽),滚动区域加 `onWheel={(e) => e.stopPropagation()}` 与容器 `data-canvas-no-zoom`(避免被画布缩放拦截)。
+2 -6
View File
@@ -1,17 +1,13 @@
# HTML 节点插件
Infinite Canvas 画布节点插件:用沙箱 iframe 渲染 HTML。源码里的 `{{input}}` 会被替换为上游文本节点内容。
MiragenFlow 画布节点插件:用沙箱 iframe 渲染 HTML。源码里的 `{{input}}` 会被替换为上游文本节点内容。
## 构建
```bash
npm install
npm run build # 产物 dist/html.js,并同步到 web/public/plugins/html.js
npm run build # 产物 dist/html.js
npm run dev # watch
```
## 安装
画布 → 左上菜单「节点插件」→ 安装 URL 填 `/plugins/html.js`(或托管后的公网 URL)。
插件契约见 `plugins/canvas/README.md`。
+1 -1
View File
@@ -1,3 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
import { buildPlugin } from "@miragenflow/plugin-sdk/build";
await buildPlugin(import.meta.url);
+3 -3
View File
@@ -8,13 +8,13 @@
"name": "canvas-plugin-html",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"name": "@miragenflow/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
@@ -26,7 +26,7 @@
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"node_modules/@miragenflow/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
+2 -2
View File
@@ -3,14 +3,14 @@
"version": "1.2.0",
"private": true,
"type": "module",
"description": "Infinite Canvas HTML 节点插件",
"description": "MiragenFlow HTML 节点插件",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
+2 -2
View File
@@ -2,8 +2,8 @@
// 交互:预览态 iframe 默认 pointer-events:none —— 鼠标事件穿透到宿主节点体,
// 因此点节点任意位置都能拖动,无需在节点上加任何标题栏/按钮。
// 「编辑/预览」与「交互」开关都放在节点外的悬浮工具条(toolbar 扩展点),状态存 metadata。
import { definePlugin, useMemo, useRef, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
import { definePlugin, useMemo, useRef, useState } from "@miragenflow/plugin-sdk";
import type { CanvasNodeContentProps } from "@miragenflow/plugin-sdk";
// 源码编辑器行高/字号,行号槽与文本域必须完全一致才能对齐
const EDITOR_FONT = 12;
+1 -1
View File
@@ -5,7 +5,7 @@
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"jsxImportSource": "@miragenflow/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
+3 -7
View File
@@ -1,22 +1,18 @@
# Markdown 节点插件
Infinite Canvas 画布节点插件:在画布里编辑与渲染 Markdown。
MiragenFlow 画布节点插件:在画布里编辑与渲染 Markdown。
## 构建
```bash
npm install
npm run build # 产物 dist/markdown.js,并同步到 web/public/plugins/markdown.js
npm run build # 产物 dist/markdown.js
npm run dev # watch,改动自动构建
```
## 安装
画布 → 左上菜单「节点插件」→ 安装 URL 填 `/plugins/markdown.js`(或托管后的公网 URL)。
## 本地开发
`npm run dev` 起 watch,在 `web/.env.local` 加 `VITE_DEV_PLUGINS=/plugins/markdown.js`,起画布后改 `src/index.jsx` 刷新页面即生效,无需反复安装。
`npm run dev` 起 watch。将需要的节点定义在应用源码中注册后,启动 `web` 并刷新画布即可验证。
插件契约见 `plugins/canvas/README.md`。
+1 -1
View File
@@ -1,3 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
import { buildPlugin } from "@miragenflow/plugin-sdk/build";
await buildPlugin(import.meta.url);
+3 -3
View File
@@ -8,13 +8,13 @@
"name": "canvas-plugin-markdown",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"name": "@miragenflow/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
@@ -26,7 +26,7 @@
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"node_modules/@miragenflow/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
+2 -2
View File
@@ -3,14 +3,14 @@
"version": "1.1.0",
"private": true,
"type": "module",
"description": "Infinite Canvas Markdown 节点插件",
"description": "MiragenFlow Markdown 节点插件",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
+2 -2
View File
@@ -4,8 +4,8 @@
// 防闪烁:解析结果按源码模块级缓存,且只在 HTML 真正变化时写入 DOM——
// 画布任何重渲染都不会重新解析或重载 Markdown 里的图片。
// styles.css 由 esbuild 以 text 方式打进 bundle,通过 plugin.css 自动注入。
import { definePlugin, useEffect, useRef, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
import { definePlugin, useEffect, useRef, useState } from "@miragenflow/plugin-sdk";
import type { CanvasNodeContentProps } from "@miragenflow/plugin-sdk";
import css from "./styles.css";
+1 -1
View File
@@ -5,7 +5,7 @@
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"jsxImportSource": "@miragenflow/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
+3 -5
View File
@@ -1,17 +1,15 @@
# 3D 全景节点插件
Infinite Canvas 画布节点插件:查看 360° 等距柱状(equirectangular)全景图,可拖拽旋转。可从上游图片节点自动取图。three.js 运行时从 CDN 按需加载,不打进插件体积。
MiragenFlow 画布节点插件:查看 360° 等距柱状(equirectangular)全景图,可拖拽旋转。可从上游图片节点自动取图。three.js 运行时从 CDN 按需加载,不打进插件体积。
## 构建
```bash
npm install
npm run build # 产物 dist/panorama.js,并同步到 web/public/plugins/panorama.js
npm run build # 产物 dist/panorama.js
npm run dev # watch
```
## 安装
画布 → 左上菜单「节点插件」→ 安装 URL 填 `/plugins/panorama.js`(或托管后的公网 URL)。连接一个全景图片节点到本节点即可查看。
构建后将节点定义作为应用源码的一部分注册。连接一个全景图片节点到本节点即可查看。
插件契约见 `plugins/canvas/README.md`。
+1 -1
View File
@@ -1,3 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
import { buildPlugin } from "@miragenflow/plugin-sdk/build";
await buildPlugin(import.meta.url);
+3 -3
View File
@@ -8,13 +8,13 @@
"name": "canvas-plugin-panorama",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"name": "@miragenflow/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
@@ -26,7 +26,7 @@
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"node_modules/@miragenflow/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
+2 -2
View File
@@ -3,14 +3,14 @@
"version": "1.1.0",
"private": true,
"type": "module",
"description": "Infinite Canvas 3D 全景节点插件",
"description": "MiragenFlow 3D 全景节点插件",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
+2 -2
View File
@@ -1,8 +1,8 @@
// 3D 全景节点:等距柱状(equirectangular)360° 全景查看器。
// 三种取图方式:① 从上游图片节点自动取图 ② 本地上传 ③ AI 生成(复用宿主内置生成面板)。
// three.js 从 CDN 按需加载,不打进 bundle。AI 生成通过 useBuiltinPanel 声明,结果写回本节点。
import { definePlugin, useCallback, useEffect, useRef, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps, CanvasNodeContext } from "@infinite-canvas/plugin-sdk";
import { definePlugin, useCallback, useEffect, useRef, useState } from "@miragenflow/plugin-sdk";
import type { CanvasNodeContentProps, CanvasNodeContext } from "@miragenflow/plugin-sdk";
// three 从 CDN 动态加载(env.d.ts 里声明为 any),不打进 bundle
let threePromise: Promise<any> | undefined;
+1 -1
View File
@@ -5,7 +5,7 @@
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"jsxImportSource": "@miragenflow/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
-39
View File
@@ -1,39 +0,0 @@
# 官方插件注册表(集中构建 + 远程发布)
本目录**只放构建脚本**,不放构建产物。官方插件由 CI 构建后发布到孤儿分支 `plugins-dist`,画布经 jsDelivr 从该分支远程拉取并一键安装。第三方插件不进本流程,由用户自行填 JS URL 安装。
```
registry/
package.json # 构建依赖(esbuild + SDK)
build.mjs # 一次进程构建所有官方插件 → dist/(gitignore)+ 生成清单
dist/ # 构建产物(gitignore,不提交;CI 发布到 plugins-dist 分支)
```
**产物不进 git**:`dist/` 与 `node_modules/` 均被 `.gitignore` 覆盖。`main` 分支只有源码与脚本。
## 发布流程(CI 自动)
`.github/workflows/publish-plugins.yml` 在**打版本 tag(`v*`)**或手动触发(`workflow_dispatch`)时,与 GitHub Pages 发布一起跑:
1. `npm install && npm run build` → 在 `dist/` 产出各 `<id>.js` 与 `official-plugins.json`;
2. 把 `dist/` 强推到孤儿分支 **`plugins-dist`**(仅含产物,force-push 覆盖)。
前端默认从下面地址读取(可用 `VITE_PLUGIN_REGISTRY_URL` 覆盖):
```
https://cdn.jsdelivr.net/gh/basketikun/infinite-canvas@plugins-dist/official-plugins.json
```
清单里每条的 `entry`(相对文件名)由前端解析成与清单同目录的绝对 URL,再走既有 URL 安装流程。jsDelivr 对分支有缓存(约数小时),需要立即生效可对该分支目录做 purge。
## 新增 / 更新官方插件
- 改完某官方插件源码后,在 `build.mjs` 的 `OFFICIAL` 里保持登记(新增插件在此加一条),提交到 `main`;
- 下次打版本 tag(或手动 `workflow_dispatch`)时,CI 自动重新构建并发布到 `plugins-dist`。
## 本地自测官方面板
```bash
cd plugins/canvas/registry && npm install && npm run build # 产出 dist/
# 用任意静态服务器伺服 dist/,把 VITE_PLUGIN_REGISTRY_URL 指向其 official-plugins.json
```
-69
View File
@@ -1,69 +0,0 @@
// 官方插件集中构建:一次进程构建所有官方插件 + 生成清单,产物进 dist/(已 gitignore)。
// 官方插件目录本身不各自安装依赖:统一从本目录 node_modules 解析 SDK(nodePaths)。
// CI(publish-plugins.yml)把 dist/ 强推到 plugins-dist 分支,前端经 jsDelivr 远程拉取。
// 本地自测:`npm install && npm run build`,再把 VITE_PLUGIN_REGISTRY_URL 指向本地 dist。
import { build } from "esbuild";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = dirname(fileURLToPath(import.meta.url));
const outDir = join(root, "dist");
const nodeModules = join(root, "node_modules");
// 官方插件登记表:新增官方插件在此登记即可。
// 版本不在这里写死 —— 从各插件的 package.json 读取(单一真源),
// 与插件 src 里 definePlugin({version}) 保持一致,避免清单与产物版本脱节。
const OFFICIAL = [
{ id: "markdown", dir: "markdown", name: "Markdown 节点", description: "在画布中编辑与渲染 Markdown", icon: "📝" },
{ id: "svg", dir: "svg", name: "SVG 节点", description: "透明背景渲染 SVG,可接收上游文本节点的 SVG 源码", icon: "🔷" },
{ id: "html", dir: "html", name: "HTML 节点", description: "沙箱 iframe 渲染 HTML,支持 {{input}} 注入上游文本", icon: "🌐" },
{ id: "panorama", dir: "panorama", name: "3D 全景节点", description: "查看 360° 等距柱状全景图,可从上游图片节点取图", icon: "🧭" },
{ id: "sticky-note", dir: "sticky-note", name: "便利贴节点", description: "可自选颜色、双击编辑、拖动即可移动的便利贴", icon: "📌" },
];
// 读取插件 package.json 的 version 作为清单版本的唯一来源
async function readPluginVersion(dir) {
const pkg = JSON.parse(await readFile(join(root, "..", dir, "package.json"), "utf8"));
if (!pkg.version) throw new Error(`插件 ${dir} 的 package.json 缺少 version`);
return pkg.version;
}
await rm(outDir, { recursive: true, force: true });
await mkdir(outDir, { recursive: true });
for (const plugin of OFFICIAL) {
await build({
entryPoints: [join(root, "..", plugin.dir, "src", "index.tsx")],
outfile: join(outDir, `${plugin.id}.js`),
bundle: true,
format: "esm",
platform: "browser",
target: "es2020",
// automatic JSX → SDK 的 jsx-runtime(内部用宿主 React),react external
jsx: "automatic",
jsxImportSource: "@infinite-canvas/plugin-sdk",
loader: { ".ts": "ts", ".tsx": "tsx", ".css": "text" },
external: ["react", "react-dom"],
minify: true,
nodePaths: [nodeModules],
});
console.log(`built ${plugin.id}.js`);
}
const manifest = {
// 清单结构版本;entry 为相对本清单的 bundle 文件名,由前端解析成绝对 URL
version: 1,
plugins: await Promise.all(
OFFICIAL.map(async (plugin) => ({
id: plugin.id,
name: plugin.name,
version: await readPluginVersion(plugin.dir),
description: plugin.description,
icon: plugin.icon,
entry: `${plugin.id}.js`,
})),
),
};
await writeFile(join(outDir, "official-plugins.json"), JSON.stringify(manifest, null, 4) + "\n");
console.log(`wrote official-plugins.json (${OFFICIAL.length} plugins) → dist/`);
-517
View File
@@ -1,517 +0,0 @@
{
"name": "canvas-official-plugins",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "canvas-official-plugins",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"esbuild": "^0.25.0"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
"@types/react": "19.1.12",
"esbuild": "^0.25.0",
"typescript": "^5"
},
"peerDependencies": {
"@types/react": ">=18"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
}
}
}
}
-14
View File
@@ -1,14 +0,0 @@
{
"name": "canvas-official-plugins",
"version": "1.0.0",
"private": true,
"type": "module",
"description": "Infinite Canvas 官方插件的集中构建(CI 发布到 plugins-dist 分支)",
"scripts": {
"build": "node build.mjs"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"esbuild": "^0.25.0"
}
}
+10 -10
View File
@@ -1,12 +1,12 @@
# @infinite-canvas/plugin-sdk
# @miragenflow/plugin-sdk
Infinite Canvas 画布节点插件的 **TypeScript SDK**。插件作者只写节点 UI 与逻辑,类型、JSX、运行时桥接、构建全部由 SDK 提供;产物仍是宿主加载器现有契约的 ESM(React external,宿主单例)。
MiragenFlow 画布节点内部扩展的 **TypeScript SDK**。扩展作者只写节点 UI 与逻辑,类型、JSX、运行时桥接和构建由 SDK 提供。当前产品不提供远程加载或安装入口,扩展需要随应用源码集成。
## 提供什么
| 能力 | 说明 |
| --- | --- |
| **完整类型** | `CanvasPlugin` / `CanvasNodeDefinition` / `CanvasNodeContext` / `CanvasAgentOp` / `CanvasTheme` / `CanvasNodeData` … 全部有提示 |
| **完整类型** | `CanvasPlugin` / `CanvasNodeDefinition` / `CanvasNodeContext` / `CanvasOp` / `CanvasTheme` / `CanvasNodeData` … 全部有提示 |
| `definePlugin(...)` | 给插件对象(或工厂)补全类型;对象形式无需再 `const { React } = runtime` |
| automatic JSX | `jsxImportSource` 指向本包,TSX 自动转发到宿主 React,**不打包第二份 React** |
| 类型化 hooks | `import { useState, useEffect, useMemo, useRef, ... }`,运行时转发宿主 React |
@@ -16,8 +16,8 @@ Infinite Canvas 画布节点插件的 **TypeScript SDK**。插件作者只写节
```tsx
// src/index.tsx
import { definePlugin, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
import { definePlugin, useState } from "@miragenflow/plugin-sdk";
import type { CanvasNodeContentProps } from "@miragenflow/plugin-sdk";
function Content({ ctx }: CanvasNodeContentProps) {
const [n, setN] = useState(0);
@@ -46,11 +46,11 @@ export default definePlugin({
```js
// build.mjs
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
import { buildPlugin } from "@miragenflow/plugin-sdk/build";
await buildPlugin(import.meta.url);
```
`npm run build` 产出 `dist/<目录名>.js` 并同步到 `web/public/plugins/`。
`npm run build` 只产出 `dist/<目录名>.js`,不会写入应用公共目录或远程清单。
## 依赖接入
@@ -61,17 +61,17 @@ await buildPlugin(import.meta.url);
"type": "module",
"scripts": { "build": "node build.mjs", "dev": "node build.mjs --watch", "typecheck": "tsc --noEmit" },
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
}
```
插件 `tsconfig.json` 关键项:`"jsx": "react-jsx"`、`"jsxImportSource": "@infinite-canvas/plugin-sdk"`、`"moduleResolution": "bundler"`。
插件 `tsconfig.json` 关键项:`"jsx": "react-jsx"`、`"jsxImportSource": "@miragenflow/plugin-sdk"`、`"moduleResolution": "bundler"`。
## 设计约束
- **React 单例**:JSX 与 hooks 惰性读取 `globalThis.InfiniteCanvasRuntime.React`(宿主在加载插件前注入),react 全程 external,绝不打包第二份。
- **React 单例**:JSX 与 hooks 惰性读取 `globalThis.MiragenFlowRuntime.React`(由源码集成层注入),react 全程 external,绝不打包第二份。
- **重依赖**:three、marked 等在源码里 `await import("https://esm.sh/...")` 动态加载,esbuild 自动 external,不进 bundle。
- **类型真源**:`src/types.ts` 是宿主 `web/src/types/canvas-plugin.ts` 公开契约的镜像;宿主契约变更时同步此处。
+4 -34
View File
@@ -1,51 +1,21 @@
// 共享构建助手:插件的 build.mjs 只需一行 `buildPlugin(import.meta.url)`。
// 统一 esbuild 配置(automatic JSX 指向本 SDK、react external、TS/TSX/CSS loader、
// 产物同步到 web/public/plugins),消除各插件重复的构建脚本。
// 统一 esbuild 配置(automatic JSX 指向本 SDK、react external、TS/TSX/CSS loader)。
import { build, context } from "esbuild";
import { cp, mkdir, readFile, writeFile } from "node:fs/promises";
import { basename, dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
/**
* @param {string} metaUrl 插件 build.mjs 的 import.meta.url
* @param {{ name?: string, entry?: string, publicDir?: string, esbuild?: object, plugins?: import("esbuild").Plugin[] }} [overrides]
* @param {{ name?: string, entry?: string, esbuild?: object, plugins?: import("esbuild").Plugin[] }} [overrides]
*/
export async function buildPlugin(metaUrl, overrides = {}) {
const root = dirname(fileURLToPath(metaUrl));
const name = overrides.name ?? basename(root); // 目录名即产物名,如 markdown → markdown.js
const distDir = join(root, "dist");
// 默认同步到仓库内 web/public/plugins(plugins/canvas/<name> → 上溯三层到仓库根)
const publicDir = overrides.publicDir ?? join(root, "..", "..", "..", "web", "public", "plugins");
const watch = process.argv.includes("--watch");
const entry = overrides.entry ?? join(root, "src", "index.tsx");
const syncToPublic = {
name: "sync-to-public",
setup(builder) {
builder.onEnd(async (result) => {
if (result.errors.length) return;
await mkdir(publicDir, { recursive: true });
await cp(join(distDir, `${name}.js`), join(publicDir, `${name}.js`));
// 维护本地插件清单 index.json,供画布启动时自动发现(该目录已 gitignore,仅本地开发用)
const indexPath = join(publicDir, "index.json");
let list = [];
try {
const parsed = JSON.parse(await readFile(indexPath, "utf8"));
if (Array.isArray(parsed)) list = parsed;
} catch {
// 无清单则新建
}
const entry = `/plugins/${name}.js`;
if (!list.includes(entry)) {
list.push(entry);
await writeFile(indexPath, JSON.stringify(list, null, 2) + "\n");
}
console.log(`[${name}] synced → web/public/plugins/${name}.js`);
});
},
};
const options = {
entryPoints: [entry],
outfile: join(distDir, `${name}.js`),
@@ -55,12 +25,12 @@ export async function buildPlugin(metaUrl, overrides = {}) {
target: "es2020",
// automatic JSX → 转发到本 SDK 的 jsx-runtime(内部用宿主 React),插件无需自带 React
jsx: "automatic",
jsxImportSource: "@infinite-canvas/plugin-sdk",
jsxImportSource: "@miragenflow/plugin-sdk",
loader: { ".js": "jsx", ".jsx": "jsx", ".ts": "ts", ".tsx": "tsx", ".css": "text" },
// 宿主提供单例 React,插件不打包 react;https:// 依赖 esbuild 自动 external
external: ["react", "react-dom"],
minify: !watch,
plugins: [syncToPublic, ...(overrides.plugins ?? [])],
plugins: overrides.plugins ?? [],
...overrides.esbuild,
};
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "@infinite-canvas/plugin-sdk",
"name": "@miragenflow/plugin-sdk",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@infinite-canvas/plugin-sdk",
"name": "@miragenflow/plugin-sdk",
"version": "0.1.0",
"devDependencies": {
"@types/react": "19.1.12",
+2 -2
View File
@@ -1,9 +1,9 @@
{
"name": "@infinite-canvas/plugin-sdk",
"name": "@miragenflow/plugin-sdk",
"version": "0.1.0",
"private": true,
"type": "module",
"description": "Infinite Canvas 画布节点插件 SDK:类型、automatic JSX 运行时、构建助手",
"description": "MiragenFlow 画布节点插件 SDK:类型、automatic JSX 运行时、构建助手",
"exports": {
".": "./src/index.ts",
"./jsx-runtime": "./src/jsx-runtime.ts",
+1 -1
View File
@@ -1,4 +1,4 @@
// @infinite-canvas/plugin-sdk 公开入口。
// @miragenflow/plugin-sdk 公开入口。
//
// 插件作者用 TS/TSX 编写,只关注节点 UI 与逻辑;类型、JSX、运行时桥接、构建
// 全部由本 SDK 提供,产物仍是宿主 loader 现有契约的 ESM(React external,宿主单例)。
+1 -1
View File
@@ -7,7 +7,7 @@ import type * as React from "react";
import { getReact } from "./runtime";
// Fragment 哨兵:渲染时才解析为宿主 React.Fragment,避免模块顶层触碰运行时。
export const Fragment = Symbol.for("infinite-canvas.jsx.fragment") as unknown as React.ExoticComponent<{ children?: React.ReactNode }>;
export const Fragment = Symbol.for("miragenflow.jsx.fragment") as unknown as React.ExoticComponent<{ children?: React.ReactNode }>;
function createElement(type: unknown, props: Record<string, unknown> | null, key?: unknown): React.ReactElement {
const react = getReact();
+4 -4
View File
@@ -6,13 +6,13 @@ import type * as React from "react";
import type { PluginRuntime } from "./types";
type RuntimeGlobal = { InfiniteCanvasRuntime?: PluginRuntime };
type RuntimeGlobal = { MiragenFlowRuntime?: PluginRuntime };
/** 取宿主注入的插件运行时(含 React、事件总线、injectCSS 等)。 */
export function getRuntime(): PluginRuntime {
const runtime = (globalThis as unknown as RuntimeGlobal).InfiniteCanvasRuntime;
const runtime = (globalThis as unknown as RuntimeGlobal).MiragenFlowRuntime;
if (!runtime) {
throw new Error("[plugin-sdk] Infinite Canvas 运行时未就绪:请在画布宿主中加载本插件");
throw new Error("[plugin-sdk] MiragenFlow 运行时未就绪:请在画布宿主中加载本插件");
}
return runtime;
}
@@ -23,7 +23,7 @@ export function getReact(): typeof React {
}
// --- 类型完整的 hooks 转发:签名取自 @types/react,运行时转发到宿主 React ---
// 这样插件作者可以直接 `import { useState } from "@infinite-canvas/plugin-sdk"`,
// 这样插件作者可以直接 `import { useState } from "@miragenflow/plugin-sdk"`,
// 无需再从 runtime 里解构 React。
export const useState: typeof React.useState = ((...args: unknown[]) => (getReact().useState as (...a: unknown[]) => unknown)(...args)) as typeof React.useState;
+16 -34
View File
@@ -1,4 +1,4 @@
// Infinite Canvas 插件公共契约类型。
// MiragenFlow 插件公共契约类型。
//
// 这是插件作者面向的「公开接口」子集,自包含、不依赖宿主 `@/` 内部模块,
// 因此可以被独立构建的插件包直接 import,获得完整的 TS 提示。
@@ -17,11 +17,11 @@ export type Position = { x: number; y: number };
export type ViewportTransform = { x: number; y: number; k: number };
// 内置节点类型;插件节点建议用 "<pluginId>:<name>"。放开为字符串以便扩展。
export type CanvasBuiltinNodeType = "image" | "text" | "config" | "video" | "audio" | "group";
export type CanvasBuiltinNodeType = "image" | "text" | "config" | "audio" | "group";
export type CanvasNodeTypeId = CanvasBuiltinNodeType | (string & {});
export type CanvasNodeStatus = "idle" | "success" | "loading" | "error";
export type CanvasGenerationMode = "text" | "image" | "video" | "audio";
export type CanvasGenerationMode = "text" | "image" | "audio";
export type CanvasImageGenerationType = "generation" | "edit";
// 节点 metadata 是扁平可选字段袋;插件自定义字段可直接写入(内容惯例放 content)。
@@ -38,10 +38,6 @@ export type CanvasNodeMetadata = {
size?: string;
quality?: string;
count?: number;
seconds?: string;
vquality?: string;
generateAudio?: string;
watermark?: string;
audioVoice?: string;
audioFormat?: string;
audioSpeed?: string;
@@ -115,10 +111,10 @@ export type CanvasTheme = {
};
// ---------------------------------------------------------------------------
// 画布指令集(ctx.applyOps):与 AI Agent 同级的画布操作能力
// 画布操作集(ctx.applyOps)
// ---------------------------------------------------------------------------
export type CanvasAgentOp =
export type CanvasOp =
| { type: "add_node"; id?: string; nodeType?: CanvasNodeTypeId; title?: string; position?: { x: number; y: number }; x?: number; y?: number; width?: number; height?: number; metadata?: CanvasNodeMetadata }
| { type: "update_node"; id: string; patch?: Partial<CanvasNodeData>; metadata?: CanvasNodeMetadata }
| { type: "delete_node"; id?: string; ids?: string[]; nodeType?: CanvasNodeTypeId }
@@ -132,18 +128,18 @@ export type CanvasAgentOp =
// 资源:插件节点作为上游输入被消费时输出什么(接入生成/引用体系)
// ---------------------------------------------------------------------------
export type CanvasResourceKind = "image" | "video" | "audio" | "text";
export type CanvasResourceKind = "image" | "audio" | "text";
export type CanvasNodeResource = { kind: CanvasResourceKind; text?: string; url?: string };
// ---------------------------------------------------------------------------
// AI 生成:插件直接复用宿主的模型/密钥配置发起生成(生图/生视频/生文本/生音频)
// AI 生成:插件直接复用宿主的模型/密钥配置发起生成(生图/生文本/生音频)
//
// 插件本身拿不到 API Key 与模型配置,这些能力由宿主注入。前置/系统提示词由
// 插件自行拼进 prompt(宿主不感知),因此不同插件可各自定制自己的提示词策略。
// 若宿主 AI 配置未就绪,会抛错(并由宿主提示用户去配置),插件用 try/catch 处理即可。
// ---------------------------------------------------------------------------
// 生成公共可选项;references 为图生图/图生视频的参考图(dataURL 或可访问 URL)
// 生成公共可选项;references 为图生图的参考图(dataURL 或可访问 URL)
export type GenerateOptions = {
signal?: AbortSignal;
references?: string[];
@@ -160,19 +156,6 @@ export type GenerateImageResult = {
images: string[];
};
export type GenerateVideoOptions = GenerateOptions & {
size?: string;
seconds?: string;
};
export type GenerateVideoResult = {
url: string; // 视频可访问 URL
mimeType: string;
width?: number;
height?: number;
durationMs?: number;
};
export type GenerateTextOptions = {
signal?: AbortSignal;
model?: string;
@@ -185,13 +168,12 @@ export type GenerateTextResult = {
};
// 一个可选模型:value 传回给 generateXxx({ model }),label 用于展示
export type ModelCapability = "image" | "video" | "text" | "audio";
export type ModelCapability = "image" | "text" | "audio";
export type ModelOption = { value: string; label: string };
// 宿主注入的 AI 生成能力,挂在 ctx.ai 下。任何插件均可调用。
export type CanvasPluginAi = {
generateImage: (prompt: string, options?: GenerateImageOptions) => Promise<GenerateImageResult>;
generateVideo: (prompt: string, options?: GenerateVideoOptions) => Promise<GenerateVideoResult>;
generateText: (prompt: string, options?: GenerateTextOptions) => Promise<GenerateTextResult>;
// 列出某能力下用户已配置的可选模型;不传能力则返回全部
listModels: (capability?: ModelCapability) => ModelOption[];
@@ -223,12 +205,12 @@ export type CanvasNodeContext = {
getConnections: () => CanvasConnection[];
getUpstream: () => CanvasNodeData[];
getDownstream: () => CanvasNodeData[];
// 画布操作(复用 Agent 指令集)
applyOps: (ops: CanvasAgentOp[]) => void;
// 画布操作
applyOps: (ops: CanvasOp[]) => void;
// 节点间/插件间通信
emit: (event: string, payload?: unknown) => void;
on: (event: string, handler: (payload: unknown) => void) => () => void;
// AI 生成能力(生图/生视频/生文本),复用宿主模型配置
// AI 生成能力(生图/生文本/生音频),复用宿主模型配置
ai: CanvasPluginAi;
// 打开/关闭本节点下方的自定义 Panel(需在节点定义里提供 Panel)
openPanel: () => void;
@@ -254,10 +236,10 @@ export type CanvasNodeToolbarItem = {
export type CanvasNodeContentProps = { ctx: CanvasNodeContext };
export type CanvasNodePanelProps = { ctx: CanvasNodeContext; onClose: () => void };
// 复用宿主内置生成面板(与图片/视频/文本节点同一个组件:模型选择、参数设置、
// 提示词库、运行/停止状态全部一致)。声明它即可获得完整生成体验,无需自写面板。
// 复用宿主内置生成面板(与图片/文本/音频节点同一个组件:模型选择、参数设置、
// 运行/停止状态全部一致)。声明它即可获得完整生成体验,无需自写面板。
export type CanvasBuiltinPanelConfig = {
mode: "image" | "video" | "text" | "audio"; // 生成类型,决定面板里的模型/设置项
mode: "image" | "text" | "audio"; // 生成类型,决定面板里的模型/设置项
// 提交给模型前自动拼在用户提示词前面的固定前缀(如全景图的 equirectangular 约束)
promptPrefix?: string;
// true(默认)时生成结果写回本节点自身 metadata.content;false 则按内置逻辑生成到下游新节点
@@ -323,7 +305,7 @@ export type CanvasPlugin = {
version: string;
description?: string;
minAppVersion?: string;
css?: string; // 插件样式,启用时自动注入、卸载/禁用时自动清理
css?: string; // 插件样式,注册时注入,集成释放时清理
nodes: CanvasNodeDefinition[];
setup?: (app: CanvasPluginApp) => void | (() => void);
};
+2 -6
View File
@@ -1,17 +1,13 @@
# 便利贴节点插件
Infinite Canvas 画布节点插件:彩色便利贴,可换色、编辑,并演示用 `ctx.applyOps` 衍生一个连到自己的文本节点(节点间交互示例)。
MiragenFlow 画布节点插件:彩色便利贴,可换色、编辑,并演示用 `ctx.applyOps` 衍生一个连到自己的文本节点(节点间交互示例)。
## 构建
```bash
npm install
npm run build # 产物 dist/sticky-note.js,并同步到 web/public/plugins/sticky-note.js
npm run build # 产物 dist/sticky-note.js
npm run dev # watch
```
## 安装
画布 → 左上菜单「节点插件」→ 安装 URL 填 `/plugins/sticky-note.js`(或托管后的公网 URL)。
插件契约见 `plugins/canvas/README.md`。
+1 -1
View File
@@ -1,3 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
import { buildPlugin } from "@miragenflow/plugin-sdk/build";
await buildPlugin(import.meta.url);
+3 -3
View File
@@ -8,13 +8,13 @@
"name": "canvas-plugin-sticky-note",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"name": "@miragenflow/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
@@ -26,7 +26,7 @@
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"node_modules/@miragenflow/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
+2 -2
View File
@@ -3,14 +3,14 @@
"version": "1.1.0",
"private": true,
"type": "module",
"description": "Infinite Canvas 便利贴节点插件",
"description": "MiragenFlow 便利贴节点插件",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
+2 -2
View File
@@ -1,7 +1,7 @@
// 便利贴节点:纯展示便利贴——整块可拖动、双击编辑、右上角自选颜色。
// 不再声明 resource(避免宿主在右上角显示「文本N」资源角标),也不再衍生节点。
import { definePlugin, useEffect, useRef, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
import { definePlugin, useEffect, useRef, useState } from "@miragenflow/plugin-sdk";
import type { CanvasNodeContentProps } from "@miragenflow/plugin-sdk";
// 预设便签色(点选切换),并额外提供自定义取色
const PRESET_COLORS = ["#fde68a", "#fca5a5", "#fdba74", "#a7f3d0", "#bfdbfe", "#ddd6fe", "#f9a8d4", "#e7e5e4"];
+1 -1
View File
@@ -5,7 +5,7 @@
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"jsxImportSource": "@miragenflow/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
+2 -6
View File
@@ -1,17 +1,13 @@
# SVG 节点插件
Infinite Canvas 画布节点插件:编辑与渲染 SVG,无自身内容时自动取上游文本节点里的 SVG 源码。
MiragenFlow 画布节点插件:编辑与渲染 SVG,无自身内容时自动取上游文本节点里的 SVG 源码。
## 构建
```bash
npm install
npm run build # 产物 dist/svg.js,并同步到 web/public/plugins/svg.js
npm run build # 产物 dist/svg.js
npm run dev # watch
```
## 安装
画布 → 左上菜单「节点插件」→ 安装 URL 填 `/plugins/svg.js`(或托管后的公网 URL)。
插件契约见 `plugins/canvas/README.md`。
+1 -1
View File
@@ -1,3 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
import { buildPlugin } from "@miragenflow/plugin-sdk/build";
await buildPlugin(import.meta.url);
+3 -3
View File
@@ -8,13 +8,13 @@
"name": "canvas-plugin-svg",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"name": "@miragenflow/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
@@ -26,7 +26,7 @@
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"node_modules/@miragenflow/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
+2 -2
View File
@@ -3,14 +3,14 @@
"version": "1.1.0",
"private": true,
"type": "module",
"description": "Infinite Canvas SVG 节点插件",
"description": "MiragenFlow SVG 节点插件",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
+2 -2
View File
@@ -1,6 +1,6 @@
// SVG 节点:编辑与渲染 SVG,透明背景直接融入画布;无自身内容时可取上游文本节点里的 SVG 源码。
import { definePlugin, useEffect, useRef, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
import { definePlugin, useEffect, useRef, useState } from "@miragenflow/plugin-sdk";
import type { CanvasNodeContentProps } from "@miragenflow/plugin-sdk";
function SvgContent({ ctx }: CanvasNodeContentProps) {
const [editing, setEditing] = useState(false);
+1 -1
View File
@@ -5,7 +5,7 @@
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"jsxImportSource": "@miragenflow/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
+3 -3
View File
@@ -9,11 +9,11 @@ cp -r plugins/canvas/template plugins/canvas/my-plugin
cd plugins/canvas/my-plugin
# 改 package.json 的 name;改 src/index.tsx 里的 id / name / type
npm install
npm run dev # watch 构建,产物同步到 web/public/plugins/my-plugin.js
npm run dev # watch 构建到 dist/my-plugin.js
npm run typecheck
```
然后启动画布 `web`,在「节点插件」管理器里启用(自动发现)即可看到你的节点。发布时把 `dist/<name>.js` 托管到任意静态地址,用户填 URL 安装。
然后将需要的节点定义在应用源码中注册,启动画布 `web` 即可看到节点。扩展随应用源码和构建产物发布,不通过 URL 安装。
> 产物名取**目录名**(`my-plugin/` → `my-plugin.js`),所以复制后记得把目录改成你的插件名。
@@ -22,4 +22,4 @@ npm run typecheck
- `src/index.tsx` 里 `Content({ ctx })` 的节点 UI 与逻辑;
- `definePlugin({...})` 里的 `id` / `nodes[].type` / `defaultSize` 等元信息。
类型、JSX、宿主 React、构建都由 `@infinite-canvas/plugin-sdk` 提供,写 TSX 全程有补全。`ctx` 的完整能力见 `plugins/canvas/README.md` 与 SDK 的类型定义。
类型、JSX、宿主 React、构建都由 `@miragenflow/plugin-sdk` 提供,写 TSX 全程有补全。`ctx` 的完整能力见 `plugins/canvas/README.md` 与 SDK 的类型定义。
+1 -1
View File
@@ -1,3 +1,3 @@
import { buildPlugin } from "@infinite-canvas/plugin-sdk/build";
import { buildPlugin } from "@miragenflow/plugin-sdk/build";
await buildPlugin(import.meta.url);
+3 -3
View File
@@ -8,13 +8,13 @@
"name": "canvas-plugin-template",
"version": "1.0.0",
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
},
"../sdk": {
"name": "@infinite-canvas/plugin-sdk",
"name": "@miragenflow/plugin-sdk",
"version": "0.1.0",
"dev": true,
"devDependencies": {
@@ -26,7 +26,7 @@
"@types/react": ">=18"
}
},
"node_modules/@infinite-canvas/plugin-sdk": {
"node_modules/@miragenflow/plugin-sdk": {
"resolved": "../sdk",
"link": true
},
+2 -2
View File
@@ -3,14 +3,14 @@
"version": "1.0.0",
"private": true,
"type": "module",
"description": "Infinite Canvas 插件起步模板(TypeScript + SDK)",
"description": "MiragenFlow 插件起步模板(TypeScript + SDK)",
"scripts": {
"build": "node build.mjs",
"dev": "node build.mjs --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@infinite-canvas/plugin-sdk": "file:../sdk",
"@miragenflow/plugin-sdk": "file:../sdk",
"@types/react": "19.1.12",
"typescript": "^5"
}
+2 -2
View File
@@ -1,7 +1,7 @@
// 插件模板:复制本目录 → 改 id/name/type → 写你的节点。
// 这是一个演示节点:编辑文本、跟随主题、读取上游节点、用画布指令衍生新节点。
import { definePlugin, useState } from "@infinite-canvas/plugin-sdk";
import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk";
import { definePlugin, useState } from "@miragenflow/plugin-sdk";
import type { CanvasNodeContentProps } from "@miragenflow/plugin-sdk";
function TemplateContent({ ctx }: CanvasNodeContentProps) {
const [editing, setEditing] = useState(false);
+1 -1
View File
@@ -5,7 +5,7 @@
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"jsxImportSource": "@infinite-canvas/plugin-sdk",
"jsxImportSource": "@miragenflow/plugin-sdk",
"strict": true,
"skipLibCheck": true,
"noEmit": true,