machinaos 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.template +71 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/cli.js +159 -0
- package/client/.dockerignore +45 -0
- package/client/Dockerfile +68 -0
- package/client/eslint.config.js +29 -0
- package/client/index.html +13 -0
- package/client/nginx.conf +66 -0
- package/client/package.json +48 -0
- package/client/src/App.tsx +27 -0
- package/client/src/Dashboard.tsx +1173 -0
- package/client/src/ParameterPanel.tsx +301 -0
- package/client/src/components/AIAgentNode.tsx +321 -0
- package/client/src/components/APIKeyValidator.tsx +118 -0
- package/client/src/components/ClaudeChatModelNode.tsx +18 -0
- package/client/src/components/ConditionalEdge.tsx +189 -0
- package/client/src/components/CredentialsModal.tsx +306 -0
- package/client/src/components/EdgeConditionEditor.tsx +443 -0
- package/client/src/components/GeminiChatModelNode.tsx +18 -0
- package/client/src/components/GenericNode.tsx +357 -0
- package/client/src/components/LocationParameterPanel.tsx +154 -0
- package/client/src/components/ModelNode.tsx +286 -0
- package/client/src/components/OpenAIChatModelNode.tsx +18 -0
- package/client/src/components/OutputPanel.tsx +471 -0
- package/client/src/components/ParameterRenderer.tsx +1874 -0
- package/client/src/components/SkillEditorModal.tsx +417 -0
- package/client/src/components/SquareNode.tsx +797 -0
- package/client/src/components/StartNode.tsx +250 -0
- package/client/src/components/ToolkitNode.tsx +365 -0
- package/client/src/components/TriggerNode.tsx +463 -0
- package/client/src/components/auth/LoginPage.tsx +247 -0
- package/client/src/components/auth/ProtectedRoute.tsx +59 -0
- package/client/src/components/base/BaseChatModelNode.tsx +271 -0
- package/client/src/components/icons/AIProviderIcons.tsx +50 -0
- package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
- package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
- package/client/src/components/maps/index.ts +26 -0
- package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
- package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
- package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
- package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
- package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
- package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
- package/client/src/components/parameterPanel/index.ts +42 -0
- package/client/src/components/shared/DataPanel.tsx +142 -0
- package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
- package/client/src/components/ui/AIResultModal.tsx +204 -0
- package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
- package/client/src/components/ui/CodeEditor.tsx +81 -0
- package/client/src/components/ui/CollapsibleSection.tsx +88 -0
- package/client/src/components/ui/ComponentItem.tsx +154 -0
- package/client/src/components/ui/ComponentPalette.tsx +321 -0
- package/client/src/components/ui/ConsolePanel.tsx +1074 -0
- package/client/src/components/ui/ErrorBoundary.tsx +196 -0
- package/client/src/components/ui/InputNodesPanel.tsx +204 -0
- package/client/src/components/ui/MapSelector.tsx +314 -0
- package/client/src/components/ui/Modal.tsx +149 -0
- package/client/src/components/ui/NodeContextMenu.tsx +192 -0
- package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
- package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
- package/client/src/components/ui/SettingsPanel.tsx +243 -0
- package/client/src/components/ui/TopToolbar.tsx +736 -0
- package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
- package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
- package/client/src/config/antdTheme.ts +186 -0
- package/client/src/config/api.ts +54 -0
- package/client/src/contexts/AuthContext.tsx +221 -0
- package/client/src/contexts/ThemeContext.tsx +42 -0
- package/client/src/contexts/WebSocketContext.tsx +1971 -0
- package/client/src/factories/baseChatModelFactory.ts +256 -0
- package/client/src/hooks/useAndroidOperations.ts +164 -0
- package/client/src/hooks/useApiKeyValidation.ts +107 -0
- package/client/src/hooks/useApiKeys.ts +238 -0
- package/client/src/hooks/useAppTheme.ts +17 -0
- package/client/src/hooks/useComponentPalette.ts +51 -0
- package/client/src/hooks/useCopyPaste.ts +155 -0
- package/client/src/hooks/useDragAndDrop.ts +124 -0
- package/client/src/hooks/useDragVariable.ts +88 -0
- package/client/src/hooks/useExecution.ts +313 -0
- package/client/src/hooks/useParameterPanel.ts +176 -0
- package/client/src/hooks/useReactFlowNodes.ts +189 -0
- package/client/src/hooks/useToolSchema.ts +209 -0
- package/client/src/hooks/useWhatsApp.ts +196 -0
- package/client/src/hooks/useWorkflowManagement.ts +46 -0
- package/client/src/index.css +315 -0
- package/client/src/main.tsx +19 -0
- package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
- package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
- package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
- package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
- package/client/src/nodeDefinitions/chatNodes.ts +135 -0
- package/client/src/nodeDefinitions/codeNodes.ts +54 -0
- package/client/src/nodeDefinitions/documentNodes.ts +379 -0
- package/client/src/nodeDefinitions/index.ts +15 -0
- package/client/src/nodeDefinitions/locationNodes.ts +463 -0
- package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
- package/client/src/nodeDefinitions/skillNodes.ts +211 -0
- package/client/src/nodeDefinitions/toolNodes.ts +198 -0
- package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
- package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
- package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
- package/client/src/nodeDefinitions.ts +104 -0
- package/client/src/schemas/workflowSchema.ts +264 -0
- package/client/src/services/dynamicParameterService.ts +96 -0
- package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
- package/client/src/services/executionService.ts +232 -0
- package/client/src/services/workflowApi.ts +91 -0
- package/client/src/store/useAppStore.ts +582 -0
- package/client/src/styles/theme.ts +508 -0
- package/client/src/styles/zIndex.ts +17 -0
- package/client/src/types/ComponentTypes.ts +39 -0
- package/client/src/types/EdgeCondition.ts +231 -0
- package/client/src/types/INodeProperties.ts +288 -0
- package/client/src/types/NodeTypes.ts +28 -0
- package/client/src/utils/formatters.ts +33 -0
- package/client/src/utils/googleMapsLoader.ts +140 -0
- package/client/src/utils/locationUtils.ts +85 -0
- package/client/src/utils/nodeUtils.ts +31 -0
- package/client/src/utils/workflow.ts +30 -0
- package/client/src/utils/workflowExport.ts +120 -0
- package/client/src/vite-env.d.ts +12 -0
- package/client/tailwind.config.js +60 -0
- package/client/tsconfig.json +25 -0
- package/client/tsconfig.node.json +11 -0
- package/client/vite.config.js +35 -0
- package/docker-compose.prod.yml +107 -0
- package/docker-compose.yml +104 -0
- package/docs-MachinaOs/README.md +85 -0
- package/docs-MachinaOs/deployment/docker.mdx +228 -0
- package/docs-MachinaOs/deployment/production.mdx +345 -0
- package/docs-MachinaOs/docs.json +75 -0
- package/docs-MachinaOs/faq.mdx +309 -0
- package/docs-MachinaOs/favicon.svg +5 -0
- package/docs-MachinaOs/installation.mdx +160 -0
- package/docs-MachinaOs/introduction.mdx +114 -0
- package/docs-MachinaOs/logo/dark.svg +6 -0
- package/docs-MachinaOs/logo/light.svg +6 -0
- package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
- package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
- package/docs-MachinaOs/nodes/android.mdx +411 -0
- package/docs-MachinaOs/nodes/overview.mdx +181 -0
- package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
- package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
- package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
- package/docs-MachinaOs/quickstart.mdx +119 -0
- package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
- package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
- package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
- package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
- package/nul +0 -0
- package/package.json +70 -0
- package/scripts/build.js +158 -0
- package/scripts/check-ports.ps1 +33 -0
- package/scripts/clean.js +40 -0
- package/scripts/docker.js +93 -0
- package/scripts/kill-port.ps1 +154 -0
- package/scripts/start.js +210 -0
- package/scripts/stop.js +325 -0
- package/server/.dockerignore +44 -0
- package/server/Dockerfile +45 -0
- package/server/constants.py +249 -0
- package/server/core/__init__.py +1 -0
- package/server/core/cache.py +461 -0
- package/server/core/config.py +128 -0
- package/server/core/container.py +99 -0
- package/server/core/database.py +1211 -0
- package/server/core/logging.py +314 -0
- package/server/main.py +289 -0
- package/server/middleware/__init__.py +5 -0
- package/server/middleware/auth.py +89 -0
- package/server/models/__init__.py +1 -0
- package/server/models/auth.py +52 -0
- package/server/models/cache.py +24 -0
- package/server/models/database.py +211 -0
- package/server/models/nodes.py +455 -0
- package/server/package.json +9 -0
- package/server/pyproject.toml +72 -0
- package/server/requirements.txt +83 -0
- package/server/routers/__init__.py +1 -0
- package/server/routers/android.py +294 -0
- package/server/routers/auth.py +203 -0
- package/server/routers/database.py +151 -0
- package/server/routers/maps.py +142 -0
- package/server/routers/nodejs_compat.py +289 -0
- package/server/routers/webhook.py +90 -0
- package/server/routers/websocket.py +2127 -0
- package/server/routers/whatsapp.py +761 -0
- package/server/routers/workflow.py +200 -0
- package/server/services/__init__.py +1 -0
- package/server/services/ai.py +2415 -0
- package/server/services/android/__init__.py +27 -0
- package/server/services/android/broadcaster.py +114 -0
- package/server/services/android/client.py +608 -0
- package/server/services/android/manager.py +78 -0
- package/server/services/android/protocol.py +165 -0
- package/server/services/android_service.py +588 -0
- package/server/services/auth.py +131 -0
- package/server/services/chat_client.py +160 -0
- package/server/services/deployment/__init__.py +12 -0
- package/server/services/deployment/manager.py +706 -0
- package/server/services/deployment/state.py +47 -0
- package/server/services/deployment/triggers.py +275 -0
- package/server/services/event_waiter.py +785 -0
- package/server/services/execution/__init__.py +77 -0
- package/server/services/execution/cache.py +769 -0
- package/server/services/execution/conditions.py +373 -0
- package/server/services/execution/dlq.py +132 -0
- package/server/services/execution/executor.py +1351 -0
- package/server/services/execution/models.py +531 -0
- package/server/services/execution/recovery.py +235 -0
- package/server/services/handlers/__init__.py +126 -0
- package/server/services/handlers/ai.py +355 -0
- package/server/services/handlers/android.py +260 -0
- package/server/services/handlers/code.py +278 -0
- package/server/services/handlers/document.py +598 -0
- package/server/services/handlers/http.py +193 -0
- package/server/services/handlers/polyglot.py +105 -0
- package/server/services/handlers/tools.py +845 -0
- package/server/services/handlers/triggers.py +107 -0
- package/server/services/handlers/utility.py +822 -0
- package/server/services/handlers/whatsapp.py +476 -0
- package/server/services/maps.py +289 -0
- package/server/services/memory_store.py +103 -0
- package/server/services/node_executor.py +375 -0
- package/server/services/parameter_resolver.py +218 -0
- package/server/services/polyglot_client.py +169 -0
- package/server/services/scheduler.py +155 -0
- package/server/services/skill_loader.py +417 -0
- package/server/services/status_broadcaster.py +826 -0
- package/server/services/temporal/__init__.py +23 -0
- package/server/services/temporal/activities.py +344 -0
- package/server/services/temporal/client.py +76 -0
- package/server/services/temporal/executor.py +147 -0
- package/server/services/temporal/worker.py +251 -0
- package/server/services/temporal/workflow.py +355 -0
- package/server/services/temporal/ws_client.py +236 -0
- package/server/services/text.py +111 -0
- package/server/services/user_auth.py +172 -0
- package/server/services/websocket_client.py +29 -0
- package/server/services/workflow.py +597 -0
- package/server/skills/android-skill/SKILL.md +82 -0
- package/server/skills/assistant-personality/SKILL.md +45 -0
- package/server/skills/code-skill/SKILL.md +140 -0
- package/server/skills/http-skill/SKILL.md +161 -0
- package/server/skills/maps-skill/SKILL.md +170 -0
- package/server/skills/memory-skill/SKILL.md +154 -0
- package/server/skills/scheduler-skill/SKILL.md +84 -0
- package/server/skills/whatsapp-skill/SKILL.md +283 -0
- package/server/uv.lock +2916 -0
- package/server/whatsapp-rpc/.dockerignore +30 -0
- package/server/whatsapp-rpc/Dockerfile +44 -0
- package/server/whatsapp-rpc/Dockerfile.web +17 -0
- package/server/whatsapp-rpc/README.md +139 -0
- package/server/whatsapp-rpc/cli.js +95 -0
- package/server/whatsapp-rpc/configs/config.yaml +7 -0
- package/server/whatsapp-rpc/docker-compose.yml +35 -0
- package/server/whatsapp-rpc/docs/API.md +410 -0
- package/server/whatsapp-rpc/go.mod +67 -0
- package/server/whatsapp-rpc/go.sum +203 -0
- package/server/whatsapp-rpc/package.json +30 -0
- package/server/whatsapp-rpc/schema.json +1294 -0
- package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
- package/server/whatsapp-rpc/scripts/cli.js +162 -0
- package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
- package/server/whatsapp-rpc/src/go/config/config.go +49 -0
- package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
- package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
- package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
- package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
- package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
- package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
- package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
- package/server/whatsapp-rpc/web/app.py +609 -0
- package/server/whatsapp-rpc/web/requirements.txt +6 -0
- package/server/whatsapp-rpc/web/rpc_client.py +427 -0
- package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
- package/server/whatsapp-rpc/web/templates/base.html +150 -0
- package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
- package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
- package/server/whatsapp-rpc/web/templates/groups.html +328 -0
- package/server/whatsapp-rpc/web/templates/messages.html +465 -0
- package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
- package/server/whatsapp-rpc/web/templates/send.html +259 -0
- package/server/whatsapp-rpc/web/templates/settings.html +459 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useExecution Hook - WebSocket-based node execution
|
|
3
|
+
*
|
|
4
|
+
* Provides node execution functionality via WebSocket instead of REST API.
|
|
5
|
+
* This replaces the REST-based ExecutionService for real-time execution.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useCallback, useState } from 'react';
|
|
9
|
+
import { Node, Edge } from 'reactflow';
|
|
10
|
+
import { useWebSocket } from '../contexts/WebSocketContext';
|
|
11
|
+
import { nodeDefinitions } from '../nodeDefinitions';
|
|
12
|
+
import { INodeExecutionData } from '../types/INodeProperties';
|
|
13
|
+
|
|
14
|
+
// Execution result interface (compatible with ExecutionService)
|
|
15
|
+
export interface ExecutionResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
nodeId: string;
|
|
18
|
+
nodeType: string;
|
|
19
|
+
nodeName: string;
|
|
20
|
+
timestamp: string;
|
|
21
|
+
executionTime: number;
|
|
22
|
+
outputs?: Record<string, any>;
|
|
23
|
+
error?: string;
|
|
24
|
+
data?: any;
|
|
25
|
+
nodeData: INodeExecutionData[][];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UseExecutionResult {
|
|
29
|
+
// Execute a node via WebSocket
|
|
30
|
+
executeNode: (
|
|
31
|
+
nodeId: string,
|
|
32
|
+
nodeType: string,
|
|
33
|
+
nodes?: Node[],
|
|
34
|
+
edges?: Edge[]
|
|
35
|
+
) => Promise<ExecutionResult>;
|
|
36
|
+
|
|
37
|
+
// Execute AI node via WebSocket
|
|
38
|
+
executeAiNode: (
|
|
39
|
+
nodeId: string,
|
|
40
|
+
nodeType: string,
|
|
41
|
+
parameters: Record<string, any>,
|
|
42
|
+
model: string
|
|
43
|
+
) => Promise<ExecutionResult>;
|
|
44
|
+
|
|
45
|
+
// Execution state
|
|
46
|
+
isExecuting: boolean;
|
|
47
|
+
executingNodeId: string | null;
|
|
48
|
+
lastError: string | null;
|
|
49
|
+
|
|
50
|
+
// Connection status
|
|
51
|
+
isConnected: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const useExecution = (): UseExecutionResult => {
|
|
55
|
+
const {
|
|
56
|
+
executeNode: wsExecuteNode,
|
|
57
|
+
executeAiNode: wsExecuteAiNode,
|
|
58
|
+
getNodeParameters,
|
|
59
|
+
isConnected
|
|
60
|
+
} = useWebSocket();
|
|
61
|
+
|
|
62
|
+
const [isExecuting, setIsExecuting] = useState(false);
|
|
63
|
+
const [executingNodeId, setExecutingNodeId] = useState<string | null>(null);
|
|
64
|
+
const [lastError, setLastError] = useState<string | null>(null);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Execute a workflow node via WebSocket
|
|
68
|
+
*/
|
|
69
|
+
const executeNode = useCallback(async (
|
|
70
|
+
nodeId: string,
|
|
71
|
+
nodeType: string,
|
|
72
|
+
nodes?: Node[],
|
|
73
|
+
edges?: Edge[]
|
|
74
|
+
): Promise<ExecutionResult> => {
|
|
75
|
+
const startTime = Date.now();
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
console.log(`[WebSocket Execution] Starting: ${nodeId} (type: ${nodeType})`);
|
|
79
|
+
|
|
80
|
+
setIsExecuting(true);
|
|
81
|
+
setExecutingNodeId(nodeId);
|
|
82
|
+
setLastError(null);
|
|
83
|
+
|
|
84
|
+
// Load parameters from WebSocket/backend
|
|
85
|
+
const paramsResult = await getNodeParameters(nodeId);
|
|
86
|
+
const parameters = paramsResult?.parameters || {};
|
|
87
|
+
|
|
88
|
+
console.log(`[WebSocket Execution] Parameters for ${nodeId}:`, parameters);
|
|
89
|
+
|
|
90
|
+
// Prepare nodes and edges for execution
|
|
91
|
+
const nodeData = nodes?.map(node => ({
|
|
92
|
+
id: node.id,
|
|
93
|
+
type: node.type || '',
|
|
94
|
+
data: node.data || {}
|
|
95
|
+
})) || [];
|
|
96
|
+
|
|
97
|
+
const edgeData = edges?.map(edge => ({
|
|
98
|
+
id: edge.id,
|
|
99
|
+
source: edge.source,
|
|
100
|
+
target: edge.target,
|
|
101
|
+
sourceHandle: edge.sourceHandle || undefined,
|
|
102
|
+
targetHandle: edge.targetHandle || undefined
|
|
103
|
+
})) || [];
|
|
104
|
+
|
|
105
|
+
// Execute via WebSocket
|
|
106
|
+
const result = await wsExecuteNode(nodeId, nodeType, parameters, nodeData, edgeData);
|
|
107
|
+
|
|
108
|
+
console.log(`[WebSocket Execution] Result:`, result);
|
|
109
|
+
|
|
110
|
+
const executionTime = Date.now() - startTime;
|
|
111
|
+
|
|
112
|
+
if (result.success) {
|
|
113
|
+
// Extract output data
|
|
114
|
+
const outputData = result.result || {
|
|
115
|
+
nodeId,
|
|
116
|
+
success: true,
|
|
117
|
+
message: 'Execution completed successfully',
|
|
118
|
+
timestamp: result.timestamp || new Date().toISOString()
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const nodeExecutionData: INodeExecutionData[][] = [[{
|
|
122
|
+
json: outputData
|
|
123
|
+
}]];
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
nodeId,
|
|
128
|
+
nodeType,
|
|
129
|
+
nodeName: nodeDefinitions[nodeType]?.displayName || nodeType,
|
|
130
|
+
timestamp: result.timestamp || new Date().toISOString(),
|
|
131
|
+
executionTime: result.execution_time || executionTime,
|
|
132
|
+
outputs: outputData,
|
|
133
|
+
data: outputData,
|
|
134
|
+
nodeData: nodeExecutionData
|
|
135
|
+
};
|
|
136
|
+
} else {
|
|
137
|
+
// Handle error
|
|
138
|
+
const errorOutputData = result.result || {
|
|
139
|
+
error: result.error,
|
|
140
|
+
nodeId,
|
|
141
|
+
success: false,
|
|
142
|
+
timestamp: result.timestamp || new Date().toISOString()
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const errorData: INodeExecutionData[][] = [[{
|
|
146
|
+
json: errorOutputData
|
|
147
|
+
}]];
|
|
148
|
+
|
|
149
|
+
setLastError(result.error || 'Execution failed');
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
nodeId,
|
|
154
|
+
nodeType,
|
|
155
|
+
nodeName: nodeDefinitions[nodeType]?.displayName || nodeType,
|
|
156
|
+
timestamp: result.timestamp || new Date().toISOString(),
|
|
157
|
+
executionTime: result.execution_time || executionTime,
|
|
158
|
+
error: result.error,
|
|
159
|
+
outputs: errorOutputData,
|
|
160
|
+
data: errorOutputData,
|
|
161
|
+
nodeData: errorData
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
} catch (error: any) {
|
|
166
|
+
console.error(`[WebSocket Execution] Failed for node ${nodeId}:`, error);
|
|
167
|
+
|
|
168
|
+
const errorMessage = error.message || 'WebSocket execution failed';
|
|
169
|
+
setLastError(errorMessage);
|
|
170
|
+
|
|
171
|
+
const catchErrorData = {
|
|
172
|
+
error: errorMessage,
|
|
173
|
+
nodeId,
|
|
174
|
+
success: false,
|
|
175
|
+
timestamp: new Date().toISOString()
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
success: false,
|
|
180
|
+
nodeId,
|
|
181
|
+
nodeType,
|
|
182
|
+
nodeName: nodeDefinitions[nodeType]?.displayName || nodeType,
|
|
183
|
+
timestamp: new Date().toISOString(),
|
|
184
|
+
executionTime: Date.now() - startTime,
|
|
185
|
+
error: errorMessage,
|
|
186
|
+
outputs: catchErrorData,
|
|
187
|
+
data: catchErrorData,
|
|
188
|
+
nodeData: [[{ json: catchErrorData }]]
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
} finally {
|
|
192
|
+
setIsExecuting(false);
|
|
193
|
+
setExecutingNodeId(null);
|
|
194
|
+
}
|
|
195
|
+
}, [wsExecuteNode, getNodeParameters]);
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Execute an AI node via WebSocket
|
|
199
|
+
*/
|
|
200
|
+
const executeAiNode = useCallback(async (
|
|
201
|
+
nodeId: string,
|
|
202
|
+
nodeType: string,
|
|
203
|
+
parameters: Record<string, any>,
|
|
204
|
+
model: string
|
|
205
|
+
): Promise<ExecutionResult> => {
|
|
206
|
+
const startTime = Date.now();
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
console.log(`[WebSocket AI Execution] Starting: ${nodeId} (type: ${nodeType})`);
|
|
210
|
+
|
|
211
|
+
setIsExecuting(true);
|
|
212
|
+
setExecutingNodeId(nodeId);
|
|
213
|
+
setLastError(null);
|
|
214
|
+
|
|
215
|
+
// Execute AI node via WebSocket
|
|
216
|
+
const result = await wsExecuteAiNode(nodeId, nodeType, parameters, model);
|
|
217
|
+
|
|
218
|
+
console.log(`[WebSocket AI Execution] Result:`, result);
|
|
219
|
+
|
|
220
|
+
const executionTime = Date.now() - startTime;
|
|
221
|
+
|
|
222
|
+
if (result.success) {
|
|
223
|
+
const outputData = result.result || {
|
|
224
|
+
nodeId,
|
|
225
|
+
success: true,
|
|
226
|
+
message: 'AI execution completed successfully',
|
|
227
|
+
timestamp: result.timestamp || new Date().toISOString()
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const nodeExecutionData: INodeExecutionData[][] = [[{
|
|
231
|
+
json: outputData
|
|
232
|
+
}]];
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
success: true,
|
|
236
|
+
nodeId,
|
|
237
|
+
nodeType,
|
|
238
|
+
nodeName: nodeDefinitions[nodeType]?.displayName || nodeType,
|
|
239
|
+
timestamp: result.timestamp || new Date().toISOString(),
|
|
240
|
+
executionTime: result.execution_time || executionTime,
|
|
241
|
+
outputs: outputData,
|
|
242
|
+
data: outputData,
|
|
243
|
+
nodeData: nodeExecutionData
|
|
244
|
+
};
|
|
245
|
+
} else {
|
|
246
|
+
const errorOutputData = result.result || {
|
|
247
|
+
error: result.error,
|
|
248
|
+
nodeId,
|
|
249
|
+
success: false,
|
|
250
|
+
timestamp: result.timestamp || new Date().toISOString()
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const errorData: INodeExecutionData[][] = [[{
|
|
254
|
+
json: errorOutputData
|
|
255
|
+
}]];
|
|
256
|
+
|
|
257
|
+
setLastError(result.error || 'AI execution failed');
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
success: false,
|
|
261
|
+
nodeId,
|
|
262
|
+
nodeType,
|
|
263
|
+
nodeName: nodeDefinitions[nodeType]?.displayName || nodeType,
|
|
264
|
+
timestamp: result.timestamp || new Date().toISOString(),
|
|
265
|
+
executionTime: result.execution_time || executionTime,
|
|
266
|
+
error: result.error,
|
|
267
|
+
outputs: errorOutputData,
|
|
268
|
+
data: errorOutputData,
|
|
269
|
+
nodeData: errorData
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
} catch (error: any) {
|
|
274
|
+
console.error(`[WebSocket AI Execution] Failed for node ${nodeId}:`, error);
|
|
275
|
+
|
|
276
|
+
const errorMessage = error.message || 'WebSocket AI execution failed';
|
|
277
|
+
setLastError(errorMessage);
|
|
278
|
+
|
|
279
|
+
const catchErrorData = {
|
|
280
|
+
error: errorMessage,
|
|
281
|
+
nodeId,
|
|
282
|
+
success: false,
|
|
283
|
+
timestamp: new Date().toISOString()
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
success: false,
|
|
288
|
+
nodeId,
|
|
289
|
+
nodeType,
|
|
290
|
+
nodeName: nodeDefinitions[nodeType]?.displayName || nodeType,
|
|
291
|
+
timestamp: new Date().toISOString(),
|
|
292
|
+
executionTime: Date.now() - startTime,
|
|
293
|
+
error: errorMessage,
|
|
294
|
+
outputs: catchErrorData,
|
|
295
|
+
data: catchErrorData,
|
|
296
|
+
nodeData: [[{ json: catchErrorData }]]
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
} finally {
|
|
300
|
+
setIsExecuting(false);
|
|
301
|
+
setExecutingNodeId(null);
|
|
302
|
+
}
|
|
303
|
+
}, [wsExecuteAiNode]);
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
executeNode,
|
|
307
|
+
executeAiNode,
|
|
308
|
+
isExecuting,
|
|
309
|
+
executingNodeId,
|
|
310
|
+
lastError,
|
|
311
|
+
isConnected
|
|
312
|
+
};
|
|
313
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import { useAppStore } from '../store/useAppStore';
|
|
3
|
+
import { nodeDefinitions } from '../nodeDefinitions';
|
|
4
|
+
import { useWebSocket } from '../contexts/WebSocketContext';
|
|
5
|
+
import { SKILL_NODE_TYPES } from '../nodeDefinitions/skillNodes';
|
|
6
|
+
|
|
7
|
+
export const useParameterPanel = () => {
|
|
8
|
+
const { selectedNode, setSelectedNode, updateNodeData } = useAppStore();
|
|
9
|
+
const [parameters, setParameters] = useState<any>({});
|
|
10
|
+
const [originalParameters, setOriginalParameters] = useState<any>({});
|
|
11
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
12
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
13
|
+
const [error, setError] = useState<string | null>(null);
|
|
14
|
+
|
|
15
|
+
// Use WebSocket for parameter operations
|
|
16
|
+
const { getNodeParameters, saveNodeParameters, sendRequest, isConnected } = useWebSocket();
|
|
17
|
+
|
|
18
|
+
// Use stable references to prevent multiple effect runs
|
|
19
|
+
const nodeId = selectedNode?.id;
|
|
20
|
+
const nodeType = selectedNode?.type;
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!nodeType || !nodeId) {
|
|
24
|
+
setParameters({});
|
|
25
|
+
setOriginalParameters({});
|
|
26
|
+
setError(null);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const loadParameters = async () => {
|
|
31
|
+
const nodeDefinition = nodeDefinitions[nodeType];
|
|
32
|
+
if (!nodeDefinition) return;
|
|
33
|
+
|
|
34
|
+
const defaults: any = {};
|
|
35
|
+
|
|
36
|
+
// Set parameter defaults
|
|
37
|
+
if (nodeDefinition.properties) {
|
|
38
|
+
nodeDefinition.properties.forEach((param: any) => {
|
|
39
|
+
defaults[param.name] = param.default !== undefined ? param.default : null;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setIsLoading(true);
|
|
44
|
+
setError(null);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Load saved parameters via WebSocket
|
|
48
|
+
const result = await getNodeParameters(nodeId);
|
|
49
|
+
// Extract parameters from NodeParameters response
|
|
50
|
+
const savedParams = result?.parameters || {};
|
|
51
|
+
let initialParams = { ...defaults, ...savedParams };
|
|
52
|
+
|
|
53
|
+
console.log('[useParameterPanel] Loading params for node:', nodeId, nodeType);
|
|
54
|
+
console.log('[useParameterPanel] Defaults:', defaults);
|
|
55
|
+
console.log('[useParameterPanel] Saved from backend:', savedParams);
|
|
56
|
+
|
|
57
|
+
// For skill nodes, load skill content (instructions) from SKILL.md files
|
|
58
|
+
if (SKILL_NODE_TYPES.includes(nodeType) && nodeType !== 'customSkill') {
|
|
59
|
+
const skillName = initialParams.skillName;
|
|
60
|
+
if (skillName) {
|
|
61
|
+
try {
|
|
62
|
+
console.log('[useParameterPanel] Loading skill content for:', skillName);
|
|
63
|
+
const skillResult = await sendRequest('get_skill_content', { skill_name: skillName });
|
|
64
|
+
if (skillResult?.success && skillResult?.instructions) {
|
|
65
|
+
initialParams = {
|
|
66
|
+
...initialParams,
|
|
67
|
+
instructions: skillResult.instructions
|
|
68
|
+
};
|
|
69
|
+
console.log('[useParameterPanel] Loaded skill instructions:', skillResult.instructions.substring(0, 100) + '...');
|
|
70
|
+
}
|
|
71
|
+
} catch (skillErr) {
|
|
72
|
+
console.error('[useParameterPanel] Failed to load skill content:', skillErr);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log('[useParameterPanel] Merged initial:', initialParams);
|
|
78
|
+
|
|
79
|
+
setParameters(initialParams);
|
|
80
|
+
setOriginalParameters(initialParams);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error('Failed to load parameters via WebSocket:', err);
|
|
83
|
+
// Use defaults if WebSocket fails
|
|
84
|
+
setParameters(defaults);
|
|
85
|
+
setOriginalParameters(defaults);
|
|
86
|
+
setError('Failed to load saved parameters');
|
|
87
|
+
} finally {
|
|
88
|
+
setIsLoading(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
loadParameters();
|
|
93
|
+
}, [nodeId, nodeType, getNodeParameters, sendRequest]);
|
|
94
|
+
|
|
95
|
+
const hasUnsavedChanges = useMemo(() => {
|
|
96
|
+
return JSON.stringify(parameters) !== JSON.stringify(originalParameters);
|
|
97
|
+
}, [parameters, originalParameters]);
|
|
98
|
+
|
|
99
|
+
const handleParameterChange = useCallback((paramName: string, value: any) => {
|
|
100
|
+
setParameters((prevParams: any) => ({
|
|
101
|
+
...prevParams,
|
|
102
|
+
[paramName]: value
|
|
103
|
+
}));
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
106
|
+
const handleSave = useCallback(async () => {
|
|
107
|
+
if (selectedNode) {
|
|
108
|
+
setIsSaving(true);
|
|
109
|
+
setError(null);
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Save parameters to database via WebSocket
|
|
113
|
+
const success = await saveNodeParameters(selectedNode.id, parameters);
|
|
114
|
+
|
|
115
|
+
if (success) {
|
|
116
|
+
// For skill nodes, also save skill content (instructions) to SKILL.md files
|
|
117
|
+
if (selectedNode.type && SKILL_NODE_TYPES.includes(selectedNode.type) && selectedNode.type !== 'customSkill') {
|
|
118
|
+
const skillName = parameters.skillName;
|
|
119
|
+
const instructions = parameters.instructions;
|
|
120
|
+
if (skillName && instructions !== undefined) {
|
|
121
|
+
try {
|
|
122
|
+
console.log('[useParameterPanel] Saving skill content for:', skillName);
|
|
123
|
+
const skillResult = await sendRequest('save_skill_content', {
|
|
124
|
+
skill_name: skillName,
|
|
125
|
+
instructions: instructions
|
|
126
|
+
});
|
|
127
|
+
if (!skillResult?.success) {
|
|
128
|
+
console.error('[useParameterPanel] Failed to save skill content:', skillResult?.error);
|
|
129
|
+
setError(skillResult?.error || 'Failed to save skill content');
|
|
130
|
+
} else {
|
|
131
|
+
console.log('[useParameterPanel] Skill content saved successfully');
|
|
132
|
+
}
|
|
133
|
+
} catch (skillErr) {
|
|
134
|
+
console.error('[useParameterPanel] Failed to save skill content:', skillErr);
|
|
135
|
+
setError('Failed to save skill content');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Also update the node data in store for faster loading
|
|
141
|
+
updateNodeData(selectedNode.id, parameters);
|
|
142
|
+
setOriginalParameters({ ...parameters });
|
|
143
|
+
} else {
|
|
144
|
+
setError('Failed to save parameters');
|
|
145
|
+
}
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error('Failed to save parameters via WebSocket:', err);
|
|
148
|
+
setError('Failed to save parameters');
|
|
149
|
+
} finally {
|
|
150
|
+
setIsSaving(false);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}, [selectedNode, parameters, updateNodeData, saveNodeParameters, sendRequest]);
|
|
154
|
+
|
|
155
|
+
const handleCancel = useCallback(() => {
|
|
156
|
+
setParameters({ ...originalParameters });
|
|
157
|
+
setSelectedNode(null);
|
|
158
|
+
}, [originalParameters, setSelectedNode]);
|
|
159
|
+
|
|
160
|
+
const nodeDefinition = selectedNode?.type ? nodeDefinitions[selectedNode.type] : null;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
selectedNode,
|
|
164
|
+
nodeDefinition,
|
|
165
|
+
parameters,
|
|
166
|
+
hasUnsavedChanges,
|
|
167
|
+
handleParameterChange,
|
|
168
|
+
handleSave,
|
|
169
|
+
handleCancel,
|
|
170
|
+
// New state for loading/saving status
|
|
171
|
+
isLoading,
|
|
172
|
+
isSaving,
|
|
173
|
+
error,
|
|
174
|
+
isConnected,
|
|
175
|
+
};
|
|
176
|
+
};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { Node, Edge, addEdge, Connection } from 'reactflow';
|
|
3
|
+
import { useAppStore } from '../store/useAppStore';
|
|
4
|
+
import { nodeDefinitions } from '../nodeDefinitions';
|
|
5
|
+
import { INodeInputDefinition, INodeOutputDefinition, NodeConnectionType } from '../types/INodeProperties';
|
|
6
|
+
|
|
7
|
+
interface UseReactFlowNodesProps {
|
|
8
|
+
setNodes: (nodes: Node[] | ((nodes: Node[]) => Node[])) => void;
|
|
9
|
+
setEdges: (edges: Edge[] | ((edges: Edge[]) => Edge[])) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const useReactFlowNodes = ({ setNodes, setEdges }: UseReactFlowNodesProps) => {
|
|
13
|
+
const { selectedNode, setSelectedNode } = useAppStore();
|
|
14
|
+
|
|
15
|
+
// Helper function to get node inputs/outputs for both enhanced and legacy nodes
|
|
16
|
+
const getNodeInputs = (nodeType: string): INodeInputDefinition[] => {
|
|
17
|
+
const definition = nodeDefinitions[nodeType];
|
|
18
|
+
if (!definition?.inputs) return [];
|
|
19
|
+
|
|
20
|
+
// Enhanced nodes: array of input objects
|
|
21
|
+
if (definition.inputs.length > 0 && typeof definition.inputs[0] === 'object') {
|
|
22
|
+
return definition.inputs as INodeInputDefinition[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Legacy nodes: array of strings - convert to input objects
|
|
26
|
+
return (definition.inputs as string[]).map((input, index) => ({
|
|
27
|
+
name: `input_${index}`,
|
|
28
|
+
displayName: 'Input',
|
|
29
|
+
type: (input as NodeConnectionType) || 'main',
|
|
30
|
+
description: 'Node input connection'
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getNodeOutputs = (nodeType: string): INodeOutputDefinition[] => {
|
|
35
|
+
const definition = nodeDefinitions[nodeType];
|
|
36
|
+
if (!definition?.outputs) return [];
|
|
37
|
+
|
|
38
|
+
// Enhanced nodes: array of output objects
|
|
39
|
+
if (definition.outputs.length > 0 && typeof definition.outputs[0] === 'object') {
|
|
40
|
+
return definition.outputs as INodeOutputDefinition[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Legacy nodes: array of strings - convert to output objects
|
|
44
|
+
return (definition.outputs as string[]).map((output, index) => ({
|
|
45
|
+
name: `output_${index}`,
|
|
46
|
+
displayName: 'Output',
|
|
47
|
+
type: (output as NodeConnectionType) || 'main',
|
|
48
|
+
description: 'Node output connection'
|
|
49
|
+
}));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Validate connection compatibility
|
|
53
|
+
const isValidConnection = (connection: Connection, nodes: Node[]): boolean => {
|
|
54
|
+
const sourceNode = nodes.find(n => n.id === connection.source);
|
|
55
|
+
const targetNode = nodes.find(n => n.id === connection.target);
|
|
56
|
+
|
|
57
|
+
if (!sourceNode || !targetNode) {
|
|
58
|
+
console.warn('Connection validation: Source or target node not found');
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const sourceOutputs = getNodeOutputs(sourceNode.type || '');
|
|
63
|
+
const targetInputs = getNodeInputs(targetNode.type || '');
|
|
64
|
+
|
|
65
|
+
// Find the specific output and input being connected
|
|
66
|
+
const sourceHandle = connection.sourceHandle || 'output_0';
|
|
67
|
+
const targetHandle = connection.targetHandle || 'input_0';
|
|
68
|
+
|
|
69
|
+
const sourceOutput = sourceOutputs.find(output =>
|
|
70
|
+
`output-${output.name}` === sourceHandle || output.name === sourceHandle
|
|
71
|
+
);
|
|
72
|
+
const targetInput = targetInputs.find(input =>
|
|
73
|
+
`input-${input.name}` === targetHandle || input.name === targetHandle
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (!sourceOutput || !targetInput) {
|
|
77
|
+
console.log('[Connection Debug]', {
|
|
78
|
+
sourceType: sourceNode.type,
|
|
79
|
+
targetType: targetNode.type,
|
|
80
|
+
sourceHandle,
|
|
81
|
+
targetHandle,
|
|
82
|
+
sourceOutputs: sourceOutputs.map(o => ({ name: o.name, computed: `output-${o.name}` })),
|
|
83
|
+
targetInputs: targetInputs.map(i => ({ name: i.name, computed: `input-${i.name}` })),
|
|
84
|
+
sourceOutput: sourceOutput ? 'found' : 'NOT FOUND',
|
|
85
|
+
targetInput: targetInput ? 'found' : 'NOT FOUND'
|
|
86
|
+
});
|
|
87
|
+
console.warn('Connection validation: Handle not found', { sourceHandle, targetHandle });
|
|
88
|
+
// Allow connection if we can't find handle definitions (fallback for legacy nodes)
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check type compatibility
|
|
93
|
+
const isCompatible = areTypesCompatible(sourceOutput.type, targetInput.type);
|
|
94
|
+
|
|
95
|
+
if (!isCompatible) {
|
|
96
|
+
console.warn(`Connection rejected: Incompatible types`, {
|
|
97
|
+
source: `${sourceNode.type}.${sourceOutput.name} (${sourceOutput.type})`,
|
|
98
|
+
target: `${targetNode.type}.${targetInput.name} (${targetInput.type})`
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return isCompatible;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Check if two connection types are compatible
|
|
106
|
+
const areTypesCompatible = (outputType: NodeConnectionType, inputType: NodeConnectionType): boolean => {
|
|
107
|
+
// Same types are always compatible
|
|
108
|
+
if (outputType === inputType) return true;
|
|
109
|
+
|
|
110
|
+
// 'main' is compatible with most types (universal data format)
|
|
111
|
+
if (outputType === 'main' || inputType === 'main') return true;
|
|
112
|
+
|
|
113
|
+
// Specific compatibility rules
|
|
114
|
+
const compatibilityMatrix: Record<NodeConnectionType, NodeConnectionType[]> = {
|
|
115
|
+
'main': ['main', 'trigger', 'ai', 'file', 'binary', 'webhook'], // Main accepts everything
|
|
116
|
+
'trigger': ['main', 'trigger'], // Triggers only connect to main or other triggers
|
|
117
|
+
'ai': ['main', 'ai'], // AI outputs connect to main or other AI inputs
|
|
118
|
+
'file': ['main', 'file', 'binary'], // Files can connect to binary
|
|
119
|
+
'binary': ['main', 'file', 'binary'], // Binary connects to file/binary
|
|
120
|
+
'webhook': ['main', 'webhook'] // Webhooks connect to main or other webhooks
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return compatibilityMatrix[outputType]?.includes(inputType) ?? false;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const onConnect = useCallback(
|
|
127
|
+
(params: Edge | Connection) => {
|
|
128
|
+
const { currentWorkflow } = useAppStore.getState();
|
|
129
|
+
const nodes = currentWorkflow?.nodes || [];
|
|
130
|
+
|
|
131
|
+
// Convert Edge to Connection format for validation
|
|
132
|
+
const connection: Connection = {
|
|
133
|
+
source: params.source,
|
|
134
|
+
target: params.target,
|
|
135
|
+
sourceHandle: params.sourceHandle ?? null,
|
|
136
|
+
targetHandle: params.targetHandle ?? null
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Debug: Log all connection attempts
|
|
140
|
+
console.log('[onConnect] Connection attempt:', {
|
|
141
|
+
source: params.source,
|
|
142
|
+
target: params.target,
|
|
143
|
+
sourceHandle: params.sourceHandle,
|
|
144
|
+
targetHandle: params.targetHandle
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Validate connection before adding
|
|
148
|
+
if (!isValidConnection(connection, nodes)) {
|
|
149
|
+
// Show user feedback for rejected connection (non-blocking)
|
|
150
|
+
console.warn('Connection rejected: Incompatible connection types', connection);
|
|
151
|
+
// Could implement a toast notification system here instead of alert
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
alert('Connection not allowed: Incompatible connection types.\n\nTip: Connect outputs to compatible inputs (e.g., AI → Main, File → Binary).');
|
|
154
|
+
}, 0);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('[onConnect] Connection accepted, adding edge');
|
|
159
|
+
// Add the connection
|
|
160
|
+
setEdges((eds) => addEdge(params, eds));
|
|
161
|
+
},
|
|
162
|
+
[setEdges]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const onNodesDelete = useCallback(
|
|
166
|
+
(deleted: Node[]) => {
|
|
167
|
+
setNodes((nds) => nds.filter((node) => !deleted.find((d) => d.id === node.id)));
|
|
168
|
+
|
|
169
|
+
// Clear selected node if it was deleted
|
|
170
|
+
if (selectedNode && deleted.find((d) => d.id === selectedNode.id)) {
|
|
171
|
+
setSelectedNode(null);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
[setNodes, selectedNode, setSelectedNode]
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const onEdgesDelete = useCallback(
|
|
178
|
+
(deleted: Edge[]) => {
|
|
179
|
+
setEdges((eds) => eds.filter((edge) => !deleted.find((d) => d.id === edge.id)));
|
|
180
|
+
},
|
|
181
|
+
[setEdges]
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
onConnect,
|
|
186
|
+
onNodesDelete,
|
|
187
|
+
onEdgesDelete,
|
|
188
|
+
};
|
|
189
|
+
};
|