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.
Files changed (288) hide show
  1. package/.env.template +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/bin/cli.js +159 -0
  5. package/client/.dockerignore +45 -0
  6. package/client/Dockerfile +68 -0
  7. package/client/eslint.config.js +29 -0
  8. package/client/index.html +13 -0
  9. package/client/nginx.conf +66 -0
  10. package/client/package.json +48 -0
  11. package/client/src/App.tsx +27 -0
  12. package/client/src/Dashboard.tsx +1173 -0
  13. package/client/src/ParameterPanel.tsx +301 -0
  14. package/client/src/components/AIAgentNode.tsx +321 -0
  15. package/client/src/components/APIKeyValidator.tsx +118 -0
  16. package/client/src/components/ClaudeChatModelNode.tsx +18 -0
  17. package/client/src/components/ConditionalEdge.tsx +189 -0
  18. package/client/src/components/CredentialsModal.tsx +306 -0
  19. package/client/src/components/EdgeConditionEditor.tsx +443 -0
  20. package/client/src/components/GeminiChatModelNode.tsx +18 -0
  21. package/client/src/components/GenericNode.tsx +357 -0
  22. package/client/src/components/LocationParameterPanel.tsx +154 -0
  23. package/client/src/components/ModelNode.tsx +286 -0
  24. package/client/src/components/OpenAIChatModelNode.tsx +18 -0
  25. package/client/src/components/OutputPanel.tsx +471 -0
  26. package/client/src/components/ParameterRenderer.tsx +1874 -0
  27. package/client/src/components/SkillEditorModal.tsx +417 -0
  28. package/client/src/components/SquareNode.tsx +797 -0
  29. package/client/src/components/StartNode.tsx +250 -0
  30. package/client/src/components/ToolkitNode.tsx +365 -0
  31. package/client/src/components/TriggerNode.tsx +463 -0
  32. package/client/src/components/auth/LoginPage.tsx +247 -0
  33. package/client/src/components/auth/ProtectedRoute.tsx +59 -0
  34. package/client/src/components/base/BaseChatModelNode.tsx +271 -0
  35. package/client/src/components/icons/AIProviderIcons.tsx +50 -0
  36. package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
  37. package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
  38. package/client/src/components/maps/index.ts +26 -0
  39. package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
  40. package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
  41. package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
  42. package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
  43. package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
  44. package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
  45. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
  46. package/client/src/components/parameterPanel/index.ts +42 -0
  47. package/client/src/components/shared/DataPanel.tsx +142 -0
  48. package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
  49. package/client/src/components/ui/AIResultModal.tsx +204 -0
  50. package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
  51. package/client/src/components/ui/CodeEditor.tsx +81 -0
  52. package/client/src/components/ui/CollapsibleSection.tsx +88 -0
  53. package/client/src/components/ui/ComponentItem.tsx +154 -0
  54. package/client/src/components/ui/ComponentPalette.tsx +321 -0
  55. package/client/src/components/ui/ConsolePanel.tsx +1074 -0
  56. package/client/src/components/ui/ErrorBoundary.tsx +196 -0
  57. package/client/src/components/ui/InputNodesPanel.tsx +204 -0
  58. package/client/src/components/ui/MapSelector.tsx +314 -0
  59. package/client/src/components/ui/Modal.tsx +149 -0
  60. package/client/src/components/ui/NodeContextMenu.tsx +192 -0
  61. package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
  62. package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
  63. package/client/src/components/ui/SettingsPanel.tsx +243 -0
  64. package/client/src/components/ui/TopToolbar.tsx +736 -0
  65. package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
  66. package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
  67. package/client/src/config/antdTheme.ts +186 -0
  68. package/client/src/config/api.ts +54 -0
  69. package/client/src/contexts/AuthContext.tsx +221 -0
  70. package/client/src/contexts/ThemeContext.tsx +42 -0
  71. package/client/src/contexts/WebSocketContext.tsx +1971 -0
  72. package/client/src/factories/baseChatModelFactory.ts +256 -0
  73. package/client/src/hooks/useAndroidOperations.ts +164 -0
  74. package/client/src/hooks/useApiKeyValidation.ts +107 -0
  75. package/client/src/hooks/useApiKeys.ts +238 -0
  76. package/client/src/hooks/useAppTheme.ts +17 -0
  77. package/client/src/hooks/useComponentPalette.ts +51 -0
  78. package/client/src/hooks/useCopyPaste.ts +155 -0
  79. package/client/src/hooks/useDragAndDrop.ts +124 -0
  80. package/client/src/hooks/useDragVariable.ts +88 -0
  81. package/client/src/hooks/useExecution.ts +313 -0
  82. package/client/src/hooks/useParameterPanel.ts +176 -0
  83. package/client/src/hooks/useReactFlowNodes.ts +189 -0
  84. package/client/src/hooks/useToolSchema.ts +209 -0
  85. package/client/src/hooks/useWhatsApp.ts +196 -0
  86. package/client/src/hooks/useWorkflowManagement.ts +46 -0
  87. package/client/src/index.css +315 -0
  88. package/client/src/main.tsx +19 -0
  89. package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
  90. package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
  91. package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
  92. package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
  93. package/client/src/nodeDefinitions/chatNodes.ts +135 -0
  94. package/client/src/nodeDefinitions/codeNodes.ts +54 -0
  95. package/client/src/nodeDefinitions/documentNodes.ts +379 -0
  96. package/client/src/nodeDefinitions/index.ts +15 -0
  97. package/client/src/nodeDefinitions/locationNodes.ts +463 -0
  98. package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
  99. package/client/src/nodeDefinitions/skillNodes.ts +211 -0
  100. package/client/src/nodeDefinitions/toolNodes.ts +198 -0
  101. package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
  102. package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
  103. package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
  104. package/client/src/nodeDefinitions.ts +104 -0
  105. package/client/src/schemas/workflowSchema.ts +264 -0
  106. package/client/src/services/dynamicParameterService.ts +96 -0
  107. package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
  108. package/client/src/services/executionService.ts +232 -0
  109. package/client/src/services/workflowApi.ts +91 -0
  110. package/client/src/store/useAppStore.ts +582 -0
  111. package/client/src/styles/theme.ts +508 -0
  112. package/client/src/styles/zIndex.ts +17 -0
  113. package/client/src/types/ComponentTypes.ts +39 -0
  114. package/client/src/types/EdgeCondition.ts +231 -0
  115. package/client/src/types/INodeProperties.ts +288 -0
  116. package/client/src/types/NodeTypes.ts +28 -0
  117. package/client/src/utils/formatters.ts +33 -0
  118. package/client/src/utils/googleMapsLoader.ts +140 -0
  119. package/client/src/utils/locationUtils.ts +85 -0
  120. package/client/src/utils/nodeUtils.ts +31 -0
  121. package/client/src/utils/workflow.ts +30 -0
  122. package/client/src/utils/workflowExport.ts +120 -0
  123. package/client/src/vite-env.d.ts +12 -0
  124. package/client/tailwind.config.js +60 -0
  125. package/client/tsconfig.json +25 -0
  126. package/client/tsconfig.node.json +11 -0
  127. package/client/vite.config.js +35 -0
  128. package/docker-compose.prod.yml +107 -0
  129. package/docker-compose.yml +104 -0
  130. package/docs-MachinaOs/README.md +85 -0
  131. package/docs-MachinaOs/deployment/docker.mdx +228 -0
  132. package/docs-MachinaOs/deployment/production.mdx +345 -0
  133. package/docs-MachinaOs/docs.json +75 -0
  134. package/docs-MachinaOs/faq.mdx +309 -0
  135. package/docs-MachinaOs/favicon.svg +5 -0
  136. package/docs-MachinaOs/installation.mdx +160 -0
  137. package/docs-MachinaOs/introduction.mdx +114 -0
  138. package/docs-MachinaOs/logo/dark.svg +6 -0
  139. package/docs-MachinaOs/logo/light.svg +6 -0
  140. package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
  141. package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
  142. package/docs-MachinaOs/nodes/android.mdx +411 -0
  143. package/docs-MachinaOs/nodes/overview.mdx +181 -0
  144. package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
  145. package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
  146. package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
  147. package/docs-MachinaOs/quickstart.mdx +119 -0
  148. package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
  149. package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
  150. package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
  151. package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
  152. package/nul +0 -0
  153. package/package.json +70 -0
  154. package/scripts/build.js +158 -0
  155. package/scripts/check-ports.ps1 +33 -0
  156. package/scripts/clean.js +40 -0
  157. package/scripts/docker.js +93 -0
  158. package/scripts/kill-port.ps1 +154 -0
  159. package/scripts/start.js +210 -0
  160. package/scripts/stop.js +325 -0
  161. package/server/.dockerignore +44 -0
  162. package/server/Dockerfile +45 -0
  163. package/server/constants.py +249 -0
  164. package/server/core/__init__.py +1 -0
  165. package/server/core/cache.py +461 -0
  166. package/server/core/config.py +128 -0
  167. package/server/core/container.py +99 -0
  168. package/server/core/database.py +1211 -0
  169. package/server/core/logging.py +314 -0
  170. package/server/main.py +289 -0
  171. package/server/middleware/__init__.py +5 -0
  172. package/server/middleware/auth.py +89 -0
  173. package/server/models/__init__.py +1 -0
  174. package/server/models/auth.py +52 -0
  175. package/server/models/cache.py +24 -0
  176. package/server/models/database.py +211 -0
  177. package/server/models/nodes.py +455 -0
  178. package/server/package.json +9 -0
  179. package/server/pyproject.toml +72 -0
  180. package/server/requirements.txt +83 -0
  181. package/server/routers/__init__.py +1 -0
  182. package/server/routers/android.py +294 -0
  183. package/server/routers/auth.py +203 -0
  184. package/server/routers/database.py +151 -0
  185. package/server/routers/maps.py +142 -0
  186. package/server/routers/nodejs_compat.py +289 -0
  187. package/server/routers/webhook.py +90 -0
  188. package/server/routers/websocket.py +2127 -0
  189. package/server/routers/whatsapp.py +761 -0
  190. package/server/routers/workflow.py +200 -0
  191. package/server/services/__init__.py +1 -0
  192. package/server/services/ai.py +2415 -0
  193. package/server/services/android/__init__.py +27 -0
  194. package/server/services/android/broadcaster.py +114 -0
  195. package/server/services/android/client.py +608 -0
  196. package/server/services/android/manager.py +78 -0
  197. package/server/services/android/protocol.py +165 -0
  198. package/server/services/android_service.py +588 -0
  199. package/server/services/auth.py +131 -0
  200. package/server/services/chat_client.py +160 -0
  201. package/server/services/deployment/__init__.py +12 -0
  202. package/server/services/deployment/manager.py +706 -0
  203. package/server/services/deployment/state.py +47 -0
  204. package/server/services/deployment/triggers.py +275 -0
  205. package/server/services/event_waiter.py +785 -0
  206. package/server/services/execution/__init__.py +77 -0
  207. package/server/services/execution/cache.py +769 -0
  208. package/server/services/execution/conditions.py +373 -0
  209. package/server/services/execution/dlq.py +132 -0
  210. package/server/services/execution/executor.py +1351 -0
  211. package/server/services/execution/models.py +531 -0
  212. package/server/services/execution/recovery.py +235 -0
  213. package/server/services/handlers/__init__.py +126 -0
  214. package/server/services/handlers/ai.py +355 -0
  215. package/server/services/handlers/android.py +260 -0
  216. package/server/services/handlers/code.py +278 -0
  217. package/server/services/handlers/document.py +598 -0
  218. package/server/services/handlers/http.py +193 -0
  219. package/server/services/handlers/polyglot.py +105 -0
  220. package/server/services/handlers/tools.py +845 -0
  221. package/server/services/handlers/triggers.py +107 -0
  222. package/server/services/handlers/utility.py +822 -0
  223. package/server/services/handlers/whatsapp.py +476 -0
  224. package/server/services/maps.py +289 -0
  225. package/server/services/memory_store.py +103 -0
  226. package/server/services/node_executor.py +375 -0
  227. package/server/services/parameter_resolver.py +218 -0
  228. package/server/services/polyglot_client.py +169 -0
  229. package/server/services/scheduler.py +155 -0
  230. package/server/services/skill_loader.py +417 -0
  231. package/server/services/status_broadcaster.py +826 -0
  232. package/server/services/temporal/__init__.py +23 -0
  233. package/server/services/temporal/activities.py +344 -0
  234. package/server/services/temporal/client.py +76 -0
  235. package/server/services/temporal/executor.py +147 -0
  236. package/server/services/temporal/worker.py +251 -0
  237. package/server/services/temporal/workflow.py +355 -0
  238. package/server/services/temporal/ws_client.py +236 -0
  239. package/server/services/text.py +111 -0
  240. package/server/services/user_auth.py +172 -0
  241. package/server/services/websocket_client.py +29 -0
  242. package/server/services/workflow.py +597 -0
  243. package/server/skills/android-skill/SKILL.md +82 -0
  244. package/server/skills/assistant-personality/SKILL.md +45 -0
  245. package/server/skills/code-skill/SKILL.md +140 -0
  246. package/server/skills/http-skill/SKILL.md +161 -0
  247. package/server/skills/maps-skill/SKILL.md +170 -0
  248. package/server/skills/memory-skill/SKILL.md +154 -0
  249. package/server/skills/scheduler-skill/SKILL.md +84 -0
  250. package/server/skills/whatsapp-skill/SKILL.md +283 -0
  251. package/server/uv.lock +2916 -0
  252. package/server/whatsapp-rpc/.dockerignore +30 -0
  253. package/server/whatsapp-rpc/Dockerfile +44 -0
  254. package/server/whatsapp-rpc/Dockerfile.web +17 -0
  255. package/server/whatsapp-rpc/README.md +139 -0
  256. package/server/whatsapp-rpc/cli.js +95 -0
  257. package/server/whatsapp-rpc/configs/config.yaml +7 -0
  258. package/server/whatsapp-rpc/docker-compose.yml +35 -0
  259. package/server/whatsapp-rpc/docs/API.md +410 -0
  260. package/server/whatsapp-rpc/go.mod +67 -0
  261. package/server/whatsapp-rpc/go.sum +203 -0
  262. package/server/whatsapp-rpc/package.json +30 -0
  263. package/server/whatsapp-rpc/schema.json +1294 -0
  264. package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
  265. package/server/whatsapp-rpc/scripts/cli.js +162 -0
  266. package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
  267. package/server/whatsapp-rpc/src/go/config/config.go +49 -0
  268. package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
  269. package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
  270. package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
  271. package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
  272. package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
  273. package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
  274. package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
  275. package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
  276. package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
  277. package/server/whatsapp-rpc/web/app.py +609 -0
  278. package/server/whatsapp-rpc/web/requirements.txt +6 -0
  279. package/server/whatsapp-rpc/web/rpc_client.py +427 -0
  280. package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
  281. package/server/whatsapp-rpc/web/templates/base.html +150 -0
  282. package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
  283. package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
  284. package/server/whatsapp-rpc/web/templates/groups.html +328 -0
  285. package/server/whatsapp-rpc/web/templates/messages.html +465 -0
  286. package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
  287. package/server/whatsapp-rpc/web/templates/send.html +259 -0
  288. package/server/whatsapp-rpc/web/templates/settings.html +459 -0
