machinaos 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/.env.template +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/bin/cli.js +159 -0
  5. package/client/.dockerignore +45 -0
  6. package/client/Dockerfile +68 -0
  7. package/client/eslint.config.js +29 -0
  8. package/client/index.html +13 -0
  9. package/client/nginx.conf +66 -0
  10. package/client/package.json +48 -0
  11. package/client/src/App.tsx +27 -0
  12. package/client/src/Dashboard.tsx +1173 -0
  13. package/client/src/ParameterPanel.tsx +301 -0
  14. package/client/src/components/AIAgentNode.tsx +321 -0
  15. package/client/src/components/APIKeyValidator.tsx +118 -0
  16. package/client/src/components/ClaudeChatModelNode.tsx +18 -0
  17. package/client/src/components/ConditionalEdge.tsx +189 -0
  18. package/client/src/components/CredentialsModal.tsx +306 -0
  19. package/client/src/components/EdgeConditionEditor.tsx +443 -0
  20. package/client/src/components/GeminiChatModelNode.tsx +18 -0
  21. package/client/src/components/GenericNode.tsx +357 -0
  22. package/client/src/components/LocationParameterPanel.tsx +154 -0
  23. package/client/src/components/ModelNode.tsx +286 -0
  24. package/client/src/components/OpenAIChatModelNode.tsx +18 -0
  25. package/client/src/components/OutputPanel.tsx +471 -0
  26. package/client/src/components/ParameterRenderer.tsx +1874 -0
  27. package/client/src/components/SkillEditorModal.tsx +417 -0
  28. package/client/src/components/SquareNode.tsx +797 -0
  29. package/client/src/components/StartNode.tsx +250 -0
  30. package/client/src/components/ToolkitNode.tsx +365 -0
  31. package/client/src/components/TriggerNode.tsx +463 -0
  32. package/client/src/components/auth/LoginPage.tsx +247 -0
  33. package/client/src/components/auth/ProtectedRoute.tsx +59 -0
  34. package/client/src/components/base/BaseChatModelNode.tsx +271 -0
  35. package/client/src/components/icons/AIProviderIcons.tsx +50 -0
  36. package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
  37. package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
  38. package/client/src/components/maps/index.ts +26 -0
  39. package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
  40. package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
  41. package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
  42. package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
  43. package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
  44. package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
  45. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
  46. package/client/src/components/parameterPanel/index.ts +42 -0
  47. package/client/src/components/shared/DataPanel.tsx +142 -0
  48. package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
  49. package/client/src/components/ui/AIResultModal.tsx +204 -0
  50. package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
  51. package/client/src/components/ui/CodeEditor.tsx +81 -0
  52. package/client/src/components/ui/CollapsibleSection.tsx +88 -0
  53. package/client/src/components/ui/ComponentItem.tsx +154 -0
  54. package/client/src/components/ui/ComponentPalette.tsx +321 -0
  55. package/client/src/components/ui/ConsolePanel.tsx +1074 -0
  56. package/client/src/components/ui/ErrorBoundary.tsx +196 -0
  57. package/client/src/components/ui/InputNodesPanel.tsx +204 -0
  58. package/client/src/components/ui/MapSelector.tsx +314 -0
  59. package/client/src/components/ui/Modal.tsx +149 -0
  60. package/client/src/components/ui/NodeContextMenu.tsx +192 -0
  61. package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
  62. package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
  63. package/client/src/components/ui/SettingsPanel.tsx +243 -0
  64. package/client/src/components/ui/TopToolbar.tsx +736 -0
  65. package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
  66. package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
  67. package/client/src/config/antdTheme.ts +186 -0
  68. package/client/src/config/api.ts +54 -0
  69. package/client/src/contexts/AuthContext.tsx +221 -0
  70. package/client/src/contexts/ThemeContext.tsx +42 -0
  71. package/client/src/contexts/WebSocketContext.tsx +1971 -0
  72. package/client/src/factories/baseChatModelFactory.ts +256 -0
  73. package/client/src/hooks/useAndroidOperations.ts +164 -0
  74. package/client/src/hooks/useApiKeyValidation.ts +107 -0
  75. package/client/src/hooks/useApiKeys.ts +238 -0
  76. package/client/src/hooks/useAppTheme.ts +17 -0
  77. package/client/src/hooks/useComponentPalette.ts +51 -0
  78. package/client/src/hooks/useCopyPaste.ts +155 -0
  79. package/client/src/hooks/useDragAndDrop.ts +124 -0
  80. package/client/src/hooks/useDragVariable.ts +88 -0
  81. package/client/src/hooks/useExecution.ts +313 -0
  82. package/client/src/hooks/useParameterPanel.ts +176 -0
  83. package/client/src/hooks/useReactFlowNodes.ts +189 -0
  84. package/client/src/hooks/useToolSchema.ts +209 -0
  85. package/client/src/hooks/useWhatsApp.ts +196 -0
  86. package/client/src/hooks/useWorkflowManagement.ts +46 -0
  87. package/client/src/index.css +315 -0
  88. package/client/src/main.tsx +19 -0
  89. package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
  90. package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
  91. package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
  92. package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
  93. package/client/src/nodeDefinitions/chatNodes.ts +135 -0
  94. package/client/src/nodeDefinitions/codeNodes.ts +54 -0
  95. package/client/src/nodeDefinitions/documentNodes.ts +379 -0
  96. package/client/src/nodeDefinitions/index.ts +15 -0
  97. package/client/src/nodeDefinitions/locationNodes.ts +463 -0
  98. package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
  99. package/client/src/nodeDefinitions/skillNodes.ts +211 -0
  100. package/client/src/nodeDefinitions/toolNodes.ts +198 -0
  101. package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
  102. package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
  103. package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
  104. package/client/src/nodeDefinitions.ts +104 -0
  105. package/client/src/schemas/workflowSchema.ts +264 -0
  106. package/client/src/services/dynamicParameterService.ts +96 -0
  107. package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
  108. package/client/src/services/executionService.ts +232 -0
  109. package/client/src/services/workflowApi.ts +91 -0
  110. package/client/src/store/useAppStore.ts +582 -0
  111. package/client/src/styles/theme.ts +508 -0
  112. package/client/src/styles/zIndex.ts +17 -0
  113. package/client/src/types/ComponentTypes.ts +39 -0
  114. package/client/src/types/EdgeCondition.ts +231 -0
  115. package/client/src/types/INodeProperties.ts +288 -0
  116. package/client/src/types/NodeTypes.ts +28 -0
  117. package/client/src/utils/formatters.ts +33 -0
  118. package/client/src/utils/googleMapsLoader.ts +140 -0
  119. package/client/src/utils/locationUtils.ts +85 -0
  120. package/client/src/utils/nodeUtils.ts +31 -0
  121. package/client/src/utils/workflow.ts +30 -0
  122. package/client/src/utils/workflowExport.ts +120 -0
  123. package/client/src/vite-env.d.ts +12 -0
  124. package/client/tailwind.config.js +60 -0
  125. package/client/tsconfig.json +25 -0
  126. package/client/tsconfig.node.json +11 -0
  127. package/client/vite.config.js +35 -0
  128. package/docker-compose.prod.yml +107 -0
  129. package/docker-compose.yml +104 -0
  130. package/docs-MachinaOs/README.md +85 -0
  131. package/docs-MachinaOs/deployment/docker.mdx +228 -0
  132. package/docs-MachinaOs/deployment/production.mdx +345 -0
  133. package/docs-MachinaOs/docs.json +75 -0
  134. package/docs-MachinaOs/faq.mdx +309 -0
  135. package/docs-MachinaOs/favicon.svg +5 -0
  136. package/docs-MachinaOs/installation.mdx +160 -0
  137. package/docs-MachinaOs/introduction.mdx +114 -0
  138. package/docs-MachinaOs/logo/dark.svg +6 -0
  139. package/docs-MachinaOs/logo/light.svg +6 -0
  140. package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
  141. package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
  142. package/docs-MachinaOs/nodes/android.mdx +411 -0
  143. package/docs-MachinaOs/nodes/overview.mdx +181 -0
  144. package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
  145. package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
  146. package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
  147. package/docs-MachinaOs/quickstart.mdx +119 -0
  148. package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
  149. package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
  150. package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
  151. package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
  152. package/nul +0 -0
  153. package/package.json +70 -0
  154. package/scripts/build.js +158 -0
  155. package/scripts/check-ports.ps1 +33 -0
  156. package/scripts/clean.js +40 -0
  157. package/scripts/docker.js +93 -0
  158. package/scripts/kill-port.ps1 +154 -0
  159. package/scripts/start.js +210 -0
  160. package/scripts/stop.js +325 -0
  161. package/server/.dockerignore +44 -0
  162. package/server/Dockerfile +45 -0
  163. package/server/constants.py +249 -0
  164. package/server/core/__init__.py +1 -0
  165. package/server/core/cache.py +461 -0
  166. package/server/core/config.py +128 -0
  167. package/server/core/container.py +99 -0
  168. package/server/core/database.py +1211 -0
  169. package/server/core/logging.py +314 -0
  170. package/server/main.py +289 -0
  171. package/server/middleware/__init__.py +5 -0
  172. package/server/middleware/auth.py +89 -0
  173. package/server/models/__init__.py +1 -0
  174. package/server/models/auth.py +52 -0
  175. package/server/models/cache.py +24 -0
  176. package/server/models/database.py +211 -0
  177. package/server/models/nodes.py +455 -0
  178. package/server/package.json +9 -0
  179. package/server/pyproject.toml +72 -0
  180. package/server/requirements.txt +83 -0
  181. package/server/routers/__init__.py +1 -0
  182. package/server/routers/android.py +294 -0
  183. package/server/routers/auth.py +203 -0
  184. package/server/routers/database.py +151 -0
  185. package/server/routers/maps.py +142 -0
  186. package/server/routers/nodejs_compat.py +289 -0
  187. package/server/routers/webhook.py +90 -0
  188. package/server/routers/websocket.py +2127 -0
  189. package/server/routers/whatsapp.py +761 -0
  190. package/server/routers/workflow.py +200 -0
  191. package/server/services/__init__.py +1 -0
  192. package/server/services/ai.py +2415 -0
  193. package/server/services/android/__init__.py +27 -0
  194. package/server/services/android/broadcaster.py +114 -0
  195. package/server/services/android/client.py +608 -0
  196. package/server/services/android/manager.py +78 -0
  197. package/server/services/android/protocol.py +165 -0
  198. package/server/services/android_service.py +588 -0
  199. package/server/services/auth.py +131 -0
  200. package/server/services/chat_client.py +160 -0
  201. package/server/services/deployment/__init__.py +12 -0
  202. package/server/services/deployment/manager.py +706 -0
  203. package/server/services/deployment/state.py +47 -0
  204. package/server/services/deployment/triggers.py +275 -0
  205. package/server/services/event_waiter.py +785 -0
  206. package/server/services/execution/__init__.py +77 -0
  207. package/server/services/execution/cache.py +769 -0
  208. package/server/services/execution/conditions.py +373 -0
  209. package/server/services/execution/dlq.py +132 -0
  210. package/server/services/execution/executor.py +1351 -0
  211. package/server/services/execution/models.py +531 -0
  212. package/server/services/execution/recovery.py +235 -0
  213. package/server/services/handlers/__init__.py +126 -0
  214. package/server/services/handlers/ai.py +355 -0
  215. package/server/services/handlers/android.py +260 -0
  216. package/server/services/handlers/code.py +278 -0
  217. package/server/services/handlers/document.py +598 -0
  218. package/server/services/handlers/http.py +193 -0
  219. package/server/services/handlers/polyglot.py +105 -0
  220. package/server/services/handlers/tools.py +845 -0
  221. package/server/services/handlers/triggers.py +107 -0
  222. package/server/services/handlers/utility.py +822 -0
  223. package/server/services/handlers/whatsapp.py +476 -0
  224. package/server/services/maps.py +289 -0
  225. package/server/services/memory_store.py +103 -0
  226. package/server/services/node_executor.py +375 -0
  227. package/server/services/parameter_resolver.py +218 -0
  228. package/server/services/polyglot_client.py +169 -0
  229. package/server/services/scheduler.py +155 -0
  230. package/server/services/skill_loader.py +417 -0
  231. package/server/services/status_broadcaster.py +826 -0
  232. package/server/services/temporal/__init__.py +23 -0
  233. package/server/services/temporal/activities.py +344 -0
  234. package/server/services/temporal/client.py +76 -0
  235. package/server/services/temporal/executor.py +147 -0
  236. package/server/services/temporal/worker.py +251 -0
  237. package/server/services/temporal/workflow.py +355 -0
  238. package/server/services/temporal/ws_client.py +236 -0
  239. package/server/services/text.py +111 -0
  240. package/server/services/user_auth.py +172 -0
  241. package/server/services/websocket_client.py +29 -0
  242. package/server/services/workflow.py +597 -0
  243. package/server/skills/android-skill/SKILL.md +82 -0
  244. package/server/skills/assistant-personality/SKILL.md +45 -0
  245. package/server/skills/code-skill/SKILL.md +140 -0
  246. package/server/skills/http-skill/SKILL.md +161 -0
  247. package/server/skills/maps-skill/SKILL.md +170 -0
  248. package/server/skills/memory-skill/SKILL.md +154 -0
  249. package/server/skills/scheduler-skill/SKILL.md +84 -0
  250. package/server/skills/whatsapp-skill/SKILL.md +283 -0
  251. package/server/uv.lock +2916 -0
  252. package/server/whatsapp-rpc/.dockerignore +30 -0
  253. package/server/whatsapp-rpc/Dockerfile +44 -0
  254. package/server/whatsapp-rpc/Dockerfile.web +17 -0
  255. package/server/whatsapp-rpc/README.md +139 -0
  256. package/server/whatsapp-rpc/cli.js +95 -0
  257. package/server/whatsapp-rpc/configs/config.yaml +7 -0
  258. package/server/whatsapp-rpc/docker-compose.yml +35 -0
  259. package/server/whatsapp-rpc/docs/API.md +410 -0
  260. package/server/whatsapp-rpc/go.mod +67 -0
  261. package/server/whatsapp-rpc/go.sum +203 -0
  262. package/server/whatsapp-rpc/package.json +30 -0
  263. package/server/whatsapp-rpc/schema.json +1294 -0
  264. package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
  265. package/server/whatsapp-rpc/scripts/cli.js +162 -0
  266. package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
  267. package/server/whatsapp-rpc/src/go/config/config.go +49 -0
  268. package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
  269. package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
  270. package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
  271. package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
  272. package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
  273. package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
  274. package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
  275. package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
  276. package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
  277. package/server/whatsapp-rpc/web/app.py +609 -0
  278. package/server/whatsapp-rpc/web/requirements.txt +6 -0
  279. package/server/whatsapp-rpc/web/rpc_client.py +427 -0
  280. package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
  281. package/server/whatsapp-rpc/web/templates/base.html +150 -0
  282. package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
  283. package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
  284. package/server/whatsapp-rpc/web/templates/groups.html +328 -0
  285. package/server/whatsapp-rpc/web/templates/messages.html +465 -0
  286. package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
  287. package/server/whatsapp-rpc/web/templates/send.html +259 -0
  288. package/server/whatsapp-rpc/web/templates/settings.html +459 -0
@@ -0,0 +1,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;