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
+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);
};