@@ -0,0 +1,797 @@
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
2
+ import { Handle, Position, NodeProps } from 'reactflow';
3
+ import { NodeData } from '../types/NodeTypes';
4
+ import { useAppStore } from '../store/useAppStore';
5
+ import { nodeDefinitions } from '../nodeDefinitions';
6
+ import { useAppTheme } from '../hooks/useAppTheme';
7
+ import { ANDROID_SERVICE_NODE_TYPES } from '../nodeDefinitions/androidServiceNodes';
8
+ import { ANDROID_DEVICE_NODE_TYPES } from '../nodeDefinitions/androidDeviceNodes';
9
+ import { useWebSocket, useWhatsAppStatus } from '../contexts/WebSocketContext';
10
+ import { useApiKeys } from '../hooks/useApiKeys';
11
+ import { getAIProviderIcon } from './icons/AIProviderIcons';
12
+ import { PlayCircleFilled, ScheduleOutlined } from '@ant-design/icons';
13
+
14
+ // All Android node types combined
15
+ const ALL_ANDROID_NODE_TYPES = [...ANDROID_SERVICE_NODE_TYPES, ...ANDROID_DEVICE_NODE_TYPES];
16
+
17
+ // Android service nodes that can connect to Android Toolkit as tools
18
+ const ANDROID_TOOL_CAPABLE_NODES = ANDROID_SERVICE_NODE_TYPES;
19
+
20
+ // Nodes with 'tool' in their group can connect to AI Agent/Chat Agent tool handles
21
+ const hasToolGroup = (definition: any): boolean => {
22
+ const groups = definition?.group || [];
23
+ return groups.includes('tool');
24
+ };
25
+
26
+ // Google Maps node types
27
+ const GOOGLE_MAPS_NODE_TYPES = ['createMap', 'addLocations', 'showNearbyPlaces'];
28
+
29
+ // WhatsApp node types
30
+ const WHATSAPP_NODE_TYPES = ['whatsappConnect', 'whatsappSend', 'whatsappReceive', 'whatsappDb'];
31
+
32
+ // Nodes that should not have output handles (input-only nodes)
33
+ const NO_OUTPUT_NODE_TYPES = ['console'];
34
+
35
+ // AI Model node types with their provider IDs
36
+ const AI_MODEL_NODE_TYPES: Record<string, string> = {
37
+ 'openaiChatModel': 'openai',
38
+ 'anthropicChatModel': 'anthropic',
39
+ 'geminiChatModel': 'gemini',
40
+ 'openrouterChatModel': 'openrouter',
41
+ 'groqChatModel': 'groq',
42
+ 'cerebrasChatModel': 'cerebras',
43
+ };
44
+
45
+ const SquareNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
46
+ const theme = useAppTheme();
47
+ const { setSelectedNode, renamingNodeId, setRenamingNodeId, updateNodeData } = useAppStore();
48
+ const [hasApiKey, setHasApiKey] = useState(false);
49
+ const [isConfigured, setIsConfigured] = useState(false);
50
+ const isDisabled = data?.disabled === true;
51
+
52
+ // Inline rename state
53
+ const [isRenaming, setIsRenaming] = useState(false);
54
+ const [editLabel, setEditLabel] = useState('');
55
+ const inputRef = useRef<HTMLInputElement>(null);
56
+
57
+ // Get Android status, node status, and API key status from WebSocket context
58
+ const { androidStatus, getNodeStatus, getApiKeyStatus } = useWebSocket();
59
+ const { getStoredApiKey, validateGoogleMapsKey } = useApiKeys();
60
+ const nodeStatus = getNodeStatus(id);
61
+ const executionStatus = nodeStatus?.status || 'idle';
62
+
63
+ // Check if this is a Google Maps node
64
+ const isGoogleMapsNode = type ? GOOGLE_MAPS_NODE_TYPES.includes(type) : false;
65
+ const googleMapsKeyStatus = isGoogleMapsNode ? getApiKeyStatus('google_maps') : undefined;
66
+
67
+ // Check if this is an AI model node and get reactive API key status
68
+ const isAIModelNode = type ? type in AI_MODEL_NODE_TYPES : false;
69
+ const aiProviderId = type && AI_MODEL_NODE_TYPES[type] ? AI_MODEL_NODE_TYPES[type] : null;
70
+ const aiKeyStatus = aiProviderId ? getApiKeyStatus(aiProviderId) : undefined;
71
+
72
+ const definition = nodeDefinitions[type as keyof typeof nodeDefinitions];
73
+
74
+ // Check if this is an Android node
75
+ const isAndroidNode = type ? ALL_ANDROID_NODE_TYPES.includes(type) : false;
76
+
77
+ // Check if this node can be used as a tool (connects to Android Toolkit or AI Agent/Chat Agent tool handle)
78
+ const isToolCapable = type ? (ANDROID_TOOL_CAPABLE_NODES.includes(type) || hasToolGroup(definition)) : false;
79
+
80
+ // Android connection status from WebSocket (real-time updates)
81
+ // Service nodes need a paired device to execute, not just relay connection
82
+ const isAndroidConnected = isAndroidNode && androidStatus.paired;
83
+
84
+ // Check if this is a WhatsApp node
85
+ const isWhatsAppNode = type ? WHATSAPP_NODE_TYPES.includes(type) : false;
86
+
87
+ // WhatsApp connection status from WebSocket (real-time updates)
88
+ const whatsappStatus = useWhatsAppStatus();
89
+
90
+ // Execution state - waiting is treated identically to executing
91
+ const isExecuting = executionStatus === 'executing' || executionStatus === 'waiting';
92
+
93
+
94
+ // Sync with global renaming state
95
+ useEffect(() => {
96
+ if (renamingNodeId === id) {
97
+ setIsRenaming(true);
98
+ setEditLabel(data?.label || definition?.displayName || type || '');
99
+ } else {
100
+ setIsRenaming(false);
101
+ }
102
+ }, [renamingNodeId, id, data?.label, definition?.displayName, type]);
103
+
104
+ // Focus and select input when entering rename mode
105
+ useEffect(() => {
106
+ if (isRenaming && inputRef.current) {
107
+ inputRef.current.focus();
108
+ inputRef.current.select();
109
+ }
110
+ }, [isRenaming]);
111
+
112
+ // Handle save rename
113
+ const handleSaveRename = useCallback(() => {
114
+ const newLabel = editLabel.trim();
115
+ const originalLabel = data?.label || definition?.displayName || type || '';
116
+
117
+ // Only save if label changed and is not empty
118
+ if (newLabel && newLabel !== originalLabel) {
119
+ updateNodeData(id, { ...data, label: newLabel });
120
+ }
121
+
122
+ setIsRenaming(false);
123
+ setRenamingNodeId(null);
124
+ }, [editLabel, data, definition?.displayName, type, id, updateNodeData, setRenamingNodeId]);
125
+
126
+ // Handle cancel rename
127
+ const handleCancelRename = useCallback(() => {
128
+ setIsRenaming(false);
129
+ setRenamingNodeId(null);
130
+ }, [setRenamingNodeId]);
131
+
132
+ // Handle double-click to rename
133
+ const handleLabelDoubleClick = useCallback(() => {
134
+ setRenamingNodeId(id);
135
+ }, [id, setRenamingNodeId]);
136
+
137
+ // Check API key and configuration status
138
+ useEffect(() => {
139
+ const checkConfiguration = async () => {
140
+ try {
141
+ // Determine provider from node definition credentials
142
+ let provider = '';
143
+ const credentials = definition?.credentials?.[0];
144
+ if (credentials?.name) {
145
+ // Map credential names to provider keys
146
+ const credentialToProvider: Record<string, string> = {
147
+ 'googleMapsApi': 'google_maps',
148
+ 'openaiApi': 'openai',
149
+ 'anthropicApi': 'anthropic',
150
+ 'googleAiApi': 'gemini'
151
+ };
152
+ provider = credentialToProvider[credentials.name] || '';
153
+ }
154
+
155
+ // Fallback: extract provider from node type if not found in credentials
156
+ if (!provider) {
157
+ if (type?.includes('map') || type?.includes('location')) provider = 'google_maps';
158
+ }
159
+
160
+ // Check if API key exists via WebSocket
161
+ const apiKey = provider ? await getStoredApiKey(provider) : null;
162
+ setHasApiKey(!!apiKey);
163
+
164
+ // Check if service is configured (has required parameters)
165
+ const hasRequiredParams = data && Object.keys(data).length > 0;
166
+ setIsConfigured(hasRequiredParams && !!apiKey);
167
+
168
+ // For Google Maps nodes, trigger validation via WebSocket
169
+ if (isGoogleMapsNode && apiKey) {
170
+ await validateGoogleMapsKey(apiKey);
171
+ }
172
+
173
+ if (!apiKey && provider) {
174
+ console.warn(`[SquareNode] ${definition?.displayName} ${id}: No API key configured for ${provider}`);
175
+ }
176
+ } catch (error) {
177
+ console.error('Configuration check error:', error);
178
+ setHasApiKey(false);
179
+ setIsConfigured(false);
180
+ }
181
+ };
182
+
183
+ checkConfiguration();
184
+ }, [data, id, type, definition?.displayName, definition?.credentials, isGoogleMapsNode, getStoredApiKey, validateGoogleMapsKey]);
185
+
186
+ // Get settings panel controls from store
187
+ const { setWhatsAppSettingsOpen, setAndroidSettingsOpen } = useAppStore();
188
+
189
+ const handleParametersClick = (e: React.MouseEvent) => {
190
+ e.stopPropagation();
191
+ // For whatsappConnect, open the WhatsApp settings panel instead
192
+ if (type === 'whatsappConnect') {
193
+ setWhatsAppSettingsOpen(true);
194
+ } else if (type === 'androidDeviceSetup') {
195
+ // For androidDeviceSetup, open the Android settings panel
196
+ setAndroidSettingsOpen(true);
197
+ } else {
198
+ setSelectedNode({ id, type, data, position: { x: 0, y: 0 } });
199
+ }
200
+ };
201
+
202
+ // Get status indicator color based on execution state
203
+ const getStatusIndicatorColor = () => {
204
+ // For executing or waiting state, show blue/cyan
205
+ if (executionStatus === 'executing' || executionStatus === 'waiting') {
206
+ return theme.dracula.cyan;
207
+ }
208
+ if (executionStatus === 'success') {
209
+ return theme.dracula.green;
210
+ }
211
+ if (executionStatus === 'error') {
212
+ return theme.dracula.red;
213
+ }
214
+
215
+ // Idle state - use Android or configuration status
216
+ if (isAndroidNode) {
217
+ return isAndroidConnected ? theme.dracula.green : theme.dracula.red;
218
+ }
219
+
220
+ // WhatsApp nodes - use WebSocket connection status
221
+ if (isWhatsAppNode) {
222
+ if (whatsappStatus.connected) return theme.dracula.green;
223
+ if (whatsappStatus.pairing) return theme.dracula.orange;
224
+ return theme.dracula.red;
225
+ }
226
+
227
+ // Google Maps nodes - use WebSocket API key validation status
228
+ if (isGoogleMapsNode && googleMapsKeyStatus) {
229
+ return googleMapsKeyStatus.valid ? theme.dracula.green : theme.dracula.red;
230
+ }
231
+
232
+ // AI Model nodes - use reactive WebSocket API key status
233
+ if (isAIModelNode) {
234
+ if (aiKeyStatus?.valid && aiKeyStatus?.hasKey) return theme.dracula.green;
235
+ if (aiKeyStatus?.hasKey) return theme.dracula.orange;
236
+ return theme.dracula.red;
237
+ }
238
+
239
+ return isConfigured ? theme.dracula.green : hasApiKey ? theme.dracula.orange : theme.dracula.red;
240
+ };
241
+
242
+ const getStatusTitle = () => {
243
+ switch (executionStatus) {
244
+ case 'executing':
245
+ return 'Executing...';
246
+ case 'waiting':
247
+ return nodeStatus?.message || 'Waiting for event...';
248
+ case 'success':
249
+ return 'Execution successful';
250
+ case 'error':
251
+ return `Error: ${nodeStatus?.data?.error || 'Unknown error'}`;
252
+ default:
253
+ if (isAndroidNode) {
254
+ return isAndroidConnected ? 'Android device connected' : 'Android device not connected';
255
+ }
256
+ // WhatsApp nodes - use WebSocket connection status
257
+ if (isWhatsAppNode) {
258
+ if (whatsappStatus.connected) return 'WhatsApp connected';
259
+ if (whatsappStatus.pairing) return 'Pairing in progress...';
260
+ if (whatsappStatus.running) return 'WhatsApp service running';
261
+ return 'WhatsApp not connected';
262
+ }
263
+ // Google Maps nodes - use WebSocket API key validation status
264
+ if (isGoogleMapsNode && googleMapsKeyStatus) {
265
+ return googleMapsKeyStatus.valid
266
+ ? 'Google Maps API key validated'
267
+ : `API key invalid: ${googleMapsKeyStatus.message || 'Validation failed'}`;
268
+ }
269
+ // AI Model nodes - use reactive WebSocket API key status
270
+ if (isAIModelNode) {
271
+ if (aiKeyStatus?.valid && aiKeyStatus?.hasKey) {
272
+ return `${aiProviderId?.charAt(0).toUpperCase()}${aiProviderId?.slice(1)} API key validated`;
273
+ }
274
+ if (aiKeyStatus?.hasKey) {
275
+ return 'API key found, validation pending';
276
+ }
277
+ return 'API key required - configure in Credentials';
278
+ }
279
+ return isConfigured
280
+ ? 'Service configured and ready'
281
+ : hasApiKey
282
+ ? 'API key found, service needs configuration'
283
+ : 'API key required';
284
+ }
285
+ };
286
+
287
+ // Common icon name to emoji mapping for fallback
288
+ const iconNameToEmoji: Record<string, string> = {
289
+ brain: '🧠',
290
+ memory: '🧠',
291
+ robot: '🤖',
292
+ ai: '🤖',
293
+ agent: '🤖',
294
+ chat: '💬',
295
+ message: '💬',
296
+ whatsapp: '📱',
297
+ phone: '📱',
298
+ email: '📧',
299
+ mail: '📧',
300
+ webhook: '🔗',
301
+ http: '🌐',
302
+ api: '🔌',
303
+ database: '🗄️',
304
+ file: '📄',
305
+ folder: '📁',
306
+ code: '💻',
307
+ python: '🐍',
308
+ javascript: '📜',
309
+ settings: '⚙️',
310
+ config: '⚙️',
311
+ clock: '⏰',
312
+ schedule: '📅',
313
+ location: '📍',
314
+ map: '🗺️',
315
+ search: '🔍',
316
+ filter: '🔍',
317
+ play: '▶️',
318
+ start: '▶️',
319
+ stop: '⏹️',
320
+ pause: '⏸️',
321
+ send: '📤',
322
+ receive: '📥',
323
+ upload: '⬆️',
324
+ download: '⬇️',
325
+ sync: '🔄',
326
+ refresh: '🔄',
327
+ warning: '⚠️',
328
+ error: '❌',
329
+ success: '✅',
330
+ info: '💡',
331
+ help: '❓',
332
+ user: '👤',
333
+ users: '👥',
334
+ key: '🔑',
335
+ lock: '🔒',
336
+ unlock: '🔓',
337
+ star: '⭐',
338
+ heart: '❤️',
339
+ thunder: '⚡',
340
+ lightning: '⚡',
341
+ fire: '🔥',
342
+ water: '💧',
343
+ cloud: '☁️',
344
+ sun: '☀️',
345
+ moon: '🌙',
346
+ camera: '📷',
347
+ image: '🖼️',
348
+ video: '🎬',
349
+ audio: '🔊',
350
+ music: '🎵',
351
+ bell: '🔔',
352
+ notification: '🔔',
353
+ link: '🔗',
354
+ chain: '🔗',
355
+ tool: '🔧',
356
+ wrench: '🔧',
357
+ hammer: '🔨',
358
+ gear: '⚙️',
359
+ cog: '⚙️',
360
+ bug: '🐛',
361
+ debug: '🐛',
362
+ test: '🧪',
363
+ experiment: '🧪',
364
+ lab: '🧪',
365
+ book: '📖',
366
+ docs: '📚',
367
+ note: '📝',
368
+ edit: '✏️',
369
+ pencil: '✏️',
370
+ trash: '🗑️',
371
+ delete: '🗑️',
372
+ copy: '📋',
373
+ paste: '📋',
374
+ cut: '✂️',
375
+ scissors: '✂️',
376
+ tag: '🏷️',
377
+ label: '🏷️',
378
+ flag: '🚩',
379
+ bookmark: '🔖',
380
+ pin: '📌',
381
+ target: '🎯',
382
+ goal: '🎯',
383
+ trophy: '🏆',
384
+ medal: '🏅',
385
+ gift: '🎁',
386
+ package: '📦',
387
+ box: '📦',
388
+ truck: '🚚',
389
+ shipping: '🚚',
390
+ cart: '🛒',
391
+ shop: '🛍️',
392
+ store: '🏪',
393
+ money: '💰',
394
+ dollar: '💵',
395
+ credit: '💳',
396
+ payment: '💳',
397
+ chart: '📊',
398
+ graph: '📈',
399
+ analytics: '📊',
400
+ stats: '📊',
401
+ dashboard: '📊',
402
+ terminal: '💻',
403
+ console: '💻',
404
+ server: '🖥️',
405
+ computer: '💻',
406
+ mobile: '📱',
407
+ tablet: '📱',
408
+ battery: '🔋',
409
+ wifi: '📶',
410
+ bluetooth: '📶',
411
+ antenna: '📡',
412
+ satellite: '🛰️',
413
+ rocket: '🚀',
414
+ plane: '✈️',
415
+ car: '🚗',
416
+ bike: '🚲',
417
+ train: '🚂',
418
+ ship: '🚢',
419
+ home: '🏠',
420
+ house: '🏠',
421
+ building: '🏢',
422
+ office: '🏢',
423
+ factory: '🏭',
424
+ hospital: '🏥',
425
+ school: '🏫',
426
+ university: '🎓',
427
+ graduation: '🎓',
428
+ world: '🌍',
429
+ globe: '🌐',
430
+ earth: '🌍',
431
+ android: '🤖',
432
+ apple: '🍎',
433
+ windows: '🪟',
434
+ linux: '🐧',
435
+ };
436
+
437
+ // Check if string is likely an emoji (contains emoji characters)
438
+ const isEmoji = (str: string): boolean => {
439
+ // Emoji regex pattern - matches most common emojis
440
+ const emojiRegex = /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2300}-\u{23FF}]|[\u{2B50}]|[\u{231A}-\u{231B}]|[\u{25AA}-\u{25AB}]|[\u{25B6}]|[\u{25C0}]|[\u{25FB}-\u{25FE}]|[\u{2614}-\u{2615}]|[\u{2648}-\u{2653}]|[\u{267F}]|[\u{2693}]|[\u{26A1}]|[\u{26AA}-\u{26AB}]|[\u{26BD}-\u{26BE}]|[\u{26C4}-\u{26C5}]|[\u{26CE}]|[\u{26D4}]|[\u{26EA}]|[\u{26F2}-\u{26F3}]|[\u{26F5}]|[\u{26FA}]|[\u{26FD}]|[\u{2702}]|[\u{2705}]|[\u{2708}-\u{270D}]|[\u{270F}]|[\u{2712}]|[\u{2714}]|[\u{2716}]|[\u{271D}]|[\u{2721}]|[\u{2728}]|[\u{2733}-\u{2734}]|[\u{2744}]|[\u{2747}]|[\u{274C}]|[\u{274E}]|[\u{2753}-\u{2755}]|[\u{2757}]|[\u{2763}-\u{2764}]|[\u{2795}-\u{2797}]|[\u{27A1}]|[\u{27B0}]|[\u{27BF}]|[\u{E000}-\u{F8FF}]/u;
441
+ return emojiRegex.test(str);
442
+ };
443
+
444
+ // Helper to render icon (handles URLs, emojis, and icon names)
445
+ const renderIcon = (icon: string) => {
446
+ // Handle image URLs
447
+ if (icon.startsWith('http') || icon.startsWith('data:') || icon.startsWith('/')) {
448
+ return (
449
+ <img
450
+ src={icon}
451
+ alt="icon"
452
+ style={{
453
+ width: '28px',
454
+ height: '28px',
455
+ objectFit: 'contain',
456
+ borderRadius: '4px'
457
+ }}
458
+ />
459
+ );
460
+ }
461
+
462
+ // If it's already an emoji, return it directly
463
+ if (isEmoji(icon)) {
464
+ return icon;
465
+ }
466
+
467
+ // Check if it's a known icon name and convert to emoji
468
+ const lowerIcon = icon.toLowerCase().trim();
469
+ if (iconNameToEmoji[lowerIcon]) {
470
+ return iconNameToEmoji[lowerIcon];
471
+ }
472
+
473
+ // Fallback: return a generic icon instead of plain text
474
+ console.warn(`[SquareNode] Unknown icon name "${icon}" - using fallback. Add emoji directly or update iconNameToEmoji mapping.`);
475
+ return '📦';
476
+ };
477
+
478
+ // Get service icon for display
479
+ const getServiceIcon = () => {
480
+ // Priority 0: Start node - use PlayCircleFilled icon with cyan color (neutral "begin" color)
481
+ if (type === 'start') {
482
+ return <PlayCircleFilled style={{ fontSize: 28, color: theme.dracula.cyan }} />;
483
+ }
484
+
485
+ // Cron Scheduler node - use ScheduleOutlined with node definition color
486
+ if (type === 'cronScheduler') {
487
+ return <ScheduleOutlined style={{ fontSize: 28, color: definition?.defaults?.color || nodeColor }} />;
488
+ }
489
+
490
+ // Priority 1: Check if this is an AI model node - use official provider icons
491
+ if (type && AI_MODEL_NODE_TYPES[type]) {
492
+ const providerId = AI_MODEL_NODE_TYPES[type];
493
+ const IconComponent = getAIProviderIcon(providerId);
494
+ if (IconComponent) {
495
+ return <IconComponent size={28} />;
496
+ }
497
+ }
498
+
499
+ // Priority 2: Custom icon set on the node instance (via data.customIcon)
500
+ if (data?.customIcon && typeof data.customIcon === 'string') {
501
+ return renderIcon(data.customIcon);
502
+ }
503
+
504
+ // Priority 3: Use the icon from the node definition if available
505
+ if (definition?.icon && typeof definition.icon === 'string') {
506
+ return renderIcon(definition.icon);
507
+ }
508
+
509
+ // Fallback logic based on node type
510
+ if (type?.includes('createMap')) return '🗺️';
511
+ if (type?.includes('addLocations')) return '🌍';
512
+ if (type?.includes('showNearbyPlaces')) return '🔍';
513
+
514
+ return '📍';
515
+ };
516
+
517
+ // Get the node color from definition or use default
518
+ const nodeColor = definition?.defaults?.color || '#1A73E8';
519
+
520
+ return (
521
+ <div
522
+ style={{
523
+ position: 'relative',
524
+ display: 'flex',
525
+ flexDirection: 'column',
526
+ alignItems: 'center',
527
+ fontFamily: 'system-ui, -apple-system, sans-serif',
528
+ fontSize: '11px',
529
+ cursor: 'pointer',
530
+ }}
531
+ >
532
+ {/* Main Square Node */}
533
+ <div
534
+ style={{
535
+ position: 'relative',
536
+ width: theme.nodeSize.square,
537
+ height: theme.nodeSize.square,
538
+ borderRadius: theme.borderRadius.lg,
539
+ background: theme.isDarkMode
540
+ ? `linear-gradient(135deg, ${nodeColor}25 0%, ${theme.colors.background} 100%)`
541
+ : `linear-gradient(145deg, #ffffff 0%, ${nodeColor}08 100%)`,
542
+ border: `2px solid ${isExecuting
543
+ ? (theme.isDarkMode ? theme.dracula.cyan : '#2563eb')
544
+ : selected
545
+ ? theme.colors.focus
546
+ : theme.isDarkMode ? nodeColor + '80' : `${nodeColor}40`}`,
547
+ display: 'flex',
548
+ alignItems: 'center',
549
+ justifyContent: 'center',
550
+ color: theme.colors.text,
551
+ fontSize: theme.nodeSize.squareIcon,
552
+ fontWeight: '600',
553
+ transition: 'all 0.2s ease',
554
+ boxShadow: isExecuting
555
+ ? theme.isDarkMode
556
+ ? `0 4px 12px ${theme.dracula.cyan}66, 0 0 0 3px ${theme.dracula.cyan}4D`
557
+ : `0 0 0 3px rgba(37, 99, 235, 0.5), 0 4px 16px rgba(37, 99, 235, 0.35)`
558
+ : selected
559
+ ? `0 4px 12px ${theme.colors.focusRing}, 0 0 0 1px ${theme.colors.focusRing}`
560
+ : theme.isDarkMode
561
+ ? `0 2px 8px ${nodeColor}40`
562
+ : `0 2px 8px ${nodeColor}20, 0 4px 12px rgba(0,0,0,0.06)`,
563
+ animation: isExecuting ? 'pulse 1.5s ease-in-out infinite' : 'none',
564
+ opacity: isDisabled ? 0.5 : 1,
565
+ }}
566
+ >
567
+ {/* Disabled Overlay */}
568
+ {isDisabled && (
569
+ <div style={{
570
+ position: 'absolute',
571
+ top: 0,
572
+ left: 0,
573
+ right: 0,
574
+ bottom: 0,
575
+ backgroundColor: 'rgba(128, 128, 128, 0.4)',
576
+ borderRadius: 'inherit',
577
+ zIndex: 35,
578
+ display: 'flex',
579
+ alignItems: 'center',
580
+ justifyContent: 'center',
581
+ pointerEvents: 'none',
582
+ }}>
583
+ <span style={{ fontSize: '20px', opacity: 0.8, color: theme.colors.textSecondary }}>||</span>
584
+ </div>
585
+ )}
586
+
587
+
588
+ {/* Service Icon */}
589
+ {getServiceIcon()}
590
+
591
+ {/* Parameters Button */}
592
+ <button
593
+ onClick={handleParametersClick}
594
+ style={{
595
+ position: 'absolute',
596
+ top: '-8px',
597
+ right: '-8px',
598
+ width: theme.nodeSize.paramButton,
599
+ height: theme.nodeSize.paramButton,
600
+ borderRadius: theme.borderRadius.sm,
601
+ backgroundColor: theme.isDarkMode ? theme.colors.backgroundAlt : '#ffffff',
602
+ border: `1px solid ${theme.isDarkMode ? theme.colors.border : '#d1d5db'}`,
603
+ cursor: 'pointer',
604
+ display: 'flex',
605
+ alignItems: 'center',
606
+ justifyContent: 'center',
607
+ fontSize: theme.fontSize.xs,
608
+ color: theme.colors.textSecondary,
609
+ fontWeight: '400',
610
+ transition: theme.transitions.fast,
611
+ zIndex: 30,
612
+ boxShadow: theme.isDarkMode
613
+ ? `0 1px 3px ${theme.colors.shadow}`
614
+ : '0 1px 4px rgba(0,0,0,0.1)'
615
+ }}
616
+ title="Edit Service Parameters"
617
+ >
618
+ ⚙️
619
+ </button>
620
+
621
+ {/* Configuration/Execution Status Indicator */}
622
+ <div
623
+ style={{
624
+ position: 'absolute',
625
+ top: '-4px',
626
+ left: '-4px',
627
+ width: theme.nodeSize.statusIndicator,
628
+ height: theme.nodeSize.statusIndicator,
629
+ borderRadius: '50%',
630
+ backgroundColor: getStatusIndicatorColor(),
631
+ border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
632
+ boxShadow: isExecuting
633
+ ? theme.isDarkMode
634
+ ? `0 0 6px ${theme.dracula.cyan}80`
635
+ : '0 0 4px rgba(37, 99, 235, 0.5)'
636
+ : theme.isDarkMode
637
+ ? `0 1px 2px ${theme.colors.shadow}`
638
+ : '0 1px 3px rgba(0,0,0,0.15)',
639
+ zIndex: 30,
640
+ animation: isExecuting ? 'pulse 1s ease-in-out infinite' : 'none',
641
+ }}
642
+ title={getStatusTitle()}
643
+ />
644
+
645
+ {/* Square Input Handle */}
646
+ <Handle
647
+ id="input-main"
648
+ type="target"
649
+ position={Position.Left}
650
+ isConnectable={isConnectable}
651
+ style={{
652
+ position: 'absolute',
653
+ left: '-6px',
654
+ top: '50%',
655
+ transform: 'translateY(-50%)',
656
+ width: theme.nodeSize.handle,
657
+ height: theme.nodeSize.handle,
658
+ backgroundColor: theme.isDarkMode ? theme.colors.background : '#ffffff',
659
+ border: `2px solid ${theme.isDarkMode ? theme.colors.textSecondary : '#6b7280'}`,
660
+ borderRadius: '50%',
661
+ zIndex: 20
662
+ }}
663
+ title="Service Input"
664
+ />
665
+
666
+ {/* Square Output Handle - hidden for input-only nodes like console */}
667
+ {!NO_OUTPUT_NODE_TYPES.includes(type || '') && (
668
+ <Handle
669
+ id="output-main"
670
+ type="source"
671
+ position={Position.Right}
672
+ isConnectable={isConnectable}
673
+ style={{
674
+ position: 'absolute',
675
+ right: '-6px',
676
+ top: '50%',
677
+ transform: 'translateY(-50%)',
678
+ width: theme.nodeSize.handle,
679
+ height: theme.nodeSize.handle,
680
+ backgroundColor: isConfigured ? nodeColor : theme.colors.textSecondary,
681
+ border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
682
+ borderRadius: '50%',
683
+ zIndex: 20
684
+ }}
685
+ title="Service Output"
686
+ />
687
+ )}
688
+
689
+ {/* Top Tool Output Handle - for nodes that can connect to AI Agent/Chat Agent tool handle */}
690
+ {isToolCapable && (
691
+ <Handle
692
+ id="output-tool"
693
+ type="source"
694
+ position={Position.Top}
695
+ isConnectable={isConnectable}
696
+ style={{
697
+ position: 'absolute',
698
+ top: '-6px',
699
+ left: '50%',
700
+ transform: 'translateX(-50%)',
701
+ width: theme.nodeSize.handle,
702
+ height: theme.nodeSize.handle,
703
+ backgroundColor: ANDROID_TOOL_CAPABLE_NODES.includes(type || '') ? '#3DDC84' : nodeColor, // Android green for Android nodes, node color for others
704
+ border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
705
+ borderRadius: '50%',
706
+ zIndex: 20
707
+ }}
708
+ title={ANDROID_TOOL_CAPABLE_NODES.includes(type || '') ? 'Connect to Android Toolkit' : 'Connect to AI Agent/Chat Agent tool handle'}
709
+ />
710
+ )}
711
+
712
+ {/* Output Data Indicator - shows when node has execution output */}
713
+ {executionStatus === 'success' && nodeStatus?.data && (
714
+ <div
715
+ style={{
716
+ position: 'absolute',
717
+ bottom: '-4px',
718
+ right: '-4px',
719
+ width: theme.nodeSize.outputBadge,
720
+ height: theme.nodeSize.outputBadge,
721
+ borderRadius: theme.borderRadius.sm,
722
+ backgroundColor: theme.dracula.green,
723
+ border: `1px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
724
+ display: 'flex',
725
+ alignItems: 'center',
726
+ justifyContent: 'center',
727
+ fontSize: theme.fontSize.xs,
728
+ color: 'white',
729
+ fontWeight: 'bold',
730
+ zIndex: 30,
731
+ boxShadow: theme.isDarkMode
732
+ ? '0 1px 3px rgba(0,0,0,0.2)'
733
+ : '0 1px 3px rgba(0,0,0,0.15)',
734
+ }}
735
+ title="Output data available - click node to view"
736
+ >
737
+ <span style={{ lineHeight: 1 }}>D</span>
738
+ </div>
739
+ )}
740
+ </div>
741
+
742
+ {/* Service Name Below Square */}
743
+ {isRenaming ? (
744
+ <input
745
+ ref={inputRef}
746
+ type="text"
747
+ value={editLabel}
748
+ onChange={(e) => setEditLabel(e.target.value)}
749
+ onKeyDown={(e) => {
750
+ if (e.key === 'Enter') {
751
+ handleSaveRename();
752
+ } else if (e.key === 'Escape') {
753
+ handleCancelRename();
754
+ }
755
+ e.stopPropagation();
756
+ }}
757
+ onBlur={handleSaveRename}
758
+ onClick={(e) => e.stopPropagation()}
759
+ style={{
760
+ marginTop: theme.spacing.sm,
761
+ width: '100%',
762
+ maxWidth: '120px',
763
+ padding: '2px 4px',
764
+ fontSize: theme.fontSize.sm,
765
+ fontWeight: theme.fontWeight.medium,
766
+ color: theme.colors.text,
767
+ backgroundColor: theme.colors.backgroundElevated,
768
+ border: `1px solid ${theme.dracula.purple}`,
769
+ borderRadius: theme.borderRadius.sm,
770
+ outline: 'none',
771
+ textAlign: 'center',
772
+ }}
773
+ />
774
+ ) : (
775
+ <div
776
+ onDoubleClick={handleLabelDoubleClick}
777
+ style={{
778
+ marginTop: theme.spacing.sm,
779
+ fontSize: theme.fontSize.sm,
780
+ fontWeight: theme.fontWeight.medium,
781
+ color: theme.colors.text,
782
+ lineHeight: '1.2',
783
+ textAlign: 'center',
784
+ maxWidth: '120px',
785
+ cursor: 'text',
786
+ }}
787
+ title="Double-click to rename"
788
+ >
789
+ {data?.label || definition?.displayName}
790
+ </div>
791
+ )}
792
+
793
+ </div>
794
+ );
795
+ };
796
+
797
+ export default SquareNode;