fix(*): 插件工作流 - 统一启停状态并闭环校验生成链路

This commit is contained in:
ddCat
2026-08-26 21:36:10 +08:00
parent 3a4a295523
commit c79e984bc3
23 changed files with 351 additions and 65 deletions

View File

@@ -13,6 +13,8 @@ import { isFrameNode, isNodeHiddenByCollapsedFrame } from "@/lib/canvas/canvas-f
import { normalizeRunningHubCapability, type AiConfig } from "@/stores/use-config-store";
import { useUserStore } from "@/stores/use-user-store";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData, type CanvasNodeMetadata, type ConnectionHandle, type ContextMenuState, type Position, type ViewportTransform } from "@/types/canvas";
import { workflowProviderPluginEnabled } from "@/lib/plugins/builtin/workflows";
import { usePluginStore } from "@/stores/use-plugin-store";
type UseCanvasConnectionControllerOptions = {
projectId: string;
@@ -78,6 +80,7 @@ export function useCanvasConnectionController({
}: UseCanvasConnectionControllerOptions) {
const { message } = App.useApp();
const tldrawLicenseKey = useUserStore((state) => state.drawingEngine.tldrawLicenseKey);
const runtimeStatuses = usePluginStore((state) => state.runtimeStatuses);
const [connectingParams, setConnectingParams] = useState<ConnectionHandle | null>(null);
const [connectionTargetNodeId, setConnectionTargetNodeId] = useState<string | null>(null);
const [connectionTargetAnchorRatio, setConnectionTargetAnchorRatio] = useState<number | undefined>();
@@ -210,8 +213,13 @@ export function useCanvasConnectionController({
? batchSourceNodeIds.length ? batchScriptPrompt : sourceNode?.type === CanvasNodeType.Text ? (sourceNode.metadata?.content || sourceNode.metadata?.prompt || "").trim() : ""
: "";
const selectedWorkflowProvider = nodeType === CanvasNodeType.Config
? workflowProvider || (config.runningHub.enabled && config.runningHub.workflows.length ? "runninghub" : config.comfyBridge.enabled && config.comfyBridge.workflows.length ? "comfyui" : "runninghub")
? workflowProvider || (workflowProviderPluginEnabled(runtimeStatuses, "runninghub") ? "runninghub" : workflowProviderPluginEnabled(runtimeStatuses, "comfyui") ? "comfyui" : undefined)
: undefined;
if (selectedWorkflowProvider && !workflowProviderPluginEnabled(runtimeStatuses, selectedWorkflowProvider)) {
message.error(`${selectedWorkflowProvider === "runninghub" ? "RunningHub" : "ComfyUI"} 工作流插件未启用`);
closeConnectionCreateMenu();
return;
}
const runningHubWorkflow = selectedWorkflowProvider === "runninghub" ? selectRunningHubWorkflow(config) : undefined;
const comfyBridgeWorkflow = selectedWorkflowProvider === "comfyui" ? selectComfyBridgeWorkflow(config) : undefined;
const workflowCapability = selectedWorkflowProvider === "runninghub"
@@ -338,10 +346,13 @@ export function useCanvasConnectionController({
else if (nodeType !== CanvasNodeType.Text && nodeType !== CanvasNodeType.Script && nodeType !== CanvasNodeType.Audio) setDialogNodeId(newNode.id);
closeConnectionCreateMenu();
setConnecting(null);
}, [closeConnectionCreateMenu, config, connectionsRef, defaultDrawingEngine, message, nodesRef, projectId, setConnecting, setConnections, setDialogNodeId, setDrawingNodeId, setNodes, setSelectedConnectionId, setSelectedNodeIds, tldrawLicenseKey]);
}, [closeConnectionCreateMenu, config, connectionsRef, defaultDrawingEngine, message, nodesRef, projectId, runtimeStatuses, setConnecting, setConnections, setDialogNodeId, setDrawingNodeId, setNodes, setSelectedConnectionId, setSelectedNodeIds, tldrawLicenseKey]);
const getConnectionCreateDisabledReason = useCallback((type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing | CanvasNodeType.Config, pending: PendingConnectionCreate, workflowProvider?: "runninghub" | "comfyui") => {
const nodeType = type;
if (nodeType === CanvasNodeType.Config) {
if (workflowProvider && !workflowProviderPluginEnabled(runtimeStatuses, workflowProvider)) return `${workflowProvider === "runninghub" ? "RunningHub" : "ComfyUI"} 工作流插件未启用`;
}
if (pending.batchSourceNodeIds?.length) {
if (nodeType === CanvasNodeType.Drawing || nodeType === CanvasNodeType.Config) return "批量连接暂不支持此节点类型";
const pendingNode: CanvasNodeData = { id: "__pending-connection-node__", type: nodeType, title: "", position: pending.position, width: getNodeSpec(nodeType).width, height: getNodeSpec(nodeType).height };
@@ -354,7 +365,7 @@ export function useCanvasConnectionController({
const connection = normalizeConnection(pending.connection.nodeId, pendingNode.id, pendingNodes, pending.connection.handleType);
if (!connection) return "当前节点类型不能这样连接";
return canvasConnectionError(config, pendingNodes, connectionsRef.current, connection);
}, [config, connectionsRef, nodesRef]);
}, [config, connectionsRef, nodesRef, runtimeStatuses]);
const getConnectionDropTarget = useCallback((clientX: number, clientY: number, current: ConnectionHandle): ConnectionDropTarget => {
const world = screenToCanvas(clientX, clientY);

View File

@@ -14,6 +14,8 @@ import { isDrawingEngineAvailable, type CanvasDrawingEngine } from "@/lib/canvas
import { useUserStore } from "@/stores/use-user-store";
import { useEffectiveConfig } from "@/stores/use-config-store";
import { createDefaultPortraitClearanceState, PORTRAIT_CLEARANCE_NODE_TYPE } from "@/lib/portrait-clearance/contracts";
import { workflowProviderPluginEnabled } from "@/lib/plugins/builtin/workflows";
import { usePluginStore } from "@/stores/use-plugin-store";
type CanvasClipboard = {
nodes: CanvasNodeData[];
@@ -58,6 +60,7 @@ export function useCanvasNodeOperations({
const { message } = App.useApp();
const effectiveConfig = useEffectiveConfig();
const tldrawLicenseKey = useUserStore((state) => state.drawingEngine.tldrawLicenseKey);
const runtimeStatuses = usePluginStore((state) => state.runtimeStatuses);
const clipboardRef = useRef<CanvasClipboard | null>(null);
const preferCopiedNodesRef = useRef(false);
const markerWritePendingRef = useRef(false);
@@ -131,13 +134,17 @@ export function useCanvasNodeOperations({
return;
}
const selectedWorkflowProvider = type === CanvasNodeType.Config
? workflowProvider || "runninghub"
? workflowProvider || (workflowProviderPluginEnabled(runtimeStatuses, "runninghub") ? "runninghub" : workflowProviderPluginEnabled(runtimeStatuses, "comfyui") ? "comfyui" : undefined)
: undefined;
if (selectedWorkflowProvider && !workflowProviderPluginEnabled(runtimeStatuses, selectedWorkflowProvider)) {
message.error(`${selectedWorkflowProvider === "runninghub" ? "RunningHub" : "ComfyUI"} 工作流插件未启用`);
return;
}
const workflowTitle = type === CanvasNodeType.Config && selectedWorkflowProvider === "runninghub" ? "RunningHub 工作流" : type === CanvasNodeType.Config && selectedWorkflowProvider === "comfyui" ? "ComfyUI Bridge" : undefined;
const metadata: CanvasNodeMetadata | undefined = type === CanvasNodeType.Drawing
? { drawingEngine: defaultDrawingEngine }
: type === CanvasNodeType.Config
? { generationMode: "image", workflowProvider: selectedWorkflowProvider }
? { generationMode: "image", workflowProvider: selectedWorkflowProvider || "model" }
: type === PORTRAIT_CLEARANCE_NODE_TYPE
? { portraitClearance: createDefaultPortraitClearanceState() }
: undefined;
@@ -146,7 +153,7 @@ export function useCanvasNodeOperations({
commitNodes([...nodesRef.current, node]);
selectNodes(new Set([node.id]));
if (type !== CanvasNodeType.Text && type !== CanvasNodeType.Script && type !== CanvasNodeType.Audio && type !== CanvasNodeType.Frame && type !== CanvasNodeType.Drawing && type !== PORTRAIT_CLEARANCE_NODE_TYPE) setDialogNodeId(node.id);
}, [commitNodes, defaultDrawingEngine, effectiveConfig.comfyBridge.enabled, effectiveConfig.comfyBridge.workflows.length, effectiveConfig.runningHub.enabled, effectiveConfig.runningHub.workflows.length, getCanvasCenter, message, nodesRef, selectNodes, setDialogNodeId, tldrawLicenseKey]);
}, [commitNodes, defaultDrawingEngine, effectiveConfig.comfyBridge.enabled, effectiveConfig.comfyBridge.workflows.length, effectiveConfig.runningHub.enabled, effectiveConfig.runningHub.workflows.length, getCanvasCenter, message, nodesRef, runtimeStatuses, selectNodes, setDialogNodeId, tldrawLicenseKey]);
const createFolder = useCallback((position?: Position, linked?: { id: string; projectId: string; title: string; style: CanvasFolderStyle; theme: CanvasFolderTheme; createdAt: string }) => {
const folder = createCanvasNode(CanvasNodeType.Frame, position || getCanvasCenter(), {