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,357 @@
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
2
+ import { Handle, Position, NodeProps } from 'reactflow';
3
+ import { nodeDefinitions } from '../nodeDefinitions';
4
+ import { NodeData } from '../types/NodeTypes';
5
+ import { INodeInputDefinition, INodeOutputDefinition, NodeConnectionType } from '../types/INodeProperties';
6
+ import { useAppStore } from '../store/useAppStore';
7
+ import { useAppTheme } from '../hooks/useAppTheme';
8
+ import { useWebSocket } from '../contexts/WebSocketContext';
9
+
10
+ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
11
+ const theme = useAppTheme();
12
+ const { setSelectedNode, renamingNodeId, setRenamingNodeId, updateNodeData } = useAppStore();
13
+ const { getNodeStatus } = useWebSocket();
14
+ const isDisabled = data?.disabled === true;
15
+
16
+ // Get execution status from WebSocket
17
+ const nodeStatus = getNodeStatus(id);
18
+ const executionStatus = nodeStatus?.status || 'idle';
19
+ const isExecuting = executionStatus === 'executing' || executionStatus === 'waiting';
20
+
21
+ // Get definition early for use in hooks
22
+ const definition = type && nodeDefinitions[type] ? nodeDefinitions[type] : null;
23
+
24
+ // Inline rename state
25
+ const [isRenaming, setIsRenaming] = useState(false);
26
+ const [editLabel, setEditLabel] = useState('');
27
+ const inputRef = useRef<HTMLInputElement>(null);
28
+
29
+ // Sync with global renaming state
30
+ useEffect(() => {
31
+ if (renamingNodeId === id) {
32
+ setIsRenaming(true);
33
+ setEditLabel(data?.label || definition?.displayName || type || '');
34
+ } else {
35
+ setIsRenaming(false);
36
+ }
37
+ }, [renamingNodeId, id, data?.label, definition?.displayName, type]);
38
+
39
+ // Focus and select input when entering rename mode
40
+ useEffect(() => {
41
+ if (isRenaming && inputRef.current) {
42
+ inputRef.current.focus();
43
+ inputRef.current.select();
44
+ }
45
+ }, [isRenaming]);
46
+
47
+ // Handle save rename
48
+ const handleSaveRename = useCallback(() => {
49
+ const newLabel = editLabel.trim();
50
+ const originalLabel = data?.label || definition?.displayName || type || '';
51
+
52
+ // Only save if label changed and is not empty
53
+ if (newLabel && newLabel !== originalLabel) {
54
+ updateNodeData(id, { ...data, label: newLabel });
55
+ }
56
+
57
+ setIsRenaming(false);
58
+ setRenamingNodeId(null);
59
+ }, [editLabel, data, definition?.displayName, type, id, updateNodeData, setRenamingNodeId]);
60
+
61
+ // Handle cancel rename
62
+ const handleCancelRename = useCallback(() => {
63
+ setIsRenaming(false);
64
+ setRenamingNodeId(null);
65
+ }, [setRenamingNodeId]);
66
+
67
+ // Handle double-click to rename
68
+ const handleLabelDoubleClick = useCallback((e: React.MouseEvent) => {
69
+ e.stopPropagation();
70
+ setRenamingNodeId(id);
71
+ }, [id, setRenamingNodeId]);
72
+
73
+ const handleParametersClick = (e: React.MouseEvent) => {
74
+ e.stopPropagation();
75
+ setSelectedNode({ id, type, data, position: { x: 0, y: 0 } });
76
+ };
77
+
78
+ if (!type || !definition) {
79
+ return (
80
+ <div style={{
81
+ padding: '8px 12px',
82
+ backgroundColor: '#ef4444',
83
+ color: 'white',
84
+ borderRadius: '8px',
85
+ fontSize: '12px',
86
+ minWidth: '120px',
87
+ textAlign: 'center'
88
+ }}>
89
+ Unknown node type
90
+ </div>
91
+ );
92
+ }
93
+
94
+ // Helper functions to get inputs/outputs for both enhanced and legacy nodes
95
+ const getNodeInputs = (): INodeInputDefinition[] => {
96
+ if (!definition.inputs) return [];
97
+
98
+ // Enhanced nodes: array of input objects
99
+ if (definition.inputs.length > 0 && typeof definition.inputs[0] === 'object') {
100
+ return definition.inputs as INodeInputDefinition[];
101
+ }
102
+
103
+ // Legacy nodes: array of strings - convert to input objects
104
+ return (definition.inputs as string[]).map((input, index) => ({
105
+ name: `input_${index}`,
106
+ displayName: 'Input',
107
+ type: (input as NodeConnectionType) || 'main',
108
+ description: 'Node input connection'
109
+ }));
110
+ };
111
+
112
+ const getNodeOutputs = (): INodeOutputDefinition[] => {
113
+ if (!definition.outputs) return [];
114
+
115
+ // Enhanced nodes: array of output objects
116
+ if (definition.outputs.length > 0 && typeof definition.outputs[0] === 'object') {
117
+ return definition.outputs as INodeOutputDefinition[];
118
+ }
119
+
120
+ // Legacy nodes: array of strings - convert to output objects
121
+ return (definition.outputs as string[]).map((output, index) => ({
122
+ name: `output_${index}`,
123
+ displayName: 'Output',
124
+ type: (output as NodeConnectionType) || 'main',
125
+ description: 'Node output connection'
126
+ }));
127
+ };
128
+
129
+ const nodeInputs = getNodeInputs();
130
+ const nodeOutputs = getNodeOutputs();
131
+
132
+ // Helper functions for color management
133
+ const getNodeColor = () => definition.defaults.color || '#9E9E9E';
134
+ const getBorderColor = () => {
135
+ const color = getNodeColor();
136
+ if (color.startsWith('#')) {
137
+ const hex = color.substring(1);
138
+ const r = Math.max(0, parseInt(hex.substring(0, 2), 16) - 40);
139
+ const g = Math.max(0, parseInt(hex.substring(2, 4), 16) - 40);
140
+ const b = Math.max(0, parseInt(hex.substring(4, 6), 16) - 40);
141
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
142
+ }
143
+ return color;
144
+ };
145
+
146
+ return (
147
+ <div
148
+ style={{
149
+ position: 'relative',
150
+ padding: '12px 32px 12px 16px',
151
+ minWidth: '160px',
152
+ minHeight: '60px',
153
+ borderRadius: '12px',
154
+ background: `linear-gradient(135deg, ${getNodeColor()} 0%, ${getBorderColor()} 100%)`,
155
+ border: `2px solid ${isExecuting
156
+ ? (theme.isDarkMode ? theme.dracula.cyan : '#2563eb')
157
+ : selected
158
+ ? '#3b82f6'
159
+ : getBorderColor()}`,
160
+ color: 'white',
161
+ fontFamily: 'system-ui, -apple-system, sans-serif',
162
+ fontSize: '14px',
163
+ fontWeight: '600',
164
+ textAlign: 'center',
165
+ cursor: 'pointer',
166
+ transition: 'all 0.2s ease',
167
+ boxShadow: isExecuting
168
+ ? theme.isDarkMode
169
+ ? `0 4px 12px ${theme.dracula.cyan}66, 0 0 0 3px ${theme.dracula.cyan}4D`
170
+ : `0 0 0 3px rgba(37, 99, 235, 0.5), 0 4px 16px rgba(37, 99, 235, 0.35)`
171
+ : selected
172
+ ? `0 8px 25px ${getNodeColor()}40, 0 0 0 2px ${theme.colors.focus}`
173
+ : theme.isDarkMode
174
+ ? `0 4px 12px ${getNodeColor()}40`
175
+ : `0 2px 8px ${getNodeColor()}25, 0 4px 16px rgba(0, 0, 0, 0.08)`,
176
+ overflow: 'visible',
177
+ opacity: isDisabled ? 0.5 : 1,
178
+ animation: isExecuting ? 'pulse 1.5s ease-in-out infinite' : 'none',
179
+ }}
180
+ >
181
+ {/* Disabled Overlay */}
182
+ {isDisabled && (
183
+ <div style={{
184
+ position: 'absolute',
185
+ top: 0,
186
+ left: 0,
187
+ right: 0,
188
+ bottom: 0,
189
+ backgroundColor: 'rgba(128, 128, 128, 0.4)',
190
+ borderRadius: 'inherit',
191
+ zIndex: 25,
192
+ display: 'flex',
193
+ alignItems: 'center',
194
+ justifyContent: 'center',
195
+ pointerEvents: 'none',
196
+ }}>
197
+ <span style={{ fontSize: '24px', opacity: 0.8 }}>||</span>
198
+ </div>
199
+ )}
200
+ {/* Input Handles - Multiple handles based on node definition */}
201
+ {nodeInputs.map((input, index) => {
202
+ const totalInputs = nodeInputs.length;
203
+ const topPosition = totalInputs === 1 ? '50%' :
204
+ `${20 + (60 * index) / Math.max(totalInputs - 1, 1)}%`;
205
+
206
+ return (
207
+ <Handle
208
+ key={`input-${input.name}-${index}`}
209
+ id={`input-${input.name}`}
210
+ type="target"
211
+ position={Position.Left}
212
+ isConnectable={isConnectable}
213
+ style={{
214
+ position: 'absolute',
215
+ left: '-6px',
216
+ top: topPosition,
217
+ transform: 'translateY(-50%)',
218
+ width: '12px',
219
+ height: '12px',
220
+ backgroundColor: 'rgba(255,255,255,0.9)',
221
+ border: `2px solid ${getBorderColor()}`,
222
+ borderRadius: '50%'
223
+ }}
224
+ title={`${input.displayName}: ${input.description}`}
225
+ />
226
+ );
227
+ })}
228
+
229
+
230
+ {/* Parameters Button */}
231
+ <button
232
+ onClick={handleParametersClick}
233
+ style={{
234
+ position: 'absolute',
235
+ top: '8px',
236
+ right: '8px',
237
+ width: '20px',
238
+ height: '20px',
239
+ borderRadius: '50%',
240
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
241
+ border: 'none',
242
+ cursor: 'pointer',
243
+ display: 'flex',
244
+ alignItems: 'center',
245
+ justifyContent: 'center',
246
+ fontSize: '10px',
247
+ color: getNodeColor(),
248
+ fontWeight: '600',
249
+ transition: 'all 0.2s ease',
250
+ boxShadow: '0 1px 3px rgba(0, 0, 0, 0.2)',
251
+ zIndex: 20
252
+ }}
253
+ onMouseEnter={(e) => {
254
+ e.currentTarget.style.backgroundColor = 'white';
255
+ e.currentTarget.style.transform = 'scale(1.15)';
256
+ e.currentTarget.style.boxShadow = '0 2px 6px rgba(0, 0, 0, 0.3)';
257
+ }}
258
+ onMouseLeave={(e) => {
259
+ e.currentTarget.style.backgroundColor = 'rgba(255, 255, 255, 0.95)';
260
+ e.currentTarget.style.transform = 'scale(1)';
261
+ e.currentTarget.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.2)';
262
+ }}
263
+ title="Edit Parameters"
264
+ >
265
+ ⚙️
266
+ </button>
267
+
268
+ <div style={{
269
+ display: 'flex',
270
+ alignItems: 'center',
271
+ justifyContent: 'center',
272
+ gap: '8px',
273
+ position: 'relative',
274
+ zIndex: 10,
275
+ paddingRight: '4px'
276
+ }}>
277
+ <span style={{ fontSize: type === 'aiAgent' ? '18px' : '24px' }}>{definition.icon}</span>
278
+ {isRenaming ? (
279
+ <input
280
+ ref={inputRef}
281
+ type="text"
282
+ value={editLabel}
283
+ onChange={(e) => setEditLabel(e.target.value)}
284
+ onKeyDown={(e) => {
285
+ if (e.key === 'Enter') {
286
+ handleSaveRename();
287
+ } else if (e.key === 'Escape') {
288
+ handleCancelRename();
289
+ }
290
+ e.stopPropagation();
291
+ }}
292
+ onBlur={handleSaveRename}
293
+ onClick={(e) => e.stopPropagation()}
294
+ style={{
295
+ padding: '2px 4px',
296
+ fontSize: theme.fontSize.sm,
297
+ fontWeight: theme.fontWeight.medium,
298
+ color: theme.colors.text,
299
+ backgroundColor: theme.colors.backgroundElevated,
300
+ border: `1px solid ${theme.dracula.purple}`,
301
+ borderRadius: theme.borderRadius.sm,
302
+ outline: 'none',
303
+ minWidth: '60px',
304
+ maxWidth: '120px',
305
+ }}
306
+ />
307
+ ) : (
308
+ <span
309
+ onDoubleClick={handleLabelDoubleClick}
310
+ style={{
311
+ whiteSpace: 'nowrap',
312
+ overflow: 'hidden',
313
+ textOverflow: 'ellipsis',
314
+ maxWidth: '100%',
315
+ cursor: 'text',
316
+ }}
317
+ title="Double-click to rename"
318
+ >
319
+ {data?.label || definition.displayName}
320
+ </span>
321
+ )}
322
+ </div>
323
+
324
+
325
+ {/* Output Handles - Multiple handles based on node definition */}
326
+ {nodeOutputs.map((output, index) => {
327
+ const totalOutputs = nodeOutputs.length;
328
+ const topPosition = totalOutputs === 1 ? '50%' :
329
+ `${20 + (60 * index) / Math.max(totalOutputs - 1, 1)}%`;
330
+
331
+ return (
332
+ <Handle
333
+ key={`output-${output.name}-${index}`}
334
+ id={`output-${output.name}`}
335
+ type="source"
336
+ position={Position.Right}
337
+ isConnectable={isConnectable}
338
+ style={{
339
+ position: 'absolute',
340
+ right: '-6px',
341
+ top: topPosition,
342
+ transform: 'translateY(-50%)',
343
+ width: '12px',
344
+ height: '12px',
345
+ backgroundColor: 'rgba(255,255,255,0.9)',
346
+ border: `2px solid ${getBorderColor()}`,
347
+ borderRadius: '50%'
348
+ }}
349
+ title={`${output.displayName}: ${output.description}`}
350
+ />
351
+ );
352
+ })}
353
+ </div>
354
+ );
355
+ };
356
+
357
+ export default GenericNode;
@@ -0,0 +1,154 @@
1
+ import React, { useState } from 'react';
2
+ import Modal from './ui/Modal';
3
+ import LocationPanelLayout from './parameterPanel/LocationPanelLayout';
4
+ import { useParameterPanel } from '../hooks/useParameterPanel';
5
+ import { ExecutionService } from '../services/executionService';
6
+ import { useAppTheme } from '../hooks/useAppTheme';
7
+
8
+ const LocationParameterPanel: React.FC = () => {
9
+ const theme = useAppTheme();
10
+ const {
11
+ selectedNode,
12
+ nodeDefinition,
13
+ parameters,
14
+ hasUnsavedChanges,
15
+ handleParameterChange,
16
+ handleSave,
17
+ handleCancel,
18
+ } = useParameterPanel();
19
+
20
+
21
+ // Execution state (kept for potential future location node execution)
22
+ const [isExecuting, setIsExecuting] = useState(false);
23
+
24
+ const handleModalClose = () => {
25
+ handleCancel();
26
+ };
27
+
28
+ // Execute the current node (simplified - location nodes typically don't execute)
29
+ const handleRun = async () => {
30
+ if (!selectedNode || !nodeDefinition) return;
31
+
32
+ if (hasUnsavedChanges) {
33
+ await handleSave();
34
+ }
35
+
36
+ setIsExecuting(true);
37
+ try {
38
+ // Location nodes like createMap typically don't need backend execution
39
+ // They're configuration nodes that output map instances
40
+ } catch (error: any) {
41
+ console.error('Location node execution failed:', error);
42
+ } finally {
43
+ setIsExecuting(false);
44
+ }
45
+ };
46
+
47
+ // Check if node can be executed
48
+ const canExecute = selectedNode && nodeDefinition &&
49
+ ExecutionService.isNodeTypeSupported(nodeDefinition.name);
50
+
51
+ if (!selectedNode || !nodeDefinition) {
52
+ return null;
53
+ }
54
+
55
+ // Only show LocationParameterPanel for createMap node
56
+ if (nodeDefinition.name !== 'createMap') {
57
+ return null;
58
+ }
59
+
60
+ // Action button style helper - matches main ParameterPanel
61
+ const actionButtonStyle = (color: string, isDisabled = false): React.CSSProperties => ({
62
+ height: '32px',
63
+ padding: '0 14px',
64
+ display: 'flex',
65
+ alignItems: 'center',
66
+ gap: '6px',
67
+ backgroundColor: isDisabled ? `${theme.colors.primary}15` : `${color}25`,
68
+ color: isDisabled ? theme.colors.primary : color,
69
+ border: `1px solid ${isDisabled ? `${theme.colors.primary}40` : `${color}60`}`,
70
+ borderRadius: theme.borderRadius.sm,
71
+ fontSize: '13px',
72
+ fontWeight: 600,
73
+ cursor: isDisabled ? 'not-allowed' : 'pointer',
74
+ transition: `all ${theme.transitions.fast}`,
75
+ });
76
+
77
+ // Header actions with node name and buttons in middle area
78
+ const headerActions = (
79
+ <div style={{
80
+ display: 'flex',
81
+ gap: '16px',
82
+ alignItems: 'center'
83
+ }}>
84
+ {/* Node Name */}
85
+ <div style={{
86
+ display: 'flex',
87
+ alignItems: 'center',
88
+ gap: '8px',
89
+ fontSize: '15px',
90
+ fontWeight: 600,
91
+ color: theme.colors.text
92
+ }}>
93
+ <span>{nodeDefinition.icon}</span>
94
+ <span>{nodeDefinition.displayName}</span>
95
+ {hasUnsavedChanges && <span style={{ color: theme.accent.orange }}>*</span>}
96
+ </div>
97
+
98
+ {/* Buttons: Run, Save, Cancel */}
99
+ <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
100
+ {/* Run Button */}
101
+ {canExecute && (
102
+ <button
103
+ style={actionButtonStyle(theme.dracula.green, isExecuting)}
104
+ onClick={handleRun}
105
+ disabled={isExecuting}
106
+ title={isExecuting ? 'Execution in progress...' : 'Execute this node'}
107
+ >
108
+ {isExecuting ? 'Running...' : 'Run'}
109
+ </button>
110
+ )}
111
+
112
+ {/* Save Button */}
113
+ <button
114
+ style={actionButtonStyle(theme.dracula.purple, !hasUnsavedChanges)}
115
+ onClick={handleSave}
116
+ disabled={!hasUnsavedChanges}
117
+ >
118
+ Save
119
+ </button>
120
+
121
+ {/* Cancel Button */}
122
+ <button
123
+ style={actionButtonStyle(theme.dracula.pink, false)}
124
+ onClick={handleCancel}
125
+ >
126
+ Cancel
127
+ </button>
128
+ </div>
129
+ </div>
130
+ );
131
+
132
+ return (
133
+ <Modal
134
+ isOpen={!!selectedNode}
135
+ onClose={handleModalClose}
136
+ title="Node Configuration"
137
+ maxWidth="95vw"
138
+ maxHeight="95vh"
139
+ headerActions={headerActions}
140
+ >
141
+ {/* Modular Location Panel Layout */}
142
+ <LocationPanelLayout
143
+ selectedNode={selectedNode}
144
+ nodeDefinition={nodeDefinition}
145
+ parameters={parameters}
146
+ hasUnsavedChanges={hasUnsavedChanges}
147
+ onParameterChange={handleParameterChange}
148
+ showInputSection={true}
149
+ />
150
+ </Modal>
151
+ );
152
+ };
153
+
154
+ export default LocationParameterPanel;