mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-04 00:01:14 +08:00
feat(plugin): implement canvas node plugin system with dynamic registration and remote installation
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import type { ComponentType, ReactNode } from "react";
|
||||
|
||||
import type { CanvasAgentOp } from "@/lib/canvas/canvas-agent-ops";
|
||||
import type { CanvasTheme } from "@/lib/canvas-theme";
|
||||
import type { CanvasConnection, CanvasNodeData, CanvasNodeMetadata } from "@/types/canvas";
|
||||
import type { CanvasResourceKind } from "@/lib/canvas/canvas-resource-references";
|
||||
|
||||
// 插件节点作为上游输入被消费时输出的资源
|
||||
export type CanvasNodeResource = { kind: CanvasResourceKind; text?: string; url?: string };
|
||||
|
||||
// 节点自带的工具栏按钮(追加到 hover 工具栏尾部)
|
||||
export type CanvasNodeToolbarItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
label: string;
|
||||
icon: ReactNode;
|
||||
onClick: () => void;
|
||||
active?: boolean;
|
||||
danger?: boolean;
|
||||
};
|
||||
|
||||
// 每个节点渲染时注入的上下文,是插件与画布交互的核心接口
|
||||
export type CanvasNodeContext = {
|
||||
node: CanvasNodeData;
|
||||
theme: CanvasTheme;
|
||||
scale: number;
|
||||
// 自身数据
|
||||
updateMetadata: (patch: CanvasNodeMetadata) => void;
|
||||
updateNode: (patch: Partial<Pick<CanvasNodeData, "title" | "width" | "height">>) => void;
|
||||
// 图访问
|
||||
getNode: (id: string) => CanvasNodeData | null;
|
||||
getNodes: () => CanvasNodeData[];
|
||||
getConnections: () => CanvasConnection[];
|
||||
getUpstream: () => CanvasNodeData[];
|
||||
getDownstream: () => CanvasNodeData[];
|
||||
// 画布操作,复用 Agent 指令集(增删节点/连线/选择/视口/触发生成)
|
||||
applyOps: (ops: CanvasAgentOp[]) => void;
|
||||
// 节点间/插件间通信
|
||||
emit: (event: string, payload?: unknown) => void;
|
||||
on: (event: string, handler: (payload: unknown) => void) => () => void;
|
||||
// 插件私有持久化,命名空间隔离
|
||||
storage: PluginStorage;
|
||||
};
|
||||
|
||||
export type PluginStorage = {
|
||||
get: <T = unknown>(key: string) => Promise<T | null>;
|
||||
set: (key: string, value: unknown) => Promise<void>;
|
||||
remove: (key: string) => Promise<void>;
|
||||
};
|
||||
|
||||
// 画布宿主提供的、与具体节点无关的能力集合(由画布页面构建、注入渲染链路)
|
||||
export type CanvasPluginHost = {
|
||||
getNode: (id: string) => CanvasNodeData | null;
|
||||
getNodes: () => CanvasNodeData[];
|
||||
getConnections: () => CanvasConnection[];
|
||||
getUpstream: (nodeId: string) => CanvasNodeData[];
|
||||
getDownstream: (nodeId: string) => CanvasNodeData[];
|
||||
updateNode: (nodeId: string, patch: Partial<Pick<CanvasNodeData, "title" | "width" | "height">>) => void;
|
||||
updateMetadata: (nodeId: string, patch: CanvasNodeMetadata) => void;
|
||||
applyOps: (ops: CanvasAgentOp[]) => void;
|
||||
};
|
||||
|
||||
// 节点类型定义:内置节点与插件节点统一走这套结构
|
||||
export type CanvasNodeDefinition = {
|
||||
type: string; // 内置如 "image";插件建议 "<pluginId>:<name>"
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
description?: string;
|
||||
defaultSize: { width: number; height: number };
|
||||
defaultMetadata?: CanvasNodeMetadata;
|
||||
minimapColor?: string;
|
||||
showInCreateMenu?: boolean; // 默认 true
|
||||
hasSourceHandle?: boolean; // 右侧输出连接点,默认 true
|
||||
keepAspectRatio?: (node: CanvasNodeData) => boolean;
|
||||
resource?: (node: CanvasNodeData) => CanvasNodeResource | null;
|
||||
// 渲染:内置节点由 canvas-node 内部渲染器负责,可不提供 Content
|
||||
Content?: ComponentType<{ ctx: CanvasNodeContext }>;
|
||||
Panel?: ComponentType<{ ctx: CanvasNodeContext; onClose: () => void }>;
|
||||
toolbar?: (ctx: CanvasNodeContext) => CanvasNodeToolbarItem[];
|
||||
onDoubleClick?: (ctx: CanvasNodeContext) => boolean; // 返回 true 表示已处理
|
||||
};
|
||||
|
||||
// 插件启动时可访问的应用能力
|
||||
export type CanvasPluginApp = {
|
||||
version: string;
|
||||
emit: (event: string, payload?: unknown) => void;
|
||||
on: (event: string, handler: (payload: unknown) => void) => () => void;
|
||||
// 注入插件样式,返回移除函数;传 key 时同 key 覆盖旧样式
|
||||
injectCSS: (css: string, key?: string) => () => void;
|
||||
};
|
||||
|
||||
// 插件包默认导出
|
||||
export type CanvasPlugin = {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
description?: string;
|
||||
minAppVersion?: string;
|
||||
css?: string; // 插件样式,启用时自动注入、卸载/禁用时自动清理
|
||||
nodes: CanvasNodeDefinition[];
|
||||
setup?: (app: CanvasPluginApp) => void | (() => void);
|
||||
};
|
||||
@@ -18,6 +18,9 @@ export enum CanvasNodeType {
|
||||
Group = "group",
|
||||
}
|
||||
|
||||
// 节点类型放开为字符串,内置类型用 CanvasNodeType,插件类型为 "<pluginId>:<name>"
|
||||
export type CanvasNodeTypeId = CanvasNodeType | (string & {});
|
||||
|
||||
export type CanvasNodeStatus = "idle" | "success" | "loading" | "error";
|
||||
export type CanvasGenerationMode = "text" | "image" | "video" | "audio";
|
||||
export type CanvasImageGenerationType = "generation" | "edit";
|
||||
@@ -62,7 +65,7 @@ export type CanvasNodeMetadata = {
|
||||
|
||||
export type CanvasNodeData = {
|
||||
id: string;
|
||||
type: CanvasNodeType;
|
||||
type: CanvasNodeTypeId;
|
||||
title: string;
|
||||
position: Position;
|
||||
width: number;
|
||||
@@ -78,7 +81,7 @@ export type CanvasConnection = {
|
||||
|
||||
export type CanvasAssistantReference = {
|
||||
id: string;
|
||||
type: CanvasNodeType;
|
||||
type: CanvasNodeTypeId;
|
||||
title: string;
|
||||
dataUrl?: string;
|
||||
storageKey?: string;
|
||||
|
||||
Reference in New Issue
Block a user