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,1150 @@
1
+ import React, { useState } from 'react';
2
+ import { useAppTheme } from '../../hooks/useAppTheme';
3
+ import { ExecutionResult } from '../../services/executionService';
4
+ import { Node } from 'reactflow';
5
+ import { copyToClipboard } from '../../utils/formatters';
6
+
7
+ // Simple JSON syntax highlighter using Dracula colors
8
+ const highlightJSON = (json: string, dracula: any): React.ReactNode => {
9
+ const lines = json.split('\n');
10
+ return lines.map((line, i) => {
11
+ // Highlight different parts of JSON
12
+ const highlighted = line
13
+ .replace(/"([^"]+)":/g, `<span style="color: ${dracula.cyan}">"$1"</span>:`)
14
+ .replace(/: "([^"]+)"/g, `: <span style="color: ${dracula.yellow}">"$1"</span>`)
15
+ .replace(/: (\d+\.?\d*)/g, `: <span style="color: ${dracula.purple}">$1</span>`)
16
+ .replace(/: (true|false)/g, `: <span style="color: ${dracula.purple}">$1</span>`)
17
+ .replace(/: (null)/g, `: <span style="color: ${dracula.orange}">$1</span>`);
18
+ return (
19
+ <div key={i} dangerouslySetInnerHTML={{ __html: highlighted }} />
20
+ );
21
+ });
22
+ };
23
+
24
+ // Collapsible thinking/reasoning block for AI model responses
25
+ interface ThinkingBlockProps {
26
+ thinking: string;
27
+ provider?: string;
28
+ theme: ReturnType<typeof useAppTheme>;
29
+ }
30
+
31
+ const ThinkingBlock: React.FC<ThinkingBlockProps> = ({ thinking, provider, theme }) => {
32
+ const [isExpanded, setIsExpanded] = useState(true);
33
+
34
+ if (!thinking || !thinking.trim()) return null;
35
+
36
+ return (
37
+ <div style={{
38
+ marginBottom: theme.spacing.md,
39
+ backgroundColor: theme.colors.backgroundElevated,
40
+ border: `1px solid ${theme.dracula.purple}40`,
41
+ borderRadius: theme.borderRadius.md,
42
+ borderLeft: `3px solid ${theme.dracula.purple}`,
43
+ overflow: 'hidden',
44
+ }}>
45
+ <div
46
+ onClick={() => setIsExpanded(!isExpanded)}
47
+ style={{
48
+ padding: theme.spacing.md,
49
+ cursor: 'pointer',
50
+ display: 'flex',
51
+ alignItems: 'center',
52
+ justifyContent: 'space-between',
53
+ backgroundColor: `${theme.dracula.purple}10`,
54
+ transition: theme.transitions.fast,
55
+ }}
56
+ >
57
+ <div style={{ display: 'flex', alignItems: 'center', gap: theme.spacing.sm }}>
58
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={theme.dracula.purple} strokeWidth="2">
59
+ <circle cx="12" cy="12" r="10"/>
60
+ <path d="M12 6v6l4 2"/>
61
+ </svg>
62
+ <span style={{
63
+ color: theme.dracula.purple,
64
+ fontWeight: theme.fontWeight.semibold,
65
+ fontSize: theme.fontSize.xs,
66
+ textTransform: 'uppercase',
67
+ letterSpacing: '0.1em',
68
+ }}>
69
+ Thinking Process {provider && `(${provider})`}
70
+ </span>
71
+ </div>
72
+ <svg
73
+ width="12" height="12" viewBox="0 0 24 24" fill="none"
74
+ stroke={theme.dracula.purple} strokeWidth="2"
75
+ style={{ transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 0.2s ease' }}
76
+ >
77
+ <polyline points="9 18 15 12 9 6"/>
78
+ </svg>
79
+ </div>
80
+ {isExpanded && (
81
+ <pre style={{
82
+ padding: theme.spacing.md,
83
+ margin: 0,
84
+ whiteSpace: 'pre-wrap',
85
+ wordBreak: 'break-word',
86
+ fontSize: theme.fontSize.sm,
87
+ color: theme.colors.textSecondary,
88
+ fontFamily: '"Fira Code", Monaco, Menlo, monospace',
89
+ lineHeight: 1.6,
90
+ maxHeight: '300px',
91
+ overflow: 'auto',
92
+ backgroundColor: theme.colors.background,
93
+ }}>
94
+ {thinking}
95
+ </pre>
96
+ )}
97
+ </div>
98
+ );
99
+ };
100
+
101
+ interface NodeOutputPanelProps {
102
+ results: ExecutionResult[];
103
+ onClear?: () => void;
104
+ selectedNode?: Node | null;
105
+ }
106
+
107
+ const NodeOutputPanel: React.FC<NodeOutputPanelProps> = ({
108
+ results,
109
+ onClear,
110
+ selectedNode
111
+ }) => {
112
+ const theme = useAppTheme();
113
+ const [showRawJson, setShowRawJson] = useState(false);
114
+
115
+ // Filter results to only show current node's output
116
+ const nodeResults = selectedNode ? results.filter(result => result.nodeId === selectedNode.id) : results;
117
+
118
+ // Get output data with fallbacks
119
+ const getOutputData = (result: ExecutionResult) => {
120
+ if (result.outputs && Object.keys(result.outputs).length > 0) {
121
+ return result.outputs;
122
+ }
123
+ if (result.data && Object.keys(result.data).length > 0) {
124
+ return result.data;
125
+ }
126
+ if (result.nodeData && result.nodeData.length > 0 && result.nodeData[0].length > 0) {
127
+ return result.nodeData[0][0].json;
128
+ }
129
+ return {
130
+ success: result.success,
131
+ message: 'Execution completed'
132
+ };
133
+ };
134
+
135
+ // Helper to parse nested JSON strings in Android responses
136
+ const parseAndroidData = (data: any): any => {
137
+ if (typeof data !== 'object' || data === null) return data;
138
+
139
+ const parsed: any = Array.isArray(data) ? [] : {};
140
+ for (const key in data) {
141
+ const value = data[key];
142
+ // Try to parse string values that look like JSON arrays or objects
143
+ if (typeof value === 'string' && (value.startsWith('[') || value.startsWith('{'))) {
144
+ try {
145
+ parsed[key] = JSON.parse(value);
146
+ } catch {
147
+ parsed[key] = value;
148
+ }
149
+ } else if (typeof value === 'object' && value !== null) {
150
+ parsed[key] = parseAndroidData(value);
151
+ } else {
152
+ parsed[key] = value;
153
+ }
154
+ }
155
+ return parsed;
156
+ };
157
+
158
+ // Extract thinking content from result data
159
+ const getThinkingContent = (result: ExecutionResult): { thinking: string | null; provider?: string } => {
160
+ const data = getOutputData(result);
161
+ // Check nested result structure first (from backend)
162
+ if (data?.result?.thinking) {
163
+ return { thinking: data.result.thinking, provider: data.result.provider };
164
+ }
165
+ // Then top-level
166
+ if (data?.thinking) {
167
+ return { thinking: data.thinking, provider: data?.provider };
168
+ }
169
+ return { thinking: null };
170
+ };
171
+
172
+ // Structured output types for different node responses
173
+ type AndroidOutput = { type: 'android'; data: any; service?: string; action?: string };
174
+ type WhatsAppHistoryOutput = { type: 'whatsapp_history'; messages: any[]; total: number; count: number; hasMore: boolean };
175
+ type MapsNearbyOutput = { type: 'maps_nearby'; places: any[]; searchParams: any; total: number };
176
+ type MapsGeocodeOutput = { type: 'maps_geocode'; locations: any[]; total: number };
177
+ type MapsCreateOutput = { type: 'maps_create'; mapUrl: string; center: { lat: number; lng: number }; zoom: number; mapType: string };
178
+ type StructuredOutput = AndroidOutput | WhatsAppHistoryOutput | MapsNearbyOutput | MapsGeocodeOutput | MapsCreateOutput;
179
+
180
+ // Extract the main response text from execution results
181
+ const getMainResponse = (result: ExecutionResult): string | StructuredOutput | null => {
182
+ const data = getOutputData(result);
183
+
184
+ // Python node output
185
+ if (data?.output !== undefined) {
186
+ return typeof data.output === 'string' ? data.output : JSON.stringify(data.output, null, 2);
187
+ }
188
+ if (data?.result?.response) return data.result.response;
189
+ if (data?.response) return data.response;
190
+ if (data?.result?.text) return data.result.text;
191
+ if (data?.text) return data.text;
192
+ if (data?.result?.content) return data.result.content;
193
+ if (data?.content) return data.content;
194
+ if (data?.result?.message) return data.result.message;
195
+ if (data?.message && typeof data.message === 'string' && data.message !== 'Execution completed') return data.message;
196
+ // WhatsApp message preview
197
+ if (data?.result?.preview) return data.result.preview;
198
+ if (data?.preview) return data.preview;
199
+ // WhatsApp Chat History output
200
+ if (data?.result?.messages !== undefined && data?.result?.total !== undefined) {
201
+ return { type: 'whatsapp_history', messages: data.result.messages, total: data.result.total, count: data.result.count, hasMore: data.result.has_more };
202
+ }
203
+ if (data?.messages !== undefined && data?.total !== undefined) {
204
+ return { type: 'whatsapp_history', messages: data.messages, total: data.total, count: data.count || data.messages?.length, hasMore: data.has_more };
205
+ }
206
+ // Google Maps nearby places output
207
+ // Check nested result structure (from backend: { success, result: { results, search_parameters } })
208
+ if (data?.result?.results !== undefined && data?.result?.search_parameters !== undefined) {
209
+ return {
210
+ type: 'maps_nearby',
211
+ places: data.result.results,
212
+ searchParams: data.result.search_parameters,
213
+ total: data.result.total_results || data.result.results.length
214
+ };
215
+ }
216
+ // Also check top-level structure (if already unwrapped)
217
+ if (data?.results !== undefined && data?.search_parameters !== undefined) {
218
+ return {
219
+ type: 'maps_nearby',
220
+ places: data.results,
221
+ searchParams: data.search_parameters,
222
+ total: data.total_results || data.results.length
223
+ };
224
+ }
225
+ // Google Maps geocoding output
226
+ if (data?.result?.locations !== undefined && data?.result?.total_found !== undefined) {
227
+ return {
228
+ type: 'maps_geocode',
229
+ locations: data.result.locations,
230
+ total: data.result.total_found
231
+ };
232
+ }
233
+ // Geocode top-level fallback
234
+ if (data?.locations !== undefined && data?.total_found !== undefined) {
235
+ return {
236
+ type: 'maps_geocode',
237
+ locations: data.locations,
238
+ total: data.total_found
239
+ };
240
+ }
241
+ // Google Maps create map output
242
+ if (data?.result?.static_map_url !== undefined) {
243
+ return {
244
+ type: 'maps_create',
245
+ mapUrl: data.result.static_map_url,
246
+ center: data.result.center,
247
+ zoom: data.result.zoom,
248
+ mapType: data.result.map_type
249
+ };
250
+ }
251
+ // Create map top-level fallback
252
+ if (data?.static_map_url !== undefined) {
253
+ return {
254
+ type: 'maps_create',
255
+ mapUrl: data.static_map_url,
256
+ center: data.center,
257
+ zoom: data.zoom,
258
+ mapType: data.map_type
259
+ };
260
+ }
261
+ // Webhook trigger output
262
+ if (data?.method && data?.path !== undefined) {
263
+ let bodyData = data.json || (data.body ? (() => { try { return JSON.parse(data.body); } catch { return data.body; } })() : null);
264
+ const bodyStr = bodyData ? JSON.stringify(bodyData) : '';
265
+ return `${data.method} /${data.path}${bodyStr ? ` - ${bodyStr}` : ''}`;
266
+ }
267
+ // HTTP Request output - format status and data
268
+ if (data?.status !== undefined && data?.data !== undefined) {
269
+ const statusText = data.status >= 200 && data.status < 300 ? 'OK' : data.status >= 400 ? 'Error' : '';
270
+ const isHtml = typeof data.data === 'string' && data.data.trim().startsWith('<');
271
+ const isLong = typeof data.data === 'string' && data.data.length > 200;
272
+
273
+ if (isHtml) {
274
+ return `${data.status} ${statusText} - HTML response (${data.data.length} chars)`;
275
+ } else if (isLong) {
276
+ return `${data.status} ${statusText} - ${data.data.substring(0, 150)}...`;
277
+ } else if (typeof data.data === 'string') {
278
+ return `${data.status} ${statusText} - ${data.data}`;
279
+ } else {
280
+ return `${data.status} ${statusText}\n${JSON.stringify(data.data, null, 2)}`;
281
+ }
282
+ }
283
+ // Android service output - show received data with parsed nested JSON
284
+ if (data?.service_id && data?.data) {
285
+ const parsedData = parseAndroidData(data.data);
286
+ return { type: 'android', data: parsedData, service: data.service_id, action: data.action };
287
+ }
288
+ return null;
289
+ };
290
+
291
+ // Get the most recent result
292
+ const latestResult = nodeResults[0];
293
+
294
+ if (nodeResults.length === 0) {
295
+ const hasOtherResults = results.length > 0;
296
+
297
+ return (
298
+ <div style={{
299
+ width: '100%',
300
+ height: '100%',
301
+ backgroundColor: theme.colors.backgroundPanel,
302
+ display: 'flex',
303
+ flexDirection: 'column',
304
+ alignItems: 'center',
305
+ justifyContent: 'center',
306
+ padding: theme.spacing.xxl,
307
+ }}>
308
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke={theme.colors.textMuted} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ marginBottom: theme.spacing.lg }}>
309
+ <polygon points="5 3 19 12 5 21 5 3"/>
310
+ </svg>
311
+ <div style={{
312
+ fontSize: theme.fontSize.base,
313
+ fontWeight: theme.fontWeight.medium,
314
+ color: theme.colors.textSecondary,
315
+ marginBottom: theme.spacing.xs
316
+ }}>
317
+ No output yet
318
+ </div>
319
+ <div style={{
320
+ fontSize: theme.fontSize.sm,
321
+ color: theme.colors.textMuted,
322
+ textAlign: 'center'
323
+ }}>
324
+ Run the node to see results
325
+ </div>
326
+ {hasOtherResults && (
327
+ <div style={{
328
+ marginTop: theme.spacing.md,
329
+ fontSize: theme.fontSize.xs,
330
+ color: theme.dracula.orange,
331
+ padding: `${theme.spacing.xs} ${theme.spacing.md}`,
332
+ backgroundColor: theme.dracula.orange + '15',
333
+ borderRadius: theme.borderRadius.sm,
334
+ }}>
335
+ {results.length} result(s) from other nodes
336
+ </div>
337
+ )}
338
+ </div>
339
+ );
340
+ }
341
+
342
+ const outputData = getOutputData(latestResult);
343
+ const mainResponse = getMainResponse(latestResult);
344
+
345
+ return (
346
+ <div style={{
347
+ width: '100%',
348
+ height: '100%',
349
+ backgroundColor: theme.colors.backgroundPanel,
350
+ display: 'flex',
351
+ flexDirection: 'column',
352
+ overflow: 'hidden',
353
+ }}>
354
+ {/* Header */}
355
+ <div style={{
356
+ padding: `${theme.spacing.md} ${theme.spacing.lg}`,
357
+ borderBottom: `1px solid ${theme.colors.border}`,
358
+ display: 'flex',
359
+ alignItems: 'center',
360
+ justifyContent: 'space-between',
361
+ backgroundColor: theme.colors.backgroundElevated,
362
+ flexShrink: 0,
363
+ }}>
364
+ <div style={{ display: 'flex', alignItems: 'center', gap: theme.spacing.md }}>
365
+ {latestResult.success ? (
366
+ <svg width="20" height="20" viewBox="0 0 24 24" fill={theme.dracula.green} stroke="none">
367
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
368
+ </svg>
369
+ ) : (
370
+ <svg width="20" height="20" viewBox="0 0 24 24" fill={theme.dracula.red} stroke="none">
371
+ <path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/>
372
+ </svg>
373
+ )}
374
+ <span style={{
375
+ fontSize: theme.fontSize.base,
376
+ fontWeight: theme.fontWeight.semibold,
377
+ color: theme.colors.text,
378
+ }}>
379
+ Output
380
+ </span>
381
+ <span style={{
382
+ fontSize: theme.fontSize.xs,
383
+ fontWeight: theme.fontWeight.semibold,
384
+ color: latestResult.success ? theme.dracula.green : theme.dracula.red,
385
+ padding: `4px ${theme.spacing.md}`,
386
+ backgroundColor: latestResult.success ? theme.dracula.green + '25' : theme.dracula.red + '25',
387
+ borderRadius: theme.borderRadius.sm,
388
+ border: `1px solid ${latestResult.success ? theme.dracula.green : theme.dracula.red}50`,
389
+ letterSpacing: '0.05em',
390
+ }}>
391
+ {latestResult.success ? 'SUCCESS' : 'FAILED'}
392
+ </span>
393
+ {latestResult.executionTime > 0 && (
394
+ <span style={{
395
+ fontSize: theme.fontSize.sm,
396
+ color: theme.colors.textSecondary,
397
+ fontFamily: 'Monaco, Menlo, monospace',
398
+ }}>
399
+ {latestResult.executionTime.toFixed(2)}ms
400
+ </span>
401
+ )}
402
+ </div>
403
+ {onClear && (
404
+ <button
405
+ onClick={onClear}
406
+ style={{
407
+ padding: `${theme.spacing.xs} ${theme.spacing.md}`,
408
+ fontSize: theme.fontSize.xs,
409
+ color: theme.colors.textSecondary,
410
+ backgroundColor: 'transparent',
411
+ border: `1px solid ${theme.colors.border}`,
412
+ borderRadius: theme.borderRadius.sm,
413
+ cursor: 'pointer',
414
+ display: 'flex',
415
+ alignItems: 'center',
416
+ gap: theme.spacing.xs,
417
+ transition: theme.transitions.fast,
418
+ }}
419
+ onMouseEnter={(e) => {
420
+ e.currentTarget.style.backgroundColor = theme.colors.backgroundAlt;
421
+ }}
422
+ onMouseLeave={(e) => {
423
+ e.currentTarget.style.backgroundColor = 'transparent';
424
+ }}
425
+ >
426
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
427
+ <polyline points="3 6 5 6 21 6"/>
428
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
429
+ </svg>
430
+ Clear
431
+ </button>
432
+ )}
433
+ </div>
434
+
435
+ {/* Content */}
436
+ <div style={{
437
+ flex: 1,
438
+ overflow: 'auto',
439
+ padding: theme.spacing.md,
440
+ }}>
441
+ {/* Error Display */}
442
+ {latestResult.error && (
443
+ <div style={{
444
+ padding: theme.spacing.md,
445
+ marginBottom: theme.spacing.md,
446
+ backgroundColor: theme.dracula.red + '10',
447
+ border: `1px solid ${theme.dracula.red}40`,
448
+ borderRadius: theme.borderRadius.md,
449
+ }}>
450
+ <div style={{
451
+ fontSize: theme.fontSize.xs,
452
+ fontWeight: theme.fontWeight.medium,
453
+ color: theme.dracula.red,
454
+ marginBottom: theme.spacing.sm,
455
+ textTransform: 'uppercase',
456
+ letterSpacing: '0.05em',
457
+ }}>
458
+ Error
459
+ </div>
460
+ <pre style={{
461
+ margin: 0,
462
+ fontSize: theme.fontSize.sm,
463
+ fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace',
464
+ color: theme.dracula.red,
465
+ whiteSpace: 'pre-wrap',
466
+ wordBreak: 'break-word',
467
+ }}>
468
+ {latestResult.error}
469
+ </pre>
470
+ </div>
471
+ )}
472
+
473
+ {/* Thinking/Reasoning Block (shown before main response) */}
474
+ {(() => {
475
+ const { thinking, provider } = getThinkingContent(latestResult);
476
+ return thinking ? <ThinkingBlock thinking={thinking} provider={provider} theme={theme} /> : null;
477
+ })()}
478
+
479
+ {/* Main Response */}
480
+ {mainResponse && typeof mainResponse === 'object' && (mainResponse as any).type === 'android' ? (
481
+ /* Android Service Response */
482
+ <div style={{
483
+ padding: theme.spacing.lg,
484
+ marginBottom: theme.spacing.md,
485
+ backgroundColor: theme.colors.backgroundElevated,
486
+ border: `1px solid ${theme.dracula.green}40`,
487
+ borderRadius: theme.borderRadius.md,
488
+ borderLeft: `3px solid ${theme.dracula.green}`,
489
+ }}>
490
+ <div style={{
491
+ fontSize: theme.fontSize.xs,
492
+ fontWeight: theme.fontWeight.semibold,
493
+ color: theme.dracula.green,
494
+ marginBottom: theme.spacing.md,
495
+ textTransform: 'uppercase',
496
+ letterSpacing: '0.1em',
497
+ display: 'flex',
498
+ alignItems: 'center',
499
+ gap: theme.spacing.sm,
500
+ }}>
501
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
502
+ <rect x="5" y="2" width="14" height="20" rx="2" ry="2"/>
503
+ <line x1="12" y1="18" x2="12" y2="18"/>
504
+ </svg>
505
+ {(mainResponse as any).service?.replace(/_/g, ' ')} - {(mainResponse as any).action}
506
+ </div>
507
+ <pre style={{
508
+ margin: 0,
509
+ fontSize: theme.fontSize.sm,
510
+ fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace',
511
+ color: theme.colors.text,
512
+ lineHeight: 1.6,
513
+ whiteSpace: 'pre-wrap',
514
+ wordBreak: 'break-word',
515
+ }}>
516
+ {highlightJSON(JSON.stringify((mainResponse as any).data, null, 2), theme.dracula)}
517
+ </pre>
518
+ </div>
519
+ ) : mainResponse && typeof mainResponse === 'object' && (mainResponse as any).type === 'whatsapp_history' ? (
520
+ /* WhatsApp Chat History Response */
521
+ <div style={{
522
+ marginBottom: theme.spacing.md,
523
+ }}>
524
+ {/* Header with count */}
525
+ <div style={{
526
+ padding: theme.spacing.md,
527
+ backgroundColor: theme.colors.backgroundElevated,
528
+ border: `1px solid #25D36640`,
529
+ borderRadius: `${theme.borderRadius.md} ${theme.borderRadius.md} 0 0`,
530
+ borderLeft: `3px solid #25D366`,
531
+ display: 'flex',
532
+ alignItems: 'center',
533
+ justifyContent: 'space-between',
534
+ }}>
535
+ <div style={{
536
+ fontSize: theme.fontSize.xs,
537
+ fontWeight: theme.fontWeight.semibold,
538
+ color: '#25D366',
539
+ textTransform: 'uppercase',
540
+ letterSpacing: '0.1em',
541
+ display: 'flex',
542
+ alignItems: 'center',
543
+ gap: theme.spacing.sm,
544
+ }}>
545
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="#25D366">
546
+ <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/>
547
+ </svg>
548
+ Chat History
549
+ </div>
550
+ <div style={{
551
+ fontSize: theme.fontSize.xs,
552
+ color: theme.colors.textSecondary,
553
+ display: 'flex',
554
+ gap: theme.spacing.md,
555
+ }}>
556
+ <span style={{ color: '#25D366' }}>{(mainResponse as any).count || 0} messages</span>
557
+ {(mainResponse as any).total > 0 && (
558
+ <span>of {(mainResponse as any).total} total</span>
559
+ )}
560
+ {(mainResponse as any).hasMore && (
561
+ <span style={{ color: theme.dracula.orange }}>more available</span>
562
+ )}
563
+ </div>
564
+ </div>
565
+
566
+ {/* Messages list */}
567
+ <div style={{
568
+ backgroundColor: theme.colors.backgroundElevated,
569
+ border: `1px solid #25D36640`,
570
+ borderTop: 'none',
571
+ borderRadius: `0 0 ${theme.borderRadius.md} ${theme.borderRadius.md}`,
572
+ maxHeight: '400px',
573
+ overflow: 'auto',
574
+ }}>
575
+ {(mainResponse as any).messages?.length === 0 ? (
576
+ <div style={{
577
+ padding: theme.spacing.xl,
578
+ textAlign: 'center',
579
+ color: theme.colors.textMuted,
580
+ fontSize: theme.fontSize.sm,
581
+ }}>
582
+ No messages found for this chat
583
+ </div>
584
+ ) : (
585
+ (mainResponse as any).messages?.map((msg: any, idx: number) => (
586
+ <div
587
+ key={msg.message_id || idx}
588
+ style={{
589
+ padding: theme.spacing.md,
590
+ borderBottom: idx < (mainResponse as any).messages.length - 1 ? `1px solid ${theme.colors.border}` : 'none',
591
+ display: 'flex',
592
+ flexDirection: 'column',
593
+ gap: theme.spacing.xs,
594
+ }}
595
+ >
596
+ {/* Message header */}
597
+ <div style={{
598
+ display: 'flex',
599
+ alignItems: 'center',
600
+ justifyContent: 'space-between',
601
+ gap: theme.spacing.sm,
602
+ }}>
603
+ <div style={{
604
+ display: 'flex',
605
+ alignItems: 'center',
606
+ gap: theme.spacing.sm,
607
+ }}>
608
+ {/* From me indicator */}
609
+ {msg.is_from_me ? (
610
+ <span style={{
611
+ fontSize: '10px',
612
+ padding: '2px 6px',
613
+ backgroundColor: theme.dracula.cyan + '20',
614
+ color: theme.dracula.cyan,
615
+ borderRadius: theme.borderRadius.sm,
616
+ fontWeight: theme.fontWeight.medium,
617
+ }}>
618
+ You
619
+ </span>
620
+ ) : (
621
+ <span style={{
622
+ fontSize: theme.fontSize.xs,
623
+ fontWeight: theme.fontWeight.medium,
624
+ color: theme.dracula.purple,
625
+ }}>
626
+ {msg.sender_phone || msg.sender?.split('@')[0] || 'Unknown'}
627
+ </span>
628
+ )}
629
+ {/* Message type badge */}
630
+ {msg.message_type !== 'text' && (
631
+ <span style={{
632
+ fontSize: '10px',
633
+ padding: '2px 6px',
634
+ backgroundColor: theme.dracula.orange + '20',
635
+ color: theme.dracula.orange,
636
+ borderRadius: theme.borderRadius.sm,
637
+ textTransform: 'uppercase',
638
+ }}>
639
+ {msg.message_type}
640
+ </span>
641
+ )}
642
+ {msg.is_group && (
643
+ <span style={{
644
+ fontSize: '10px',
645
+ padding: '2px 6px',
646
+ backgroundColor: theme.dracula.green + '20',
647
+ color: theme.dracula.green,
648
+ borderRadius: theme.borderRadius.sm,
649
+ }}>
650
+ Group
651
+ </span>
652
+ )}
653
+ </div>
654
+ {/* Timestamp */}
655
+ <span style={{
656
+ fontSize: theme.fontSize.xs,
657
+ color: theme.colors.textMuted,
658
+ }}>
659
+ {msg.timestamp ? new Date(msg.timestamp).toLocaleString() : ''}
660
+ </span>
661
+ </div>
662
+ {/* Message text */}
663
+ {msg.text && (
664
+ <div style={{
665
+ fontSize: theme.fontSize.sm,
666
+ color: theme.colors.text,
667
+ lineHeight: 1.5,
668
+ whiteSpace: 'pre-wrap',
669
+ wordBreak: 'break-word',
670
+ paddingLeft: theme.spacing.sm,
671
+ borderLeft: `2px solid ${msg.is_from_me ? theme.dracula.cyan : theme.dracula.purple}30`,
672
+ }}>
673
+ {msg.text}
674
+ </div>
675
+ )}
676
+ </div>
677
+ ))
678
+ )}
679
+ </div>
680
+ </div>
681
+ ) : mainResponse && typeof mainResponse === 'object' && (mainResponse as any).type === 'maps_nearby' ? (
682
+ /* Google Maps Nearby Places Response */
683
+ <div style={{
684
+ marginBottom: theme.spacing.md,
685
+ }}>
686
+ {/* Header with search params */}
687
+ <div style={{
688
+ padding: theme.spacing.md,
689
+ backgroundColor: theme.colors.backgroundElevated,
690
+ border: `1px solid #34a85340`,
691
+ borderRadius: `${theme.borderRadius.md} ${theme.borderRadius.md} 0 0`,
692
+ borderLeft: `3px solid #34a853`,
693
+ display: 'flex',
694
+ alignItems: 'center',
695
+ justifyContent: 'space-between',
696
+ }}>
697
+ <div style={{
698
+ fontSize: theme.fontSize.xs,
699
+ fontWeight: theme.fontWeight.semibold,
700
+ color: '#34a853',
701
+ textTransform: 'uppercase',
702
+ letterSpacing: '0.1em',
703
+ display: 'flex',
704
+ alignItems: 'center',
705
+ gap: theme.spacing.sm,
706
+ }}>
707
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="#34a853">
708
+ <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
709
+ </svg>
710
+ Nearby Places
711
+ </div>
712
+ <div style={{
713
+ fontSize: theme.fontSize.xs,
714
+ color: theme.colors.textSecondary,
715
+ display: 'flex',
716
+ gap: theme.spacing.md,
717
+ }}>
718
+ <span style={{ color: '#34a853' }}>{(mainResponse as any).total} places found</span>
719
+ {(mainResponse as any).searchParams?.type && (
720
+ <span>Type: {(mainResponse as any).searchParams.type}</span>
721
+ )}
722
+ {(mainResponse as any).searchParams?.keyword && (
723
+ <span>Keyword: {(mainResponse as any).searchParams.keyword}</span>
724
+ )}
725
+ </div>
726
+ </div>
727
+
728
+ {/* Places list */}
729
+ <div style={{
730
+ backgroundColor: theme.colors.backgroundElevated,
731
+ border: `1px solid #34a85340`,
732
+ borderTop: 'none',
733
+ borderRadius: `0 0 ${theme.borderRadius.md} ${theme.borderRadius.md}`,
734
+ maxHeight: '400px',
735
+ overflow: 'auto',
736
+ }}>
737
+ {(mainResponse as any).places?.length === 0 ? (
738
+ <div style={{
739
+ padding: theme.spacing.xl,
740
+ textAlign: 'center',
741
+ color: theme.colors.textMuted,
742
+ fontSize: theme.fontSize.sm,
743
+ }}>
744
+ No places found for this search
745
+ </div>
746
+ ) : (
747
+ (mainResponse as any).places?.map((place: any, idx: number) => (
748
+ <div
749
+ key={place.place_id || idx}
750
+ style={{
751
+ padding: theme.spacing.md,
752
+ borderBottom: idx < (mainResponse as any).places.length - 1 ? `1px solid ${theme.colors.border}` : 'none',
753
+ display: 'flex',
754
+ flexDirection: 'column',
755
+ gap: theme.spacing.xs,
756
+ }}
757
+ >
758
+ {/* Place header */}
759
+ <div style={{
760
+ display: 'flex',
761
+ alignItems: 'flex-start',
762
+ justifyContent: 'space-between',
763
+ gap: theme.spacing.sm,
764
+ }}>
765
+ <div style={{ flex: 1 }}>
766
+ <div style={{
767
+ fontSize: theme.fontSize.sm,
768
+ fontWeight: theme.fontWeight.semibold,
769
+ color: theme.colors.text,
770
+ marginBottom: '2px',
771
+ }}>
772
+ {place.name}
773
+ </div>
774
+ {place.vicinity && (
775
+ <div style={{
776
+ fontSize: theme.fontSize.xs,
777
+ color: theme.colors.textMuted,
778
+ }}>
779
+ {place.vicinity}
780
+ </div>
781
+ )}
782
+ </div>
783
+ <div style={{
784
+ display: 'flex',
785
+ alignItems: 'center',
786
+ gap: theme.spacing.sm,
787
+ }}>
788
+ {/* Rating */}
789
+ {place.rating && (
790
+ <div style={{
791
+ display: 'flex',
792
+ alignItems: 'center',
793
+ gap: '4px',
794
+ padding: '2px 8px',
795
+ backgroundColor: theme.dracula.yellow + '20',
796
+ borderRadius: theme.borderRadius.sm,
797
+ }}>
798
+ <svg width="12" height="12" viewBox="0 0 24 24" fill={theme.dracula.yellow}>
799
+ <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
800
+ </svg>
801
+ <span style={{ fontSize: theme.fontSize.xs, color: theme.dracula.yellow, fontWeight: theme.fontWeight.medium }}>
802
+ {place.rating}
803
+ </span>
804
+ {place.user_ratings_total && (
805
+ <span style={{ fontSize: '10px', color: theme.colors.textMuted }}>
806
+ ({place.user_ratings_total})
807
+ </span>
808
+ )}
809
+ </div>
810
+ )}
811
+ {/* Open now */}
812
+ {place.opening_hours?.open_now !== undefined && (
813
+ <span style={{
814
+ fontSize: '10px',
815
+ padding: '2px 6px',
816
+ backgroundColor: place.opening_hours.open_now ? theme.dracula.green + '20' : theme.dracula.red + '20',
817
+ color: place.opening_hours.open_now ? theme.dracula.green : theme.dracula.red,
818
+ borderRadius: theme.borderRadius.sm,
819
+ fontWeight: theme.fontWeight.medium,
820
+ }}>
821
+ {place.opening_hours.open_now ? 'Open' : 'Closed'}
822
+ </span>
823
+ )}
824
+ {/* Price level */}
825
+ {place.price_level !== undefined && (
826
+ <span style={{
827
+ fontSize: theme.fontSize.xs,
828
+ color: theme.colors.textMuted,
829
+ }}>
830
+ {'$'.repeat(place.price_level + 1)}
831
+ </span>
832
+ )}
833
+ </div>
834
+ </div>
835
+ {/* Place types */}
836
+ {place.types && place.types.length > 0 && (
837
+ <div style={{
838
+ display: 'flex',
839
+ flexWrap: 'wrap',
840
+ gap: '4px',
841
+ marginTop: '4px',
842
+ }}>
843
+ {place.types.slice(0, 4).map((type: string) => (
844
+ <span key={type} style={{
845
+ fontSize: '10px',
846
+ padding: '2px 6px',
847
+ backgroundColor: theme.colors.backgroundAlt,
848
+ color: theme.colors.textSecondary,
849
+ borderRadius: theme.borderRadius.sm,
850
+ }}>
851
+ {type.replace(/_/g, ' ')}
852
+ </span>
853
+ ))}
854
+ </div>
855
+ )}
856
+ </div>
857
+ ))
858
+ )}
859
+ </div>
860
+ </div>
861
+ ) : mainResponse && typeof mainResponse === 'object' && (mainResponse as any).type === 'maps_geocode' ? (
862
+ /* Google Maps Geocoding Response */
863
+ <div style={{
864
+ marginBottom: theme.spacing.md,
865
+ }}>
866
+ <div style={{
867
+ padding: theme.spacing.md,
868
+ backgroundColor: theme.colors.backgroundElevated,
869
+ border: `1px solid #4285f440`,
870
+ borderRadius: `${theme.borderRadius.md} ${theme.borderRadius.md} 0 0`,
871
+ borderLeft: `3px solid #4285f4`,
872
+ display: 'flex',
873
+ alignItems: 'center',
874
+ justifyContent: 'space-between',
875
+ }}>
876
+ <div style={{
877
+ fontSize: theme.fontSize.xs,
878
+ fontWeight: theme.fontWeight.semibold,
879
+ color: '#4285f4',
880
+ textTransform: 'uppercase',
881
+ letterSpacing: '0.1em',
882
+ display: 'flex',
883
+ alignItems: 'center',
884
+ gap: theme.spacing.sm,
885
+ }}>
886
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="#4285f4">
887
+ <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
888
+ </svg>
889
+ Geocoded Locations
890
+ </div>
891
+ <span style={{ fontSize: theme.fontSize.xs, color: '#4285f4' }}>
892
+ {(mainResponse as any).total} location(s) found
893
+ </span>
894
+ </div>
895
+ <div style={{
896
+ backgroundColor: theme.colors.backgroundElevated,
897
+ border: `1px solid #4285f440`,
898
+ borderTop: 'none',
899
+ borderRadius: `0 0 ${theme.borderRadius.md} ${theme.borderRadius.md}`,
900
+ maxHeight: '300px',
901
+ overflow: 'auto',
902
+ }}>
903
+ {(mainResponse as any).locations?.map((loc: any, idx: number) => (
904
+ <div
905
+ key={idx}
906
+ style={{
907
+ padding: theme.spacing.md,
908
+ borderBottom: idx < (mainResponse as any).locations.length - 1 ? `1px solid ${theme.colors.border}` : 'none',
909
+ }}
910
+ >
911
+ <div style={{ fontWeight: theme.fontWeight.medium, color: theme.colors.text, marginBottom: '4px' }}>
912
+ {loc.formatted_address || loc.address}
913
+ </div>
914
+ <div style={{ fontSize: theme.fontSize.xs, color: theme.colors.textMuted }}>
915
+ Lat: {loc.lat?.toFixed(6)}, Lng: {loc.lng?.toFixed(6)}
916
+ </div>
917
+ </div>
918
+ ))}
919
+ </div>
920
+ </div>
921
+ ) : mainResponse && typeof mainResponse === 'object' && (mainResponse as any).type === 'maps_create' ? (
922
+ /* Google Maps Create Map Response */
923
+ <div style={{
924
+ marginBottom: theme.spacing.md,
925
+ backgroundColor: theme.colors.backgroundElevated,
926
+ border: `1px solid #ea433540`,
927
+ borderRadius: theme.borderRadius.md,
928
+ borderLeft: `3px solid #ea4335`,
929
+ overflow: 'hidden',
930
+ }}>
931
+ <div style={{
932
+ padding: theme.spacing.md,
933
+ display: 'flex',
934
+ alignItems: 'center',
935
+ justifyContent: 'space-between',
936
+ borderBottom: `1px solid ${theme.colors.border}`,
937
+ }}>
938
+ <div style={{
939
+ fontSize: theme.fontSize.xs,
940
+ fontWeight: theme.fontWeight.semibold,
941
+ color: '#ea4335',
942
+ textTransform: 'uppercase',
943
+ letterSpacing: '0.1em',
944
+ display: 'flex',
945
+ alignItems: 'center',
946
+ gap: theme.spacing.sm,
947
+ }}>
948
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="#ea4335">
949
+ <path d="M20.5 3l-.16.03L15 5.1 9 3 3.36 4.9c-.21.07-.36.25-.36.48V20.5c0 .28.22.5.5.5l.16-.03L9 18.9l6 2.1 5.64-1.9c.21-.07.36-.25.36-.48V3.5c0-.28-.22-.5-.5-.5zM15 19l-6-2.11V5l6 2.11V19z"/>
950
+ </svg>
951
+ Map Created
952
+ </div>
953
+ <div style={{ fontSize: theme.fontSize.xs, color: theme.colors.textSecondary }}>
954
+ {(mainResponse as any).mapType} | Zoom: {(mainResponse as any).zoom}
955
+ </div>
956
+ </div>
957
+ <div style={{ padding: theme.spacing.md }}>
958
+ <div style={{ fontSize: theme.fontSize.sm, color: theme.colors.textMuted, marginBottom: theme.spacing.sm }}>
959
+ Center: {(mainResponse as any).center?.lat?.toFixed(4)}, {(mainResponse as any).center?.lng?.toFixed(4)}
960
+ </div>
961
+ <a
962
+ href={(mainResponse as any).mapUrl}
963
+ target="_blank"
964
+ rel="noopener noreferrer"
965
+ style={{
966
+ display: 'inline-flex',
967
+ alignItems: 'center',
968
+ gap: theme.spacing.sm,
969
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
970
+ backgroundColor: '#ea433520',
971
+ color: '#ea4335',
972
+ borderRadius: theme.borderRadius.sm,
973
+ border: `1px solid #ea433540`,
974
+ fontSize: theme.fontSize.xs,
975
+ fontWeight: theme.fontWeight.medium,
976
+ textDecoration: 'none',
977
+ cursor: 'pointer',
978
+ }}
979
+ >
980
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
981
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
982
+ <polyline points="15 3 21 3 21 9"/>
983
+ <line x1="10" y1="14" x2="21" y2="3"/>
984
+ </svg>
985
+ Open Map Preview
986
+ </a>
987
+ </div>
988
+ </div>
989
+ ) : mainResponse && (
990
+ /* Standard Response (AI, etc) */
991
+ <div style={{
992
+ padding: theme.spacing.lg,
993
+ marginBottom: theme.spacing.md,
994
+ backgroundColor: theme.colors.backgroundElevated,
995
+ border: `1px solid ${theme.dracula.cyan}40`,
996
+ borderRadius: theme.borderRadius.md,
997
+ borderLeft: `3px solid ${theme.dracula.cyan}`,
998
+ }}>
999
+ <div style={{
1000
+ fontSize: theme.fontSize.xs,
1001
+ fontWeight: theme.fontWeight.semibold,
1002
+ color: theme.dracula.cyan,
1003
+ marginBottom: theme.spacing.md,
1004
+ textTransform: 'uppercase',
1005
+ letterSpacing: '0.1em',
1006
+ display: 'flex',
1007
+ alignItems: 'center',
1008
+ gap: theme.spacing.sm,
1009
+ }}>
1010
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1011
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
1012
+ </svg>
1013
+ Response
1014
+ </div>
1015
+ <div style={{
1016
+ fontSize: theme.fontSize.base,
1017
+ color: theme.colors.text,
1018
+ lineHeight: 1.7,
1019
+ whiteSpace: 'pre-wrap',
1020
+ wordBreak: 'break-word',
1021
+ fontWeight: theme.fontWeight.normal,
1022
+ }}>
1023
+ {mainResponse as string}
1024
+ </div>
1025
+ </div>
1026
+ )}
1027
+
1028
+ {/* JSON Output Toggle */}
1029
+ <div style={{
1030
+ backgroundColor: theme.colors.backgroundElevated,
1031
+ border: `1px solid ${theme.colors.border}`,
1032
+ borderRadius: theme.borderRadius.md,
1033
+ overflow: 'hidden',
1034
+ }}>
1035
+ <div
1036
+ onClick={() => setShowRawJson(!showRawJson)}
1037
+ style={{
1038
+ display: 'flex',
1039
+ alignItems: 'center',
1040
+ justifyContent: 'space-between',
1041
+ padding: `${theme.spacing.sm} ${theme.spacing.md}`,
1042
+ backgroundColor: theme.colors.backgroundAlt,
1043
+ cursor: 'pointer',
1044
+ transition: theme.transitions.fast,
1045
+ borderBottom: showRawJson ? `1px solid ${theme.colors.border}` : 'none',
1046
+ }}
1047
+ >
1048
+ <div style={{ display: 'flex', alignItems: 'center', gap: theme.spacing.sm }}>
1049
+ <svg
1050
+ width="12"
1051
+ height="12"
1052
+ viewBox="0 0 24 24"
1053
+ fill="none"
1054
+ stroke={theme.dracula.purple}
1055
+ strokeWidth="2"
1056
+ strokeLinecap="round"
1057
+ strokeLinejoin="round"
1058
+ style={{
1059
+ transform: showRawJson ? 'rotate(90deg)' : 'rotate(0deg)',
1060
+ transition: 'transform 0.2s ease',
1061
+ }}
1062
+ >
1063
+ <polyline points="9 18 15 12 9 6"/>
1064
+ </svg>
1065
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={theme.dracula.purple} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1066
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
1067
+ <polyline points="14 2 14 8 20 8"/>
1068
+ <line x1="16" y1="13" x2="8" y2="13"/>
1069
+ <line x1="16" y1="17" x2="8" y2="17"/>
1070
+ </svg>
1071
+ <span style={{
1072
+ fontSize: theme.fontSize.sm,
1073
+ fontWeight: theme.fontWeight.medium,
1074
+ color: theme.colors.text,
1075
+ }}>
1076
+ {showRawJson ? 'Hide' : 'Show'} Raw JSON
1077
+ </span>
1078
+ </div>
1079
+ <button
1080
+ onClick={(e) => {
1081
+ e.stopPropagation();
1082
+ copyToClipboard(outputData, 'JSON copied to clipboard!');
1083
+ }}
1084
+ style={{
1085
+ padding: `${theme.spacing.xs} ${theme.spacing.md}`,
1086
+ fontSize: theme.fontSize.xs,
1087
+ color: theme.dracula.cyan,
1088
+ backgroundColor: `${theme.dracula.cyan}15`,
1089
+ border: `1px solid ${theme.dracula.cyan}40`,
1090
+ borderRadius: theme.borderRadius.sm,
1091
+ cursor: 'pointer',
1092
+ display: 'flex',
1093
+ alignItems: 'center',
1094
+ gap: theme.spacing.xs,
1095
+ transition: theme.transitions.fast,
1096
+ fontWeight: theme.fontWeight.medium,
1097
+ }}
1098
+ onMouseEnter={(e) => {
1099
+ e.currentTarget.style.backgroundColor = `${theme.dracula.cyan}25`;
1100
+ }}
1101
+ onMouseLeave={(e) => {
1102
+ e.currentTarget.style.backgroundColor = `${theme.dracula.cyan}15`;
1103
+ }}
1104
+ >
1105
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1106
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
1107
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
1108
+ </svg>
1109
+ Copy
1110
+ </button>
1111
+ </div>
1112
+
1113
+ {/* Expanded JSON with syntax highlighting */}
1114
+ {showRawJson && (
1115
+ <div style={{
1116
+ margin: 0,
1117
+ padding: theme.spacing.md,
1118
+ fontSize: theme.fontSize.sm,
1119
+ fontFamily: '"Fira Code", Monaco, Menlo, "Ubuntu Mono", monospace',
1120
+ lineHeight: 1.6,
1121
+ overflow: 'auto',
1122
+ maxHeight: '400px',
1123
+ backgroundColor: '#1a1a2e',
1124
+ color: theme.dracula.foreground,
1125
+ }}>
1126
+ {highlightJSON(JSON.stringify(outputData, null, 2), theme.dracula)}
1127
+ </div>
1128
+ )}
1129
+ </div>
1130
+ </div>
1131
+
1132
+ {/* Footer */}
1133
+ {nodeResults.length > 1 && (
1134
+ <div style={{
1135
+ padding: `${theme.spacing.sm} ${theme.spacing.lg}`,
1136
+ borderTop: `1px solid ${theme.colors.border}`,
1137
+ backgroundColor: theme.colors.background,
1138
+ fontSize: theme.fontSize.xs,
1139
+ color: theme.colors.textMuted,
1140
+ textAlign: 'center',
1141
+ flexShrink: 0,
1142
+ }}>
1143
+ Showing latest of {nodeResults.length} results
1144
+ </div>
1145
+ )}
1146
+ </div>
1147
+ );
1148
+ };
1149
+
1150
+ export default NodeOutputPanel;