machinaos 0.0.1 → 0.0.7

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