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
6.3 KiB
6.3 KiB
MiragenFlow 画布节点插件
本目录保留画布节点的源码级内部扩展能力。每个扩展是一个独立 TypeScript 目录,自带 package.json / build.mjs / src/index.tsx / dist/,可单独开发和构建。
内置节点包括文本、图片、音频、生成配置和组。Markdown、SVG、HTML、3D 全景和便利贴目录是 SDK 与内部集成示例。
作者只写节点 UI 与逻辑,类型、JSX、宿主 React 桥接和构建由 @miragenflow/plugin-sdk 提供。当前产品不包含远程插件市场、注册表、自动发现或 URL 安装入口;需要上线的扩展应作为应用源码的一部分接入并随产品构建。
目录约定
plugins/canvas/
sdk/ # 插件 SDK(类型 + automatic JSX 运行时 + 构建助手)
template/ # 起步模板:复制它开始写新插件
markdown/ # 每个插件一个独立目录
package.json
build.mjs # 一行 buildPlugin,产物名取目录名 → dist/markdown.js
tsconfig.json
src/index.tsx # 插件源码(默认导出 definePlugin(...))
README.md
svg/ html/ panorama/ sticky-note/ ...
创建内部扩展
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 构建 dist/my-plugin.js
npm run typecheck # tsc --noEmit,类型自检
产物名取目录名,复制后记得把目录改成插件名。
构建
cd plugins/canvas/<name>
npm install
npm run build # → dist/<name>.js
npm run dev # watch,改动自动构建
buildPlugin 只生成 dist/ 产物,不会写入 web/public/ 或生成远程清单。示例扩展默认不在产品界面中出现;商业构建需要在应用源码中显式注册准备启用的节点定义。
用 SDK 写插件
默认导出 definePlugin({...})(对象形式,无需再 const { React } = runtime):
import { definePlugin, useState } from "@miragenflow/plugin-sdk";
import type { CanvasNodeContentProps } from "@miragenflow/plugin-sdk";
function Content({ ctx }: CanvasNodeContentProps) {
const [n, setN] = useState(0);
return (
<button onMouseDown={(e) => e.stopPropagation()} onClick={() => setN((v) => v + 1)} style={{ color: ctx.theme.node.text }}>
{ctx.node.title}: {n}
</button>
);
}
export default definePlugin({
id: "my-plugin",
name: "我的插件",
version: "1.0.0",
css: "…", // 可选:插件样式,自动注入/清理
nodes: [ /* CanvasNodeDefinition[] */ ],
setup(app) { return () => {}; }, // 可选,返回清理函数;app 含 injectCSS/emit/on
});
SDK 导出的 hooks(useState/useEffect/useMemo/useRef/...)运行时转发宿主 React,类型来自 @types/react。SDK 与依赖接入见 sdk/README.md。
CanvasNodeDefinition
{
type: string; // 建议 "<pluginId>:<name>",全局唯一
title: string; // 创建菜单/默认标题
icon: ReactNode; // emoji 字符串或任意 ReactNode
description?: string;
defaultSize: { width, height };
defaultMetadata?: object; // 新建节点初始 metadata(文本内容放 content)
minimapColor?: string;
showInCreateMenu?: boolean; // 默认 true
hasSourceHandle?: boolean; // 右侧输出连接点,默认 true
keepAspectRatio?: (node) => boolean;
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 工具栏
onDoubleClick?: (ctx) => boolean; // 返回 true 表示已处理双击
}
ctx:节点与画布交互接口
Content / Panel / toolbar 都会拿到 ctx(类型 CanvasNodeContext):
| 能力 | 说明 |
|---|---|
ctx.node |
当前节点数据(含 metadata.content 等) |
ctx.theme / ctx.scale |
当前画布主题 token 与缩放,用来让 UI 跟随主题 |
ctx.updateMetadata(patch) |
更新自身 metadata(如保存内容) |
ctx.updateNode(patch) |
更新自身 title/width/height |
ctx.getNode(id) / ctx.getNodes() / ctx.getConnections() |
读画布 |
ctx.getUpstream() / ctx.getDownstream() |
取上/下游相连节点 |
ctx.applyOps(ops) |
用画布指令集增删节点/连线、选择、触发生成(见下) |
ctx.emit(event, payload) / ctx.on(event, handler) |
节点/插件间事件通信 |
ctx.storage |
插件私有持久化(按插件 id 命名空间) |
metadata的内置字段(content、status、model…)是强类型;插件写入的自定义字段读出为unknown,按需as断言(参考sticky-note的pluginColor)。
画布指令集(ctx.applyOps)
ctx.applyOps([
{ type: "add_node", id?, nodeType, title?, x?, y?, width?, height?, metadata? },
{ type: "update_node", id, patch?, metadata? },
{ type: "delete_node", id? | ids? },
{ type: "connect_nodes", fromNodeId, toNodeId },
{ type: "delete_connections", id? | ids? | all? },
{ type: "select_nodes", ids },
{ type: "set_viewport", viewport },
{ type: "run_generation", nodeId, mode?, prompt? },
]);
重依赖 / 资源
- 重依赖(three.js、marked 等):不要打进 bundle,运行时
await import("https://esm.sh/...")动态加载(esbuild 自动 external);在src/env.d.ts声明该模块以通过 tsc。参考panorama/、markdown/。 - CSS:写独立
.css,import css from "./styles.css"拿到字符串(esbuildtextloader),放到css字段自动注入/清理;src/env.d.ts声明*.css。参考markdown/。 - HTML:HTML 节点把 HTML 字符串塞进 sandbox iframe 的
srcDoc,自带<style>,不需要插件级 CSS。参考html/。
注意
- 扩展代码会在画布页面内直接执行,可以访问浏览器本地数据(含 AI API Key),接入商业构建前需要完成代码审查。
- 交互控件记得
onMouseDown={(e) => e.stopPropagation()}(避免触发节点拖拽),滚动区域加onWheel={(e) => e.stopPropagation()}与容器data-canvas-no-zoom(避免被画布缩放拦截)。