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,736 +1,736 @@
1
- import React, { useState } from 'react';
2
- import { useTheme } from '../../contexts/ThemeContext';
3
- import { useAuth } from '../../contexts/AuthContext';
4
- import { useAppTheme } from '../../hooks/useAppTheme';
5
-
6
- interface TopToolbarProps {
7
- workflowName: string;
8
- onWorkflowNameChange: (name: string) => void;
9
- onSave: () => void;
10
- onNew: () => void;
11
- onOpen: () => void;
12
- onRun: () => void;
13
- isRunning?: boolean;
14
- onDeploy: () => void;
15
- onCancelDeployment: () => void;
16
- isDeploying?: boolean;
17
- hasUnsavedChanges: boolean;
18
- sidebarVisible: boolean;
19
- onToggleSidebar: () => void;
20
- componentPaletteVisible: boolean;
21
- onToggleComponentPalette: () => void;
22
- proMode: boolean;
23
- onToggleProMode: () => void;
24
- onOpenSettings: () => void;
25
- onOpenCredentials: () => void;
26
- onExportJSON: () => void;
27
- onExportFile: () => void;
28
- onImportJSON: () => void;
29
- }
30
-
31
- const TopToolbar: React.FC<TopToolbarProps> = ({
32
- workflowName,
33
- onWorkflowNameChange,
34
- onSave,
35
- onNew,
36
- onOpen,
37
- onRun,
38
- isRunning = false,
39
- onDeploy,
40
- onCancelDeployment,
41
- isDeploying = false,
42
- hasUnsavedChanges,
43
- sidebarVisible,
44
- onToggleSidebar,
45
- componentPaletteVisible,
46
- onToggleComponentPalette,
47
- proMode,
48
- onToggleProMode,
49
- onOpenSettings,
50
- onOpenCredentials,
51
- onExportJSON,
52
- onExportFile,
53
- onImportJSON,
54
- }) => {
55
- const [isEditing, setIsEditing] = useState(false);
56
- const [tempName, setTempName] = useState(workflowName);
57
- const [fileMenuOpen, setFileMenuOpen] = useState(false);
58
- const { isDarkMode, toggleTheme } = useTheme();
59
- const { user, logout } = useAuth();
60
- const theme = useAppTheme();
61
-
62
- const handleNameClick = () => {
63
- setTempName(workflowName);
64
- setIsEditing(true);
65
- };
66
-
67
- const handleNameSubmit = () => {
68
- onWorkflowNameChange(tempName.trim() || 'Untitled Workflow');
69
- setIsEditing(false);
70
- };
71
-
72
- const handleNameKeyDown = (e: React.KeyboardEvent) => {
73
- if (e.key === 'Enter') {
74
- handleNameSubmit();
75
- } else if (e.key === 'Escape') {
76
- setTempName(workflowName);
77
- setIsEditing(false);
78
- }
79
- };
80
-
81
- // Icon-only button style - subtle with colored icon
82
- const iconButtonStyle: React.CSSProperties = {
83
- width: theme.buttonSize.lg,
84
- height: theme.buttonSize.lg,
85
- display: 'flex',
86
- alignItems: 'center',
87
- justifyContent: 'center',
88
- backgroundColor: 'transparent',
89
- color: theme.colors.textSecondary,
90
- border: 'none',
91
- borderRadius: theme.borderRadius.md,
92
- cursor: 'pointer',
93
- transition: `all ${theme.transitions.fast}`,
94
- };
95
-
96
- // Text button style - cleaner with subtle border and Dracula accent
97
- const textButtonStyle: React.CSSProperties = {
98
- height: theme.buttonSize.md,
99
- padding: `0 ${theme.spacing.md}`,
100
- display: 'flex',
101
- alignItems: 'center',
102
- gap: theme.spacing.sm,
103
- backgroundColor: 'transparent',
104
- color: theme.dracula.green,
105
- border: `1px solid ${theme.dracula.green}40`,
106
- borderRadius: theme.borderRadius.sm,
107
- fontSize: theme.fontSize.base,
108
- fontWeight: theme.fontWeight.semibold,
109
- cursor: 'pointer',
110
- transition: `all ${theme.transitions.fast}`,
111
- fontFamily: 'system-ui, sans-serif',
112
- };
113
-
114
- // Action button style - Dracula theme for visibility
115
- const actionButtonStyle = (color: string, isDisabled = false): React.CSSProperties => ({
116
- height: theme.buttonSize.md,
117
- padding: `0 ${theme.spacing.lg}`,
118
- display: 'flex',
119
- alignItems: 'center',
120
- gap: theme.spacing.sm,
121
- backgroundColor: isDisabled ? `${theme.colors.primary}15` : `${color}25`,
122
- color: isDisabled ? theme.colors.primary : color,
123
- border: `1px solid ${isDisabled ? `${theme.colors.primary}40` : `${color}60`}`,
124
- borderRadius: theme.borderRadius.sm,
125
- fontSize: theme.fontSize.sm,
126
- fontWeight: theme.fontWeight.semibold,
127
- cursor: isDisabled ? 'not-allowed' : 'pointer',
128
- transition: `all ${theme.transitions.fast}`,
129
- fontFamily: 'system-ui, sans-serif',
130
- letterSpacing: '0.3px',
131
- });
132
-
133
- return (
134
- <div
135
- style={{
136
- height: theme.layout.toolbarHeight,
137
- backgroundColor: theme.colors.backgroundPanel,
138
- borderBottom: `1px solid ${theme.colors.border}`,
139
- display: 'flex',
140
- alignItems: 'center',
141
- justifyContent: 'space-between',
142
- padding: `0 ${theme.spacing.md}`,
143
- gap: theme.spacing.md,
144
- }}
145
- >
146
- {/* Left Section */}
147
- <div style={{ display: 'flex', alignItems: 'center', gap: theme.spacing.sm }}>
148
- {/* Sidebar Toggle */}
149
- <button
150
- onClick={onToggleSidebar}
151
- style={{
152
- ...iconButtonStyle,
153
- backgroundColor: sidebarVisible ? `${theme.colors.actionSidebar}30` : `${theme.colors.actionSidebar}15`,
154
- color: theme.colors.actionSidebar,
155
- border: `1px solid ${sidebarVisible ? `${theme.colors.actionSidebar}60` : `${theme.colors.actionSidebar}40`}`,
156
- }}
157
- onMouseEnter={(e) => {
158
- e.currentTarget.style.backgroundColor = `${theme.colors.actionSidebar}40`;
159
- e.currentTarget.style.borderColor = `${theme.colors.actionSidebar}80`;
160
- }}
161
- onMouseLeave={(e) => {
162
- e.currentTarget.style.backgroundColor = sidebarVisible ? `${theme.colors.actionSidebar}30` : `${theme.colors.actionSidebar}15`;
163
- e.currentTarget.style.borderColor = sidebarVisible ? `${theme.colors.actionSidebar}60` : `${theme.colors.actionSidebar}40`;
164
- }}
165
- title={sidebarVisible ? 'Hide sidebar' : 'Show sidebar'}
166
- >
167
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
168
- {sidebarVisible ? (
169
- <>
170
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
171
- <line x1="9" y1="3" x2="9" y2="21"/>
172
- </>
173
- ) : (
174
- <>
175
- <line x1="3" y1="12" x2="21" y2="12"/>
176
- <line x1="3" y1="6" x2="21" y2="6"/>
177
- <line x1="3" y1="18" x2="21" y2="18"/>
178
- </>
179
- )}
180
- </svg>
181
- </button>
182
-
183
- {/* Divider */}
184
- <div style={{ width: '1px', height: theme.spacing.xl, backgroundColor: theme.colors.border, margin: `0 ${theme.spacing.xs}` }} />
185
-
186
- {/* File Menu */}
187
- <div style={{ position: 'relative', marginLeft: sidebarVisible ? `calc(${theme.layout.workflowSidebarWidth} - ${theme.spacing.xxl} - ${theme.spacing.xxl})` : 0 }}>
188
- <button
189
- onClick={() => setFileMenuOpen(!fileMenuOpen)}
190
- style={{
191
- ...textButtonStyle,
192
- backgroundColor: fileMenuOpen ? theme.colors.backgroundHover : 'transparent',
193
- }}
194
- onMouseEnter={(e) => {
195
- e.currentTarget.style.backgroundColor = theme.colors.backgroundHover;
196
- }}
197
- onMouseLeave={(e) => {
198
- if (!fileMenuOpen) {
199
- e.currentTarget.style.backgroundColor = 'transparent';
200
- }
201
- }}
202
- >
203
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
204
- <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
205
- <polyline points="14 2 14 8 20 8"/>
206
- </svg>
207
- File
208
- <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
209
- <path d="M6 9l6 6 6-6"/>
210
- </svg>
211
- </button>
212
-
213
- {fileMenuOpen && (
214
- <>
215
- <div
216
- style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, zIndex: 999 }}
217
- onClick={() => setFileMenuOpen(false)}
218
- />
219
- <div
220
- style={{
221
- position: 'absolute',
222
- top: 'calc(100% + 4px)',
223
- left: 0,
224
- background: `linear-gradient(135deg, ${theme.colors.backgroundPanel} 0%, ${theme.colors.background} 100%)`,
225
- border: `1px solid ${theme.colors.border}`,
226
- borderRadius: theme.borderRadius.lg,
227
- boxShadow: `0 8px 24px ${theme.colors.shadow}, 0 2px 8px ${theme.colors.shadowLight}`,
228
- minWidth: '200px',
229
- zIndex: 1000,
230
- overflow: 'hidden',
231
- padding: theme.spacing.sm,
232
- backdropFilter: 'blur(8px)',
233
- }}
234
- >
235
- {/* Menu Header */}
236
- <div
237
- style={{
238
- padding: `${theme.spacing.sm} ${theme.spacing.md}`,
239
- marginBottom: theme.spacing.xs,
240
- borderBottom: `1px solid ${theme.colors.border}`,
241
- }}
242
- >
243
- <span
244
- style={{
245
- fontSize: theme.fontSize.xs,
246
- fontWeight: theme.fontWeight.semibold,
247
- color: theme.colors.textMuted,
248
- textTransform: 'uppercase',
249
- letterSpacing: '0.5px',
250
- fontFamily: 'system-ui, sans-serif',
251
- }}
252
- >
253
- File Operations
254
- </span>
255
- </div>
256
- {[
257
- { label: 'New Workflow', icon: 'M12 5v14M5 12h14', action: onNew, color: theme.dracula.green },
258
- { label: 'Open', icon: 'M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z', action: onOpen, color: theme.accent.blue },
259
- { divider: true },
260
- { label: 'Export', icon: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12', action: onExportFile, color: theme.accent.cyan },
261
- { label: 'Import', icon: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3', action: onImportJSON, color: theme.accent.cyan },
262
- { divider: true },
263
- { label: 'Copy as JSON', icon: 'M8 17.929H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v0M18 9h-8a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V11a2 2 0 0 0-2-2z', action: onExportJSON, color: theme.dracula.purple },
264
- ].map((item, index) =>
265
- item.divider ? (
266
- <div key={index} style={{ height: '1px', backgroundColor: theme.colors.border, margin: `${theme.spacing.xs} 0` }} />
267
- ) : (
268
- <button
269
- key={index}
270
- onClick={() => { item.action?.(); setFileMenuOpen(false); }}
271
- style={{
272
- width: '100%',
273
- padding: `${theme.spacing.sm} ${theme.spacing.md}`,
274
- backgroundColor: 'transparent',
275
- border: 'none',
276
- borderRadius: theme.borderRadius.md,
277
- color: theme.colors.textSecondary,
278
- fontSize: theme.fontSize.sm,
279
- cursor: 'pointer',
280
- display: 'flex',
281
- alignItems: 'center',
282
- gap: theme.spacing.md,
283
- textAlign: 'left',
284
- fontFamily: 'system-ui, sans-serif',
285
- transition: `all ${theme.transitions.fast}`,
286
- }}
287
- onMouseEnter={(e) => {
288
- e.currentTarget.style.backgroundColor = `${item.color}15`;
289
- e.currentTarget.style.color = item.color || theme.colors.text;
290
- }}
291
- onMouseLeave={(e) => {
292
- e.currentTarget.style.backgroundColor = 'transparent';
293
- e.currentTarget.style.color = theme.colors.textSecondary;
294
- }}
295
- >
296
- <div
297
- style={{
298
- width: theme.iconSize.lg,
299
- height: theme.iconSize.lg,
300
- borderRadius: theme.borderRadius.sm,
301
- backgroundColor: `${item.color}15`,
302
- display: 'flex',
303
- alignItems: 'center',
304
- justifyContent: 'center',
305
- flexShrink: 0,
306
- }}
307
- >
308
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={item.color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
309
- <path d={item.icon} />
310
- </svg>
311
- </div>
312
- <span style={{ fontWeight: theme.fontWeight.medium }}>{item.label}</span>
313
- </button>
314
- )
315
- )}
316
- </div>
317
- </>
318
- )}
319
- </div>
320
- </div>
321
-
322
- {/* Center Section - Workflow Name */}
323
- <div style={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
324
- {isEditing ? (
325
- <input
326
- type="text"
327
- value={tempName}
328
- onChange={(e) => setTempName(e.target.value)}
329
- onBlur={handleNameSubmit}
330
- onKeyDown={handleNameKeyDown}
331
- autoFocus
332
- style={{
333
- fontSize: '14px',
334
- fontWeight: 500,
335
- color: theme.colors.textSecondary,
336
- backgroundColor: theme.colors.backgroundAlt,
337
- border: `1px solid ${theme.accent.cyan}`,
338
- borderRadius: theme.borderRadius.sm,
339
- padding: '6px 12px',
340
- outline: 'none',
341
- fontFamily: 'system-ui, sans-serif',
342
- minWidth: '200px',
343
- textAlign: 'center',
344
- }}
345
- />
346
- ) : (
347
- <button
348
- onClick={handleNameClick}
349
- style={{
350
- display: 'flex',
351
- alignItems: 'center',
352
- gap: '6px',
353
- padding: '6px 12px',
354
- backgroundColor: 'transparent',
355
- border: 'none',
356
- borderRadius: theme.borderRadius.sm,
357
- cursor: 'pointer',
358
- transition: `all ${theme.transitions.fast}`,
359
- }}
360
- onMouseEnter={(e) => {
361
- e.currentTarget.style.backgroundColor = theme.colors.backgroundHover;
362
- }}
363
- onMouseLeave={(e) => {
364
- e.currentTarget.style.backgroundColor = 'transparent';
365
- }}
366
- title="Click to rename"
367
- >
368
- <span style={{
369
- fontSize: '14px',
370
- fontWeight: 500,
371
- color: theme.dracula.purple,
372
- fontFamily: 'system-ui, sans-serif',
373
- }}>
374
- {workflowName}
375
- </span>
376
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke={theme.colors.textMuted} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
377
- <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
378
- <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
379
- </svg>
380
- </button>
381
- )}
382
- </div>
383
-
384
- {/* Right Section */}
385
- <div style={{ display: 'flex', alignItems: 'center', gap: theme.spacing.sm }}>
386
- {/* Mode Toggle - Segmented control style */}
387
- <div style={{ display: 'flex', alignItems: 'center', gap: theme.spacing.sm }}>
388
- <span style={{
389
- fontSize: theme.fontSize.sm,
390
- color: theme.accent.cyan,
391
- fontWeight: theme.fontWeight.semibold,
392
- fontFamily: 'system-ui, sans-serif',
393
- }}>
394
- Mode:
395
- </span>
396
- <div
397
- style={{
398
- display: 'flex',
399
- alignItems: 'center',
400
- backgroundColor: theme.colors.backgroundAlt,
401
- borderRadius: theme.borderRadius.md,
402
- padding: '2px',
403
- border: `1px solid ${theme.colors.border}`,
404
- }}
405
- title={proMode ? 'Dev mode: All components visible' : 'Normal mode: Only AI components'}
406
- >
407
- <button
408
- onClick={() => !proMode ? undefined : onToggleProMode()}
409
- style={{
410
- padding: '4px 10px',
411
- display: 'flex',
412
- alignItems: 'center',
413
- gap: '4px',
414
- backgroundColor: !proMode ? `${theme.dracula.green}25` : 'transparent',
415
- color: !proMode ? theme.dracula.green : theme.dracula.orange,
416
- border: !proMode ? `1px solid ${theme.dracula.green}60` : '1px solid transparent',
417
- borderRadius: theme.borderRadius.sm,
418
- fontSize: theme.fontSize.xs,
419
- fontWeight: theme.fontWeight.semibold,
420
- cursor: proMode ? 'pointer' : 'default',
421
- transition: `all ${theme.transitions.fast}`,
422
- fontFamily: 'system-ui, sans-serif',
423
- }}
424
- >
425
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
426
- <circle cx="12" cy="12" r="10"/>
427
- <path d="M12 6v6l4 2"/>
428
- </svg>
429
- Normal
430
- </button>
431
- <button
432
- onClick={() => proMode ? undefined : onToggleProMode()}
433
- style={{
434
- padding: '4px 10px',
435
- display: 'flex',
436
- alignItems: 'center',
437
- gap: '4px',
438
- backgroundColor: proMode ? `${theme.dracula.purple}25` : 'transparent',
439
- color: proMode ? theme.dracula.purple : theme.dracula.orange,
440
- border: proMode ? `1px solid ${theme.dracula.purple}60` : '1px solid transparent',
441
- borderRadius: theme.borderRadius.sm,
442
- fontSize: theme.fontSize.xs,
443
- fontWeight: theme.fontWeight.semibold,
444
- cursor: !proMode ? 'pointer' : 'default',
445
- transition: `all ${theme.transitions.fast}`,
446
- fontFamily: 'system-ui, sans-serif',
447
- }}
448
- >
449
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
450
- <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
451
- </svg>
452
- Dev
453
- </button>
454
- </div>
455
- </div>
456
-
457
- {/* Divider */}
458
- <div style={{ width: '1px', height: theme.spacing.xl, backgroundColor: theme.colors.border, margin: `0 ${theme.spacing.xs}` }} />
459
-
460
- {/* Settings Button */}
461
- <button
462
- onClick={onOpenSettings}
463
- style={{
464
- ...iconButtonStyle,
465
- backgroundColor: `${theme.colors.actionSettings}15`,
466
- color: theme.colors.actionSettings,
467
- border: `1px solid ${theme.colors.actionSettings}40`,
468
- }}
469
- onMouseEnter={(e) => {
470
- e.currentTarget.style.backgroundColor = `${theme.colors.actionSettings}30`;
471
- e.currentTarget.style.borderColor = `${theme.colors.actionSettings}60`;
472
- }}
473
- onMouseLeave={(e) => {
474
- e.currentTarget.style.backgroundColor = `${theme.colors.actionSettings}15`;
475
- e.currentTarget.style.borderColor = `${theme.colors.actionSettings}40`;
476
- }}
477
- title="Settings"
478
- >
479
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
480
- <circle cx="12" cy="12" r="3"/>
481
- <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
482
- </svg>
483
- </button>
484
-
485
- {/* Credentials Button */}
486
- <button
487
- onClick={onOpenCredentials}
488
- style={{
489
- ...iconButtonStyle,
490
- backgroundColor: `${theme.colors.actionCredentials}15`,
491
- color: theme.colors.actionCredentials,
492
- border: `1px solid ${theme.colors.actionCredentials}40`,
493
- }}
494
- onMouseEnter={(e) => {
495
- e.currentTarget.style.backgroundColor = `${theme.colors.actionCredentials}30`;
496
- e.currentTarget.style.borderColor = `${theme.colors.actionCredentials}60`;
497
- }}
498
- onMouseLeave={(e) => {
499
- e.currentTarget.style.backgroundColor = `${theme.colors.actionCredentials}15`;
500
- e.currentTarget.style.borderColor = `${theme.colors.actionCredentials}40`;
501
- }}
502
- title="API Credentials"
503
- >
504
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
505
- <path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/>
506
- </svg>
507
- </button>
508
-
509
- {/* Theme Toggle Button */}
510
- <button
511
- onClick={toggleTheme}
512
- style={{
513
- ...iconButtonStyle,
514
- backgroundColor: `${theme.colors.actionTheme}15`,
515
- color: theme.colors.actionTheme,
516
- border: `1px solid ${theme.colors.actionTheme}40`,
517
- }}
518
- onMouseEnter={(e) => {
519
- e.currentTarget.style.backgroundColor = `${theme.colors.actionTheme}30`;
520
- e.currentTarget.style.borderColor = `${theme.colors.actionTheme}60`;
521
- }}
522
- onMouseLeave={(e) => {
523
- e.currentTarget.style.backgroundColor = `${theme.colors.actionTheme}15`;
524
- e.currentTarget.style.borderColor = `${theme.colors.actionTheme}40`;
525
- }}
526
- title={isDarkMode ? 'Switch to Light mode' : 'Switch to Dark mode'}
527
- >
528
- {isDarkMode ? (
529
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
530
- <circle cx="12" cy="12" r="5"/>
531
- <line x1="12" y1="1" x2="12" y2="3"/>
532
- <line x1="12" y1="21" x2="12" y2="23"/>
533
- <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
534
- <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
535
- <line x1="1" y1="12" x2="3" y2="12"/>
536
- <line x1="21" y1="12" x2="23" y2="12"/>
537
- <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
538
- <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
539
- </svg>
540
- ) : (
541
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
542
- <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
543
- </svg>
544
- )}
545
- </button>
546
-
547
- {/* User & Logout */}
548
- {user && (
549
- <button
550
- onClick={logout}
551
- style={{
552
- ...iconButtonStyle,
553
- backgroundColor: `${theme.dracula.pink}15`,
554
- color: theme.dracula.pink,
555
- border: `1px solid ${theme.dracula.pink}40`,
556
- }}
557
- onMouseEnter={(e) => {
558
- e.currentTarget.style.backgroundColor = `${theme.dracula.pink}30`;
559
- e.currentTarget.style.borderColor = `${theme.dracula.pink}60`;
560
- }}
561
- onMouseLeave={(e) => {
562
- e.currentTarget.style.backgroundColor = `${theme.dracula.pink}15`;
563
- e.currentTarget.style.borderColor = `${theme.dracula.pink}40`;
564
- }}
565
- title={`Logout ${user.display_name}`}
566
- >
567
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
568
- <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
569
- <polyline points="16 17 21 12 16 7"/>
570
- <line x1="21" y1="12" x2="9" y2="12"/>
571
- </svg>
572
- </button>
573
- )}
574
-
575
- {/* Divider */}
576
- <div style={{ width: '1px', height: theme.spacing.xl, backgroundColor: theme.colors.border, margin: `0 ${theme.spacing.sm}` }} />
577
-
578
- {/* Action Buttons */}
579
- <button
580
- onClick={onRun}
581
- disabled={isRunning || isDeploying}
582
- style={actionButtonStyle(theme.colors.actionRun, isRunning || isDeploying)}
583
- onMouseEnter={(e) => {
584
- if (!isRunning && !isDeploying) {
585
- e.currentTarget.style.backgroundColor = `${theme.colors.actionRun}40`;
586
- }
587
- }}
588
- onMouseLeave={(e) => {
589
- if (!isRunning && !isDeploying) {
590
- e.currentTarget.style.backgroundColor = `${theme.colors.actionRun}25`;
591
- }
592
- }}
593
- title={isRunning ? 'Running...' : isDeploying ? 'Cannot run while deploying' : 'Run workflow once'}
594
- >
595
- <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
596
- <path d="M8 5v14l11-7z"/>
597
- </svg>
598
- {isRunning ? 'Running' : 'Run'}
599
- </button>
600
-
601
- {!isDeploying ? (
602
- <button
603
- onClick={onDeploy}
604
- disabled={isRunning}
605
- style={actionButtonStyle(theme.colors.actionDeploy, isRunning)}
606
- onMouseEnter={(e) => {
607
- if (!isRunning) {
608
- e.currentTarget.style.backgroundColor = `${theme.colors.actionDeploy}40`;
609
- }
610
- }}
611
- onMouseLeave={(e) => {
612
- if (!isRunning) {
613
- e.currentTarget.style.backgroundColor = `${theme.colors.actionDeploy}25`;
614
- }
615
- }}
616
- title="Deploy workflow continuously"
617
- >
618
- <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
619
- <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
620
- </svg>
621
- Deploy
622
- </button>
623
- ) : (
624
- <button
625
- onClick={onCancelDeployment}
626
- style={actionButtonStyle(theme.colors.actionStop, false)}
627
- onMouseEnter={(e) => {
628
- e.currentTarget.style.backgroundColor = `${theme.colors.actionStop}40`;
629
- }}
630
- onMouseLeave={(e) => {
631
- e.currentTarget.style.backgroundColor = `${theme.colors.actionStop}25`;
632
- }}
633
- title="Stop deployment"
634
- >
635
- <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
636
- <rect x="6" y="6" width="12" height="12" rx="1"/>
637
- </svg>
638
- Stop
639
- </button>
640
- )}
641
-
642
- <button
643
- onClick={() => {
644
- if (typeof onSave === 'function') {
645
- onSave();
646
- }
647
- }}
648
- style={actionButtonStyle(theme.colors.actionSave, !hasUnsavedChanges)}
649
- onMouseEnter={(e) => {
650
- if (hasUnsavedChanges) {
651
- e.currentTarget.style.backgroundColor = `${theme.colors.actionSave}40`;
652
- }
653
- }}
654
- onMouseLeave={(e) => {
655
- if (hasUnsavedChanges) {
656
- e.currentTarget.style.backgroundColor = `${theme.colors.actionSave}25`;
657
- }
658
- }}
659
- title={hasUnsavedChanges ? 'Save changes' : 'No changes to save'}
660
- >
661
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
662
- <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
663
- <polyline points="17 21 17 13 7 13 7 21"/>
664
- <polyline points="7 3 7 8 15 8"/>
665
- </svg>
666
- Save
667
- </button>
668
-
669
- {/* Status Indicator */}
670
- <div
671
- style={{
672
- display: 'flex',
673
- alignItems: 'center',
674
- gap: theme.spacing.sm,
675
- padding: `${theme.spacing.xs} ${theme.spacing.md}`,
676
- backgroundColor: 'transparent',
677
- borderRadius: theme.borderRadius.sm,
678
- fontSize: theme.fontSize.xs,
679
- fontWeight: theme.fontWeight.medium,
680
- color: hasUnsavedChanges ? theme.colors.statusModified : theme.colors.statusSaved,
681
- fontFamily: 'system-ui, sans-serif',
682
- }}
683
- >
684
- <div style={{
685
- width: theme.spacing.sm,
686
- height: theme.spacing.sm,
687
- borderRadius: '50%',
688
- backgroundColor: hasUnsavedChanges ? theme.colors.statusModified : theme.colors.statusSaved,
689
- }} />
690
- {hasUnsavedChanges ? 'Modified' : 'Saved'}
691
- </div>
692
-
693
- {/* Divider */}
694
- <div style={{ width: '1px', height: theme.spacing.xl, backgroundColor: theme.colors.border, margin: `0 ${theme.spacing.sm}` }} />
695
-
696
- {/* Component Palette Toggle */}
697
- <button
698
- onClick={onToggleComponentPalette}
699
- style={{
700
- ...iconButtonStyle,
701
- backgroundColor: componentPaletteVisible ? `${theme.colors.actionPalette}30` : `${theme.colors.actionPalette}15`,
702
- color: theme.colors.actionPalette,
703
- border: `1px solid ${componentPaletteVisible ? `${theme.colors.actionPalette}60` : `${theme.colors.actionPalette}40`}`,
704
- }}
705
- onMouseEnter={(e) => {
706
- e.currentTarget.style.backgroundColor = `${theme.colors.actionPalette}40`;
707
- e.currentTarget.style.borderColor = `${theme.colors.actionPalette}80`;
708
- }}
709
- onMouseLeave={(e) => {
710
- e.currentTarget.style.backgroundColor = componentPaletteVisible ? `${theme.colors.actionPalette}30` : `${theme.colors.actionPalette}15`;
711
- e.currentTarget.style.borderColor = componentPaletteVisible ? `${theme.colors.actionPalette}60` : `${theme.colors.actionPalette}40`;
712
- }}
713
- title={componentPaletteVisible ? 'Hide components' : 'Show components'}
714
- >
715
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
716
- {componentPaletteVisible ? (
717
- <>
718
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
719
- <line x1="15" y1="3" x2="15" y2="21"/>
720
- </>
721
- ) : (
722
- <>
723
- <rect x="3" y="3" width="7" height="7"/>
724
- <rect x="14" y="3" width="7" height="7"/>
725
- <rect x="14" y="14" width="7" height="7"/>
726
- <rect x="3" y="14" width="7" height="7"/>
727
- </>
728
- )}
729
- </svg>
730
- </button>
731
- </div>
732
- </div>
733
- );
734
- };
735
-
736
- export default TopToolbar;
1
+ import React, { useState } from 'react';
2
+ import { useTheme } from '../../contexts/ThemeContext';
3
+ import { useAuth } from '../../contexts/AuthContext';
4
+ import { useAppTheme } from '../../hooks/useAppTheme';
5
+
6
+ interface TopToolbarProps {
7
+ workflowName: string;
8
+ onWorkflowNameChange: (name: string) => void;
9
+ onSave: () => void;
10
+ onNew: () => void;
11
+ onOpen: () => void;
12
+ onRun: () => void;
13
+ isRunning?: boolean;
14
+ onDeploy: () => void;
15
+ onCancelDeployment: () => void;
16
+ isDeploying?: boolean;
17
+ hasUnsavedChanges: boolean;
18
+ sidebarVisible: boolean;
19
+ onToggleSidebar: () => void;
20
+ componentPaletteVisible: boolean;
21
+ onToggleComponentPalette: () => void;
22
+ proMode: boolean;
23
+ onToggleProMode: () => void;
24
+ onOpenSettings: () => void;
25
+ onOpenCredentials: () => void;
26
+ onExportJSON: () => void;
27
+ onExportFile: () => void;
28
+ onImportJSON: () => void;
29
+ }
30
+
31
+ const TopToolbar: React.FC<TopToolbarProps> = ({
32
+ workflowName,
33
+ onWorkflowNameChange,
34
+ onSave,
35
+ onNew,
36
+ onOpen,
37
+ onRun,
38
+ isRunning = false,
39
+ onDeploy,
40
+ onCancelDeployment,
41
+ isDeploying = false,
42
+ hasUnsavedChanges,
43
+ sidebarVisible,
44
+ onToggleSidebar,
45
+ componentPaletteVisible,
46
+ onToggleComponentPalette,
47
+ proMode,
48
+ onToggleProMode,
49
+ onOpenSettings,
50
+ onOpenCredentials,
51
+ onExportJSON,
52
+ onExportFile,
53
+ onImportJSON,
54
+ }) => {
55
+ const [isEditing, setIsEditing] = useState(false);
56
+ const [tempName, setTempName] = useState(workflowName);
57
+ const [fileMenuOpen, setFileMenuOpen] = useState(false);
58
+ const { isDarkMode, toggleTheme } = useTheme();
59
+ const { user, logout } = useAuth();
60
+ const theme = useAppTheme();
61
+
62
+ const handleNameClick = () => {
63
+ setTempName(workflowName);
64
+ setIsEditing(true);
65
+ };
66
+
67
+ const handleNameSubmit = () => {
68
+ onWorkflowNameChange(tempName.trim() || 'Untitled Workflow');
69
+ setIsEditing(false);
70
+ };
71
+
72
+ const handleNameKeyDown = (e: React.KeyboardEvent) => {
73
+ if (e.key === 'Enter') {
74
+ handleNameSubmit();
75
+ } else if (e.key === 'Escape') {
76
+ setTempName(workflowName);
77
+ setIsEditing(false);
78
+ }
79
+ };
80
+
81
+ // Icon-only button style - subtle with colored icon
82
+ const iconButtonStyle: React.CSSProperties = {
83
+ width: theme.buttonSize.lg,
84
+ height: theme.buttonSize.lg,
85
+ display: 'flex',
86
+ alignItems: 'center',
87
+ justifyContent: 'center',
88
+ backgroundColor: 'transparent',
89
+ color: theme.colors.textSecondary,
90
+ border: 'none',
91
+ borderRadius: theme.borderRadius.md,
92
+ cursor: 'pointer',
93
+ transition: `all ${theme.transitions.fast}`,
94
+ };
95
+
96
+ // Text button style - cleaner with subtle border and Dracula accent
97
+ const textButtonStyle: React.CSSProperties = {
98
+ height: theme.buttonSize.md,
99
+ padding: `0 ${theme.spacing.md}`,
100
+ display: 'flex',
101
+ alignItems: 'center',
102
+ gap: theme.spacing.sm,
103
+ backgroundColor: 'transparent',
104
+ color: theme.dracula.green,
105
+ border: `1px solid ${theme.dracula.green}40`,
106
+ borderRadius: theme.borderRadius.sm,
107
+ fontSize: theme.fontSize.base,
108
+ fontWeight: theme.fontWeight.semibold,
109
+ cursor: 'pointer',
110
+ transition: `all ${theme.transitions.fast}`,
111
+ fontFamily: 'system-ui, sans-serif',
112
+ };
113
+
114
+ // Action button style - Dracula theme for visibility
115
+ const actionButtonStyle = (color: string, isDisabled = false): React.CSSProperties => ({
116
+ height: theme.buttonSize.md,
117
+ padding: `0 ${theme.spacing.lg}`,
118
+ display: 'flex',
119
+ alignItems: 'center',
120
+ gap: theme.spacing.sm,
121
+ backgroundColor: isDisabled ? `${theme.colors.primary}15` : `${color}25`,
122
+ color: isDisabled ? theme.colors.primary : color,
123
+ border: `1px solid ${isDisabled ? `${theme.colors.primary}40` : `${color}60`}`,
124
+ borderRadius: theme.borderRadius.sm,
125
+ fontSize: theme.fontSize.sm,
126
+ fontWeight: theme.fontWeight.semibold,
127
+ cursor: isDisabled ? 'not-allowed' : 'pointer',
128
+ transition: `all ${theme.transitions.fast}`,
129
+ fontFamily: 'system-ui, sans-serif',
130
+ letterSpacing: '0.3px',
131
+ });
132
+
133
+ return (
134
+ <div
135
+ style={{
136
+ height: theme.layout.toolbarHeight,
137
+ backgroundColor: theme.colors.backgroundPanel,
138
+ borderBottom: `1px solid ${theme.colors.border}`,
139
+ display: 'flex',
140
+ alignItems: 'center',
141
+ justifyContent: 'space-between',
142
+ padding: `0 ${theme.spacing.md}`,
143
+ gap: theme.spacing.md,
144
+ }}
145
+ >
146
+ {/* Left Section */}
147
+ <div style={{ display: 'flex', alignItems: 'center', gap: theme.spacing.sm }}>
148
+ {/* Sidebar Toggle */}
149
+ <button
150
+ onClick={onToggleSidebar}
151
+ style={{
152
+ ...iconButtonStyle,
153
+ backgroundColor: sidebarVisible ? `${theme.colors.actionSidebar}30` : `${theme.colors.actionSidebar}15`,
154
+ color: theme.colors.actionSidebar,
155
+ border: `1px solid ${sidebarVisible ? `${theme.colors.actionSidebar}60` : `${theme.colors.actionSidebar}40`}`,
156
+ }}
157
+ onMouseEnter={(e) => {
158
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionSidebar}40`;
159
+ e.currentTarget.style.borderColor = `${theme.colors.actionSidebar}80`;
160
+ }}
161
+ onMouseLeave={(e) => {
162
+ e.currentTarget.style.backgroundColor = sidebarVisible ? `${theme.colors.actionSidebar}30` : `${theme.colors.actionSidebar}15`;
163
+ e.currentTarget.style.borderColor = sidebarVisible ? `${theme.colors.actionSidebar}60` : `${theme.colors.actionSidebar}40`;
164
+ }}
165
+ title={sidebarVisible ? 'Hide sidebar' : 'Show sidebar'}
166
+ >
167
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
168
+ {sidebarVisible ? (
169
+ <>
170
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
171
+ <line x1="9" y1="3" x2="9" y2="21"/>
172
+ </>
173
+ ) : (
174
+ <>
175
+ <line x1="3" y1="12" x2="21" y2="12"/>
176
+ <line x1="3" y1="6" x2="21" y2="6"/>
177
+ <line x1="3" y1="18" x2="21" y2="18"/>
178
+ </>
179
+ )}
180
+ </svg>
181
+ </button>
182
+
183
+ {/* Divider */}
184
+ <div style={{ width: '1px', height: theme.spacing.xl, backgroundColor: theme.colors.border, margin: `0 ${theme.spacing.xs}` }} />
185
+
186
+ {/* File Menu */}
187
+ <div style={{ position: 'relative', marginLeft: sidebarVisible ? `calc(${theme.layout.workflowSidebarWidth} - ${theme.spacing.xxl} - ${theme.spacing.xxl})` : 0 }}>
188
+ <button
189
+ onClick={() => setFileMenuOpen(!fileMenuOpen)}
190
+ style={{
191
+ ...textButtonStyle,
192
+ backgroundColor: fileMenuOpen ? theme.colors.backgroundHover : 'transparent',
193
+ }}
194
+ onMouseEnter={(e) => {
195
+ e.currentTarget.style.backgroundColor = theme.colors.backgroundHover;
196
+ }}
197
+ onMouseLeave={(e) => {
198
+ if (!fileMenuOpen) {
199
+ e.currentTarget.style.backgroundColor = 'transparent';
200
+ }
201
+ }}
202
+ >
203
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
204
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
205
+ <polyline points="14 2 14 8 20 8"/>
206
+ </svg>
207
+ File
208
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
209
+ <path d="M6 9l6 6 6-6"/>
210
+ </svg>
211
+ </button>
212
+
213
+ {fileMenuOpen && (
214
+ <>
215
+ <div
216
+ style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, zIndex: 999 }}
217
+ onClick={() => setFileMenuOpen(false)}
218
+ />
219
+ <div
220
+ style={{
221
+ position: 'absolute',
222
+ top: 'calc(100% + 4px)',
223
+ left: 0,
224
+ background: `linear-gradient(135deg, ${theme.colors.backgroundPanel} 0%, ${theme.colors.background} 100%)`,
225
+ border: `1px solid ${theme.colors.border}`,
226
+ borderRadius: theme.borderRadius.lg,
227
+ boxShadow: `0 8px 24px ${theme.colors.shadow}, 0 2px 8px ${theme.colors.shadowLight}`,
228
+ minWidth: '200px',
229
+ zIndex: 1000,
230
+ overflow: 'hidden',
231
+ padding: theme.spacing.sm,
232
+ backdropFilter: 'blur(8px)',
233
+ }}
234
+ >
235
+ {/* Menu Header */}
236
+ <div
237
+ style={{
238
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
239
+ marginBottom: theme.spacing.xs,
240
+ borderBottom: `1px solid ${theme.colors.border}`,
241
+ }}
242
+ >
243
+ <span
244
+ style={{
245
+ fontSize: theme.fontSize.xs,
246
+ fontWeight: theme.fontWeight.semibold,
247
+ color: theme.colors.textMuted,
248
+ textTransform: 'uppercase',
249
+ letterSpacing: '0.5px',
250
+ fontFamily: 'system-ui, sans-serif',
251
+ }}
252
+ >
253
+ File Operations
254
+ </span>
255
+ </div>
256
+ {[
257
+ { label: 'New Workflow', icon: 'M12 5v14M5 12h14', action: onNew, color: theme.dracula.green },
258
+ { label: 'Open', icon: 'M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z', action: onOpen, color: theme.accent.blue },
259
+ { divider: true },
260
+ { label: 'Export', icon: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12', action: onExportFile, color: theme.accent.cyan },
261
+ { label: 'Import', icon: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3', action: onImportJSON, color: theme.accent.cyan },
262
+ { divider: true },
263
+ { label: 'Copy as JSON', icon: 'M8 17.929H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v0M18 9h-8a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V11a2 2 0 0 0-2-2z', action: onExportJSON, color: theme.dracula.purple },
264
+ ].map((item, index) =>
265
+ item.divider ? (
266
+ <div key={index} style={{ height: '1px', backgroundColor: theme.colors.border, margin: `${theme.spacing.xs} 0` }} />
267
+ ) : (
268
+ <button
269
+ key={index}
270
+ onClick={() => { item.action?.(); setFileMenuOpen(false); }}
271
+ style={{
272
+ width: '100%',
273
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
274
+ backgroundColor: 'transparent',
275
+ border: 'none',
276
+ borderRadius: theme.borderRadius.md,
277
+ color: theme.colors.textSecondary,
278
+ fontSize: theme.fontSize.sm,
279
+ cursor: 'pointer',
280
+ display: 'flex',
281
+ alignItems: 'center',
282
+ gap: theme.spacing.md,
283
+ textAlign: 'left',
284
+ fontFamily: 'system-ui, sans-serif',
285
+ transition: `all ${theme.transitions.fast}`,
286
+ }}
287
+ onMouseEnter={(e) => {
288
+ e.currentTarget.style.backgroundColor = `${item.color}15`;
289
+ e.currentTarget.style.color = item.color || theme.colors.text;
290
+ }}
291
+ onMouseLeave={(e) => {
292
+ e.currentTarget.style.backgroundColor = 'transparent';
293
+ e.currentTarget.style.color = theme.colors.textSecondary;
294
+ }}
295
+ >
296
+ <div
297
+ style={{
298
+ width: theme.iconSize.lg,
299
+ height: theme.iconSize.lg,
300
+ borderRadius: theme.borderRadius.sm,
301
+ backgroundColor: `${item.color}15`,
302
+ display: 'flex',
303
+ alignItems: 'center',
304
+ justifyContent: 'center',
305
+ flexShrink: 0,
306
+ }}
307
+ >
308
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={item.color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
309
+ <path d={item.icon} />
310
+ </svg>
311
+ </div>
312
+ <span style={{ fontWeight: theme.fontWeight.medium }}>{item.label}</span>
313
+ </button>
314
+ )
315
+ )}
316
+ </div>
317
+ </>
318
+ )}
319
+ </div>
320
+ </div>
321
+
322
+ {/* Center Section - Workflow Name */}
323
+ <div style={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
324
+ {isEditing ? (
325
+ <input
326
+ type="text"
327
+ value={tempName}
328
+ onChange={(e) => setTempName(e.target.value)}
329
+ onBlur={handleNameSubmit}
330
+ onKeyDown={handleNameKeyDown}
331
+ autoFocus
332
+ style={{
333
+ fontSize: '14px',
334
+ fontWeight: 500,
335
+ color: theme.colors.textSecondary,
336
+ backgroundColor: theme.colors.backgroundAlt,
337
+ border: `1px solid ${theme.accent.cyan}`,
338
+ borderRadius: theme.borderRadius.sm,
339
+ padding: '6px 12px',
340
+ outline: 'none',
341
+ fontFamily: 'system-ui, sans-serif',
342
+ minWidth: '200px',
343
+ textAlign: 'center',
344
+ }}
345
+ />
346
+ ) : (
347
+ <button
348
+ onClick={handleNameClick}
349
+ style={{
350
+ display: 'flex',
351
+ alignItems: 'center',
352
+ gap: '6px',
353
+ padding: '6px 12px',
354
+ backgroundColor: 'transparent',
355
+ border: 'none',
356
+ borderRadius: theme.borderRadius.sm,
357
+ cursor: 'pointer',
358
+ transition: `all ${theme.transitions.fast}`,
359
+ }}
360
+ onMouseEnter={(e) => {
361
+ e.currentTarget.style.backgroundColor = theme.colors.backgroundHover;
362
+ }}
363
+ onMouseLeave={(e) => {
364
+ e.currentTarget.style.backgroundColor = 'transparent';
365
+ }}
366
+ title="Click to rename"
367
+ >
368
+ <span style={{
369
+ fontSize: '14px',
370
+ fontWeight: 500,
371
+ color: theme.dracula.purple,
372
+ fontFamily: 'system-ui, sans-serif',
373
+ }}>
374
+ {workflowName}
375
+ </span>
376
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke={theme.colors.textMuted} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
377
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
378
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
379
+ </svg>
380
+ </button>
381
+ )}
382
+ </div>
383
+
384
+ {/* Right Section */}
385
+ <div style={{ display: 'flex', alignItems: 'center', gap: theme.spacing.sm }}>
386
+ {/* Mode Toggle - Segmented control style */}
387
+ <div style={{ display: 'flex', alignItems: 'center', gap: theme.spacing.sm }}>
388
+ <span style={{
389
+ fontSize: theme.fontSize.sm,
390
+ color: theme.accent.cyan,
391
+ fontWeight: theme.fontWeight.semibold,
392
+ fontFamily: 'system-ui, sans-serif',
393
+ }}>
394
+ Mode:
395
+ </span>
396
+ <div
397
+ style={{
398
+ display: 'flex',
399
+ alignItems: 'center',
400
+ backgroundColor: theme.colors.backgroundAlt,
401
+ borderRadius: theme.borderRadius.md,
402
+ padding: '2px',
403
+ border: `1px solid ${theme.colors.border}`,
404
+ }}
405
+ title={proMode ? 'Dev mode: All components visible' : 'Normal mode: Only AI components'}
406
+ >
407
+ <button
408
+ onClick={() => !proMode ? undefined : onToggleProMode()}
409
+ style={{
410
+ padding: '4px 10px',
411
+ display: 'flex',
412
+ alignItems: 'center',
413
+ gap: '4px',
414
+ backgroundColor: !proMode ? `${theme.dracula.green}25` : 'transparent',
415
+ color: !proMode ? theme.dracula.green : theme.dracula.orange,
416
+ border: !proMode ? `1px solid ${theme.dracula.green}60` : '1px solid transparent',
417
+ borderRadius: theme.borderRadius.sm,
418
+ fontSize: theme.fontSize.xs,
419
+ fontWeight: theme.fontWeight.semibold,
420
+ cursor: proMode ? 'pointer' : 'default',
421
+ transition: `all ${theme.transitions.fast}`,
422
+ fontFamily: 'system-ui, sans-serif',
423
+ }}
424
+ >
425
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
426
+ <circle cx="12" cy="12" r="10"/>
427
+ <path d="M12 6v6l4 2"/>
428
+ </svg>
429
+ Normal
430
+ </button>
431
+ <button
432
+ onClick={() => proMode ? undefined : onToggleProMode()}
433
+ style={{
434
+ padding: '4px 10px',
435
+ display: 'flex',
436
+ alignItems: 'center',
437
+ gap: '4px',
438
+ backgroundColor: proMode ? `${theme.dracula.purple}25` : 'transparent',
439
+ color: proMode ? theme.dracula.purple : theme.dracula.orange,
440
+ border: proMode ? `1px solid ${theme.dracula.purple}60` : '1px solid transparent',
441
+ borderRadius: theme.borderRadius.sm,
442
+ fontSize: theme.fontSize.xs,
443
+ fontWeight: theme.fontWeight.semibold,
444
+ cursor: !proMode ? 'pointer' : 'default',
445
+ transition: `all ${theme.transitions.fast}`,
446
+ fontFamily: 'system-ui, sans-serif',
447
+ }}
448
+ >
449
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
450
+ <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
451
+ </svg>
452
+ Dev
453
+ </button>
454
+ </div>
455
+ </div>
456
+
457
+ {/* Divider */}
458
+ <div style={{ width: '1px', height: theme.spacing.xl, backgroundColor: theme.colors.border, margin: `0 ${theme.spacing.xs}` }} />
459
+
460
+ {/* Settings Button */}
461
+ <button
462
+ onClick={onOpenSettings}
463
+ style={{
464
+ ...iconButtonStyle,
465
+ backgroundColor: `${theme.colors.actionSettings}15`,
466
+ color: theme.colors.actionSettings,
467
+ border: `1px solid ${theme.colors.actionSettings}40`,
468
+ }}
469
+ onMouseEnter={(e) => {
470
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionSettings}30`;
471
+ e.currentTarget.style.borderColor = `${theme.colors.actionSettings}60`;
472
+ }}
473
+ onMouseLeave={(e) => {
474
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionSettings}15`;
475
+ e.currentTarget.style.borderColor = `${theme.colors.actionSettings}40`;
476
+ }}
477
+ title="Settings"
478
+ >
479
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
480
+ <circle cx="12" cy="12" r="3"/>
481
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
482
+ </svg>
483
+ </button>
484
+
485
+ {/* Credentials Button */}
486
+ <button
487
+ onClick={onOpenCredentials}
488
+ style={{
489
+ ...iconButtonStyle,
490
+ backgroundColor: `${theme.colors.actionCredentials}15`,
491
+ color: theme.colors.actionCredentials,
492
+ border: `1px solid ${theme.colors.actionCredentials}40`,
493
+ }}
494
+ onMouseEnter={(e) => {
495
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionCredentials}30`;
496
+ e.currentTarget.style.borderColor = `${theme.colors.actionCredentials}60`;
497
+ }}
498
+ onMouseLeave={(e) => {
499
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionCredentials}15`;
500
+ e.currentTarget.style.borderColor = `${theme.colors.actionCredentials}40`;
501
+ }}
502
+ title="API Credentials"
503
+ >
504
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
505
+ <path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/>
506
+ </svg>
507
+ </button>
508
+
509
+ {/* Theme Toggle Button */}
510
+ <button
511
+ onClick={toggleTheme}
512
+ style={{
513
+ ...iconButtonStyle,
514
+ backgroundColor: `${theme.colors.actionTheme}15`,
515
+ color: theme.colors.actionTheme,
516
+ border: `1px solid ${theme.colors.actionTheme}40`,
517
+ }}
518
+ onMouseEnter={(e) => {
519
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionTheme}30`;
520
+ e.currentTarget.style.borderColor = `${theme.colors.actionTheme}60`;
521
+ }}
522
+ onMouseLeave={(e) => {
523
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionTheme}15`;
524
+ e.currentTarget.style.borderColor = `${theme.colors.actionTheme}40`;
525
+ }}
526
+ title={isDarkMode ? 'Switch to Light mode' : 'Switch to Dark mode'}
527
+ >
528
+ {isDarkMode ? (
529
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
530
+ <circle cx="12" cy="12" r="5"/>
531
+ <line x1="12" y1="1" x2="12" y2="3"/>
532
+ <line x1="12" y1="21" x2="12" y2="23"/>
533
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
534
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
535
+ <line x1="1" y1="12" x2="3" y2="12"/>
536
+ <line x1="21" y1="12" x2="23" y2="12"/>
537
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
538
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
539
+ </svg>
540
+ ) : (
541
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
542
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
543
+ </svg>
544
+ )}
545
+ </button>
546
+
547
+ {/* User & Logout */}
548
+ {user && (
549
+ <button
550
+ onClick={logout}
551
+ style={{
552
+ ...iconButtonStyle,
553
+ backgroundColor: `${theme.dracula.pink}15`,
554
+ color: theme.dracula.pink,
555
+ border: `1px solid ${theme.dracula.pink}40`,
556
+ }}
557
+ onMouseEnter={(e) => {
558
+ e.currentTarget.style.backgroundColor = `${theme.dracula.pink}30`;
559
+ e.currentTarget.style.borderColor = `${theme.dracula.pink}60`;
560
+ }}
561
+ onMouseLeave={(e) => {
562
+ e.currentTarget.style.backgroundColor = `${theme.dracula.pink}15`;
563
+ e.currentTarget.style.borderColor = `${theme.dracula.pink}40`;
564
+ }}
565
+ title={`Logout ${user.display_name}`}
566
+ >
567
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
568
+ <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
569
+ <polyline points="16 17 21 12 16 7"/>
570
+ <line x1="21" y1="12" x2="9" y2="12"/>
571
+ </svg>
572
+ </button>
573
+ )}
574
+
575
+ {/* Divider */}
576
+ <div style={{ width: '1px', height: theme.spacing.xl, backgroundColor: theme.colors.border, margin: `0 ${theme.spacing.sm}` }} />
577
+
578
+ {/* Action Buttons */}
579
+ <button
580
+ onClick={onRun}
581
+ disabled={isRunning || isDeploying}
582
+ style={actionButtonStyle(theme.colors.actionRun, isRunning || isDeploying)}
583
+ onMouseEnter={(e) => {
584
+ if (!isRunning && !isDeploying) {
585
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionRun}40`;
586
+ }
587
+ }}
588
+ onMouseLeave={(e) => {
589
+ if (!isRunning && !isDeploying) {
590
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionRun}25`;
591
+ }
592
+ }}
593
+ title={isRunning ? 'Running...' : isDeploying ? 'Cannot run while deploying' : 'Run workflow once'}
594
+ >
595
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
596
+ <path d="M8 5v14l11-7z"/>
597
+ </svg>
598
+ {isRunning ? 'Running' : 'Run'}
599
+ </button>
600
+
601
+ {!isDeploying ? (
602
+ <button
603
+ onClick={onDeploy}
604
+ disabled={isRunning}
605
+ style={actionButtonStyle(theme.colors.actionDeploy, isRunning)}
606
+ onMouseEnter={(e) => {
607
+ if (!isRunning) {
608
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionDeploy}40`;
609
+ }
610
+ }}
611
+ onMouseLeave={(e) => {
612
+ if (!isRunning) {
613
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionDeploy}25`;
614
+ }
615
+ }}
616
+ title="Deploy workflow continuously"
617
+ >
618
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
619
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
620
+ </svg>
621
+ Deploy
622
+ </button>
623
+ ) : (
624
+ <button
625
+ onClick={onCancelDeployment}
626
+ style={actionButtonStyle(theme.colors.actionStop, false)}
627
+ onMouseEnter={(e) => {
628
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionStop}40`;
629
+ }}
630
+ onMouseLeave={(e) => {
631
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionStop}25`;
632
+ }}
633
+ title="Stop deployment"
634
+ >
635
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
636
+ <rect x="6" y="6" width="12" height="12" rx="1"/>
637
+ </svg>
638
+ Stop
639
+ </button>
640
+ )}
641
+
642
+ <button
643
+ onClick={() => {
644
+ if (typeof onSave === 'function') {
645
+ onSave();
646
+ }
647
+ }}
648
+ style={actionButtonStyle(theme.colors.actionSave, !hasUnsavedChanges)}
649
+ onMouseEnter={(e) => {
650
+ if (hasUnsavedChanges) {
651
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionSave}40`;
652
+ }
653
+ }}
654
+ onMouseLeave={(e) => {
655
+ if (hasUnsavedChanges) {
656
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionSave}25`;
657
+ }
658
+ }}
659
+ title={hasUnsavedChanges ? 'Save changes' : 'No changes to save'}
660
+ >
661
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
662
+ <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
663
+ <polyline points="17 21 17 13 7 13 7 21"/>
664
+ <polyline points="7 3 7 8 15 8"/>
665
+ </svg>
666
+ Save
667
+ </button>
668
+
669
+ {/* Status Indicator */}
670
+ <div
671
+ style={{
672
+ display: 'flex',
673
+ alignItems: 'center',
674
+ gap: theme.spacing.sm,
675
+ padding: `${theme.spacing.xs} ${theme.spacing.md}`,
676
+ backgroundColor: 'transparent',
677
+ borderRadius: theme.borderRadius.sm,
678
+ fontSize: theme.fontSize.xs,
679
+ fontWeight: theme.fontWeight.medium,
680
+ color: hasUnsavedChanges ? theme.colors.statusModified : theme.colors.statusSaved,
681
+ fontFamily: 'system-ui, sans-serif',
682
+ }}
683
+ >
684
+ <div style={{
685
+ width: theme.spacing.sm,
686
+ height: theme.spacing.sm,
687
+ borderRadius: '50%',
688
+ backgroundColor: hasUnsavedChanges ? theme.colors.statusModified : theme.colors.statusSaved,
689
+ }} />
690
+ {hasUnsavedChanges ? 'Modified' : 'Saved'}
691
+ </div>
692
+
693
+ {/* Divider */}
694
+ <div style={{ width: '1px', height: theme.spacing.xl, backgroundColor: theme.colors.border, margin: `0 ${theme.spacing.sm}` }} />
695
+
696
+ {/* Component Palette Toggle */}
697
+ <button
698
+ onClick={onToggleComponentPalette}
699
+ style={{
700
+ ...iconButtonStyle,
701
+ backgroundColor: componentPaletteVisible ? `${theme.colors.actionPalette}30` : `${theme.colors.actionPalette}15`,
702
+ color: theme.colors.actionPalette,
703
+ border: `1px solid ${componentPaletteVisible ? `${theme.colors.actionPalette}60` : `${theme.colors.actionPalette}40`}`,
704
+ }}
705
+ onMouseEnter={(e) => {
706
+ e.currentTarget.style.backgroundColor = `${theme.colors.actionPalette}40`;
707
+ e.currentTarget.style.borderColor = `${theme.colors.actionPalette}80`;
708
+ }}
709
+ onMouseLeave={(e) => {
710
+ e.currentTarget.style.backgroundColor = componentPaletteVisible ? `${theme.colors.actionPalette}30` : `${theme.colors.actionPalette}15`;
711
+ e.currentTarget.style.borderColor = componentPaletteVisible ? `${theme.colors.actionPalette}60` : `${theme.colors.actionPalette}40`;
712
+ }}
713
+ title={componentPaletteVisible ? 'Hide components' : 'Show components'}
714
+ >
715
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
716
+ {componentPaletteVisible ? (
717
+ <>
718
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
719
+ <line x1="15" y1="3" x2="15" y2="21"/>
720
+ </>
721
+ ) : (
722
+ <>
723
+ <rect x="3" y="3" width="7" height="7"/>
724
+ <rect x="14" y="3" width="7" height="7"/>
725
+ <rect x="14" y="14" width="7" height="7"/>
726
+ <rect x="3" y="14" width="7" height="7"/>
727
+ </>
728
+ )}
729
+ </svg>
730
+ </button>
731
+ </div>
732
+ </div>
733
+ );
734
+ };
735
+
736
+ export default TopToolbar;