feat(plugin-sdk): add TypeScript SDK for Infinite Canvas plugins with automatic JSX and build support

This commit is contained in:
HouYunFei
2026-07-15 15:31:48 +08:00
parent 8d3f244524
commit 75aafc115f
82 changed files with 3228 additions and 3288 deletions
+23 -9
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import type { ChangeEvent as ReactChangeEvent, DragEvent as ReactDragEvent, MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent } from "react";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { BookOpen, Bot, Group, Home, ImageIcon, Images, List, Menu, Music2, Plus, Puzzle, Redo2, Settings2, Trash2, Undo2, Upload, Video, X } from "lucide-react";
import { BookOpen, Bot, Group, Home, ImageIcon, Images, List, Menu, Music2, Plus, Redo2, Settings2, Trash2, Undo2, Upload, Video, X } from "lucide-react";
import { saveAs } from "file-saver";
import { requestEdit, requestGeneration, requestImageQuestion } from "@/services/api/image";
@@ -38,6 +38,7 @@ import { CanvasNode } from "@/components/canvas/canvas-node";
import { CanvasNodePromptPanel, type CanvasNodeGenerationMode } from "@/components/canvas/canvas-node-prompt-panel";
import { CanvasToolbar } from "@/components/canvas/canvas-toolbar";
import { AssetPickerModal, type InsertAssetPayload } from "@/components/canvas/asset-picker-modal";
import { CanvasSidePanel } from "@/components/canvas/canvas-side-panel";
import { CanvasZoomControls } from "@/components/canvas/canvas-zoom-controls";
import { useAgentStore } from "@/stores/use-agent-store";
import { useCanvasStore } from "@/stores/canvas/use-canvas-store";
@@ -1049,6 +1050,21 @@ function InfiniteCanvasPage() {
setContextMenu(null);
}, [size.height, size.width]);
const focusNode = useCallback(
(nodeId: string) => {
const node = nodesRef.current.find((item) => item.id === nodeId);
if (!node) return;
const worldX = node.position.x + node.width / 2;
const worldY = node.position.y + node.height / 2;
const k = Math.min(Math.max(Math.min((size.width * 0.6) / node.width, (size.height * 0.6) / node.height), 0.05), 1.5);
setViewport({ x: size.width / 2 - worldX * k, y: size.height / 2 - worldY * k, k });
setSelectedNodeIds(new Set([nodeId]));
setSelectedConnectionId(null);
setContextMenu(null);
},
[size.height, size.width],
);
const setZoomScale = useCallback(
(scale: number) => {
const nextScale = Math.min(Math.max(scale, 0.05), 5);
@@ -1651,13 +1667,13 @@ function InfiniteCanvasPage() {
const content = node.metadata?.content?.trim();
if (!content) return message.error("没有可保存的文本");
addAsset({ kind: "text", title: node.metadata?.prompt?.slice(0, 24) || "画布文本", coverUrl: "", tags: [], source: "Canvas", data: { content }, metadata: { source: "canvas", nodeId: node.id } });
message.success("已加入我的素材");
message.success("已加入我的资产");
return;
}
if (node.type === CanvasNodeType.Video) {
if (!node.metadata?.content) return message.error("没有可保存的视频");
addAsset({ kind: "video", title: node.metadata?.prompt?.slice(0, 24) || "画布视频", coverUrl: "", tags: [], source: "Canvas", data: { url: node.metadata.content, storageKey: node.metadata.storageKey, width: node.width, height: node.height, bytes: node.metadata.bytes || 0, mimeType: node.metadata.mimeType || "video/mp4" }, metadata: { source: "canvas", nodeId: node.id, prompt: node.metadata?.prompt } });
message.success("已加入我的素材");
message.success("已加入我的资产");
return;
}
if (!node.metadata?.content) return message.error("没有可保存的图片");
@@ -1678,7 +1694,7 @@ function InfiniteCanvasPage() {
},
metadata: { source: "canvas", nodeId: node.id, prompt: node.metadata?.prompt },
});
message.success("已加入我的素材");
message.success("已加入我的资产");
},
[addAsset, message],
);
@@ -2555,6 +2571,7 @@ function InfiniteCanvasPage() {
return (
<main className="flex h-full min-h-0 overflow-hidden" style={{ background: theme.canvas.background, color: theme.node.text }}>
<CanvasSidePanel nodes={nodes} selectedNodeIds={selectedNodeIds} onFocusNode={focusNode} onInsertAsset={handleAssetInsert} />
<section className="relative min-w-0 flex-1 overflow-hidden">
<CanvasTopBar
title={currentProject?.title || "未命名画布"}
@@ -2796,9 +2813,6 @@ function InfiniteCanvasPage() {
onDeselect={deselectCanvas}
onBackgroundModeChange={setBackgroundMode}
onShowImageInfoChange={setShowImageInfo}
onOpenMyAssets={() => {
setAssetPickerOpen(true);
}}
/>
{isMiniMapOpen ? <Minimap nodes={nodes} viewport={viewport} viewportSize={size} onViewportChange={setViewport} /> : null}
@@ -2957,8 +2971,7 @@ function CanvasTopBar({
{ key: "new", icon: <Plus className="size-4" />, label: "新建画布", onClick: onCreateProject },
{ key: "delete", danger: true, icon: <Trash2 className="size-4" />, label: "删除当前画布", onClick: onDeleteProject },
{ type: "divider" },
{ key: "import", icon: <Upload className="size-4" />, label: "导入素材", onClick: onImportImage },
{ key: "plugins", icon: <Puzzle className="size-4" />, label: "节点插件", onClick: onOpenPlugins },
{ key: "import", icon: <Upload className="size-4" />, label: "导入资产", onClick: onImportImage },
{ type: "divider" },
{ key: "undo", disabled: !canUndo, icon: <Undo2 className="size-4" />, label: <MenuLabel text="撤销" shortcut="⌘ Z" />, onClick: onUndo },
{ key: "redo", disabled: !canRedo, icon: <Redo2 className="size-4" />, label: <MenuLabel text="重做" shortcut="⌘ ⇧ Z / ⌘ Y" />, onClick: onRedo },
@@ -3002,6 +3015,7 @@ function CanvasTopBar({
<UserStatusActions
variant="canvas"
onOpenShortcuts={() => setShortcutsOpen(true)}
onOpenPlugins={onOpenPlugins}
/>
<span className="h-6 w-px" style={{ background: theme.toolbar.border }} />
<Button