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,238 @@
1
+ /**
2
+ * useApiKeys Hook - WebSocket-based API key management
3
+ *
4
+ * Provides API key validation, storage, and retrieval via WebSocket.
5
+ * This replaces the REST-based ApiKeyManagerService for real-time operations.
6
+ */
7
+
8
+ import { useCallback, useState } from 'react';
9
+ import { useWebSocket } from '../contexts/WebSocketContext';
10
+
11
+ export interface ApiKeyValidationResult {
12
+ isValid: boolean;
13
+ error?: string;
14
+ models?: string[];
15
+ }
16
+
17
+ export interface UseApiKeysResult {
18
+ // Validate and store API key
19
+ validateApiKey: (provider: string, apiKey: string) => Promise<ApiKeyValidationResult>;
20
+
21
+ // Save API key without validation
22
+ saveApiKey: (provider: string, apiKey: string) => Promise<ApiKeyValidationResult>;
23
+
24
+ // Get stored API key
25
+ getStoredApiKey: (provider: string) => Promise<string | null>;
26
+
27
+ // Check if API key exists
28
+ hasStoredKey: (provider: string) => Promise<boolean>;
29
+
30
+ // Get stored models for a provider
31
+ getStoredModels: (provider: string) => Promise<string[] | null>;
32
+
33
+ // Remove stored API key
34
+ removeApiKey: (provider: string) => Promise<void>;
35
+
36
+ // Validate Google Maps API key
37
+ validateGoogleMapsKey: (apiKey: string) => Promise<ApiKeyValidationResult>;
38
+
39
+ // Get AI models for a provider
40
+ getAiModels: (provider: string, apiKey: string) => Promise<string[]>;
41
+
42
+ // State
43
+ isValidating: boolean;
44
+ validationError: string | null;
45
+ isConnected: boolean;
46
+ }
47
+
48
+ export const useApiKeys = (): UseApiKeysResult => {
49
+ const {
50
+ validateApiKey: wsValidateApiKey,
51
+ getStoredApiKey: wsGetStoredApiKey,
52
+ saveApiKey: wsSaveApiKey,
53
+ deleteApiKey: wsDeleteApiKey,
54
+ validateMapsKey: wsValidateMapsKey,
55
+ getAiModels: wsGetAiModels,
56
+ isConnected
57
+ } = useWebSocket();
58
+
59
+ const [isValidating, setIsValidating] = useState(false);
60
+ const [validationError, setValidationError] = useState<string | null>(null);
61
+
62
+ /**
63
+ * Validate API key and store if valid
64
+ */
65
+ const validateApiKey = useCallback(async (
66
+ provider: string,
67
+ apiKey: string
68
+ ): Promise<ApiKeyValidationResult> => {
69
+ setIsValidating(true);
70
+ setValidationError(null);
71
+
72
+ try {
73
+ const result = await wsValidateApiKey(provider, apiKey);
74
+
75
+ if (!result.valid) {
76
+ setValidationError(result.message || 'Validation failed');
77
+ }
78
+
79
+ return {
80
+ isValid: result.valid,
81
+ error: result.message,
82
+ models: result.models
83
+ };
84
+ } catch (error: any) {
85
+ const errorMsg = error.message || 'Validation failed';
86
+ setValidationError(errorMsg);
87
+ return {
88
+ isValid: false,
89
+ error: errorMsg
90
+ };
91
+ } finally {
92
+ setIsValidating(false);
93
+ }
94
+ }, [wsValidateApiKey]);
95
+
96
+ /**
97
+ * Save API key without validation (for keys that can't be validated beforehand)
98
+ */
99
+ const saveApiKey = useCallback(async (
100
+ provider: string,
101
+ apiKey: string
102
+ ): Promise<ApiKeyValidationResult> => {
103
+ try {
104
+ const success = await wsSaveApiKey(provider, apiKey);
105
+ return {
106
+ isValid: success,
107
+ error: success ? undefined : 'Failed to save API key'
108
+ };
109
+ } catch (error: any) {
110
+ return {
111
+ isValid: false,
112
+ error: error.message || 'Failed to save API key'
113
+ };
114
+ }
115
+ }, [wsSaveApiKey]);
116
+
117
+ /**
118
+ * Get stored API key for a provider
119
+ */
120
+ const getStoredApiKey = useCallback(async (
121
+ provider: string
122
+ ): Promise<string | null> => {
123
+ try {
124
+ const result = await wsGetStoredApiKey(provider);
125
+ return result.hasKey ? (result.apiKey || null) : null;
126
+ } catch (error) {
127
+ console.warn(`Error retrieving API key for ${provider}:`, error);
128
+ return null;
129
+ }
130
+ }, [wsGetStoredApiKey]);
131
+
132
+ /**
133
+ * Check if a stored key exists for a provider
134
+ */
135
+ const hasStoredKey = useCallback(async (
136
+ provider: string
137
+ ): Promise<boolean> => {
138
+ try {
139
+ const result = await wsGetStoredApiKey(provider);
140
+ return result.hasKey;
141
+ } catch (error) {
142
+ return false;
143
+ }
144
+ }, [wsGetStoredApiKey]);
145
+
146
+ /**
147
+ * Get stored models for a provider
148
+ */
149
+ const getStoredModels = useCallback(async (
150
+ provider: string
151
+ ): Promise<string[] | null> => {
152
+ try {
153
+ // Get models directly from stored API key response (includes models from DB)
154
+ const result = await wsGetStoredApiKey(provider);
155
+ if (result.hasKey && result.models && result.models.length > 0) {
156
+ return result.models;
157
+ }
158
+ return null;
159
+ } catch (error) {
160
+ console.warn(`Error retrieving models for ${provider}:`, error);
161
+ return null;
162
+ }
163
+ }, [wsGetStoredApiKey]);
164
+
165
+ /**
166
+ * Remove stored API key
167
+ */
168
+ const removeApiKey = useCallback(async (
169
+ provider: string
170
+ ): Promise<void> => {
171
+ try {
172
+ await wsDeleteApiKey(provider);
173
+ } catch (error) {
174
+ console.warn(`Error removing API key for ${provider}:`, error);
175
+ }
176
+ }, [wsDeleteApiKey]);
177
+
178
+ /**
179
+ * Validate Google Maps API key
180
+ */
181
+ const validateGoogleMapsKey = useCallback(async (
182
+ apiKey: string
183
+ ): Promise<ApiKeyValidationResult> => {
184
+ setIsValidating(true);
185
+ setValidationError(null);
186
+
187
+ try {
188
+ const result = await wsValidateMapsKey(apiKey);
189
+
190
+ if (!result.valid) {
191
+ setValidationError(result.message || 'Validation failed');
192
+ }
193
+
194
+ return {
195
+ isValid: result.valid,
196
+ error: result.message
197
+ };
198
+ } catch (error: any) {
199
+ const errorMsg = error.message || 'Validation failed';
200
+ setValidationError(errorMsg);
201
+ return {
202
+ isValid: false,
203
+ error: errorMsg
204
+ };
205
+ } finally {
206
+ setIsValidating(false);
207
+ }
208
+ }, [wsValidateMapsKey]);
209
+
210
+ /**
211
+ * Get available AI models for a provider
212
+ */
213
+ const getAiModels = useCallback(async (
214
+ provider: string,
215
+ apiKey: string
216
+ ): Promise<string[]> => {
217
+ try {
218
+ return await wsGetAiModels(provider, apiKey);
219
+ } catch (error) {
220
+ console.warn(`Error fetching AI models for ${provider}:`, error);
221
+ return [];
222
+ }
223
+ }, [wsGetAiModels]);
224
+
225
+ return {
226
+ validateApiKey,
227
+ saveApiKey,
228
+ getStoredApiKey,
229
+ hasStoredKey,
230
+ getStoredModels,
231
+ removeApiKey,
232
+ validateGoogleMapsKey,
233
+ getAiModels,
234
+ isValidating,
235
+ validationError,
236
+ isConnected
237
+ };
238
+ };
@@ -0,0 +1,17 @@
1
+ import { useMemo } from 'react';
2
+ import { useTheme } from '../contexts/ThemeContext';
3
+ import { theme as baseTheme, lightColors, darkColors } from '../styles/theme';
4
+
5
+ export const useAppTheme = () => {
6
+ const { isDarkMode } = useTheme();
7
+
8
+ const dynamicTheme = useMemo(() => {
9
+ return {
10
+ ...baseTheme,
11
+ colors: isDarkMode ? darkColors : lightColors,
12
+ isDarkMode,
13
+ };
14
+ }, [isDarkMode]);
15
+
16
+ return dynamicTheme;
17
+ };
@@ -0,0 +1,51 @@
1
+ import { useState, useCallback, useEffect } from 'react';
2
+ import { nodeDefinitions } from '../nodeDefinitions';
3
+
4
+ const STORAGE_KEY = 'component_palette_collapsed_sections';
5
+
6
+ export const useComponentPalette = () => {
7
+ const [collapsedSections, setCollapsedSections] = useState<Record<string, boolean>>(() => {
8
+ // Try to load from localStorage first
9
+ try {
10
+ const saved = localStorage.getItem(STORAGE_KEY);
11
+ if (saved) {
12
+ return JSON.parse(saved);
13
+ }
14
+ } catch {
15
+ // Ignore parsing errors
16
+ }
17
+
18
+ // Default: all sections collapsed
19
+ const initialCollapsed: Record<string, boolean> = {};
20
+ const categories = new Set(Object.values(nodeDefinitions).flatMap(def => def.group || []));
21
+ categories.forEach(category => {
22
+ initialCollapsed[category] = true;
23
+ });
24
+ return initialCollapsed;
25
+ });
26
+
27
+ const [searchQuery, setSearchQuery] = useState<string>('');
28
+
29
+ // Persist collapsed sections to localStorage
30
+ useEffect(() => {
31
+ try {
32
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(collapsedSections));
33
+ } catch {
34
+ // Ignore storage errors
35
+ }
36
+ }, [collapsedSections]);
37
+
38
+ const toggleSection = useCallback((sectionId: string) => {
39
+ setCollapsedSections(prev => ({
40
+ ...prev,
41
+ [sectionId]: !prev[sectionId]
42
+ }));
43
+ }, []);
44
+
45
+ return {
46
+ collapsedSections,
47
+ searchQuery,
48
+ setSearchQuery,
49
+ toggleSection,
50
+ };
51
+ };
@@ -0,0 +1,155 @@
1
+ import { useCallback, useRef } from 'react';
2
+ import { Node, Edge } from 'reactflow';
3
+ import { generateUniqueLabel } from './useDragAndDrop';
4
+ import { nodeDefinitions } from '../nodeDefinitions';
5
+
6
+ interface UseCopyPasteProps {
7
+ nodes: Node[];
8
+ edges: Edge[];
9
+ setNodes: (nodes: Node[] | ((nodes: Node[]) => Node[])) => void;
10
+ setEdges: (edges: Edge[] | ((edges: Edge[]) => Edge[])) => void;
11
+ saveNodeParameters?: (nodeId: string, parameters: Record<string, any>) => Promise<boolean>;
12
+ }
13
+
14
+ interface ClipboardData {
15
+ nodes: Node[];
16
+ edges: Edge[];
17
+ }
18
+
19
+ /**
20
+ * Hook for copy/paste functionality with n8n-style auto-labeling.
21
+ *
22
+ * Features:
23
+ * - Copy selected nodes and their connecting edges
24
+ * - Paste with offset position and unique labels
25
+ * - Persist node parameters to database
26
+ */
27
+ export const useCopyPaste = ({
28
+ nodes,
29
+ edges,
30
+ setNodes,
31
+ setEdges,
32
+ saveNodeParameters
33
+ }: UseCopyPasteProps) => {
34
+ // In-memory clipboard (simpler than browser clipboard API)
35
+ const clipboardRef = useRef<ClipboardData | null>(null);
36
+
37
+ /**
38
+ * Copy selected nodes and their connecting edges to clipboard.
39
+ */
40
+ const copySelectedNodes = useCallback(() => {
41
+ const selectedNodes = nodes.filter(n => n.selected);
42
+ if (selectedNodes.length === 0) {
43
+ console.log('[CopyPaste] No nodes selected to copy');
44
+ return;
45
+ }
46
+
47
+ // Get edges that connect selected nodes (both ends must be selected)
48
+ const selectedNodeIds = new Set(selectedNodes.map(n => n.id));
49
+ const selectedEdges = edges.filter(
50
+ e => selectedNodeIds.has(e.source) && selectedNodeIds.has(e.target)
51
+ );
52
+
53
+ clipboardRef.current = {
54
+ nodes: selectedNodes,
55
+ edges: selectedEdges,
56
+ };
57
+
58
+ console.log(`[CopyPaste] Copied ${selectedNodes.length} nodes and ${selectedEdges.length} edges`);
59
+ }, [nodes, edges]);
60
+
61
+ /**
62
+ * Paste nodes from clipboard with offset and unique labels.
63
+ */
64
+ const pasteNodes = useCallback(async () => {
65
+ if (!clipboardRef.current) {
66
+ console.log('[CopyPaste] Nothing in clipboard to paste');
67
+ return;
68
+ }
69
+
70
+ const { nodes: copiedNodes, edges: copiedEdges } = clipboardRef.current;
71
+
72
+ // Generate ID mapping (old ID -> new ID)
73
+ const idMap = new Map<string, string>();
74
+ const now = Date.now();
75
+
76
+ copiedNodes.forEach((node, index) => {
77
+ // Use timestamp + index to ensure unique IDs
78
+ idMap.set(node.id, `${node.type}-${now + index}`);
79
+ });
80
+
81
+ // Offset for pasted nodes to avoid stacking on original
82
+ const PASTE_OFFSET = 50;
83
+
84
+ // Create new nodes with offset, new IDs, and unique labels
85
+ const newNodes: Node[] = [];
86
+
87
+ for (let i = 0; i < copiedNodes.length; i++) {
88
+ const node = copiedNodes[i];
89
+ const newId = idMap.get(node.id)!;
90
+
91
+ // Always use the original display name from node definition as base
92
+ // This ensures "WhatsApp Receive 2" copies become "WhatsApp Receive 3", not "WhatsApp Receive 2 1"
93
+ const baseDisplayName = nodeDefinitions[node.type!]?.displayName || node.type!;
94
+
95
+ // Generate unique label considering existing nodes AND nodes we're about to add
96
+ const allNodes = [...nodes, ...newNodes];
97
+ const uniqueLabel = generateUniqueLabel(baseDisplayName, node.type!, allNodes);
98
+
99
+ const newNode: Node = {
100
+ ...node,
101
+ id: newId,
102
+ position: {
103
+ x: node.position.x + PASTE_OFFSET,
104
+ y: node.position.y + PASTE_OFFSET,
105
+ },
106
+ selected: true,
107
+ data: {
108
+ ...node.data,
109
+ label: uniqueLabel,
110
+ },
111
+ };
112
+
113
+ // Save parameters for new node to database
114
+ if (saveNodeParameters && newNode.data) {
115
+ try {
116
+ await saveNodeParameters(newId, newNode.data);
117
+ } catch (error) {
118
+ console.error(`[CopyPaste] Failed to save parameters for ${newId}:`, error);
119
+ }
120
+ }
121
+
122
+ newNodes.push(newNode);
123
+ }
124
+
125
+ // Create new edges with updated source/target IDs
126
+ const newEdges: Edge[] = copiedEdges.map(edge => ({
127
+ ...edge,
128
+ id: `e-${idMap.get(edge.source)}-${idMap.get(edge.target)}-${Date.now()}`,
129
+ source: idMap.get(edge.source)!,
130
+ target: idMap.get(edge.target)!,
131
+ selected: false,
132
+ }));
133
+
134
+ // Deselect existing nodes and add new ones as selected
135
+ setNodes(nds => [
136
+ ...nds.map(n => ({ ...n, selected: false })),
137
+ ...newNodes,
138
+ ]);
139
+
140
+ setEdges(eds => [...eds, ...newEdges]);
141
+
142
+ console.log(`[CopyPaste] Pasted ${newNodes.length} nodes and ${newEdges.length} edges`);
143
+ }, [nodes, setNodes, setEdges, saveNodeParameters]);
144
+
145
+ /**
146
+ * Check if clipboard has content.
147
+ */
148
+ const hasClipboard = clipboardRef.current !== null && clipboardRef.current.nodes.length > 0;
149
+
150
+ return {
151
+ copySelectedNodes,
152
+ pasteNodes,
153
+ hasClipboard,
154
+ };
155
+ };
@@ -0,0 +1,124 @@
1
+ import { useCallback } from 'react';
2
+ import { Node } from 'reactflow';
3
+ import { snapToGrid, getDefaultNodePosition } from '../utils/workflow';
4
+ import { theme } from '../styles/theme';
5
+ import { nodeDefinitions } from '../nodeDefinitions';
6
+
7
+ interface UseDragAndDropProps {
8
+ nodes: Node[];
9
+ setNodes: (nodes: Node[] | ((nodes: Node[]) => Node[])) => void;
10
+ saveNodeParameters?: (nodeId: string, parameters: Record<string, any>) => Promise<boolean>;
11
+ }
12
+
13
+ /**
14
+ * Generate a unique label for a new node (n8n pattern).
15
+ * First node of type: "Cron Scheduler"
16
+ * Second node of type: "Cron Scheduler 1"
17
+ * Third node of type: "Cron Scheduler 2"
18
+ */
19
+ export const generateUniqueLabel = (displayName: string, nodeType: string, existingNodes: Node[]): string => {
20
+ // Collect all labels from nodes of the same type
21
+ const existingLabels = existingNodes
22
+ .filter(n => n.type === nodeType)
23
+ .map(n => n.data?.label as string | undefined)
24
+ .filter((label): label is string => !!label);
25
+
26
+ // If no nodes have this base displayName, use it as-is
27
+ if (!existingLabels.includes(displayName)) {
28
+ return displayName;
29
+ }
30
+
31
+ // Find next available suffix
32
+ let suffix = 1;
33
+ while (existingLabels.includes(`${displayName} ${suffix}`)) {
34
+ suffix++;
35
+ }
36
+ return `${displayName} ${suffix}`;
37
+ };
38
+
39
+ export const useDragAndDrop = ({ nodes, setNodes, saveNodeParameters }: UseDragAndDropProps) => {
40
+ const onDragOver = useCallback((event: React.DragEvent) => {
41
+ event.preventDefault();
42
+ event.dataTransfer.dropEffect = 'move';
43
+ }, []);
44
+
45
+ const onDrop = useCallback(
46
+ async (event: React.DragEvent) => {
47
+ event.preventDefault();
48
+
49
+ try {
50
+ const nodeData = JSON.parse(event.dataTransfer.getData('application/reactflow'));
51
+ if (!nodeData || !nodeData.type) {
52
+ return;
53
+ }
54
+
55
+ const reactFlowBounds = (event.target as Element).getBoundingClientRect();
56
+
57
+ let position = {
58
+ x: event.clientX - reactFlowBounds.left - theme.constants.dragOffset.x,
59
+ y: event.clientY - reactFlowBounds.top - theme.constants.dragOffset.y,
60
+ };
61
+
62
+ // Snap to grid for better alignment
63
+ position = snapToGrid(position);
64
+
65
+ // If no nodes exist, use default position
66
+ if (nodes.length === 0) {
67
+ position = getDefaultNodePosition(nodes.length);
68
+ }
69
+
70
+ // Get node definition to access displayName
71
+ const nodeDef = nodeDefinitions[nodeData.type];
72
+ const displayName = nodeDef?.displayName || nodeData.type;
73
+
74
+ // Generate unique label for template variable resolution (n8n pattern)
75
+ const uniqueLabel = generateUniqueLabel(displayName, nodeData.type, nodes);
76
+
77
+ const newNode: Node = {
78
+ id: `${nodeData.type}-${Date.now()}`,
79
+ type: nodeData.type,
80
+ position,
81
+ data: {
82
+ ...(nodeData.data || {}),
83
+ label: uniqueLabel, // Add unique label for template resolution
84
+ },
85
+ };
86
+
87
+ // Save node parameters to database immediately via WebSocket (including hidden parameters)
88
+ if (newNode.data && Object.keys(newNode.data).length > 0 && saveNodeParameters) {
89
+ try {
90
+ await saveNodeParameters(newNode.id, newNode.data);
91
+ console.log(`[DragAndDrop] Saved default parameters for node ${newNode.id}:`, newNode.data);
92
+ } catch (error) {
93
+ console.error(`[DragAndDrop] Failed to save parameters for node ${newNode.id}:`, error);
94
+ }
95
+ }
96
+
97
+ setNodes((nds) => nds.concat(newNode));
98
+ } catch (error) {
99
+ console.error('Error dropping node:', error);
100
+ }
101
+ },
102
+ [setNodes, nodes, saveNodeParameters]
103
+ );
104
+
105
+ const handleComponentDragStart = useCallback((event: React.DragEvent, definition: any) => {
106
+ const defaults: any = {};
107
+ // Handle both old (parameters) and new (properties) interface formats
108
+ const properties = definition.properties || definition.parameters || [];
109
+ properties.forEach((param: any) => {
110
+ defaults[param.name] = param.default;
111
+ });
112
+ event.dataTransfer.setData('application/reactflow', JSON.stringify({
113
+ type: definition.name || definition.type, // Handle both new (name) and old (type) formats
114
+ data: defaults
115
+ }));
116
+ event.dataTransfer.effectAllowed = 'move';
117
+ }, []);
118
+
119
+ return {
120
+ onDragOver,
121
+ onDrop,
122
+ handleComponentDragStart,
123
+ };
124
+ };
@@ -0,0 +1,88 @@
1
+ import { useCallback } from 'react';
2
+ import { useAppStore } from '../store/useAppStore';
3
+ import { nodeDefinitions } from '../nodeDefinitions';
4
+ import { Node } from 'reactflow';
5
+
6
+ interface DragVariableHookReturn {
7
+ handleVariableDragStart: (
8
+ e: React.DragEvent,
9
+ sourceNodeId: string,
10
+ propertyPath: string,
11
+ value: any
12
+ ) => void;
13
+ getTemplateVariableName: (sourceNodeId: string) => string;
14
+ }
15
+
16
+ /**
17
+ * Hook for handling drag-and-drop of template variables from connected nodes.
18
+ *
19
+ * Template variables use the SOURCE NODE LABEL as the key to ensure uniqueness
20
+ * when multiple nodes of the same type are connected to the same target.
21
+ * This follows the n8n pattern where each node has a unique label.
22
+ *
23
+ * Format: {{normalizedLabel.propertyPath}}
24
+ * Example: {{cronscheduler.data}} or {{cronscheduler1.data}}
25
+ *
26
+ * @param targetNodeId - The ID of the node receiving the drag (target of edge)
27
+ */
28
+ export const useDragVariable = (_targetNodeId: string): DragVariableHookReturn => {
29
+ const { currentWorkflow } = useAppStore();
30
+
31
+ /**
32
+ * Get template variable name for a source node.
33
+ * Uses node label (n8n pattern) for uniqueness when multiple same-type nodes connect.
34
+ * Falls back to displayName -> nodeType -> nodeId for backward compatibility.
35
+ */
36
+ const getTemplateVariableName = useCallback((sourceNodeId: string): string => {
37
+ if (!currentWorkflow) return sourceNodeId;
38
+
39
+ // Find the source node by ID
40
+ const sourceNode = (currentWorkflow.nodes || []).find(
41
+ (node: Node) => node.id === sourceNodeId
42
+ );
43
+
44
+ if (!sourceNode) return sourceNodeId;
45
+
46
+ // Priority: node.data.label > displayName > nodeType > nodeId
47
+ const label = sourceNode.data?.label;
48
+ if (label && typeof label === 'string') {
49
+ // Normalize label: lowercase, remove spaces
50
+ return label.toLowerCase().replace(/\s+/g, '');
51
+ }
52
+
53
+ // Fallback to displayName from node definition
54
+ const nodeDef = nodeDefinitions[sourceNode.type || ''];
55
+ if (nodeDef?.displayName) {
56
+ return nodeDef.displayName.toLowerCase().replace(/\s+/g, '');
57
+ }
58
+
59
+ // Ultimate fallback: node type or ID
60
+ return (sourceNode.type || sourceNodeId).toLowerCase().replace(/\s+/g, '');
61
+ }, [currentWorkflow]);
62
+
63
+ const handleVariableDragStart = useCallback((
64
+ e: React.DragEvent,
65
+ sourceNodeId: string,
66
+ propertyPath: string,
67
+ value: any
68
+ ) => {
69
+ const templateName = getTemplateVariableName(sourceNodeId);
70
+ const variableTemplate = `{{${templateName}.${propertyPath}}}`;
71
+
72
+ e.dataTransfer.setData('text/plain', variableTemplate);
73
+ e.dataTransfer.setData('application/json', JSON.stringify({
74
+ type: 'nodeVariable',
75
+ nodeId: sourceNodeId, // Include source node ID for reference
76
+ nodeName: templateName,
77
+ key: propertyPath,
78
+ variableTemplate,
79
+ dataType: typeof value
80
+ }));
81
+ e.dataTransfer.effectAllowed = 'copy';
82
+ }, [getTemplateVariableName]);
83
+
84
+ return {
85
+ handleVariableDragStart,
86
+ getTemplateVariableName
87
+ };
88
+ };