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,250 @@
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
2
+ import { Handle, Position, NodeProps } from 'reactflow';
3
+ import { NodeData } from '../types/NodeTypes';
4
+ import { useAppStore } from '../store/useAppStore';
5
+ import { useAppTheme } from '../hooks/useAppTheme';
6
+ import { PlayCircleFilled } from '@ant-design/icons';
7
+
8
+ const StartNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
9
+ const { setSelectedNode, renamingNodeId, setRenamingNodeId, updateNodeData } = useAppStore();
10
+ const theme = useAppTheme();
11
+
12
+ // Inline rename state
13
+ const [isRenaming, setIsRenaming] = useState(false);
14
+ const [editLabel, setEditLabel] = useState('');
15
+ const inputRef = useRef<HTMLInputElement>(null);
16
+
17
+ const defaultLabel = 'Start';
18
+
19
+ // Sync with global renaming state
20
+ useEffect(() => {
21
+ if (renamingNodeId === id) {
22
+ setIsRenaming(true);
23
+ setEditLabel(data?.label || defaultLabel);
24
+ } else {
25
+ setIsRenaming(false);
26
+ }
27
+ }, [renamingNodeId, id, data?.label]);
28
+
29
+ // Focus and select input when entering rename mode
30
+ useEffect(() => {
31
+ if (isRenaming && inputRef.current) {
32
+ inputRef.current.focus();
33
+ inputRef.current.select();
34
+ }
35
+ }, [isRenaming]);
36
+
37
+ // Handle save rename
38
+ const handleSaveRename = useCallback(() => {
39
+ const newLabel = editLabel.trim();
40
+ const originalLabel = data?.label || defaultLabel;
41
+
42
+ // Only save if label changed and is not empty
43
+ if (newLabel && newLabel !== originalLabel) {
44
+ updateNodeData(id, { ...data, label: newLabel });
45
+ }
46
+
47
+ setIsRenaming(false);
48
+ setRenamingNodeId(null);
49
+ }, [editLabel, data, id, updateNodeData, setRenamingNodeId]);
50
+
51
+ // Handle cancel rename
52
+ const handleCancelRename = useCallback(() => {
53
+ setIsRenaming(false);
54
+ setRenamingNodeId(null);
55
+ }, [setRenamingNodeId]);
56
+
57
+ // Handle double-click to rename
58
+ const handleLabelDoubleClick = useCallback(() => {
59
+ setRenamingNodeId(id);
60
+ }, [id, setRenamingNodeId]);
61
+
62
+ const handleParametersClick = (e: React.MouseEvent) => {
63
+ e.stopPropagation();
64
+ setSelectedNode({ id, type, data, position: { x: 0, y: 0 } });
65
+ };
66
+
67
+ const nodeColor = theme.dracula.cyan; // Cyan color for start node (neutral "begin" color)
68
+
69
+ return (
70
+ <div
71
+ style={{
72
+ position: 'relative',
73
+ display: 'flex',
74
+ flexDirection: 'column',
75
+ alignItems: 'center',
76
+ fontFamily: 'system-ui, -apple-system, sans-serif',
77
+ fontSize: '11px',
78
+ cursor: 'pointer',
79
+ }}
80
+ >
81
+ {/* Main Square Node */}
82
+ <div
83
+ style={{
84
+ position: 'relative',
85
+ width: '60px',
86
+ height: '60px',
87
+ borderRadius: '8px',
88
+ background: theme.isDarkMode
89
+ ? `linear-gradient(135deg, ${nodeColor}20 0%, ${theme.colors.background} 100%)`
90
+ : `linear-gradient(145deg, #ffffff 0%, ${nodeColor}10 100%)`,
91
+ border: `2px solid ${selected
92
+ ? theme.colors.focus
93
+ : theme.isDarkMode ? `${nodeColor}60` : `${nodeColor}50`}`,
94
+ display: 'flex',
95
+ alignItems: 'center',
96
+ justifyContent: 'center',
97
+ color: theme.colors.text,
98
+ fontSize: '28px',
99
+ fontWeight: '600',
100
+ transition: 'all 0.2s ease',
101
+ boxShadow: selected
102
+ ? `0 4px 12px ${theme.colors.focusRing}, 0 0 0 1px ${theme.colors.focusRing}`
103
+ : theme.isDarkMode
104
+ ? `0 2px 8px ${nodeColor}30`
105
+ : `0 2px 8px ${nodeColor}25, 0 4px 12px rgba(0,0,0,0.06)`,
106
+ }}
107
+ >
108
+ {/* Play Icon */}
109
+ <PlayCircleFilled style={{ fontSize: 28, color: nodeColor }} />
110
+
111
+ {/* Parameters Button */}
112
+ <button
113
+ onClick={handleParametersClick}
114
+ style={{
115
+ position: 'absolute',
116
+ top: '-8px',
117
+ right: '-8px',
118
+ width: '16px',
119
+ height: '16px',
120
+ borderRadius: '3px',
121
+ backgroundColor: theme.isDarkMode ? theme.colors.backgroundAlt : '#ffffff',
122
+ border: `1px solid ${theme.isDarkMode ? theme.colors.border : `${nodeColor}40`}`,
123
+ cursor: 'pointer',
124
+ display: 'flex',
125
+ alignItems: 'center',
126
+ justifyContent: 'center',
127
+ fontSize: '8px',
128
+ color: theme.colors.textSecondary,
129
+ fontWeight: '400',
130
+ transition: 'all 0.2s ease',
131
+ zIndex: 30,
132
+ boxShadow: theme.isDarkMode
133
+ ? `0 1px 3px ${theme.colors.shadow}`
134
+ : `0 1px 4px ${nodeColor}20`
135
+ }}
136
+ title="Edit Parameters"
137
+ >
138
+ {'\u2699\uFE0F'}
139
+ </button>
140
+
141
+ {/* Status Indicator - always green for start */}
142
+ <div
143
+ style={{
144
+ position: 'absolute',
145
+ top: '-4px',
146
+ left: '-4px',
147
+ width: '10px',
148
+ height: '10px',
149
+ borderRadius: '50%',
150
+ backgroundColor: nodeColor,
151
+ border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
152
+ boxShadow: theme.isDarkMode
153
+ ? `0 1px 2px ${theme.colors.shadow}`
154
+ : '0 1px 3px rgba(0,0,0,0.15)',
155
+ zIndex: 30,
156
+ }}
157
+ title="Workflow start point"
158
+ />
159
+
160
+ {/* Input Handle - hidden but present for consistency */}
161
+ <Handle
162
+ id="input-main"
163
+ type="target"
164
+ position={Position.Left}
165
+ isConnectable={false}
166
+ style={{
167
+ visibility: 'hidden',
168
+ width: '1px',
169
+ height: '1px',
170
+ }}
171
+ />
172
+
173
+ {/* Output Handle */}
174
+ <Handle
175
+ id="output-main"
176
+ type="source"
177
+ position={Position.Right}
178
+ isConnectable={isConnectable}
179
+ style={{
180
+ position: 'absolute',
181
+ right: '-6px',
182
+ top: '50%',
183
+ transform: 'translateY(-50%)',
184
+ width: '8px',
185
+ height: '8px',
186
+ backgroundColor: nodeColor,
187
+ border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
188
+ borderRadius: '50%',
189
+ zIndex: 20
190
+ }}
191
+ title="Workflow Output"
192
+ />
193
+ </div>
194
+
195
+ {/* Name Below Square */}
196
+ {isRenaming ? (
197
+ <input
198
+ ref={inputRef}
199
+ type="text"
200
+ value={editLabel}
201
+ onChange={(e) => setEditLabel(e.target.value)}
202
+ onKeyDown={(e) => {
203
+ if (e.key === 'Enter') {
204
+ handleSaveRename();
205
+ } else if (e.key === 'Escape') {
206
+ handleCancelRename();
207
+ }
208
+ e.stopPropagation();
209
+ }}
210
+ onBlur={handleSaveRename}
211
+ onClick={(e) => e.stopPropagation()}
212
+ style={{
213
+ marginTop: '8px',
214
+ width: '100%',
215
+ maxWidth: '120px',
216
+ padding: '2px 4px',
217
+ fontSize: '12px',
218
+ fontWeight: '500',
219
+ color: theme.colors.text,
220
+ backgroundColor: theme.colors.backgroundElevated,
221
+ border: `1px solid ${theme.dracula.purple}`,
222
+ borderRadius: theme.borderRadius.sm,
223
+ outline: 'none',
224
+ textAlign: 'center',
225
+ }}
226
+ />
227
+ ) : (
228
+ <div
229
+ onDoubleClick={handleLabelDoubleClick}
230
+ style={{
231
+ marginTop: '8px',
232
+ fontSize: '12px',
233
+ fontWeight: '500',
234
+ color: theme.colors.text,
235
+ lineHeight: '1.2',
236
+ textAlign: 'center',
237
+ maxWidth: '120px',
238
+ cursor: 'text',
239
+ }}
240
+ title="Double-click to rename"
241
+ >
242
+ {data?.label || defaultLabel}
243
+ </div>
244
+ )}
245
+
246
+ </div>
247
+ );
248
+ };
249
+
250
+ export default StartNode;
@@ -0,0 +1,365 @@
1
+ /**
2
+ * ToolkitNode - A node component with top/bottom connectors for aggregating services.
3
+ * Used by Android Toolkit to receive Android service nodes from bottom and output to AI Agent from top.
4
+ *
5
+ * Based on SquareNode but with vertical handle layout:
6
+ * - Input handle at BOTTOM (receives from Android service nodes)
7
+ * - Output handle at TOP (connects to AI Agent's input-tools)
8
+ */
9
+
10
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
11
+ import { Handle, Position, NodeProps } from 'reactflow';
12
+ import { NodeData } from '../types/NodeTypes';
13
+ import { useAppStore } from '../store/useAppStore';
14
+ import { nodeDefinitions } from '../nodeDefinitions';
15
+ import { useAppTheme } from '../hooks/useAppTheme';
16
+ import { useWebSocket } from '../contexts/WebSocketContext';
17
+ import { SKILL_NODE_TYPES } from '../nodeDefinitions/skillNodes';
18
+
19
+ const ToolkitNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
20
+ const theme = useAppTheme();
21
+ const { setSelectedNode, renamingNodeId, setRenamingNodeId, updateNodeData } = useAppStore();
22
+
23
+ // Inline rename state
24
+ const [isRenaming, setIsRenaming] = useState(false);
25
+ const [editLabel, setEditLabel] = useState('');
26
+ const inputRef = useRef<HTMLInputElement>(null);
27
+
28
+ // Get node status from WebSocket context
29
+ const { getNodeStatus } = useWebSocket();
30
+ const nodeStatus = getNodeStatus(id);
31
+ const executionStatus = nodeStatus?.status || 'idle';
32
+
33
+ const definition = nodeDefinitions[type as keyof typeof nodeDefinitions];
34
+
35
+ // Check if this is a skill node (skill nodes don't have bottom input handle)
36
+ const isSkillNode = type ? SKILL_NODE_TYPES.includes(type) : false;
37
+
38
+ // Execution state
39
+ const isExecuting = executionStatus === 'executing' || executionStatus === 'waiting';
40
+
41
+ // Sync with global renaming state
42
+ useEffect(() => {
43
+ if (renamingNodeId === id) {
44
+ setIsRenaming(true);
45
+ setEditLabel(data?.label || definition?.displayName || type || '');
46
+ } else {
47
+ setIsRenaming(false);
48
+ }
49
+ }, [renamingNodeId, id, data?.label, definition?.displayName, type]);
50
+
51
+ // Focus and select input when entering rename mode
52
+ useEffect(() => {
53
+ if (isRenaming && inputRef.current) {
54
+ inputRef.current.focus();
55
+ inputRef.current.select();
56
+ }
57
+ }, [isRenaming]);
58
+
59
+ // Handle save rename
60
+ const handleSaveRename = useCallback(() => {
61
+ const newLabel = editLabel.trim();
62
+ const originalLabel = data?.label || definition?.displayName || type || '';
63
+
64
+ if (newLabel && newLabel !== originalLabel) {
65
+ updateNodeData(id, { ...data, label: newLabel });
66
+ }
67
+
68
+ setIsRenaming(false);
69
+ setRenamingNodeId(null);
70
+ }, [editLabel, data, definition?.displayName, type, id, updateNodeData, setRenamingNodeId]);
71
+
72
+ // Handle cancel rename
73
+ const handleCancelRename = useCallback(() => {
74
+ setIsRenaming(false);
75
+ setRenamingNodeId(null);
76
+ }, [setRenamingNodeId]);
77
+
78
+ // Handle double-click to rename
79
+ const handleLabelDoubleClick = useCallback(() => {
80
+ setRenamingNodeId(id);
81
+ }, [id, setRenamingNodeId]);
82
+
83
+ // Handle parameters click
84
+ const handleParametersClick = (e: React.MouseEvent) => {
85
+ e.stopPropagation();
86
+ setSelectedNode({ id, type, data, position: { x: 0, y: 0 } });
87
+ };
88
+
89
+ // Get the node color from definition or use Android green
90
+ const nodeColor = definition?.defaults?.color || '#3DDC84';
91
+
92
+ // Helper to check if string is emoji
93
+ const isEmoji = (str: string): boolean => {
94
+ const emojiRegex = /^(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\u200D(?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F))*$/u;
95
+ return emojiRegex.test(str);
96
+ };
97
+
98
+ // Helper to render icon (handles URLs, emojis, and icon names)
99
+ const renderIcon = (icon: string) => {
100
+ // Handle image URLs and data URIs
101
+ if (icon.startsWith('http') || icon.startsWith('data:') || icon.startsWith('/')) {
102
+ return (
103
+ <img
104
+ src={icon}
105
+ alt="icon"
106
+ style={{
107
+ width: '28px',
108
+ height: '28px',
109
+ objectFit: 'contain',
110
+ borderRadius: '4px'
111
+ }}
112
+ />
113
+ );
114
+ }
115
+
116
+ // If it's already an emoji, return it directly
117
+ if (isEmoji(icon)) {
118
+ return icon;
119
+ }
120
+
121
+ // Fallback
122
+ return icon;
123
+ };
124
+
125
+ // Get icon from definition
126
+ const getIcon = () => {
127
+ if (definition?.icon) {
128
+ return renderIcon(definition.icon);
129
+ }
130
+ return '📱'; // Default Android icon
131
+ };
132
+
133
+ // Get status indicator color
134
+ const getStatusIndicatorColor = () => {
135
+ if (isExecuting) return theme.dracula.cyan;
136
+ if (executionStatus === 'success') return theme.dracula.green;
137
+ if (executionStatus === 'error') return theme.dracula.red;
138
+ return theme.dracula.green; // Toolkit is always ready
139
+ };
140
+
141
+ return (
142
+ <div
143
+ style={{
144
+ position: 'relative',
145
+ display: 'flex',
146
+ flexDirection: 'column',
147
+ alignItems: 'center',
148
+ fontFamily: 'system-ui, -apple-system, sans-serif',
149
+ fontSize: '11px',
150
+ cursor: 'pointer',
151
+ }}
152
+ >
153
+ {/* Main Square Node */}
154
+ <div
155
+ style={{
156
+ position: 'relative',
157
+ width: theme.nodeSize.square,
158
+ height: theme.nodeSize.square,
159
+ borderRadius: theme.borderRadius.lg,
160
+ background: theme.isDarkMode
161
+ ? `linear-gradient(135deg, ${nodeColor}25 0%, ${theme.colors.background} 100%)`
162
+ : `linear-gradient(145deg, #ffffff 0%, ${nodeColor}08 100%)`,
163
+ border: `2px solid ${isExecuting
164
+ ? (theme.isDarkMode ? theme.dracula.cyan : '#2563eb')
165
+ : selected
166
+ ? theme.colors.focus
167
+ : theme.isDarkMode ? nodeColor + '80' : `${nodeColor}40`}`,
168
+ display: 'flex',
169
+ alignItems: 'center',
170
+ justifyContent: 'center',
171
+ color: theme.colors.text,
172
+ fontSize: theme.nodeSize.squareIcon,
173
+ fontWeight: '600',
174
+ transition: 'all 0.2s ease',
175
+ boxShadow: isExecuting
176
+ ? theme.isDarkMode
177
+ ? `0 4px 12px ${theme.dracula.cyan}66, 0 0 0 3px ${theme.dracula.cyan}4D`
178
+ : `0 0 0 3px rgba(37, 99, 235, 0.5), 0 4px 16px rgba(37, 99, 235, 0.35)`
179
+ : selected
180
+ ? `0 4px 12px ${theme.colors.focusRing}, 0 0 0 1px ${theme.colors.focusRing}`
181
+ : theme.isDarkMode
182
+ ? `0 2px 8px ${nodeColor}40`
183
+ : `0 2px 8px ${nodeColor}20, 0 4px 12px rgba(0,0,0,0.06)`,
184
+ animation: isExecuting ? 'pulse 1.5s ease-in-out infinite' : 'none',
185
+ }}
186
+ >
187
+ {/* Service Icon */}
188
+ {getIcon()}
189
+
190
+ {/* Parameters Button */}
191
+ <button
192
+ onClick={handleParametersClick}
193
+ style={{
194
+ position: 'absolute',
195
+ top: '-8px',
196
+ right: '-8px',
197
+ width: theme.nodeSize.paramButton,
198
+ height: theme.nodeSize.paramButton,
199
+ borderRadius: theme.borderRadius.sm,
200
+ backgroundColor: theme.isDarkMode ? theme.colors.backgroundAlt : '#ffffff',
201
+ border: `1px solid ${theme.isDarkMode ? theme.colors.border : '#d1d5db'}`,
202
+ cursor: 'pointer',
203
+ display: 'flex',
204
+ alignItems: 'center',
205
+ justifyContent: 'center',
206
+ fontSize: theme.fontSize.xs,
207
+ color: theme.colors.textSecondary,
208
+ fontWeight: '400',
209
+ transition: theme.transitions.fast,
210
+ zIndex: 30,
211
+ boxShadow: theme.isDarkMode
212
+ ? `0 1px 3px ${theme.colors.shadow}`
213
+ : '0 1px 4px rgba(0,0,0,0.1)'
214
+ }}
215
+ title="Edit Toolkit Parameters"
216
+ >
217
+ ⚙️
218
+ </button>
219
+
220
+ {/* Status Indicator */}
221
+ <div
222
+ style={{
223
+ position: 'absolute',
224
+ top: '-4px',
225
+ left: '-4px',
226
+ width: theme.nodeSize.statusIndicator,
227
+ height: theme.nodeSize.statusIndicator,
228
+ borderRadius: '50%',
229
+ backgroundColor: getStatusIndicatorColor(),
230
+ border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
231
+ boxShadow: isExecuting
232
+ ? theme.isDarkMode
233
+ ? `0 0 6px ${theme.dracula.cyan}80`
234
+ : '0 0 4px rgba(37, 99, 235, 0.5)'
235
+ : theme.isDarkMode
236
+ ? `0 1px 2px ${theme.colors.shadow}`
237
+ : '0 1px 3px rgba(0,0,0,0.15)',
238
+ zIndex: 30,
239
+ animation: isExecuting ? 'pulse 1s ease-in-out infinite' : 'none',
240
+ }}
241
+ title={isExecuting ? 'Executing...' : 'Toolkit ready'}
242
+ />
243
+
244
+ {/* TOP Output Handle - connects to AI Agent/Chat Agent */}
245
+ <Handle
246
+ id="output-main"
247
+ type="source"
248
+ position={Position.Top}
249
+ isConnectable={isConnectable}
250
+ style={{
251
+ position: 'absolute',
252
+ top: '-6px',
253
+ left: '50%',
254
+ transform: 'translateX(-50%)',
255
+ width: theme.nodeSize.handle,
256
+ height: theme.nodeSize.handle,
257
+ backgroundColor: nodeColor,
258
+ border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
259
+ borderRadius: '50%',
260
+ zIndex: 20
261
+ }}
262
+ title={isSkillNode ? "Connect to Chat Agent's skill input" : "Connect to AI Agent's tool input"}
263
+ />
264
+
265
+ {/* BOTTOM Input Handle - receives from Android nodes (not shown for skill nodes) */}
266
+ {!isSkillNode && (
267
+ <Handle
268
+ id="input-main"
269
+ type="target"
270
+ position={Position.Bottom}
271
+ isConnectable={isConnectable}
272
+ style={{
273
+ position: 'absolute',
274
+ bottom: '-6px',
275
+ left: '50%',
276
+ transform: 'translateX(-50%)',
277
+ width: theme.nodeSize.handle,
278
+ height: theme.nodeSize.handle,
279
+ backgroundColor: theme.isDarkMode ? theme.colors.background : '#ffffff',
280
+ border: `2px solid ${theme.isDarkMode ? theme.colors.textSecondary : '#6b7280'}`,
281
+ borderRadius: '50%',
282
+ zIndex: 20
283
+ }}
284
+ title="Connect Android service nodes here"
285
+ />
286
+ )}
287
+
288
+ {/* Output Data Indicator */}
289
+ {executionStatus === 'success' && nodeStatus?.data && (
290
+ <div
291
+ style={{
292
+ position: 'absolute',
293
+ bottom: '-4px',
294
+ right: '-4px',
295
+ width: theme.nodeSize.outputBadge,
296
+ height: theme.nodeSize.outputBadge,
297
+ borderRadius: theme.borderRadius.sm,
298
+ backgroundColor: theme.dracula.green,
299
+ border: `1px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
300
+ display: 'flex',
301
+ alignItems: 'center',
302
+ justifyContent: 'center',
303
+ fontSize: theme.fontSize.xs,
304
+ color: 'white',
305
+ fontWeight: 'bold',
306
+ zIndex: 30,
307
+ boxShadow: theme.isDarkMode
308
+ ? '0 1px 3px rgba(0,0,0,0.2)'
309
+ : '0 1px 3px rgba(0,0,0,0.15)',
310
+ }}
311
+ title="Output data available"
312
+ >
313
+ <span style={{ lineHeight: 1 }}>D</span>
314
+ </div>
315
+ )}
316
+ </div>
317
+
318
+ {/* Node Name Below */}
319
+ {isRenaming ? (
320
+ <input
321
+ ref={inputRef}
322
+ type="text"
323
+ value={editLabel}
324
+ onChange={(e) => setEditLabel(e.target.value)}
325
+ onBlur={handleSaveRename}
326
+ onKeyDown={(e) => {
327
+ if (e.key === 'Enter') handleSaveRename();
328
+ if (e.key === 'Escape') handleCancelRename();
329
+ }}
330
+ style={{
331
+ marginTop: theme.spacing.sm,
332
+ fontSize: theme.fontSize.sm,
333
+ fontWeight: theme.fontWeight.medium,
334
+ color: theme.colors.text,
335
+ backgroundColor: theme.colors.background,
336
+ border: `1px solid ${theme.colors.focus}`,
337
+ borderRadius: theme.borderRadius.sm,
338
+ padding: '2px 6px',
339
+ textAlign: 'center',
340
+ width: '100px',
341
+ outline: 'none',
342
+ }}
343
+ />
344
+ ) : (
345
+ <div
346
+ onDoubleClick={handleLabelDoubleClick}
347
+ style={{
348
+ marginTop: theme.spacing.sm,
349
+ fontSize: theme.fontSize.sm,
350
+ fontWeight: theme.fontWeight.medium,
351
+ color: theme.colors.text,
352
+ lineHeight: '1.2',
353
+ textAlign: 'center',
354
+ maxWidth: '120px',
355
+ cursor: 'text',
356
+ }}
357
+ >
358
+ {data?.label || definition?.displayName}
359
+ </div>
360
+ )}
361
+ </div>
362
+ );
363
+ };
364
+
365
+ export default ToolkitNode;