machinaos 0.0.1 → 0.0.6

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 (420) hide show
  1. package/.env.template +71 -71
  2. package/LICENSE +21 -21
  3. package/README.md +145 -87
  4. package/bin/cli.js +62 -106
  5. package/client/.dockerignore +45 -45
  6. package/client/Dockerfile +68 -68
  7. package/client/dist/assets/index-DFSC53FP.css +1 -0
  8. package/client/dist/assets/index-fJ-1gTf5.js +613 -0
  9. package/client/dist/index.html +14 -0
  10. package/client/eslint.config.js +34 -16
  11. package/client/nginx.conf +66 -66
  12. package/client/package.json +61 -48
  13. package/client/src/App.tsx +27 -27
  14. package/client/src/Dashboard.tsx +1200 -1172
  15. package/client/src/ParameterPanel.tsx +302 -300
  16. package/client/src/components/AIAgentNode.tsx +315 -321
  17. package/client/src/components/APIKeyValidator.tsx +117 -117
  18. package/client/src/components/ClaudeChatModelNode.tsx +17 -17
  19. package/client/src/components/CredentialsModal.tsx +1200 -306
  20. package/client/src/components/GeminiChatModelNode.tsx +17 -17
  21. package/client/src/components/GenericNode.tsx +356 -356
  22. package/client/src/components/LocationParameterPanel.tsx +153 -153
  23. package/client/src/components/ModelNode.tsx +285 -285
  24. package/client/src/components/OpenAIChatModelNode.tsx +17 -17
  25. package/client/src/components/OutputPanel.tsx +470 -470
  26. package/client/src/components/ParameterRenderer.tsx +1873 -1873
  27. package/client/src/components/SkillEditorModal.tsx +3 -3
  28. package/client/src/components/SquareNode.tsx +812 -796
  29. package/client/src/components/ToolkitNode.tsx +365 -365
  30. package/client/src/components/auth/LoginPage.tsx +247 -247
  31. package/client/src/components/auth/ProtectedRoute.tsx +59 -59
  32. package/client/src/components/base/BaseChatModelNode.tsx +270 -270
  33. package/client/src/components/icons/AIProviderIcons.tsx +50 -50
  34. package/client/src/components/maps/GoogleMapsPicker.tsx +136 -136
  35. package/client/src/components/maps/MapsPreviewPanel.tsx +109 -109
  36. package/client/src/components/maps/index.ts +25 -25
  37. package/client/src/components/parameterPanel/InputSection.tsx +1094 -1094
  38. package/client/src/components/parameterPanel/LocationPanelLayout.tsx +64 -64
  39. package/client/src/components/parameterPanel/MapsSection.tsx +91 -91
  40. package/client/src/components/parameterPanel/MiddleSection.tsx +867 -571
  41. package/client/src/components/parameterPanel/OutputSection.tsx +80 -80
  42. package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +81 -81
  43. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -436
  44. package/client/src/components/parameterPanel/index.ts +41 -41
  45. package/client/src/components/shared/DataPanel.tsx +142 -142
  46. package/client/src/components/shared/JSONTreeRenderer.tsx +105 -105
  47. package/client/src/components/ui/AIResultModal.tsx +203 -203
  48. package/client/src/components/ui/ApiKeyInput.tsx +93 -0
  49. package/client/src/components/ui/CodeEditor.tsx +81 -81
  50. package/client/src/components/ui/CollapsibleSection.tsx +87 -87
  51. package/client/src/components/ui/ComponentItem.tsx +153 -153
  52. package/client/src/components/ui/ComponentPalette.tsx +320 -320
  53. package/client/src/components/ui/ConsolePanel.tsx +151 -43
  54. package/client/src/components/ui/ErrorBoundary.tsx +195 -195
  55. package/client/src/components/ui/InputNodesPanel.tsx +203 -203
  56. package/client/src/components/ui/MapSelector.tsx +313 -313
  57. package/client/src/components/ui/Modal.tsx +151 -148
  58. package/client/src/components/ui/NodeOutputPanel.tsx +1150 -1150
  59. package/client/src/components/ui/OutputDisplayPanel.tsx +381 -381
  60. package/client/src/components/ui/QRCodeDisplay.tsx +182 -0
  61. package/client/src/components/ui/TopToolbar.tsx +736 -736
  62. package/client/src/components/ui/WorkflowSidebar.tsx +293 -293
  63. package/client/src/config/antdTheme.ts +186 -186
  64. package/client/src/contexts/AuthContext.tsx +221 -221
  65. package/client/src/contexts/ThemeContext.tsx +42 -42
  66. package/client/src/contexts/WebSocketContext.tsx +2144 -1971
  67. package/client/src/factories/baseChatModelFactory.ts +255 -255
  68. package/client/src/hooks/useAndroidOperations.ts +118 -164
  69. package/client/src/hooks/useApiKeyValidation.ts +106 -106
  70. package/client/src/hooks/useApiKeys.ts +238 -238
  71. package/client/src/hooks/useAppTheme.ts +17 -17
  72. package/client/src/hooks/useComponentPalette.ts +50 -50
  73. package/client/src/hooks/useDragAndDrop.ts +123 -123
  74. package/client/src/hooks/useDragVariable.ts +88 -88
  75. package/client/src/hooks/useExecution.ts +319 -313
  76. package/client/src/hooks/useParameterPanel.ts +176 -176
  77. package/client/src/hooks/useReactFlowNodes.ts +188 -188
  78. package/client/src/hooks/useToolSchema.ts +209 -209
  79. package/client/src/hooks/useWhatsApp.ts +196 -196
  80. package/client/src/hooks/useWorkflowManagement.ts +45 -45
  81. package/client/src/index.css +314 -314
  82. package/client/src/nodeDefinitions/aiAgentNodes.ts +335 -335
  83. package/client/src/nodeDefinitions/aiModelNodes.ts +340 -340
  84. package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -383
  85. package/client/src/nodeDefinitions/chatNodes.ts +135 -135
  86. package/client/src/nodeDefinitions/codeNodes.ts +54 -54
  87. package/client/src/nodeDefinitions/index.ts +14 -14
  88. package/client/src/nodeDefinitions/locationNodes.ts +462 -462
  89. package/client/src/nodeDefinitions/schedulerNodes.ts +220 -220
  90. package/client/src/nodeDefinitions/skillNodes.ts +17 -5
  91. package/client/src/nodeDefinitions/utilityNodes.ts +284 -284
  92. package/client/src/nodeDefinitions/whatsappNodes.ts +821 -865
  93. package/client/src/nodeDefinitions.ts +101 -103
  94. package/client/src/services/dynamicParameterService.ts +95 -95
  95. package/client/src/services/execution/aiAgentExecutionService.ts +34 -34
  96. package/client/src/services/executionService.ts +227 -231
  97. package/client/src/services/workflowApi.ts +91 -91
  98. package/client/src/store/useAppStore.ts +578 -581
  99. package/client/src/styles/theme.ts +513 -508
  100. package/client/src/styles/zIndex.ts +16 -16
  101. package/client/src/types/ComponentTypes.ts +38 -38
  102. package/client/src/types/INodeProperties.ts +287 -287
  103. package/client/src/types/NodeTypes.ts +27 -27
  104. package/client/src/utils/formatters.ts +32 -32
  105. package/client/src/utils/googleMapsLoader.ts +139 -139
  106. package/client/src/utils/locationUtils.ts +84 -84
  107. package/client/src/utils/nodeUtils.ts +30 -30
  108. package/client/src/utils/workflow.ts +29 -29
  109. package/client/src/vite-env.d.ts +12 -12
  110. package/client/tailwind.config.js +59 -59
  111. package/client/tsconfig.json +25 -25
  112. package/client/vite.config.js +35 -35
  113. package/package.json +78 -70
  114. package/scripts/build.js +153 -45
  115. package/scripts/clean.js +40 -40
  116. package/scripts/start.js +234 -210
  117. package/scripts/stop.js +301 -325
  118. package/server/.dockerignore +44 -44
  119. package/server/Dockerfile +45 -45
  120. package/server/constants.py +244 -249
  121. package/server/core/cache.py +460 -460
  122. package/server/core/config.py +127 -127
  123. package/server/core/container.py +98 -98
  124. package/server/core/database.py +1296 -1210
  125. package/server/core/logging.py +313 -313
  126. package/server/main.py +288 -288
  127. package/server/middleware/__init__.py +5 -5
  128. package/server/middleware/auth.py +89 -89
  129. package/server/models/auth.py +52 -52
  130. package/server/models/cache.py +24 -24
  131. package/server/models/database.py +235 -210
  132. package/server/models/nodes.py +435 -455
  133. package/server/pyproject.toml +75 -72
  134. package/server/requirements.txt +83 -83
  135. package/server/routers/android.py +294 -294
  136. package/server/routers/auth.py +203 -203
  137. package/server/routers/database.py +150 -150
  138. package/server/routers/maps.py +141 -141
  139. package/server/routers/nodejs_compat.py +288 -288
  140. package/server/routers/webhook.py +90 -90
  141. package/server/routers/websocket.py +2239 -2127
  142. package/server/routers/whatsapp.py +761 -761
  143. package/server/routers/workflow.py +199 -199
  144. package/server/services/ai.py +2444 -2414
  145. package/server/services/android_service.py +588 -588
  146. package/server/services/auth.py +130 -130
  147. package/server/services/chat_client.py +160 -160
  148. package/server/services/deployment/manager.py +706 -706
  149. package/server/services/event_waiter.py +675 -785
  150. package/server/services/execution/executor.py +1351 -1351
  151. package/server/services/execution/models.py +1 -1
  152. package/server/services/handlers/__init__.py +122 -126
  153. package/server/services/handlers/ai.py +390 -355
  154. package/server/services/handlers/android.py +69 -260
  155. package/server/services/handlers/code.py +278 -278
  156. package/server/services/handlers/http.py +193 -193
  157. package/server/services/handlers/tools.py +146 -32
  158. package/server/services/handlers/triggers.py +107 -107
  159. package/server/services/handlers/utility.py +822 -822
  160. package/server/services/handlers/whatsapp.py +423 -476
  161. package/server/services/maps.py +288 -288
  162. package/server/services/memory_store.py +103 -103
  163. package/server/services/node_executor.py +372 -375
  164. package/server/services/scheduler.py +155 -155
  165. package/server/services/skill_loader.py +1 -1
  166. package/server/services/status_broadcaster.py +834 -826
  167. package/server/services/temporal/__init__.py +23 -23
  168. package/server/services/temporal/activities.py +344 -344
  169. package/server/services/temporal/client.py +76 -76
  170. package/server/services/temporal/executor.py +147 -147
  171. package/server/services/temporal/worker.py +251 -251
  172. package/server/services/temporal/workflow.py +355 -355
  173. package/server/services/temporal/ws_client.py +236 -236
  174. package/server/services/text.py +110 -110
  175. package/server/services/user_auth.py +172 -172
  176. package/server/services/websocket_client.py +29 -29
  177. package/server/services/workflow.py +597 -597
  178. package/server/skills/android-skill/SKILL.md +4 -4
  179. package/server/skills/code-skill/SKILL.md +123 -89
  180. package/server/skills/maps-skill/SKILL.md +3 -3
  181. package/server/skills/memory-skill/SKILL.md +1 -1
  182. package/server/skills/web-search-skill/SKILL.md +154 -0
  183. package/server/skills/whatsapp-skill/SKILL.md +3 -3
  184. package/server/uv.lock +461 -100
  185. package/server/whatsapp-rpc/.dockerignore +30 -30
  186. package/server/whatsapp-rpc/Dockerfile +44 -44
  187. package/server/whatsapp-rpc/Dockerfile.web +17 -17
  188. package/server/whatsapp-rpc/README.md +139 -139
  189. package/server/whatsapp-rpc/bin/whatsapp-rpc-server +0 -0
  190. package/server/whatsapp-rpc/cli.js +95 -95
  191. package/server/whatsapp-rpc/configs/config.yaml +6 -6
  192. package/server/whatsapp-rpc/docker-compose.yml +35 -35
  193. package/server/whatsapp-rpc/docs/API.md +410 -410
  194. package/server/whatsapp-rpc/node_modules/.package-lock.json +259 -0
  195. package/server/whatsapp-rpc/node_modules/chalk/license +9 -0
  196. package/server/whatsapp-rpc/node_modules/chalk/package.json +83 -0
  197. package/server/whatsapp-rpc/node_modules/chalk/readme.md +297 -0
  198. package/server/whatsapp-rpc/node_modules/chalk/source/index.d.ts +325 -0
  199. package/server/whatsapp-rpc/node_modules/chalk/source/index.js +225 -0
  200. package/server/whatsapp-rpc/node_modules/chalk/source/utilities.js +33 -0
  201. package/server/whatsapp-rpc/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  202. package/server/whatsapp-rpc/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  203. package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  204. package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  205. package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  206. package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  207. package/server/whatsapp-rpc/node_modules/commander/LICENSE +22 -0
  208. package/server/whatsapp-rpc/node_modules/commander/Readme.md +1148 -0
  209. package/server/whatsapp-rpc/node_modules/commander/esm.mjs +16 -0
  210. package/server/whatsapp-rpc/node_modules/commander/index.js +26 -0
  211. package/server/whatsapp-rpc/node_modules/commander/lib/argument.js +145 -0
  212. package/server/whatsapp-rpc/node_modules/commander/lib/command.js +2179 -0
  213. package/server/whatsapp-rpc/node_modules/commander/lib/error.js +43 -0
  214. package/server/whatsapp-rpc/node_modules/commander/lib/help.js +462 -0
  215. package/server/whatsapp-rpc/node_modules/commander/lib/option.js +329 -0
  216. package/server/whatsapp-rpc/node_modules/commander/lib/suggestSimilar.js +100 -0
  217. package/server/whatsapp-rpc/node_modules/commander/package-support.json +16 -0
  218. package/server/whatsapp-rpc/node_modules/commander/package.json +80 -0
  219. package/server/whatsapp-rpc/node_modules/commander/typings/esm.d.mts +3 -0
  220. package/server/whatsapp-rpc/node_modules/commander/typings/index.d.ts +884 -0
  221. package/server/whatsapp-rpc/node_modules/cross-spawn/LICENSE +21 -0
  222. package/server/whatsapp-rpc/node_modules/cross-spawn/README.md +89 -0
  223. package/server/whatsapp-rpc/node_modules/cross-spawn/index.js +39 -0
  224. package/server/whatsapp-rpc/node_modules/cross-spawn/lib/enoent.js +59 -0
  225. package/server/whatsapp-rpc/node_modules/cross-spawn/lib/parse.js +91 -0
  226. package/server/whatsapp-rpc/node_modules/cross-spawn/lib/util/escape.js +47 -0
  227. package/server/whatsapp-rpc/node_modules/cross-spawn/lib/util/readShebang.js +23 -0
  228. package/server/whatsapp-rpc/node_modules/cross-spawn/lib/util/resolveCommand.js +52 -0
  229. package/server/whatsapp-rpc/node_modules/cross-spawn/package.json +73 -0
  230. package/server/whatsapp-rpc/node_modules/execa/index.d.ts +955 -0
  231. package/server/whatsapp-rpc/node_modules/execa/index.js +309 -0
  232. package/server/whatsapp-rpc/node_modules/execa/lib/command.js +119 -0
  233. package/server/whatsapp-rpc/node_modules/execa/lib/error.js +87 -0
  234. package/server/whatsapp-rpc/node_modules/execa/lib/kill.js +102 -0
  235. package/server/whatsapp-rpc/node_modules/execa/lib/pipe.js +42 -0
  236. package/server/whatsapp-rpc/node_modules/execa/lib/promise.js +36 -0
  237. package/server/whatsapp-rpc/node_modules/execa/lib/stdio.js +49 -0
  238. package/server/whatsapp-rpc/node_modules/execa/lib/stream.js +133 -0
  239. package/server/whatsapp-rpc/node_modules/execa/lib/verbose.js +19 -0
  240. package/server/whatsapp-rpc/node_modules/execa/license +9 -0
  241. package/server/whatsapp-rpc/node_modules/execa/package.json +90 -0
  242. package/server/whatsapp-rpc/node_modules/execa/readme.md +822 -0
  243. package/server/whatsapp-rpc/node_modules/get-stream/license +9 -0
  244. package/server/whatsapp-rpc/node_modules/get-stream/package.json +53 -0
  245. package/server/whatsapp-rpc/node_modules/get-stream/readme.md +291 -0
  246. package/server/whatsapp-rpc/node_modules/get-stream/source/array-buffer.js +84 -0
  247. package/server/whatsapp-rpc/node_modules/get-stream/source/array.js +32 -0
  248. package/server/whatsapp-rpc/node_modules/get-stream/source/buffer.js +20 -0
  249. package/server/whatsapp-rpc/node_modules/get-stream/source/contents.js +101 -0
  250. package/server/whatsapp-rpc/node_modules/get-stream/source/index.d.ts +119 -0
  251. package/server/whatsapp-rpc/node_modules/get-stream/source/index.js +5 -0
  252. package/server/whatsapp-rpc/node_modules/get-stream/source/string.js +36 -0
  253. package/server/whatsapp-rpc/node_modules/get-stream/source/utils.js +11 -0
  254. package/server/whatsapp-rpc/node_modules/get-them-args/LICENSE +21 -0
  255. package/server/whatsapp-rpc/node_modules/get-them-args/README.md +95 -0
  256. package/server/whatsapp-rpc/node_modules/get-them-args/index.js +97 -0
  257. package/server/whatsapp-rpc/node_modules/get-them-args/package.json +36 -0
  258. package/server/whatsapp-rpc/node_modules/human-signals/LICENSE +201 -0
  259. package/server/whatsapp-rpc/node_modules/human-signals/README.md +168 -0
  260. package/server/whatsapp-rpc/node_modules/human-signals/build/src/core.js +273 -0
  261. package/server/whatsapp-rpc/node_modules/human-signals/build/src/main.d.ts +73 -0
  262. package/server/whatsapp-rpc/node_modules/human-signals/build/src/main.js +70 -0
  263. package/server/whatsapp-rpc/node_modules/human-signals/build/src/realtime.js +16 -0
  264. package/server/whatsapp-rpc/node_modules/human-signals/build/src/signals.js +34 -0
  265. package/server/whatsapp-rpc/node_modules/human-signals/package.json +61 -0
  266. package/server/whatsapp-rpc/node_modules/is-stream/index.d.ts +81 -0
  267. package/server/whatsapp-rpc/node_modules/is-stream/index.js +29 -0
  268. package/server/whatsapp-rpc/node_modules/is-stream/license +9 -0
  269. package/server/whatsapp-rpc/node_modules/is-stream/package.json +44 -0
  270. package/server/whatsapp-rpc/node_modules/is-stream/readme.md +60 -0
  271. package/server/whatsapp-rpc/node_modules/isexe/LICENSE +15 -0
  272. package/server/whatsapp-rpc/node_modules/isexe/README.md +51 -0
  273. package/server/whatsapp-rpc/node_modules/isexe/index.js +57 -0
  274. package/server/whatsapp-rpc/node_modules/isexe/mode.js +41 -0
  275. package/server/whatsapp-rpc/node_modules/isexe/package.json +31 -0
  276. package/server/whatsapp-rpc/node_modules/isexe/test/basic.js +221 -0
  277. package/server/whatsapp-rpc/node_modules/isexe/windows.js +42 -0
  278. package/server/whatsapp-rpc/node_modules/kill-port/.editorconfig +12 -0
  279. package/server/whatsapp-rpc/node_modules/kill-port/.gitattributes +1 -0
  280. package/server/whatsapp-rpc/node_modules/kill-port/LICENSE +21 -0
  281. package/server/whatsapp-rpc/node_modules/kill-port/README.md +140 -0
  282. package/server/whatsapp-rpc/node_modules/kill-port/cli.js +25 -0
  283. package/server/whatsapp-rpc/node_modules/kill-port/example.js +21 -0
  284. package/server/whatsapp-rpc/node_modules/kill-port/index.js +46 -0
  285. package/server/whatsapp-rpc/node_modules/kill-port/logo.png +0 -0
  286. package/server/whatsapp-rpc/node_modules/kill-port/package.json +41 -0
  287. package/server/whatsapp-rpc/node_modules/kill-port/pnpm-lock.yaml +4606 -0
  288. package/server/whatsapp-rpc/node_modules/kill-port/test.js +16 -0
  289. package/server/whatsapp-rpc/node_modules/merge-stream/LICENSE +21 -0
  290. package/server/whatsapp-rpc/node_modules/merge-stream/README.md +78 -0
  291. package/server/whatsapp-rpc/node_modules/merge-stream/index.js +41 -0
  292. package/server/whatsapp-rpc/node_modules/merge-stream/package.json +19 -0
  293. package/server/whatsapp-rpc/node_modules/mimic-fn/index.d.ts +52 -0
  294. package/server/whatsapp-rpc/node_modules/mimic-fn/index.js +71 -0
  295. package/server/whatsapp-rpc/node_modules/mimic-fn/license +9 -0
  296. package/server/whatsapp-rpc/node_modules/mimic-fn/package.json +45 -0
  297. package/server/whatsapp-rpc/node_modules/mimic-fn/readme.md +90 -0
  298. package/server/whatsapp-rpc/node_modules/npm-run-path/index.d.ts +90 -0
  299. package/server/whatsapp-rpc/node_modules/npm-run-path/index.js +52 -0
  300. package/server/whatsapp-rpc/node_modules/npm-run-path/license +9 -0
  301. package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/index.d.ts +31 -0
  302. package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/index.js +12 -0
  303. package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/license +9 -0
  304. package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/package.json +41 -0
  305. package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/readme.md +57 -0
  306. package/server/whatsapp-rpc/node_modules/npm-run-path/package.json +49 -0
  307. package/server/whatsapp-rpc/node_modules/npm-run-path/readme.md +104 -0
  308. package/server/whatsapp-rpc/node_modules/onetime/index.d.ts +59 -0
  309. package/server/whatsapp-rpc/node_modules/onetime/index.js +41 -0
  310. package/server/whatsapp-rpc/node_modules/onetime/license +9 -0
  311. package/server/whatsapp-rpc/node_modules/onetime/package.json +45 -0
  312. package/server/whatsapp-rpc/node_modules/onetime/readme.md +94 -0
  313. package/server/whatsapp-rpc/node_modules/path-key/index.d.ts +40 -0
  314. package/server/whatsapp-rpc/node_modules/path-key/index.js +16 -0
  315. package/server/whatsapp-rpc/node_modules/path-key/license +9 -0
  316. package/server/whatsapp-rpc/node_modules/path-key/package.json +39 -0
  317. package/server/whatsapp-rpc/node_modules/path-key/readme.md +61 -0
  318. package/server/whatsapp-rpc/node_modules/shebang-command/index.js +19 -0
  319. package/server/whatsapp-rpc/node_modules/shebang-command/license +9 -0
  320. package/server/whatsapp-rpc/node_modules/shebang-command/package.json +34 -0
  321. package/server/whatsapp-rpc/node_modules/shebang-command/readme.md +34 -0
  322. package/server/whatsapp-rpc/node_modules/shebang-regex/index.d.ts +22 -0
  323. package/server/whatsapp-rpc/node_modules/shebang-regex/index.js +2 -0
  324. package/server/whatsapp-rpc/node_modules/shebang-regex/license +9 -0
  325. package/server/whatsapp-rpc/node_modules/shebang-regex/package.json +35 -0
  326. package/server/whatsapp-rpc/node_modules/shebang-regex/readme.md +33 -0
  327. package/server/whatsapp-rpc/node_modules/shell-exec/LICENSE +21 -0
  328. package/server/whatsapp-rpc/node_modules/shell-exec/README.md +60 -0
  329. package/server/whatsapp-rpc/node_modules/shell-exec/index.js +47 -0
  330. package/server/whatsapp-rpc/node_modules/shell-exec/package.json +29 -0
  331. package/server/whatsapp-rpc/node_modules/signal-exit/LICENSE.txt +16 -0
  332. package/server/whatsapp-rpc/node_modules/signal-exit/README.md +74 -0
  333. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.d.ts +12 -0
  334. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.d.ts.map +1 -0
  335. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.js +10 -0
  336. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.js.map +1 -0
  337. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.d.ts +48 -0
  338. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.d.ts.map +1 -0
  339. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.js +279 -0
  340. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.js.map +1 -0
  341. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/package.json +3 -0
  342. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.d.ts +29 -0
  343. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.d.ts.map +1 -0
  344. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.js +42 -0
  345. package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.js.map +1 -0
  346. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.d.ts +12 -0
  347. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.d.ts.map +1 -0
  348. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.js +4 -0
  349. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.js.map +1 -0
  350. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.d.ts +48 -0
  351. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.d.ts.map +1 -0
  352. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.js +275 -0
  353. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.js.map +1 -0
  354. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/package.json +3 -0
  355. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.d.ts +29 -0
  356. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.d.ts.map +1 -0
  357. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.js +39 -0
  358. package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.js.map +1 -0
  359. package/server/whatsapp-rpc/node_modules/signal-exit/package.json +106 -0
  360. package/server/whatsapp-rpc/node_modules/strip-final-newline/index.js +14 -0
  361. package/server/whatsapp-rpc/node_modules/strip-final-newline/license +9 -0
  362. package/server/whatsapp-rpc/node_modules/strip-final-newline/package.json +43 -0
  363. package/server/whatsapp-rpc/node_modules/strip-final-newline/readme.md +35 -0
  364. package/server/whatsapp-rpc/node_modules/which/CHANGELOG.md +166 -0
  365. package/server/whatsapp-rpc/node_modules/which/LICENSE +15 -0
  366. package/server/whatsapp-rpc/node_modules/which/README.md +54 -0
  367. package/server/whatsapp-rpc/node_modules/which/bin/node-which +52 -0
  368. package/server/whatsapp-rpc/node_modules/which/package.json +43 -0
  369. package/server/whatsapp-rpc/node_modules/which/which.js +125 -0
  370. package/server/whatsapp-rpc/package-lock.json +272 -0
  371. package/server/whatsapp-rpc/package.json +30 -30
  372. package/server/whatsapp-rpc/schema.json +1294 -1294
  373. package/server/whatsapp-rpc/scripts/clean.cjs +66 -66
  374. package/server/whatsapp-rpc/scripts/cli.js +162 -162
  375. package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -166
  376. package/server/whatsapp-rpc/src/python/pyproject.toml +15 -15
  377. package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -4
  378. package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -427
  379. package/server/whatsapp-rpc/web/app.py +609 -609
  380. package/server/whatsapp-rpc/web/requirements.txt +6 -6
  381. package/server/whatsapp-rpc/web/rpc_client.py +427 -427
  382. package/server/whatsapp-rpc/web/static/openapi.yaml +59 -59
  383. package/server/whatsapp-rpc/web/templates/base.html +149 -149
  384. package/server/whatsapp-rpc/web/templates/contacts.html +240 -240
  385. package/server/whatsapp-rpc/web/templates/dashboard.html +319 -319
  386. package/server/whatsapp-rpc/web/templates/groups.html +328 -328
  387. package/server/whatsapp-rpc/web/templates/messages.html +465 -465
  388. package/server/whatsapp-rpc/web/templates/messaging.html +680 -680
  389. package/server/whatsapp-rpc/web/templates/send.html +258 -258
  390. package/server/whatsapp-rpc/web/templates/settings.html +459 -459
  391. package/client/src/components/ui/AndroidSettingsPanel.tsx +0 -401
  392. package/client/src/components/ui/WhatsAppSettingsPanel.tsx +0 -345
  393. package/client/src/nodeDefinitions/androidDeviceNodes.ts +0 -140
  394. package/docker-compose.prod.yml +0 -107
  395. package/docker-compose.yml +0 -104
  396. package/docs-MachinaOs/README.md +0 -85
  397. package/docs-MachinaOs/deployment/docker.mdx +0 -228
  398. package/docs-MachinaOs/deployment/production.mdx +0 -345
  399. package/docs-MachinaOs/docs.json +0 -75
  400. package/docs-MachinaOs/faq.mdx +0 -309
  401. package/docs-MachinaOs/favicon.svg +0 -5
  402. package/docs-MachinaOs/installation.mdx +0 -160
  403. package/docs-MachinaOs/introduction.mdx +0 -114
  404. package/docs-MachinaOs/logo/dark.svg +0 -6
  405. package/docs-MachinaOs/logo/light.svg +0 -6
  406. package/docs-MachinaOs/nodes/ai-agent.mdx +0 -216
  407. package/docs-MachinaOs/nodes/ai-models.mdx +0 -240
  408. package/docs-MachinaOs/nodes/android.mdx +0 -411
  409. package/docs-MachinaOs/nodes/overview.mdx +0 -181
  410. package/docs-MachinaOs/nodes/schedulers.mdx +0 -316
  411. package/docs-MachinaOs/nodes/webhooks.mdx +0 -330
  412. package/docs-MachinaOs/nodes/whatsapp.mdx +0 -305
  413. package/docs-MachinaOs/quickstart.mdx +0 -119
  414. package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +0 -177
  415. package/docs-MachinaOs/tutorials/android-automation.mdx +0 -242
  416. package/docs-MachinaOs/tutorials/first-workflow.mdx +0 -134
  417. package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +0 -185
  418. package/nul +0 -0
  419. package/scripts/check-ports.ps1 +0 -33
  420. package/scripts/kill-port.ps1 +0 -154
@@ -1,1150 +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;
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;