feat(platform): 插件协议系统 - 支持声明式渠道、工作流与参考媒体映射
This commit is contained in:
@@ -10,7 +10,7 @@ import { canvasNodeAssetCategory } from "@/lib/canvas/canvas-node-asset";
|
||||
import { isCanvasFolderNode } from "@/lib/canvas/canvas-frame";
|
||||
import { resolveAddNodeMenuCommands, type AddNodeMenuContext } from "@/lib/canvas/tool-registry";
|
||||
import { useThemeStore } from "@/stores/use-theme-store";
|
||||
import { CanvasNodeType, type CanvasNodeData, type CanvasWorkspaceMode, type ContextMenuState, type Position } from "@/types/canvas";
|
||||
import { CanvasNodeType, type CanvasNodeData, type CanvasNodeTypeId, type CanvasWorkspaceMode, type ContextMenuState, type Position } from "@/types/canvas";
|
||||
|
||||
type CanvasAssetCategory = NonNullable<NonNullable<CanvasNodeData["metadata"]>["assetCategory"]>;
|
||||
|
||||
@@ -33,7 +33,7 @@ type CanvasNodeContextMenuProps = {
|
||||
canRedo: boolean;
|
||||
canPaste: boolean;
|
||||
onClose: () => void;
|
||||
onAddNode: (type: CanvasNodeType) => void;
|
||||
onAddNode: (type: CanvasNodeTypeId) => void;
|
||||
onAddFolder: () => void;
|
||||
onChooseStyle: () => void;
|
||||
onOpenDirector: (position: Position) => void;
|
||||
@@ -258,7 +258,7 @@ export function CanvasNodeContextMenu({
|
||||
);
|
||||
}
|
||||
|
||||
function AddNodeContextMenu({ parentPosition, workspaceMode, isProjectLinked, reducedMotion, onAddNode, onAddFolder, onChooseStyle, onOpenDirector, onUpload, onOpenAssets, onOpenProjectCharacters }: { parentPosition: { left: number; top: number }; workspaceMode: CanvasWorkspaceMode; isProjectLinked: boolean; reducedMotion: boolean; onAddNode: (type: CanvasNodeType) => void; onAddFolder: () => void; onChooseStyle: () => void; onOpenDirector: () => void; onUpload: () => void; onOpenAssets: () => void; onOpenProjectCharacters: () => void }) {
|
||||
function AddNodeContextMenu({ parentPosition, workspaceMode, isProjectLinked, reducedMotion, onAddNode, onAddFolder, onChooseStyle, onOpenDirector, onUpload, onOpenAssets, onOpenProjectCharacters }: { parentPosition: { left: number; top: number }; workspaceMode: CanvasWorkspaceMode; isProjectLinked: boolean; reducedMotion: boolean; onAddNode: (type: CanvasNodeTypeId) => void; onAddFolder: () => void; onChooseStyle: () => void; onOpenDirector: () => void; onUpload: () => void; onOpenAssets: () => void; onOpenProjectCharacters: () => void }) {
|
||||
const theme = canvasThemes[useThemeStore((state) => state.theme)];
|
||||
const left = getSubmenuLeft(parentPosition.left);
|
||||
const createContext: AddNodeMenuContext = {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { resourceIdFromStorageKey } from "@/services/api/resources";
|
||||
import type { GenerationTask } from "@/services/api/task-center";
|
||||
import { cacheResourceObjectUrl, getCachedResourceObjectUrl } from "@/services/resource-blob-cache";
|
||||
import { CanvasNodeType, type CanvasNodeData } from "@/types/canvas";
|
||||
import { getNodeDefinition } from "@/lib/canvas/node-registry";
|
||||
import { createDefaultSubtitleStyle } from "@/types/timeline";
|
||||
import { CanvasResourceMentionTextarea } from "./canvas-resource-mention-textarea";
|
||||
import { useCanvasNodeActions } from "./canvas-node-action-context";
|
||||
@@ -61,11 +62,34 @@ export function CanvasNodeContent(props: CanvasNodeContentProps) {
|
||||
if (props.node.metadata?.status === "loading") return <LoadingContent node={props.node} theme={props.theme} onOpenTaskDetails={props.onOpenTaskDetails} />;
|
||||
if (props.node.metadata?.status === "error") return <ErrorContent node={props.node} theme={props.theme} onRetry={props.onRetry} onReloadResource={props.onReloadResource} />;
|
||||
|
||||
const pluginDefinition = getNodeDefinition(props.node.type)?.plugin;
|
||||
if (pluginDefinition) return <PluginCanvasNodeContent {...props} renderer={pluginDefinition.renderer} schema={pluginDefinition.schema} />;
|
||||
const Renderer = nodeContentRenderers[props.node.type];
|
||||
return Renderer ? <Renderer {...props} /> : <UnknownNodeContent theme={props.theme} />;
|
||||
}
|
||||
|
||||
const nodeContentRenderers = {
|
||||
function PluginCanvasNodeContent({ node, theme, renderer, schema }: CanvasNodeContentProps & { renderer: "declarative" | "sandbox"; schema: Record<string, unknown> }) {
|
||||
if (renderer === "sandbox") {
|
||||
return <div className="flex h-full w-full items-center justify-center p-4 text-center text-xs" style={{ color: theme.node.placeholder }}>
|
||||
插件节点等待隔离运行时
|
||||
</div>;
|
||||
}
|
||||
const data = node.metadata?.pluginData || {};
|
||||
const fields = Object.keys(schema.properties && typeof schema.properties === "object" ? schema.properties as Record<string, unknown> : schema);
|
||||
return <div className="flex h-full w-full flex-col gap-3 overflow-auto p-4 pt-10 text-xs" style={{ color: theme.node.text }}>
|
||||
{fields.length ? fields.map((field) => <div key={field} className="flex flex-col gap-1">
|
||||
<span className="font-medium opacity-60">{field}</span>
|
||||
<span className="whitespace-pre-wrap break-words opacity-90">{formatPluginValue(data[field] ?? (field === "content" ? node.metadata?.content : undefined))}</span>
|
||||
</div>) : <span className="whitespace-pre-wrap break-words">{node.metadata?.content || "插件节点"}</span>}
|
||||
</div>;
|
||||
}
|
||||
|
||||
function formatPluginValue(value: unknown) {
|
||||
if (value === undefined || value === null || value === "") return "未设置";
|
||||
return typeof value === "string" ? value : JSON.stringify(value);
|
||||
}
|
||||
|
||||
const nodeContentRenderers: Partial<Record<string, (props: CanvasNodeContentProps) => ReactNode>> = {
|
||||
[CanvasNodeType.Text]: TextContent,
|
||||
[CanvasNodeType.Script]: UnknownNodeContent,
|
||||
[CanvasNodeType.Skill]: SkillContent,
|
||||
@@ -82,7 +106,7 @@ const nodeContentRenderers = {
|
||||
[CanvasNodeType.Compare]: CompareNodeContent,
|
||||
[CanvasNodeType.Chart]: ChartNodeContent,
|
||||
[CanvasNodeType.ColorGrade]: ColorGradeNodeContent,
|
||||
} satisfies Record<CanvasNodeType, (props: CanvasNodeContentProps) => ReactNode>;
|
||||
};
|
||||
|
||||
function DrawingContent({ node, theme, drawingProjectId }: CanvasNodeContentProps) {
|
||||
const shapeCount = node.metadata?.drawingShapeCount || 0;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { canvasThemes } from "@/lib/canvas-theme";
|
||||
import { storyboardMinNodeHeight } from "@/lib/canvas/canvas-storyboard-layout";
|
||||
import { resourceStorageLabel, resourceStorageLocation, resourceStorageTitle } from "@/lib/canvas/resource-storage-status";
|
||||
import { useThemeStore } from "@/stores/use-theme-store";
|
||||
import { CanvasNodeType, type CanvasNodeData, type Position } from "@/types/canvas";
|
||||
import { CanvasNodeType, type CanvasNodeData, type CanvasNodeTypeId, type Position } from "@/types/canvas";
|
||||
import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references";
|
||||
import { getNodeMinSize, shouldKeepAspectRatio } from "@/lib/canvas/node-registry";
|
||||
import { CanvasNodeContent, CanvasNodeImageInfo } from "./canvas-node-content";
|
||||
@@ -660,7 +660,7 @@ function NodeExternalHeader({ node, scale, active, editable, editing, draft, the
|
||||
);
|
||||
}
|
||||
|
||||
function nodeTypeIcon(type: CanvasNodeType) {
|
||||
function nodeTypeIcon(type: CanvasNodeTypeId) {
|
||||
if (type === CanvasNodeType.Image) return ImageIcon;
|
||||
if (type === CanvasNodeType.Video) return Video;
|
||||
if (type === CanvasNodeType.Audio) return Music2;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { canvasDockStyle } from "@/lib/canvas/canvas-aceternity-style";
|
||||
import { canvasThemes, type CanvasBackgroundMode, type CanvasColorTheme, type CanvasTheme } from "@/lib/canvas-theme";
|
||||
import { defaultToolbarPrefs, readToolbarPrefs, resolveAddNodeMenuCommands, resolveToolbarEntries, type AddNodeMenuCommand, type ToolContext, type ToolbarHandlers, type ToolbarPrefs } from "@/lib/canvas/tool-registry";
|
||||
import { useThemeStore } from "@/stores/use-theme-store";
|
||||
import type { CanvasNodeType, CanvasToolMode, CanvasWorkspaceMode } from "@/types/canvas";
|
||||
import type { CanvasNodeTypeId, CanvasToolMode, CanvasWorkspaceMode } from "@/types/canvas";
|
||||
|
||||
export function CanvasToolbar({
|
||||
selectedCount,
|
||||
@@ -65,7 +65,7 @@ export function CanvasToolbar({
|
||||
onAddFrame: () => void;
|
||||
onAddFolder: () => void;
|
||||
onAddDrawing: () => void;
|
||||
onAddExtensionNode: (type: CanvasNodeType) => void;
|
||||
onAddExtensionNode: (type: CanvasNodeTypeId) => void;
|
||||
onOpenDirector: () => void;
|
||||
onUndo: () => void;
|
||||
onRedo: () => void;
|
||||
|
||||
Reference in New Issue
Block a user