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.
- package/.env.template +71 -71
- package/LICENSE +21 -21
- package/README.md +145 -87
- package/bin/cli.js +62 -106
- package/client/.dockerignore +45 -45
- package/client/Dockerfile +68 -68
- package/client/dist/assets/index-DFSC53FP.css +1 -0
- package/client/dist/assets/index-fJ-1gTf5.js +613 -0
- package/client/dist/index.html +14 -0
- package/client/eslint.config.js +34 -16
- package/client/nginx.conf +66 -66
- package/client/package.json +61 -48
- package/client/src/App.tsx +27 -27
- package/client/src/Dashboard.tsx +1200 -1172
- package/client/src/ParameterPanel.tsx +302 -300
- package/client/src/components/AIAgentNode.tsx +315 -321
- package/client/src/components/APIKeyValidator.tsx +117 -117
- package/client/src/components/ClaudeChatModelNode.tsx +17 -17
- package/client/src/components/CredentialsModal.tsx +1200 -306
- package/client/src/components/GeminiChatModelNode.tsx +17 -17
- package/client/src/components/GenericNode.tsx +356 -356
- package/client/src/components/LocationParameterPanel.tsx +153 -153
- package/client/src/components/ModelNode.tsx +285 -285
- package/client/src/components/OpenAIChatModelNode.tsx +17 -17
- package/client/src/components/OutputPanel.tsx +470 -470
- package/client/src/components/ParameterRenderer.tsx +1873 -1873
- package/client/src/components/SkillEditorModal.tsx +3 -3
- package/client/src/components/SquareNode.tsx +812 -796
- package/client/src/components/ToolkitNode.tsx +365 -365
- package/client/src/components/auth/LoginPage.tsx +247 -247
- package/client/src/components/auth/ProtectedRoute.tsx +59 -59
- package/client/src/components/base/BaseChatModelNode.tsx +270 -270
- package/client/src/components/icons/AIProviderIcons.tsx +50 -50
- package/client/src/components/maps/GoogleMapsPicker.tsx +136 -136
- package/client/src/components/maps/MapsPreviewPanel.tsx +109 -109
- package/client/src/components/maps/index.ts +25 -25
- package/client/src/components/parameterPanel/InputSection.tsx +1094 -1094
- package/client/src/components/parameterPanel/LocationPanelLayout.tsx +64 -64
- package/client/src/components/parameterPanel/MapsSection.tsx +91 -91
- package/client/src/components/parameterPanel/MiddleSection.tsx +867 -571
- package/client/src/components/parameterPanel/OutputSection.tsx +80 -80
- package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +81 -81
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -436
- package/client/src/components/parameterPanel/index.ts +41 -41
- package/client/src/components/shared/DataPanel.tsx +142 -142
- package/client/src/components/shared/JSONTreeRenderer.tsx +105 -105
- package/client/src/components/ui/AIResultModal.tsx +203 -203
- package/client/src/components/ui/ApiKeyInput.tsx +93 -0
- package/client/src/components/ui/CodeEditor.tsx +81 -81
- package/client/src/components/ui/CollapsibleSection.tsx +87 -87
- package/client/src/components/ui/ComponentItem.tsx +153 -153
- package/client/src/components/ui/ComponentPalette.tsx +320 -320
- package/client/src/components/ui/ConsolePanel.tsx +151 -43
- package/client/src/components/ui/ErrorBoundary.tsx +195 -195
- package/client/src/components/ui/InputNodesPanel.tsx +203 -203
- package/client/src/components/ui/MapSelector.tsx +313 -313
- package/client/src/components/ui/Modal.tsx +151 -148
- package/client/src/components/ui/NodeOutputPanel.tsx +1150 -1150
- package/client/src/components/ui/OutputDisplayPanel.tsx +381 -381
- package/client/src/components/ui/QRCodeDisplay.tsx +182 -0
- package/client/src/components/ui/TopToolbar.tsx +736 -736
- package/client/src/components/ui/WorkflowSidebar.tsx +293 -293
- package/client/src/config/antdTheme.ts +186 -186
- package/client/src/contexts/AuthContext.tsx +221 -221
- package/client/src/contexts/ThemeContext.tsx +42 -42
- package/client/src/contexts/WebSocketContext.tsx +2144 -1971
- package/client/src/factories/baseChatModelFactory.ts +255 -255
- package/client/src/hooks/useAndroidOperations.ts +118 -164
- package/client/src/hooks/useApiKeyValidation.ts +106 -106
- package/client/src/hooks/useApiKeys.ts +238 -238
- package/client/src/hooks/useAppTheme.ts +17 -17
- package/client/src/hooks/useComponentPalette.ts +50 -50
- package/client/src/hooks/useDragAndDrop.ts +123 -123
- package/client/src/hooks/useDragVariable.ts +88 -88
- package/client/src/hooks/useExecution.ts +319 -313
- package/client/src/hooks/useParameterPanel.ts +176 -176
- package/client/src/hooks/useReactFlowNodes.ts +188 -188
- package/client/src/hooks/useToolSchema.ts +209 -209
- package/client/src/hooks/useWhatsApp.ts +196 -196
- package/client/src/hooks/useWorkflowManagement.ts +45 -45
- package/client/src/index.css +314 -314
- package/client/src/nodeDefinitions/aiAgentNodes.ts +335 -335
- package/client/src/nodeDefinitions/aiModelNodes.ts +340 -340
- package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -383
- package/client/src/nodeDefinitions/chatNodes.ts +135 -135
- package/client/src/nodeDefinitions/codeNodes.ts +54 -54
- package/client/src/nodeDefinitions/index.ts +14 -14
- package/client/src/nodeDefinitions/locationNodes.ts +462 -462
- package/client/src/nodeDefinitions/schedulerNodes.ts +220 -220
- package/client/src/nodeDefinitions/skillNodes.ts +17 -5
- package/client/src/nodeDefinitions/utilityNodes.ts +284 -284
- package/client/src/nodeDefinitions/whatsappNodes.ts +821 -865
- package/client/src/nodeDefinitions.ts +101 -103
- package/client/src/services/dynamicParameterService.ts +95 -95
- package/client/src/services/execution/aiAgentExecutionService.ts +34 -34
- package/client/src/services/executionService.ts +227 -231
- package/client/src/services/workflowApi.ts +91 -91
- package/client/src/store/useAppStore.ts +578 -581
- package/client/src/styles/theme.ts +513 -508
- package/client/src/styles/zIndex.ts +16 -16
- package/client/src/types/ComponentTypes.ts +38 -38
- package/client/src/types/INodeProperties.ts +287 -287
- package/client/src/types/NodeTypes.ts +27 -27
- package/client/src/utils/formatters.ts +32 -32
- package/client/src/utils/googleMapsLoader.ts +139 -139
- package/client/src/utils/locationUtils.ts +84 -84
- package/client/src/utils/nodeUtils.ts +30 -30
- package/client/src/utils/workflow.ts +29 -29
- package/client/src/vite-env.d.ts +12 -12
- package/client/tailwind.config.js +59 -59
- package/client/tsconfig.json +25 -25
- package/client/vite.config.js +35 -35
- package/package.json +78 -70
- package/scripts/build.js +153 -45
- package/scripts/clean.js +40 -40
- package/scripts/start.js +234 -210
- package/scripts/stop.js +301 -325
- package/server/.dockerignore +44 -44
- package/server/Dockerfile +45 -45
- package/server/constants.py +244 -249
- package/server/core/cache.py +460 -460
- package/server/core/config.py +127 -127
- package/server/core/container.py +98 -98
- package/server/core/database.py +1296 -1210
- package/server/core/logging.py +313 -313
- package/server/main.py +288 -288
- package/server/middleware/__init__.py +5 -5
- package/server/middleware/auth.py +89 -89
- package/server/models/auth.py +52 -52
- package/server/models/cache.py +24 -24
- package/server/models/database.py +235 -210
- package/server/models/nodes.py +435 -455
- package/server/pyproject.toml +75 -72
- package/server/requirements.txt +83 -83
- package/server/routers/android.py +294 -294
- package/server/routers/auth.py +203 -203
- package/server/routers/database.py +150 -150
- package/server/routers/maps.py +141 -141
- package/server/routers/nodejs_compat.py +288 -288
- package/server/routers/webhook.py +90 -90
- package/server/routers/websocket.py +2239 -2127
- package/server/routers/whatsapp.py +761 -761
- package/server/routers/workflow.py +199 -199
- package/server/services/ai.py +2444 -2414
- package/server/services/android_service.py +588 -588
- package/server/services/auth.py +130 -130
- package/server/services/chat_client.py +160 -160
- package/server/services/deployment/manager.py +706 -706
- package/server/services/event_waiter.py +675 -785
- package/server/services/execution/executor.py +1351 -1351
- package/server/services/execution/models.py +1 -1
- package/server/services/handlers/__init__.py +122 -126
- package/server/services/handlers/ai.py +390 -355
- package/server/services/handlers/android.py +69 -260
- package/server/services/handlers/code.py +278 -278
- package/server/services/handlers/http.py +193 -193
- package/server/services/handlers/tools.py +146 -32
- package/server/services/handlers/triggers.py +107 -107
- package/server/services/handlers/utility.py +822 -822
- package/server/services/handlers/whatsapp.py +423 -476
- package/server/services/maps.py +288 -288
- package/server/services/memory_store.py +103 -103
- package/server/services/node_executor.py +372 -375
- package/server/services/scheduler.py +155 -155
- package/server/services/skill_loader.py +1 -1
- package/server/services/status_broadcaster.py +834 -826
- package/server/services/temporal/__init__.py +23 -23
- package/server/services/temporal/activities.py +344 -344
- package/server/services/temporal/client.py +76 -76
- package/server/services/temporal/executor.py +147 -147
- package/server/services/temporal/worker.py +251 -251
- package/server/services/temporal/workflow.py +355 -355
- package/server/services/temporal/ws_client.py +236 -236
- package/server/services/text.py +110 -110
- package/server/services/user_auth.py +172 -172
- package/server/services/websocket_client.py +29 -29
- package/server/services/workflow.py +597 -597
- package/server/skills/android-skill/SKILL.md +4 -4
- package/server/skills/code-skill/SKILL.md +123 -89
- package/server/skills/maps-skill/SKILL.md +3 -3
- package/server/skills/memory-skill/SKILL.md +1 -1
- package/server/skills/web-search-skill/SKILL.md +154 -0
- package/server/skills/whatsapp-skill/SKILL.md +3 -3
- package/server/uv.lock +461 -100
- package/server/whatsapp-rpc/.dockerignore +30 -30
- package/server/whatsapp-rpc/Dockerfile +44 -44
- package/server/whatsapp-rpc/Dockerfile.web +17 -17
- package/server/whatsapp-rpc/README.md +139 -139
- package/server/whatsapp-rpc/bin/whatsapp-rpc-server +0 -0
- package/server/whatsapp-rpc/cli.js +95 -95
- package/server/whatsapp-rpc/configs/config.yaml +6 -6
- package/server/whatsapp-rpc/docker-compose.yml +35 -35
- package/server/whatsapp-rpc/docs/API.md +410 -410
- package/server/whatsapp-rpc/node_modules/.package-lock.json +259 -0
- package/server/whatsapp-rpc/node_modules/chalk/license +9 -0
- package/server/whatsapp-rpc/node_modules/chalk/package.json +83 -0
- package/server/whatsapp-rpc/node_modules/chalk/readme.md +297 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/index.d.ts +325 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/index.js +225 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/utilities.js +33 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/server/whatsapp-rpc/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/server/whatsapp-rpc/node_modules/commander/LICENSE +22 -0
- package/server/whatsapp-rpc/node_modules/commander/Readme.md +1148 -0
- package/server/whatsapp-rpc/node_modules/commander/esm.mjs +16 -0
- package/server/whatsapp-rpc/node_modules/commander/index.js +26 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/argument.js +145 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/command.js +2179 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/error.js +43 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/help.js +462 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/option.js +329 -0
- package/server/whatsapp-rpc/node_modules/commander/lib/suggestSimilar.js +100 -0
- package/server/whatsapp-rpc/node_modules/commander/package-support.json +16 -0
- package/server/whatsapp-rpc/node_modules/commander/package.json +80 -0
- package/server/whatsapp-rpc/node_modules/commander/typings/esm.d.mts +3 -0
- package/server/whatsapp-rpc/node_modules/commander/typings/index.d.ts +884 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/LICENSE +21 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/README.md +89 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/index.js +39 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/lib/enoent.js +59 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/lib/parse.js +91 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/lib/util/escape.js +47 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/lib/util/readShebang.js +23 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/lib/util/resolveCommand.js +52 -0
- package/server/whatsapp-rpc/node_modules/cross-spawn/package.json +73 -0
- package/server/whatsapp-rpc/node_modules/execa/index.d.ts +955 -0
- package/server/whatsapp-rpc/node_modules/execa/index.js +309 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/command.js +119 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/error.js +87 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/kill.js +102 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/pipe.js +42 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/promise.js +36 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/stdio.js +49 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/stream.js +133 -0
- package/server/whatsapp-rpc/node_modules/execa/lib/verbose.js +19 -0
- package/server/whatsapp-rpc/node_modules/execa/license +9 -0
- package/server/whatsapp-rpc/node_modules/execa/package.json +90 -0
- package/server/whatsapp-rpc/node_modules/execa/readme.md +822 -0
- package/server/whatsapp-rpc/node_modules/get-stream/license +9 -0
- package/server/whatsapp-rpc/node_modules/get-stream/package.json +53 -0
- package/server/whatsapp-rpc/node_modules/get-stream/readme.md +291 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/array-buffer.js +84 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/array.js +32 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/buffer.js +20 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/contents.js +101 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/index.d.ts +119 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/index.js +5 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/string.js +36 -0
- package/server/whatsapp-rpc/node_modules/get-stream/source/utils.js +11 -0
- package/server/whatsapp-rpc/node_modules/get-them-args/LICENSE +21 -0
- package/server/whatsapp-rpc/node_modules/get-them-args/README.md +95 -0
- package/server/whatsapp-rpc/node_modules/get-them-args/index.js +97 -0
- package/server/whatsapp-rpc/node_modules/get-them-args/package.json +36 -0
- package/server/whatsapp-rpc/node_modules/human-signals/LICENSE +201 -0
- package/server/whatsapp-rpc/node_modules/human-signals/README.md +168 -0
- package/server/whatsapp-rpc/node_modules/human-signals/build/src/core.js +273 -0
- package/server/whatsapp-rpc/node_modules/human-signals/build/src/main.d.ts +73 -0
- package/server/whatsapp-rpc/node_modules/human-signals/build/src/main.js +70 -0
- package/server/whatsapp-rpc/node_modules/human-signals/build/src/realtime.js +16 -0
- package/server/whatsapp-rpc/node_modules/human-signals/build/src/signals.js +34 -0
- package/server/whatsapp-rpc/node_modules/human-signals/package.json +61 -0
- package/server/whatsapp-rpc/node_modules/is-stream/index.d.ts +81 -0
- package/server/whatsapp-rpc/node_modules/is-stream/index.js +29 -0
- package/server/whatsapp-rpc/node_modules/is-stream/license +9 -0
- package/server/whatsapp-rpc/node_modules/is-stream/package.json +44 -0
- package/server/whatsapp-rpc/node_modules/is-stream/readme.md +60 -0
- package/server/whatsapp-rpc/node_modules/isexe/LICENSE +15 -0
- package/server/whatsapp-rpc/node_modules/isexe/README.md +51 -0
- package/server/whatsapp-rpc/node_modules/isexe/index.js +57 -0
- package/server/whatsapp-rpc/node_modules/isexe/mode.js +41 -0
- package/server/whatsapp-rpc/node_modules/isexe/package.json +31 -0
- package/server/whatsapp-rpc/node_modules/isexe/test/basic.js +221 -0
- package/server/whatsapp-rpc/node_modules/isexe/windows.js +42 -0
- package/server/whatsapp-rpc/node_modules/kill-port/.editorconfig +12 -0
- package/server/whatsapp-rpc/node_modules/kill-port/.gitattributes +1 -0
- package/server/whatsapp-rpc/node_modules/kill-port/LICENSE +21 -0
- package/server/whatsapp-rpc/node_modules/kill-port/README.md +140 -0
- package/server/whatsapp-rpc/node_modules/kill-port/cli.js +25 -0
- package/server/whatsapp-rpc/node_modules/kill-port/example.js +21 -0
- package/server/whatsapp-rpc/node_modules/kill-port/index.js +46 -0
- package/server/whatsapp-rpc/node_modules/kill-port/logo.png +0 -0
- package/server/whatsapp-rpc/node_modules/kill-port/package.json +41 -0
- package/server/whatsapp-rpc/node_modules/kill-port/pnpm-lock.yaml +4606 -0
- package/server/whatsapp-rpc/node_modules/kill-port/test.js +16 -0
- package/server/whatsapp-rpc/node_modules/merge-stream/LICENSE +21 -0
- package/server/whatsapp-rpc/node_modules/merge-stream/README.md +78 -0
- package/server/whatsapp-rpc/node_modules/merge-stream/index.js +41 -0
- package/server/whatsapp-rpc/node_modules/merge-stream/package.json +19 -0
- package/server/whatsapp-rpc/node_modules/mimic-fn/index.d.ts +52 -0
- package/server/whatsapp-rpc/node_modules/mimic-fn/index.js +71 -0
- package/server/whatsapp-rpc/node_modules/mimic-fn/license +9 -0
- package/server/whatsapp-rpc/node_modules/mimic-fn/package.json +45 -0
- package/server/whatsapp-rpc/node_modules/mimic-fn/readme.md +90 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/index.d.ts +90 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/index.js +52 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/license +9 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/index.d.ts +31 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/index.js +12 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/license +9 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/package.json +41 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/node_modules/path-key/readme.md +57 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/package.json +49 -0
- package/server/whatsapp-rpc/node_modules/npm-run-path/readme.md +104 -0
- package/server/whatsapp-rpc/node_modules/onetime/index.d.ts +59 -0
- package/server/whatsapp-rpc/node_modules/onetime/index.js +41 -0
- package/server/whatsapp-rpc/node_modules/onetime/license +9 -0
- package/server/whatsapp-rpc/node_modules/onetime/package.json +45 -0
- package/server/whatsapp-rpc/node_modules/onetime/readme.md +94 -0
- package/server/whatsapp-rpc/node_modules/path-key/index.d.ts +40 -0
- package/server/whatsapp-rpc/node_modules/path-key/index.js +16 -0
- package/server/whatsapp-rpc/node_modules/path-key/license +9 -0
- package/server/whatsapp-rpc/node_modules/path-key/package.json +39 -0
- package/server/whatsapp-rpc/node_modules/path-key/readme.md +61 -0
- package/server/whatsapp-rpc/node_modules/shebang-command/index.js +19 -0
- package/server/whatsapp-rpc/node_modules/shebang-command/license +9 -0
- package/server/whatsapp-rpc/node_modules/shebang-command/package.json +34 -0
- package/server/whatsapp-rpc/node_modules/shebang-command/readme.md +34 -0
- package/server/whatsapp-rpc/node_modules/shebang-regex/index.d.ts +22 -0
- package/server/whatsapp-rpc/node_modules/shebang-regex/index.js +2 -0
- package/server/whatsapp-rpc/node_modules/shebang-regex/license +9 -0
- package/server/whatsapp-rpc/node_modules/shebang-regex/package.json +35 -0
- package/server/whatsapp-rpc/node_modules/shebang-regex/readme.md +33 -0
- package/server/whatsapp-rpc/node_modules/shell-exec/LICENSE +21 -0
- package/server/whatsapp-rpc/node_modules/shell-exec/README.md +60 -0
- package/server/whatsapp-rpc/node_modules/shell-exec/index.js +47 -0
- package/server/whatsapp-rpc/node_modules/shell-exec/package.json +29 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/LICENSE.txt +16 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/README.md +74 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.d.ts +12 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.js +10 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/browser.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.d.ts +48 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.js +279 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/index.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/package.json +3 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.d.ts +29 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.js +42 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/cjs/signals.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.d.ts +12 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.js +4 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/browser.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.d.ts +48 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.js +275 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/index.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/package.json +3 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.d.ts +29 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.d.ts.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.js +39 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/dist/mjs/signals.js.map +1 -0
- package/server/whatsapp-rpc/node_modules/signal-exit/package.json +106 -0
- package/server/whatsapp-rpc/node_modules/strip-final-newline/index.js +14 -0
- package/server/whatsapp-rpc/node_modules/strip-final-newline/license +9 -0
- package/server/whatsapp-rpc/node_modules/strip-final-newline/package.json +43 -0
- package/server/whatsapp-rpc/node_modules/strip-final-newline/readme.md +35 -0
- package/server/whatsapp-rpc/node_modules/which/CHANGELOG.md +166 -0
- package/server/whatsapp-rpc/node_modules/which/LICENSE +15 -0
- package/server/whatsapp-rpc/node_modules/which/README.md +54 -0
- package/server/whatsapp-rpc/node_modules/which/bin/node-which +52 -0
- package/server/whatsapp-rpc/node_modules/which/package.json +43 -0
- package/server/whatsapp-rpc/node_modules/which/which.js +125 -0
- package/server/whatsapp-rpc/package-lock.json +272 -0
- package/server/whatsapp-rpc/package.json +30 -30
- package/server/whatsapp-rpc/schema.json +1294 -1294
- package/server/whatsapp-rpc/scripts/clean.cjs +66 -66
- package/server/whatsapp-rpc/scripts/cli.js +162 -162
- package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -166
- package/server/whatsapp-rpc/src/python/pyproject.toml +15 -15
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -4
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -427
- package/server/whatsapp-rpc/web/app.py +609 -609
- package/server/whatsapp-rpc/web/requirements.txt +6 -6
- package/server/whatsapp-rpc/web/rpc_client.py +427 -427
- package/server/whatsapp-rpc/web/static/openapi.yaml +59 -59
- package/server/whatsapp-rpc/web/templates/base.html +149 -149
- package/server/whatsapp-rpc/web/templates/contacts.html +240 -240
- package/server/whatsapp-rpc/web/templates/dashboard.html +319 -319
- package/server/whatsapp-rpc/web/templates/groups.html +328 -328
- package/server/whatsapp-rpc/web/templates/messages.html +465 -465
- package/server/whatsapp-rpc/web/templates/messaging.html +680 -680
- package/server/whatsapp-rpc/web/templates/send.html +258 -258
- package/server/whatsapp-rpc/web/templates/settings.html +459 -459
- package/client/src/components/ui/AndroidSettingsPanel.tsx +0 -401
- package/client/src/components/ui/WhatsAppSettingsPanel.tsx +0 -345
- package/client/src/nodeDefinitions/androidDeviceNodes.ts +0 -140
- package/docker-compose.prod.yml +0 -107
- package/docker-compose.yml +0 -104
- package/docs-MachinaOs/README.md +0 -85
- package/docs-MachinaOs/deployment/docker.mdx +0 -228
- package/docs-MachinaOs/deployment/production.mdx +0 -345
- package/docs-MachinaOs/docs.json +0 -75
- package/docs-MachinaOs/faq.mdx +0 -309
- package/docs-MachinaOs/favicon.svg +0 -5
- package/docs-MachinaOs/installation.mdx +0 -160
- package/docs-MachinaOs/introduction.mdx +0 -114
- package/docs-MachinaOs/logo/dark.svg +0 -6
- package/docs-MachinaOs/logo/light.svg +0 -6
- package/docs-MachinaOs/nodes/ai-agent.mdx +0 -216
- package/docs-MachinaOs/nodes/ai-models.mdx +0 -240
- package/docs-MachinaOs/nodes/android.mdx +0 -411
- package/docs-MachinaOs/nodes/overview.mdx +0 -181
- package/docs-MachinaOs/nodes/schedulers.mdx +0 -316
- package/docs-MachinaOs/nodes/webhooks.mdx +0 -330
- package/docs-MachinaOs/nodes/whatsapp.mdx +0 -305
- package/docs-MachinaOs/quickstart.mdx +0 -119
- package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +0 -177
- package/docs-MachinaOs/tutorials/android-automation.mdx +0 -242
- package/docs-MachinaOs/tutorials/first-workflow.mdx +0 -134
- package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +0 -185
- package/nul +0 -0
- package/scripts/check-ports.ps1 +0 -33
- package/scripts/kill-port.ps1 +0 -154
|
@@ -1,1971 +1,2144 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WebSocket Context for real-time communication with Python backend.
|
|
3
|
-
*
|
|
4
|
-
* Provides WebSocket connection for:
|
|
5
|
-
* - Request/response operations (parameters, execution, API keys)
|
|
6
|
-
* - Real-time broadcasts (status updates, multi-client sync)
|
|
7
|
-
* - Android device connection status
|
|
8
|
-
* - Node execution status (scoped by workflow_id - n8n pattern)
|
|
9
|
-
* - Variable/parameter updates
|
|
10
|
-
* - Workflow state changes
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import React, { createContext, useContext, useEffect, useState, useCallback, useRef, useMemo } from 'react';
|
|
14
|
-
import { API_CONFIG } from '../config/api';
|
|
15
|
-
import { useAppStore } from '../store/useAppStore';
|
|
16
|
-
import { useAuth } from './AuthContext';
|
|
17
|
-
|
|
18
|
-
// Generate unique request ID
|
|
19
|
-
const generateRequestId = (): string => {
|
|
20
|
-
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// Pending request tracking
|
|
24
|
-
interface PendingRequest {
|
|
25
|
-
resolve: (value: any) => void;
|
|
26
|
-
reject: (reason: any) => void;
|
|
27
|
-
timeout: NodeJS.Timeout | null; // null for no timeout (trigger nodes)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Request timeout (30 seconds)
|
|
31
|
-
const REQUEST_TIMEOUT = 30000;
|
|
32
|
-
|
|
33
|
-
// Trigger node types that wait indefinitely for events
|
|
34
|
-
const TRIGGER_NODE_TYPES = ['whatsappReceive', 'webhookTrigger', 'cronScheduler', 'chatTrigger'];
|
|
35
|
-
|
|
36
|
-
// Status types
|
|
37
|
-
export interface AndroidStatus {
|
|
38
|
-
connected: boolean;
|
|
39
|
-
paired: boolean;
|
|
40
|
-
device_id: string | null;
|
|
41
|
-
device_name: string | null;
|
|
42
|
-
connected_devices: string[];
|
|
43
|
-
connection_type: string | null;
|
|
44
|
-
qr_data: string | null;
|
|
45
|
-
session_token: string | null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface NodeStatus {
|
|
49
|
-
status: 'idle' | 'executing' | 'success' | 'error' | 'waiting';
|
|
50
|
-
data?: Record<string, any>;
|
|
51
|
-
output?: any;
|
|
52
|
-
timestamp?: number;
|
|
53
|
-
// Per-workflow scoping (n8n pattern)
|
|
54
|
-
workflow_id?: string;
|
|
55
|
-
// Waiting state data
|
|
56
|
-
message?: string;
|
|
57
|
-
waiter_id?: string;
|
|
58
|
-
timeout?: number;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface WorkflowStatus {
|
|
62
|
-
executing: boolean;
|
|
63
|
-
current_node: string | null;
|
|
64
|
-
progress?: number;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface DeploymentStatus {
|
|
68
|
-
isRunning: boolean;
|
|
69
|
-
activeRuns: number;
|
|
70
|
-
status: 'idle' | 'starting' | 'running' | 'stopped' | 'cancelled' | 'error';
|
|
71
|
-
workflow_id?: string | null; // Which workflow is deployed (for scoping)
|
|
72
|
-
totalTime?: number;
|
|
73
|
-
error?: string;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface WorkflowLock {
|
|
77
|
-
locked: boolean;
|
|
78
|
-
workflow_id: string | null;
|
|
79
|
-
locked_at: number | null;
|
|
80
|
-
reason: string | null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export interface WhatsAppStatus {
|
|
84
|
-
connected: boolean;
|
|
85
|
-
has_session: boolean;
|
|
86
|
-
running: boolean;
|
|
87
|
-
pairing: boolean;
|
|
88
|
-
device_id?: string;
|
|
89
|
-
qr?: string;
|
|
90
|
-
timestamp?: number;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
//
|
|
527
|
-
if (
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
[
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
// Load
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
message:
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
//
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
//
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
//
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
//
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
//
|
|
1160
|
-
//
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
): Promise<
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
return
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
try {
|
|
1404
|
-
const response = await sendRequest<any>('
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
});
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
};
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
return
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
//
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Context for real-time communication with Python backend.
|
|
3
|
+
*
|
|
4
|
+
* Provides WebSocket connection for:
|
|
5
|
+
* - Request/response operations (parameters, execution, API keys)
|
|
6
|
+
* - Real-time broadcasts (status updates, multi-client sync)
|
|
7
|
+
* - Android device connection status
|
|
8
|
+
* - Node execution status (scoped by workflow_id - n8n pattern)
|
|
9
|
+
* - Variable/parameter updates
|
|
10
|
+
* - Workflow state changes
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import React, { createContext, useContext, useEffect, useState, useCallback, useRef, useMemo } from 'react';
|
|
14
|
+
import { API_CONFIG } from '../config/api';
|
|
15
|
+
import { useAppStore } from '../store/useAppStore';
|
|
16
|
+
import { useAuth } from './AuthContext';
|
|
17
|
+
|
|
18
|
+
// Generate unique request ID
|
|
19
|
+
const generateRequestId = (): string => {
|
|
20
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Pending request tracking
|
|
24
|
+
interface PendingRequest {
|
|
25
|
+
resolve: (value: any) => void;
|
|
26
|
+
reject: (reason: any) => void;
|
|
27
|
+
timeout: NodeJS.Timeout | null; // null for no timeout (trigger nodes)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Request timeout (30 seconds)
|
|
31
|
+
const REQUEST_TIMEOUT = 30000;
|
|
32
|
+
|
|
33
|
+
// Trigger node types that wait indefinitely for events
|
|
34
|
+
const TRIGGER_NODE_TYPES = ['whatsappReceive', 'webhookTrigger', 'cronScheduler', 'chatTrigger'];
|
|
35
|
+
|
|
36
|
+
// Status types
|
|
37
|
+
export interface AndroidStatus {
|
|
38
|
+
connected: boolean;
|
|
39
|
+
paired: boolean;
|
|
40
|
+
device_id: string | null;
|
|
41
|
+
device_name: string | null;
|
|
42
|
+
connected_devices: string[];
|
|
43
|
+
connection_type: string | null;
|
|
44
|
+
qr_data: string | null;
|
|
45
|
+
session_token: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface NodeStatus {
|
|
49
|
+
status: 'idle' | 'executing' | 'success' | 'error' | 'waiting';
|
|
50
|
+
data?: Record<string, any>;
|
|
51
|
+
output?: any;
|
|
52
|
+
timestamp?: number;
|
|
53
|
+
// Per-workflow scoping (n8n pattern)
|
|
54
|
+
workflow_id?: string;
|
|
55
|
+
// Waiting state data
|
|
56
|
+
message?: string;
|
|
57
|
+
waiter_id?: string;
|
|
58
|
+
timeout?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface WorkflowStatus {
|
|
62
|
+
executing: boolean;
|
|
63
|
+
current_node: string | null;
|
|
64
|
+
progress?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface DeploymentStatus {
|
|
68
|
+
isRunning: boolean;
|
|
69
|
+
activeRuns: number;
|
|
70
|
+
status: 'idle' | 'starting' | 'running' | 'stopped' | 'cancelled' | 'error';
|
|
71
|
+
workflow_id?: string | null; // Which workflow is deployed (for scoping)
|
|
72
|
+
totalTime?: number;
|
|
73
|
+
error?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface WorkflowLock {
|
|
77
|
+
locked: boolean;
|
|
78
|
+
workflow_id: string | null;
|
|
79
|
+
locked_at: number | null;
|
|
80
|
+
reason: string | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface WhatsAppStatus {
|
|
84
|
+
connected: boolean;
|
|
85
|
+
has_session: boolean;
|
|
86
|
+
running: boolean;
|
|
87
|
+
pairing: boolean;
|
|
88
|
+
device_id?: string;
|
|
89
|
+
qr?: string;
|
|
90
|
+
timestamp?: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// WhatsApp Rate Limit types (from Go RPC schema)
|
|
94
|
+
export interface RateLimitConfig {
|
|
95
|
+
enabled: boolean;
|
|
96
|
+
min_delay_ms: number;
|
|
97
|
+
max_delay_ms: number;
|
|
98
|
+
typing_delay_ms: number;
|
|
99
|
+
link_extra_delay_ms: number;
|
|
100
|
+
max_messages_per_minute: number;
|
|
101
|
+
max_messages_per_hour: number;
|
|
102
|
+
max_new_contacts_per_day: number;
|
|
103
|
+
simulate_typing: boolean;
|
|
104
|
+
randomize_delays: boolean;
|
|
105
|
+
pause_on_low_response: boolean;
|
|
106
|
+
response_rate_threshold: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface RateLimitStats {
|
|
110
|
+
messages_sent_last_minute: number;
|
|
111
|
+
messages_sent_last_hour: number;
|
|
112
|
+
messages_sent_today: number;
|
|
113
|
+
new_contacts_today: number;
|
|
114
|
+
responses_received: number;
|
|
115
|
+
response_rate: number;
|
|
116
|
+
is_paused: boolean;
|
|
117
|
+
pause_reason?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ApiKeyStatus {
|
|
121
|
+
valid: boolean;
|
|
122
|
+
hasKey?: boolean;
|
|
123
|
+
message?: string;
|
|
124
|
+
models?: string[];
|
|
125
|
+
timestamp?: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Console log entry from Console nodes
|
|
129
|
+
export interface ConsoleLogEntry {
|
|
130
|
+
node_id: string;
|
|
131
|
+
label: string;
|
|
132
|
+
timestamp: string;
|
|
133
|
+
data: any;
|
|
134
|
+
formatted: string;
|
|
135
|
+
format: 'json' | 'json_compact' | 'text' | 'table';
|
|
136
|
+
workflow_id?: string;
|
|
137
|
+
// Source node info (the node whose output is being logged)
|
|
138
|
+
source_node_id?: string;
|
|
139
|
+
source_node_type?: string;
|
|
140
|
+
source_node_label?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Terminal/server log entry
|
|
144
|
+
export interface TerminalLogEntry {
|
|
145
|
+
timestamp: string;
|
|
146
|
+
level: 'debug' | 'info' | 'warning' | 'error';
|
|
147
|
+
message: string;
|
|
148
|
+
source?: string; // e.g., 'workflow', 'ai', 'android', 'whatsapp'
|
|
149
|
+
details?: any;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Chat message for chatTrigger nodes
|
|
153
|
+
export interface ChatMessage {
|
|
154
|
+
role: 'user' | 'assistant';
|
|
155
|
+
message: string;
|
|
156
|
+
timestamp: string;
|
|
157
|
+
session_id?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// WhatsApp received message structure (from Go service via whatsapp_message_received event)
|
|
161
|
+
export interface WhatsAppMessage {
|
|
162
|
+
message_id: string;
|
|
163
|
+
sender: string;
|
|
164
|
+
chat_id: string;
|
|
165
|
+
type: 'text' | 'image' | 'video' | 'audio' | 'document' | 'location' | 'contact' | 'sticker';
|
|
166
|
+
text?: string;
|
|
167
|
+
timestamp: number;
|
|
168
|
+
is_group: boolean;
|
|
169
|
+
push_name?: string;
|
|
170
|
+
media_url?: string;
|
|
171
|
+
media_data?: string; // Base64 if includeMediaData is enabled
|
|
172
|
+
caption?: string;
|
|
173
|
+
// Location message fields
|
|
174
|
+
latitude?: number;
|
|
175
|
+
longitude?: number;
|
|
176
|
+
// Contact message fields
|
|
177
|
+
contact_name?: string;
|
|
178
|
+
vcard?: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface NodeParameters {
|
|
182
|
+
parameters: Record<string, any>;
|
|
183
|
+
version: number;
|
|
184
|
+
timestamp?: number;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface FullStatus {
|
|
188
|
+
android: AndroidStatus;
|
|
189
|
+
api_keys: Record<string, ApiKeyStatus>;
|
|
190
|
+
nodes: Record<string, NodeStatus>;
|
|
191
|
+
node_parameters: Record<string, NodeParameters>;
|
|
192
|
+
variables: Record<string, any>;
|
|
193
|
+
workflow: WorkflowStatus;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Context value type
|
|
197
|
+
interface WebSocketContextValue {
|
|
198
|
+
// Connection state
|
|
199
|
+
isConnected: boolean;
|
|
200
|
+
reconnecting: boolean;
|
|
201
|
+
|
|
202
|
+
// Status data
|
|
203
|
+
androidStatus: AndroidStatus;
|
|
204
|
+
whatsappStatus: WhatsAppStatus;
|
|
205
|
+
whatsappMessages: WhatsAppMessage[]; // History of received messages
|
|
206
|
+
lastWhatsAppMessage: WhatsAppMessage | null; // Most recent message
|
|
207
|
+
apiKeyStatuses: Record<string, ApiKeyStatus>;
|
|
208
|
+
consoleLogs: ConsoleLogEntry[]; // Console node output logs
|
|
209
|
+
terminalLogs: TerminalLogEntry[]; // Server/terminal logs
|
|
210
|
+
chatMessages: ChatMessage[]; // Chat messages for chatTrigger
|
|
211
|
+
nodeStatuses: Record<string, NodeStatus>; // Current workflow's node statuses
|
|
212
|
+
nodeParameters: Record<string, NodeParameters>;
|
|
213
|
+
variables: Record<string, any>;
|
|
214
|
+
workflowStatus: WorkflowStatus;
|
|
215
|
+
deploymentStatus: DeploymentStatus;
|
|
216
|
+
workflowLock: WorkflowLock;
|
|
217
|
+
|
|
218
|
+
// Status getters
|
|
219
|
+
getNodeStatus: (nodeId: string) => NodeStatus | undefined;
|
|
220
|
+
getApiKeyStatus: (provider: string) => ApiKeyStatus | undefined;
|
|
221
|
+
getVariable: (name: string) => any;
|
|
222
|
+
requestStatus: () => void;
|
|
223
|
+
clearNodeStatus: (nodeId: string) => Promise<void>;
|
|
224
|
+
clearWhatsAppMessages: () => void;
|
|
225
|
+
clearConsoleLogs: () => void;
|
|
226
|
+
clearTerminalLogs: () => void;
|
|
227
|
+
clearChatMessages: () => void;
|
|
228
|
+
sendChatMessage: (message: string, nodeId?: string) => Promise<void>;
|
|
229
|
+
|
|
230
|
+
// Generic request method
|
|
231
|
+
sendRequest: <T = any>(type: string, data?: Record<string, any>) => Promise<T>;
|
|
232
|
+
|
|
233
|
+
// Node Parameters
|
|
234
|
+
getNodeParameters: (nodeId: string) => Promise<NodeParameters | null>;
|
|
235
|
+
getAllNodeParameters: (nodeIds: string[]) => Promise<Record<string, NodeParameters>>;
|
|
236
|
+
saveNodeParameters: (nodeId: string, parameters: Record<string, any>, version?: number) => Promise<boolean>;
|
|
237
|
+
deleteNodeParameters: (nodeId: string) => Promise<boolean>;
|
|
238
|
+
|
|
239
|
+
// Node Execution
|
|
240
|
+
executeNode: (nodeId: string, nodeType: string, parameters: Record<string, any>, nodes?: any[], edges?: any[]) => Promise<any>;
|
|
241
|
+
executeWorkflow: (nodes: any[], edges: any[], sessionId?: string) => Promise<any>;
|
|
242
|
+
getNodeOutput: (nodeId: string, outputName?: string) => Promise<any>;
|
|
243
|
+
|
|
244
|
+
// Trigger/Event Waiting
|
|
245
|
+
cancelEventWait: (nodeId: string, waiterId?: string) => Promise<{ success: boolean; cancelled_count?: number }>;
|
|
246
|
+
|
|
247
|
+
// Deployment Operations
|
|
248
|
+
deployWorkflow: (workflowId: string, nodes: any[], edges: any[], sessionId?: string) => Promise<any>;
|
|
249
|
+
cancelDeployment: (workflowId?: string) => Promise<any>;
|
|
250
|
+
getDeploymentStatus: (workflowId?: string) => Promise<{ isRunning: boolean; activeRuns: number; settings?: any; workflow_id?: string }>;
|
|
251
|
+
|
|
252
|
+
// AI Operations
|
|
253
|
+
executeAiNode: (nodeId: string, nodeType: string, parameters: Record<string, any>, model: string, workflowId: string, nodes: any[], edges: any[]) => Promise<any>;
|
|
254
|
+
getAiModels: (provider: string, apiKey: string) => Promise<string[]>;
|
|
255
|
+
|
|
256
|
+
// API Key Operations
|
|
257
|
+
validateApiKey: (provider: string, apiKey: string) => Promise<{ valid: boolean; message?: string; models?: string[] }>;
|
|
258
|
+
getStoredApiKey: (provider: string) => Promise<{ hasKey: boolean; apiKey?: string; models?: string[] }>;
|
|
259
|
+
saveApiKey: (provider: string, apiKey: string, models?: string[]) => Promise<boolean>;
|
|
260
|
+
deleteApiKey: (provider: string) => Promise<boolean>;
|
|
261
|
+
|
|
262
|
+
// Android Operations
|
|
263
|
+
getAndroidDevices: () => Promise<string[]>;
|
|
264
|
+
executeAndroidAction: (serviceId: string, action: string, parameters: Record<string, any>, deviceId?: string) => Promise<any>;
|
|
265
|
+
|
|
266
|
+
// Maps Operations
|
|
267
|
+
validateMapsKey: (apiKey: string) => Promise<{ valid: boolean; message?: string }>;
|
|
268
|
+
|
|
269
|
+
// WhatsApp Operations
|
|
270
|
+
getWhatsAppStatus: () => Promise<{ connected: boolean; deviceId?: string; data?: any }>;
|
|
271
|
+
getWhatsAppQR: () => Promise<{ connected: boolean; qr?: string; message?: string }>;
|
|
272
|
+
sendWhatsAppMessage: (phone: string, message: string) => Promise<{ success: boolean; messageId?: string; error?: string }>;
|
|
273
|
+
startWhatsAppConnection: () => Promise<{ success: boolean; message?: string }>;
|
|
274
|
+
restartWhatsAppConnection: () => Promise<{ success: boolean; message?: string }>;
|
|
275
|
+
getWhatsAppGroups: () => Promise<{ success: boolean; groups: Array<{ jid: string; name: string; topic?: string; size?: number; is_community?: boolean }>; error?: string }>;
|
|
276
|
+
getWhatsAppGroupInfo: (groupId: string) => Promise<{ success: boolean; participants: Array<{ phone: string; name: string; jid: string; is_admin?: boolean }>; name?: string; error?: string }>;
|
|
277
|
+
getWhatsAppRateLimitConfig: () => Promise<{ success: boolean; config?: RateLimitConfig; stats?: RateLimitStats; error?: string }>;
|
|
278
|
+
setWhatsAppRateLimitConfig: (config: Partial<RateLimitConfig>) => Promise<{ success: boolean; config?: RateLimitConfig; error?: string }>;
|
|
279
|
+
getWhatsAppRateLimitStats: () => Promise<{ success: boolean; stats?: RateLimitStats; error?: string }>;
|
|
280
|
+
unpauseWhatsAppRateLimit: () => Promise<{ success: boolean; stats?: RateLimitStats; error?: string }>;
|
|
281
|
+
|
|
282
|
+
// Memory and Skill Operations
|
|
283
|
+
clearMemory: (sessionId: string, clearLongTerm?: boolean) => Promise<{ success: boolean; default_content?: string; cleared_vector_store?: boolean; error?: string }>;
|
|
284
|
+
resetSkill: (skillName: string) => Promise<{ success: boolean; original_content?: string; is_builtin?: boolean; error?: string }>;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Default values
|
|
288
|
+
const defaultAndroidStatus: AndroidStatus = {
|
|
289
|
+
connected: false,
|
|
290
|
+
paired: false,
|
|
291
|
+
device_id: null,
|
|
292
|
+
device_name: null,
|
|
293
|
+
connected_devices: [],
|
|
294
|
+
connection_type: null,
|
|
295
|
+
qr_data: null,
|
|
296
|
+
session_token: null
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const defaultWorkflowStatus: WorkflowStatus = {
|
|
300
|
+
executing: false,
|
|
301
|
+
current_node: null
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const defaultDeploymentStatus: DeploymentStatus = {
|
|
305
|
+
isRunning: false,
|
|
306
|
+
activeRuns: 0,
|
|
307
|
+
status: 'idle'
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const defaultWorkflowLock: WorkflowLock = {
|
|
311
|
+
locked: false,
|
|
312
|
+
workflow_id: null,
|
|
313
|
+
locked_at: null,
|
|
314
|
+
reason: null
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const defaultWhatsAppStatus: WhatsAppStatus = {
|
|
318
|
+
connected: false,
|
|
319
|
+
has_session: false,
|
|
320
|
+
running: false,
|
|
321
|
+
pairing: false
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const WebSocketContext = createContext<WebSocketContextValue | null>(null);
|
|
325
|
+
|
|
326
|
+
// WebSocket URL (convert http to ws)
|
|
327
|
+
const getWebSocketUrl = () => {
|
|
328
|
+
const baseUrl = API_CONFIG.PYTHON_BASE_URL;
|
|
329
|
+
|
|
330
|
+
// Production: empty base URL means use current origin
|
|
331
|
+
if (!baseUrl) {
|
|
332
|
+
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
|
333
|
+
return `${wsProtocol}://${window.location.host}/ws/status`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Development: convert http(s) to ws(s)
|
|
337
|
+
const wsProtocol = baseUrl.startsWith('https') ? 'wss' : 'ws';
|
|
338
|
+
const wsUrl = baseUrl.replace(/^https?/, wsProtocol);
|
|
339
|
+
return `${wsUrl}/ws/status`;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Max number of WhatsApp messages to keep in history
|
|
343
|
+
const MAX_WHATSAPP_MESSAGE_HISTORY = 100;
|
|
344
|
+
|
|
345
|
+
export const WebSocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
346
|
+
// Get authentication state - only connect WebSocket when authenticated
|
|
347
|
+
const { isAuthenticated, isLoading: authLoading } = useAuth();
|
|
348
|
+
|
|
349
|
+
// Get current workflow ID for filtering node status updates (n8n pattern)
|
|
350
|
+
const currentWorkflow = useAppStore(state => state.currentWorkflow);
|
|
351
|
+
const currentWorkflowId = currentWorkflow?.id;
|
|
352
|
+
|
|
353
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
354
|
+
const [reconnecting, setReconnecting] = useState(false);
|
|
355
|
+
const [androidStatus, setAndroidStatus] = useState<AndroidStatus>(defaultAndroidStatus);
|
|
356
|
+
const [whatsappStatus, setWhatsappStatus] = useState<WhatsAppStatus>(defaultWhatsAppStatus);
|
|
357
|
+
const [whatsappMessages, setWhatsappMessages] = useState<WhatsAppMessage[]>([]);
|
|
358
|
+
const [lastWhatsAppMessage, setLastWhatsAppMessage] = useState<WhatsAppMessage | null>(null);
|
|
359
|
+
const [apiKeyStatuses, setApiKeyStatuses] = useState<Record<string, ApiKeyStatus>>({});
|
|
360
|
+
const [consoleLogs, setConsoleLogs] = useState<ConsoleLogEntry[]>([]);
|
|
361
|
+
const [terminalLogs, setTerminalLogs] = useState<TerminalLogEntry[]>([]);
|
|
362
|
+
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
|
|
363
|
+
// Per-workflow node statuses: workflow_id -> node_id -> NodeStatus (n8n pattern)
|
|
364
|
+
const [allNodeStatuses, setAllNodeStatuses] = useState<Record<string, Record<string, NodeStatus>>>({});
|
|
365
|
+
const [nodeParameters, setNodeParameters] = useState<Record<string, NodeParameters>>({});
|
|
366
|
+
// Per-workflow variables: workflow_id -> variable_name -> value (n8n pattern)
|
|
367
|
+
const [allVariables, setAllVariables] = useState<Record<string, Record<string, any>>>({});
|
|
368
|
+
const [workflowStatus, setWorkflowStatus] = useState<WorkflowStatus>(defaultWorkflowStatus);
|
|
369
|
+
const [deploymentStatus, setDeploymentStatus] = useState<DeploymentStatus>(defaultDeploymentStatus);
|
|
370
|
+
const [workflowLock, setWorkflowLock] = useState<WorkflowLock>(defaultWorkflowLock);
|
|
371
|
+
|
|
372
|
+
const wsRef = useRef<WebSocket | null>(null);
|
|
373
|
+
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
374
|
+
const pingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
375
|
+
const pendingRequestsRef = useRef<Map<string, PendingRequest>>(new Map());
|
|
376
|
+
// Ref for current workflow ID - allows message handler to access latest value
|
|
377
|
+
// without recreating the WebSocket connection (n8n pattern)
|
|
378
|
+
const currentWorkflowIdRef = useRef<string | undefined>(currentWorkflowId);
|
|
379
|
+
|
|
380
|
+
// Keep the ref in sync with the state and clear node statuses on workflow switch (n8n pattern)
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
const previousWorkflowId = currentWorkflowIdRef.current;
|
|
383
|
+
currentWorkflowIdRef.current = currentWorkflowId;
|
|
384
|
+
|
|
385
|
+
// No need to clear node statuses - they are now stored per-workflow (n8n pattern)
|
|
386
|
+
// Each workflow's statuses are isolated in allNodeStatuses[workflow_id]
|
|
387
|
+
if (previousWorkflowId && currentWorkflowId && previousWorkflowId !== currentWorkflowId) {
|
|
388
|
+
|
|
389
|
+
// Fetch deployment status for the new workflow (n8n pattern)
|
|
390
|
+
// This ensures the deploy button shows correct state when switching workflows
|
|
391
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
392
|
+
const fetchDeploymentStatus = async () => {
|
|
393
|
+
try {
|
|
394
|
+
const requestId = generateRequestId();
|
|
395
|
+
const response = await new Promise<any>((resolve, reject) => {
|
|
396
|
+
const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
|
|
397
|
+
|
|
398
|
+
const handler = (event: MessageEvent) => {
|
|
399
|
+
try {
|
|
400
|
+
const msg = JSON.parse(event.data);
|
|
401
|
+
if (msg.request_id === requestId) {
|
|
402
|
+
clearTimeout(timeout);
|
|
403
|
+
wsRef.current?.removeEventListener('message', handler);
|
|
404
|
+
resolve(msg);
|
|
405
|
+
}
|
|
406
|
+
} catch {}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
wsRef.current?.addEventListener('message', handler);
|
|
410
|
+
wsRef.current?.send(JSON.stringify({
|
|
411
|
+
type: 'get_deployment_status',
|
|
412
|
+
request_id: requestId,
|
|
413
|
+
workflow_id: currentWorkflowId
|
|
414
|
+
}));
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Update deployment status based on response
|
|
418
|
+
const isRunning = response.is_running || false;
|
|
419
|
+
setDeploymentStatus({
|
|
420
|
+
isRunning,
|
|
421
|
+
activeRuns: response.active_runs || 0,
|
|
422
|
+
status: isRunning ? 'running' : 'idle',
|
|
423
|
+
workflow_id: response.workflow_id || null
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Sync with Zustand store's per-workflow isExecuting state (n8n pattern)
|
|
427
|
+
// This ensures Dashboard's isExecuting reflects the actual backend state
|
|
428
|
+
const { setWorkflowExecuting } = useAppStore.getState();
|
|
429
|
+
setWorkflowExecuting(currentWorkflowId, isRunning);
|
|
430
|
+
|
|
431
|
+
// Also update workflow lock based on deployment status (n8n pattern)
|
|
432
|
+
// A running workflow should be locked
|
|
433
|
+
setWorkflowLock({
|
|
434
|
+
locked: isRunning,
|
|
435
|
+
workflow_id: isRunning ? currentWorkflowId : null,
|
|
436
|
+
locked_at: isRunning ? Date.now() : null,
|
|
437
|
+
reason: isRunning ? 'Workflow is running' : null
|
|
438
|
+
});
|
|
439
|
+
} catch (err) {
|
|
440
|
+
console.error('[WebSocket] Failed to fetch deployment status:', err);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
fetchDeploymentStatus();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}, [currentWorkflowId]);
|
|
447
|
+
|
|
448
|
+
// Handle incoming messages
|
|
449
|
+
const handleMessage = useCallback((event: MessageEvent) => {
|
|
450
|
+
try {
|
|
451
|
+
const message = JSON.parse(event.data);
|
|
452
|
+
const { type, data, node_id, name, value, output, variables: varsUpdate, request_id } = message;
|
|
453
|
+
|
|
454
|
+
// Handle request/response pattern - resolve pending requests
|
|
455
|
+
if (request_id && pendingRequestsRef.current.has(request_id)) {
|
|
456
|
+
const pending = pendingRequestsRef.current.get(request_id)!;
|
|
457
|
+
if (pending.timeout) {
|
|
458
|
+
clearTimeout(pending.timeout);
|
|
459
|
+
}
|
|
460
|
+
pendingRequestsRef.current.delete(request_id);
|
|
461
|
+
pending.resolve(message);
|
|
462
|
+
return; // Response handled, don't process as broadcast
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
switch (type) {
|
|
466
|
+
case 'initial_status':
|
|
467
|
+
case 'full_status':
|
|
468
|
+
if (data) {
|
|
469
|
+
if (data.android) setAndroidStatus(data.android);
|
|
470
|
+
if (data.whatsapp) setWhatsappStatus(data.whatsapp);
|
|
471
|
+
if (data.api_keys) setApiKeyStatuses(data.api_keys);
|
|
472
|
+
// Node statuses from initial_status - group by workflow_id (n8n pattern)
|
|
473
|
+
if (data.nodes) {
|
|
474
|
+
const groupedStatuses: Record<string, Record<string, NodeStatus>> = {};
|
|
475
|
+
for (const [nodeId, status] of Object.entries(data.nodes)) {
|
|
476
|
+
const nodeStatus = status as NodeStatus;
|
|
477
|
+
const wfId = nodeStatus?.workflow_id || 'unknown';
|
|
478
|
+
if (!groupedStatuses[wfId]) groupedStatuses[wfId] = {};
|
|
479
|
+
groupedStatuses[wfId][nodeId] = nodeStatus;
|
|
480
|
+
}
|
|
481
|
+
setAllNodeStatuses(prev => ({ ...prev, ...groupedStatuses }));
|
|
482
|
+
}
|
|
483
|
+
if (data.node_parameters) setNodeParameters(data.node_parameters);
|
|
484
|
+
// Variables from initial_status - group by workflow_id (n8n pattern)
|
|
485
|
+
if (data.variables) {
|
|
486
|
+
// Variables may come with workflow_id or need grouping
|
|
487
|
+
const groupedVars: Record<string, Record<string, any>> = {};
|
|
488
|
+
for (const [varName, varData] of Object.entries(data.variables)) {
|
|
489
|
+
const wfId = (varData as any)?.workflow_id || 'unknown';
|
|
490
|
+
if (!groupedVars[wfId]) groupedVars[wfId] = {};
|
|
491
|
+
groupedVars[wfId][varName] = varData;
|
|
492
|
+
}
|
|
493
|
+
setAllVariables(prev => ({ ...prev, ...groupedVars }));
|
|
494
|
+
}
|
|
495
|
+
if (data.workflow) setWorkflowStatus(data.workflow);
|
|
496
|
+
if (data.workflow_lock) setWorkflowLock(data.workflow_lock);
|
|
497
|
+
// Handle deployment status from initial_status (n8n/Conductor pattern)
|
|
498
|
+
if (data.deployment) {
|
|
499
|
+
setDeploymentStatus({
|
|
500
|
+
isRunning: data.deployment.isRunning || false,
|
|
501
|
+
activeRuns: data.deployment.activeRuns || 0,
|
|
502
|
+
status: data.deployment.status || 'idle'
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
break;
|
|
507
|
+
|
|
508
|
+
case 'api_key_status':
|
|
509
|
+
if (message.provider) {
|
|
510
|
+
setApiKeyStatuses(prev => ({
|
|
511
|
+
...prev,
|
|
512
|
+
[message.provider]: data
|
|
513
|
+
}));
|
|
514
|
+
}
|
|
515
|
+
break;
|
|
516
|
+
|
|
517
|
+
case 'android_status':
|
|
518
|
+
setAndroidStatus(data || defaultAndroidStatus);
|
|
519
|
+
break;
|
|
520
|
+
|
|
521
|
+
case 'whatsapp_status':
|
|
522
|
+
setWhatsappStatus(data || defaultWhatsAppStatus);
|
|
523
|
+
break;
|
|
524
|
+
|
|
525
|
+
case 'whatsapp_message_received':
|
|
526
|
+
// Handle incoming WhatsApp message from Go service
|
|
527
|
+
if (data) {
|
|
528
|
+
const message: WhatsAppMessage = {
|
|
529
|
+
message_id: data.message_id || data.id || '',
|
|
530
|
+
sender: data.sender || data.from || '',
|
|
531
|
+
chat_id: data.chat_id || data.chat || '',
|
|
532
|
+
type: data.type || 'text',
|
|
533
|
+
text: data.text || data.message || data.body || '',
|
|
534
|
+
timestamp: data.timestamp || Date.now(),
|
|
535
|
+
is_group: data.is_group || data.isGroup || false,
|
|
536
|
+
push_name: data.push_name || data.pushName || data.name,
|
|
537
|
+
media_url: data.media_url || data.mediaUrl,
|
|
538
|
+
media_data: data.media_data || data.mediaData,
|
|
539
|
+
caption: data.caption,
|
|
540
|
+
latitude: data.latitude,
|
|
541
|
+
longitude: data.longitude,
|
|
542
|
+
contact_name: data.contact_name || data.contactName,
|
|
543
|
+
vcard: data.vcard
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// Update last message
|
|
547
|
+
setLastWhatsAppMessage(message);
|
|
548
|
+
|
|
549
|
+
// Add to message history (newest first, limit size)
|
|
550
|
+
setWhatsappMessages(prev => {
|
|
551
|
+
const updated = [message, ...prev];
|
|
552
|
+
return updated.slice(0, MAX_WHATSAPP_MESSAGE_HISTORY);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
}
|
|
556
|
+
break;
|
|
557
|
+
|
|
558
|
+
case 'node_status':
|
|
559
|
+
// Per-workflow node status storage (n8n pattern)
|
|
560
|
+
// Store status under workflow_id -> node_id structure
|
|
561
|
+
if (node_id) {
|
|
562
|
+
const statusWorkflowId = message.workflow_id || 'unknown';
|
|
563
|
+
// Phase and tool_name are inside data.data (nested structure from broadcaster)
|
|
564
|
+
const innerData = data?.data || {};
|
|
565
|
+
|
|
566
|
+
// Flatten the structure: merge inner data with outer data for easier access
|
|
567
|
+
const flattenedData = { ...data, ...innerData, workflow_id: statusWorkflowId };
|
|
568
|
+
|
|
569
|
+
setAllNodeStatuses((prev: Record<string, Record<string, NodeStatus>>) => ({
|
|
570
|
+
...prev,
|
|
571
|
+
[statusWorkflowId]: {
|
|
572
|
+
...(prev[statusWorkflowId] || {}),
|
|
573
|
+
[node_id]: flattenedData
|
|
574
|
+
}
|
|
575
|
+
}));
|
|
576
|
+
}
|
|
577
|
+
break;
|
|
578
|
+
|
|
579
|
+
case 'node_output':
|
|
580
|
+
// Per-workflow node output storage (n8n pattern)
|
|
581
|
+
if (node_id) {
|
|
582
|
+
const outputWorkflowId = message.workflow_id || 'unknown';
|
|
583
|
+
setAllNodeStatuses((prev: Record<string, Record<string, NodeStatus>>) => ({
|
|
584
|
+
...prev,
|
|
585
|
+
[outputWorkflowId]: {
|
|
586
|
+
...(prev[outputWorkflowId] || {}),
|
|
587
|
+
[node_id]: {
|
|
588
|
+
...(prev[outputWorkflowId]?.[node_id] || {}),
|
|
589
|
+
output,
|
|
590
|
+
workflow_id: outputWorkflowId
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}));
|
|
594
|
+
}
|
|
595
|
+
break;
|
|
596
|
+
|
|
597
|
+
case 'node_status_cleared':
|
|
598
|
+
// Handle broadcast from server when node status is cleared
|
|
599
|
+
if (node_id || message.node_id) {
|
|
600
|
+
const clearedNodeId = node_id || message.node_id;
|
|
601
|
+
const clearWorkflowId = message.workflow_id;
|
|
602
|
+
setAllNodeStatuses((prev: Record<string, Record<string, NodeStatus>>) => {
|
|
603
|
+
// If workflow_id specified, only clear from that workflow
|
|
604
|
+
if (clearWorkflowId && prev[clearWorkflowId]) {
|
|
605
|
+
const workflowStatuses = { ...prev[clearWorkflowId] };
|
|
606
|
+
delete workflowStatuses[clearedNodeId];
|
|
607
|
+
return { ...prev, [clearWorkflowId]: workflowStatuses };
|
|
608
|
+
}
|
|
609
|
+
// Otherwise clear from all workflows
|
|
610
|
+
const newStatuses: Record<string, Record<string, NodeStatus>> = {};
|
|
611
|
+
for (const [wfId, nodes] of Object.entries(prev)) {
|
|
612
|
+
const filteredNodes = { ...nodes };
|
|
613
|
+
delete filteredNodes[clearedNodeId];
|
|
614
|
+
newStatuses[wfId] = filteredNodes;
|
|
615
|
+
}
|
|
616
|
+
return newStatuses;
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
break;
|
|
620
|
+
|
|
621
|
+
// Node parameters broadcasts (from other clients)
|
|
622
|
+
case 'node_parameters_updated':
|
|
623
|
+
if (node_id) {
|
|
624
|
+
setNodeParameters(prev => ({
|
|
625
|
+
...prev,
|
|
626
|
+
[node_id]: {
|
|
627
|
+
parameters: message.parameters,
|
|
628
|
+
version: message.version,
|
|
629
|
+
timestamp: message.timestamp
|
|
630
|
+
}
|
|
631
|
+
}));
|
|
632
|
+
}
|
|
633
|
+
break;
|
|
634
|
+
|
|
635
|
+
case 'node_parameters_deleted':
|
|
636
|
+
if (node_id) {
|
|
637
|
+
setNodeParameters(prev => {
|
|
638
|
+
const updated = { ...prev };
|
|
639
|
+
delete updated[node_id];
|
|
640
|
+
return updated;
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
break;
|
|
644
|
+
|
|
645
|
+
case 'variable_update':
|
|
646
|
+
// Per-workflow variable storage (n8n pattern)
|
|
647
|
+
if (name !== undefined) {
|
|
648
|
+
const varWorkflowId = message.workflow_id || 'unknown';
|
|
649
|
+
setAllVariables((prev: Record<string, Record<string, any>>) => ({
|
|
650
|
+
...prev,
|
|
651
|
+
[varWorkflowId]: {
|
|
652
|
+
...(prev[varWorkflowId] || {}),
|
|
653
|
+
[name]: value
|
|
654
|
+
}
|
|
655
|
+
}));
|
|
656
|
+
}
|
|
657
|
+
break;
|
|
658
|
+
|
|
659
|
+
case 'variables_update':
|
|
660
|
+
// Per-workflow batch variable update (n8n pattern)
|
|
661
|
+
if (varsUpdate) {
|
|
662
|
+
const batchWorkflowId = message.workflow_id || 'unknown';
|
|
663
|
+
setAllVariables((prev: Record<string, Record<string, any>>) => ({
|
|
664
|
+
...prev,
|
|
665
|
+
[batchWorkflowId]: {
|
|
666
|
+
...(prev[batchWorkflowId] || {}),
|
|
667
|
+
...varsUpdate
|
|
668
|
+
}
|
|
669
|
+
}));
|
|
670
|
+
}
|
|
671
|
+
break;
|
|
672
|
+
|
|
673
|
+
case 'workflow_status':
|
|
674
|
+
setWorkflowStatus(data || defaultWorkflowStatus);
|
|
675
|
+
break;
|
|
676
|
+
|
|
677
|
+
case 'deployment_status':
|
|
678
|
+
// Handle deployment status updates (event-driven, no iterations)
|
|
679
|
+
// Per-workflow scoping (n8n pattern): Only apply updates for current workflow
|
|
680
|
+
if (message.status) {
|
|
681
|
+
const deploymentWorkflowId = message.workflow_id;
|
|
682
|
+
const activeWorkflowId = currentWorkflowIdRef.current;
|
|
683
|
+
|
|
684
|
+
// Apply deployment update if:
|
|
685
|
+
// 1. It's for the current workflow, OR
|
|
686
|
+
// 2. It's a stop/cancel/error (affects any workflow that was running), OR
|
|
687
|
+
// 3. No specific workflow context (backward compatibility)
|
|
688
|
+
const isTerminalStatus = ['stopped', 'cancelled', 'error'].includes(message.status);
|
|
689
|
+
const shouldApplyDeployment = !deploymentWorkflowId ||
|
|
690
|
+
deploymentWorkflowId === activeWorkflowId ||
|
|
691
|
+
isTerminalStatus;
|
|
692
|
+
|
|
693
|
+
if (shouldApplyDeployment) {
|
|
694
|
+
setDeploymentStatus(prev => {
|
|
695
|
+
const newStatus: DeploymentStatus = { ...prev };
|
|
696
|
+
// Capture workflow_id from message
|
|
697
|
+
if (message.workflow_id) {
|
|
698
|
+
newStatus.workflow_id = message.workflow_id;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
switch (message.status) {
|
|
702
|
+
case 'starting':
|
|
703
|
+
newStatus.isRunning = true;
|
|
704
|
+
newStatus.status = 'starting';
|
|
705
|
+
newStatus.activeRuns = 0;
|
|
706
|
+
break;
|
|
707
|
+
case 'running':
|
|
708
|
+
case 'started':
|
|
709
|
+
newStatus.isRunning = true;
|
|
710
|
+
newStatus.status = 'running';
|
|
711
|
+
newStatus.activeRuns = message.data?.active_runs ?? prev.activeRuns;
|
|
712
|
+
break;
|
|
713
|
+
case 'run_started':
|
|
714
|
+
newStatus.isRunning = true;
|
|
715
|
+
newStatus.status = 'running';
|
|
716
|
+
newStatus.activeRuns = message.data?.active_runs || prev.activeRuns + 1;
|
|
717
|
+
break;
|
|
718
|
+
case 'run_complete':
|
|
719
|
+
newStatus.activeRuns = Math.max(0, message.data?.active_runs || prev.activeRuns - 1);
|
|
720
|
+
break;
|
|
721
|
+
case 'stopped':
|
|
722
|
+
// Only clear if this was our workflow or no workflow was tracked
|
|
723
|
+
if (!prev.workflow_id || prev.workflow_id === deploymentWorkflowId) {
|
|
724
|
+
newStatus.isRunning = false;
|
|
725
|
+
newStatus.status = 'stopped';
|
|
726
|
+
newStatus.totalTime = message.data?.total_time;
|
|
727
|
+
newStatus.activeRuns = 0;
|
|
728
|
+
newStatus.workflow_id = null;
|
|
729
|
+
}
|
|
730
|
+
break;
|
|
731
|
+
case 'cancelled':
|
|
732
|
+
// Only clear if this was our workflow or no workflow was tracked
|
|
733
|
+
if (!prev.workflow_id || prev.workflow_id === deploymentWorkflowId) {
|
|
734
|
+
newStatus.isRunning = false;
|
|
735
|
+
newStatus.status = 'cancelled';
|
|
736
|
+
newStatus.activeRuns = 0;
|
|
737
|
+
newStatus.workflow_id = null;
|
|
738
|
+
}
|
|
739
|
+
break;
|
|
740
|
+
case 'error':
|
|
741
|
+
// Only clear if this was our workflow or no workflow was tracked
|
|
742
|
+
if (!prev.workflow_id || prev.workflow_id === deploymentWorkflowId) {
|
|
743
|
+
newStatus.isRunning = false;
|
|
744
|
+
newStatus.status = 'error';
|
|
745
|
+
newStatus.error = message.error;
|
|
746
|
+
newStatus.workflow_id = null;
|
|
747
|
+
}
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return newStatus;
|
|
752
|
+
});
|
|
753
|
+
// Sync with Zustand store's per-workflow isExecuting state (n8n pattern)
|
|
754
|
+
if (deploymentWorkflowId) {
|
|
755
|
+
const { setWorkflowExecuting } = useAppStore.getState();
|
|
756
|
+
const isRunning = ['starting', 'running', 'started', 'run_started'].includes(message.status);
|
|
757
|
+
const isStopped = ['stopped', 'cancelled', 'error'].includes(message.status);
|
|
758
|
+
if (isRunning || isStopped) {
|
|
759
|
+
setWorkflowExecuting(deploymentWorkflowId, isRunning);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
break;
|
|
765
|
+
|
|
766
|
+
case 'pong':
|
|
767
|
+
// Keep-alive response, no action needed
|
|
768
|
+
break;
|
|
769
|
+
|
|
770
|
+
case 'console_log':
|
|
771
|
+
// Handle console log entries from Console nodes
|
|
772
|
+
if (data) {
|
|
773
|
+
const logEntry: ConsoleLogEntry = {
|
|
774
|
+
node_id: data.node_id || '',
|
|
775
|
+
label: data.label || 'Console',
|
|
776
|
+
timestamp: data.timestamp || new Date().toISOString(),
|
|
777
|
+
data: data.data,
|
|
778
|
+
formatted: data.formatted || JSON.stringify(data.data, null, 2),
|
|
779
|
+
format: data.format || 'json',
|
|
780
|
+
workflow_id: data.workflow_id,
|
|
781
|
+
source_node_id: data.source_node_id,
|
|
782
|
+
source_node_type: data.source_node_type,
|
|
783
|
+
source_node_label: data.source_node_label
|
|
784
|
+
};
|
|
785
|
+
// Add to logs (newest first, limit to 100 entries)
|
|
786
|
+
setConsoleLogs(prev => {
|
|
787
|
+
const updated = [logEntry, ...prev];
|
|
788
|
+
return updated.slice(0, 100);
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
break;
|
|
792
|
+
|
|
793
|
+
case 'console_logs_cleared':
|
|
794
|
+
// Handle console logs cleared from server
|
|
795
|
+
if (message.workflow_id) {
|
|
796
|
+
setConsoleLogs(prev => prev.filter(log => log.workflow_id !== message.workflow_id));
|
|
797
|
+
} else {
|
|
798
|
+
setConsoleLogs([]);
|
|
799
|
+
}
|
|
800
|
+
break;
|
|
801
|
+
|
|
802
|
+
case 'terminal_log':
|
|
803
|
+
// Handle terminal/server log entries
|
|
804
|
+
if (data) {
|
|
805
|
+
const terminalEntry: TerminalLogEntry = {
|
|
806
|
+
timestamp: data.timestamp || new Date().toISOString(),
|
|
807
|
+
level: data.level || 'info',
|
|
808
|
+
message: data.message || '',
|
|
809
|
+
source: data.source,
|
|
810
|
+
details: data.details
|
|
811
|
+
};
|
|
812
|
+
// Add to logs (newest first, limit to 200 entries)
|
|
813
|
+
setTerminalLogs(prev => {
|
|
814
|
+
const updated = [terminalEntry, ...prev];
|
|
815
|
+
return updated.slice(0, 200);
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
break;
|
|
819
|
+
|
|
820
|
+
case 'terminal_logs_cleared':
|
|
821
|
+
// Handle terminal logs cleared from server
|
|
822
|
+
setTerminalLogs([]);
|
|
823
|
+
break;
|
|
824
|
+
|
|
825
|
+
case 'workflow_lock':
|
|
826
|
+
// Handle workflow lock status updates (per-workflow locking - n8n pattern)
|
|
827
|
+
// Only update lock state if it's for the current workflow or if unlocking
|
|
828
|
+
if (data) {
|
|
829
|
+
const lockWorkflowId = message.workflow_id || data.workflow_id;
|
|
830
|
+
const activeWorkflowId = currentWorkflowIdRef.current;
|
|
831
|
+
|
|
832
|
+
// Apply lock update if:
|
|
833
|
+
// 1. It's for the current workflow, OR
|
|
834
|
+
// 2. We're unlocking (locked=false), OR
|
|
835
|
+
// 3. No specific workflow context (backward compatibility)
|
|
836
|
+
const shouldApplyLock = !lockWorkflowId ||
|
|
837
|
+
lockWorkflowId === activeWorkflowId ||
|
|
838
|
+
!data.locked;
|
|
839
|
+
|
|
840
|
+
if (shouldApplyLock) {
|
|
841
|
+
setWorkflowLock({
|
|
842
|
+
locked: data.locked || false,
|
|
843
|
+
workflow_id: data.workflow_id || null,
|
|
844
|
+
locked_at: data.locked_at || null,
|
|
845
|
+
reason: data.reason || null
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
break;
|
|
850
|
+
|
|
851
|
+
case 'error':
|
|
852
|
+
console.error('[WebSocket] Server error:', message.code, message.message);
|
|
853
|
+
break;
|
|
854
|
+
|
|
855
|
+
default:
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
} catch (error) {
|
|
859
|
+
console.error('[WebSocket] Failed to parse message:', error);
|
|
860
|
+
}
|
|
861
|
+
}, []); // Empty deps - uses ref for currentWorkflowId to avoid reconnecting WebSocket
|
|
862
|
+
|
|
863
|
+
// Connect to WebSocket
|
|
864
|
+
const connect = useCallback(() => {
|
|
865
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const wsUrl = getWebSocketUrl();
|
|
870
|
+
|
|
871
|
+
try {
|
|
872
|
+
const ws = new WebSocket(wsUrl);
|
|
873
|
+
|
|
874
|
+
ws.onopen = async () => {
|
|
875
|
+
setIsConnected(true);
|
|
876
|
+
setReconnecting(false);
|
|
877
|
+
|
|
878
|
+
// Start ping interval
|
|
879
|
+
pingIntervalRef.current = setInterval(() => {
|
|
880
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
881
|
+
ws.send(JSON.stringify({ type: 'ping' }));
|
|
882
|
+
}
|
|
883
|
+
}, 30000);
|
|
884
|
+
|
|
885
|
+
// Load initial API key statuses for known providers
|
|
886
|
+
const providers = ['openai', 'anthropic', 'gemini', 'google_maps', 'android_remote'];
|
|
887
|
+
for (const provider of providers) {
|
|
888
|
+
try {
|
|
889
|
+
const response = await new Promise<any>((resolve, reject) => {
|
|
890
|
+
const requestId = `init_${provider}_${Date.now()}`;
|
|
891
|
+
const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
|
|
892
|
+
|
|
893
|
+
const handler = (event: MessageEvent) => {
|
|
894
|
+
try {
|
|
895
|
+
const msg = JSON.parse(event.data);
|
|
896
|
+
if (msg.request_id === requestId) {
|
|
897
|
+
clearTimeout(timeout);
|
|
898
|
+
ws.removeEventListener('message', handler);
|
|
899
|
+
resolve(msg);
|
|
900
|
+
}
|
|
901
|
+
} catch {}
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
ws.addEventListener('message', handler);
|
|
905
|
+
ws.send(JSON.stringify({ type: 'get_stored_api_key', provider, request_id: requestId }));
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
if (response.has_key) {
|
|
909
|
+
setApiKeyStatuses(prev => ({
|
|
910
|
+
...prev,
|
|
911
|
+
[provider]: { hasKey: true, valid: true }
|
|
912
|
+
}));
|
|
913
|
+
}
|
|
914
|
+
} catch {
|
|
915
|
+
// Ignore errors during initial check
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Load terminal log history
|
|
920
|
+
try {
|
|
921
|
+
const terminalResponse = await new Promise<any>((resolve, reject) => {
|
|
922
|
+
const requestId = `terminal_logs_${Date.now()}`;
|
|
923
|
+
const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
|
|
924
|
+
|
|
925
|
+
const handler = (event: MessageEvent) => {
|
|
926
|
+
try {
|
|
927
|
+
const msg = JSON.parse(event.data);
|
|
928
|
+
if (msg.request_id === requestId) {
|
|
929
|
+
clearTimeout(timeout);
|
|
930
|
+
ws.removeEventListener('message', handler);
|
|
931
|
+
resolve(msg);
|
|
932
|
+
}
|
|
933
|
+
} catch {}
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
ws.addEventListener('message', handler);
|
|
937
|
+
ws.send(JSON.stringify({ type: 'get_terminal_logs', request_id: requestId }));
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
if (terminalResponse.success && terminalResponse.logs) {
|
|
941
|
+
// Map server logs to TerminalLogEntry format (newest first)
|
|
942
|
+
const logs: TerminalLogEntry[] = terminalResponse.logs.map((log: any) => ({
|
|
943
|
+
timestamp: log.timestamp || new Date().toISOString(),
|
|
944
|
+
level: log.level || 'info',
|
|
945
|
+
message: log.message || '',
|
|
946
|
+
source: log.source,
|
|
947
|
+
details: log.details
|
|
948
|
+
})).reverse(); // Server stores oldest first, we want newest first
|
|
949
|
+
setTerminalLogs(logs);
|
|
950
|
+
}
|
|
951
|
+
} catch {
|
|
952
|
+
// Ignore errors loading terminal logs
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Load chat message history from database
|
|
956
|
+
try {
|
|
957
|
+
const chatResponse = await new Promise<any>((resolve, reject) => {
|
|
958
|
+
const requestId = `chat_messages_${Date.now()}`;
|
|
959
|
+
const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
|
|
960
|
+
|
|
961
|
+
const handler = (event: MessageEvent) => {
|
|
962
|
+
try {
|
|
963
|
+
const msg = JSON.parse(event.data);
|
|
964
|
+
if (msg.request_id === requestId) {
|
|
965
|
+
clearTimeout(timeout);
|
|
966
|
+
ws.removeEventListener('message', handler);
|
|
967
|
+
resolve(msg);
|
|
968
|
+
}
|
|
969
|
+
} catch {}
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
ws.addEventListener('message', handler);
|
|
973
|
+
ws.send(JSON.stringify({ type: 'get_chat_messages', session_id: 'default', request_id: requestId }));
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
if (chatResponse.success && chatResponse.messages) {
|
|
977
|
+
const messages: ChatMessage[] = chatResponse.messages.map((msg: any) => ({
|
|
978
|
+
role: msg.role as 'user' | 'assistant',
|
|
979
|
+
message: msg.message,
|
|
980
|
+
timestamp: msg.timestamp
|
|
981
|
+
}));
|
|
982
|
+
setChatMessages(messages);
|
|
983
|
+
}
|
|
984
|
+
} catch {
|
|
985
|
+
// Ignore errors loading chat messages
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Load console logs from database
|
|
989
|
+
try {
|
|
990
|
+
const consoleRequestId = `console_${Date.now()}`;
|
|
991
|
+
const consoleResponse = await new Promise<any>((resolve, reject) => {
|
|
992
|
+
const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
|
|
993
|
+
|
|
994
|
+
const handler = (event: MessageEvent) => {
|
|
995
|
+
try {
|
|
996
|
+
const msg = JSON.parse(event.data);
|
|
997
|
+
if (msg.request_id === consoleRequestId) {
|
|
998
|
+
clearTimeout(timeout);
|
|
999
|
+
ws.removeEventListener('message', handler);
|
|
1000
|
+
resolve(msg);
|
|
1001
|
+
}
|
|
1002
|
+
} catch {}
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
ws.addEventListener('message', handler);
|
|
1006
|
+
ws.send(JSON.stringify({ type: 'get_console_logs', limit: 100, request_id: consoleRequestId }));
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
if (consoleResponse.success && consoleResponse.logs) {
|
|
1010
|
+
const logs: ConsoleLogEntry[] = consoleResponse.logs.map((log: any) => ({
|
|
1011
|
+
node_id: log.node_id,
|
|
1012
|
+
label: log.label,
|
|
1013
|
+
timestamp: log.timestamp,
|
|
1014
|
+
data: log.data,
|
|
1015
|
+
formatted: log.formatted,
|
|
1016
|
+
format: log.format,
|
|
1017
|
+
workflow_id: log.workflow_id,
|
|
1018
|
+
source_node_id: log.source_node_id,
|
|
1019
|
+
source_node_type: log.source_node_type,
|
|
1020
|
+
source_node_label: log.source_node_label,
|
|
1021
|
+
}));
|
|
1022
|
+
setConsoleLogs(logs);
|
|
1023
|
+
}
|
|
1024
|
+
} catch {
|
|
1025
|
+
// Ignore errors loading console logs
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
ws.onmessage = handleMessage;
|
|
1030
|
+
|
|
1031
|
+
ws.onclose = (event) => {
|
|
1032
|
+
console.log('[WebSocket] Disconnected:', event.code, event.reason);
|
|
1033
|
+
setIsConnected(false);
|
|
1034
|
+
wsRef.current = null;
|
|
1035
|
+
|
|
1036
|
+
// Clear ping interval
|
|
1037
|
+
if (pingIntervalRef.current) {
|
|
1038
|
+
clearInterval(pingIntervalRef.current);
|
|
1039
|
+
pingIntervalRef.current = null;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Reconnect after delay (unless intentional close)
|
|
1043
|
+
if (event.code !== 1000) {
|
|
1044
|
+
setReconnecting(true);
|
|
1045
|
+
reconnectTimeoutRef.current = setTimeout(() => {
|
|
1046
|
+
connect();
|
|
1047
|
+
}, 3000);
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
ws.onerror = (error) => {
|
|
1052
|
+
console.error('[WebSocket] Error:', error);
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
wsRef.current = ws;
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
console.error('[WebSocket] Failed to create connection:', error);
|
|
1058
|
+
setReconnecting(true);
|
|
1059
|
+
reconnectTimeoutRef.current = setTimeout(connect, 3000);
|
|
1060
|
+
}
|
|
1061
|
+
}, [handleMessage]);
|
|
1062
|
+
|
|
1063
|
+
// Request current status
|
|
1064
|
+
const requestStatus = useCallback(() => {
|
|
1065
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
1066
|
+
wsRef.current.send(JSON.stringify({ type: 'get_status' }));
|
|
1067
|
+
}
|
|
1068
|
+
}, []);
|
|
1069
|
+
|
|
1070
|
+
// Get node status for current workflow (n8n pattern)
|
|
1071
|
+
// IMPORTANT: Use currentWorkflowId state directly (not ref) to ensure reactivity on workflow switch
|
|
1072
|
+
const getNodeStatus = useCallback((nodeId: string) => {
|
|
1073
|
+
if (!currentWorkflowId) {
|
|
1074
|
+
return undefined;
|
|
1075
|
+
}
|
|
1076
|
+
return allNodeStatuses[currentWorkflowId]?.[nodeId];
|
|
1077
|
+
}, [allNodeStatuses, currentWorkflowId]);
|
|
1078
|
+
|
|
1079
|
+
// Get API key status
|
|
1080
|
+
const getApiKeyStatus = useCallback((provider: string) => {
|
|
1081
|
+
return apiKeyStatuses[provider];
|
|
1082
|
+
}, [apiKeyStatuses]);
|
|
1083
|
+
|
|
1084
|
+
// Get variable value for current workflow (n8n pattern)
|
|
1085
|
+
// IMPORTANT: Use currentWorkflowId state directly (not ref) to ensure reactivity on workflow switch
|
|
1086
|
+
const getVariable = useCallback((name: string) => {
|
|
1087
|
+
if (!currentWorkflowId) return undefined;
|
|
1088
|
+
return allVariables[currentWorkflowId]?.[name];
|
|
1089
|
+
}, [allVariables, currentWorkflowId]);
|
|
1090
|
+
|
|
1091
|
+
// Clear node status (used when clearing execution results)
|
|
1092
|
+
// Also clears the backend node_outputs storage
|
|
1093
|
+
const clearNodeStatus = useCallback(async (nodeId: string) => {
|
|
1094
|
+
const workflowId = currentWorkflowIdRef.current;
|
|
1095
|
+
// Clear local state for current workflow
|
|
1096
|
+
setAllNodeStatuses((prev: Record<string, Record<string, NodeStatus>>) => {
|
|
1097
|
+
if (!workflowId || !prev[workflowId]) return prev;
|
|
1098
|
+
const workflowStatuses = { ...prev[workflowId] };
|
|
1099
|
+
delete workflowStatuses[nodeId];
|
|
1100
|
+
return { ...prev, [workflowId]: workflowStatuses };
|
|
1101
|
+
});
|
|
1102
|
+
// Clear backend storage
|
|
1103
|
+
try {
|
|
1104
|
+
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
|
|
1105
|
+
wsRef.current.send(JSON.stringify({
|
|
1106
|
+
type: 'clear_node_output',
|
|
1107
|
+
node_id: nodeId,
|
|
1108
|
+
workflow_id: workflowId
|
|
1109
|
+
}));
|
|
1110
|
+
}
|
|
1111
|
+
} catch (err) {
|
|
1112
|
+
console.error('[WebSocket] Failed to clear backend node output:', err);
|
|
1113
|
+
}
|
|
1114
|
+
}, []);
|
|
1115
|
+
|
|
1116
|
+
// Clear WhatsApp message history
|
|
1117
|
+
const clearWhatsAppMessages = useCallback(() => {
|
|
1118
|
+
setWhatsappMessages([]);
|
|
1119
|
+
setLastWhatsAppMessage(null);
|
|
1120
|
+
}, []);
|
|
1121
|
+
|
|
1122
|
+
// Clear console logs (both local state and database)
|
|
1123
|
+
const clearConsoleLogs = useCallback(() => {
|
|
1124
|
+
setConsoleLogs([]);
|
|
1125
|
+
// Also clear from database via direct WebSocket send
|
|
1126
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
1127
|
+
wsRef.current.send(JSON.stringify({ type: 'clear_console_logs' }));
|
|
1128
|
+
}
|
|
1129
|
+
}, []);
|
|
1130
|
+
|
|
1131
|
+
// Clear terminal logs (also clears on server)
|
|
1132
|
+
const clearTerminalLogs = useCallback(() => {
|
|
1133
|
+
setTerminalLogs([]);
|
|
1134
|
+
// Also notify server to clear its terminal log history
|
|
1135
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
1136
|
+
wsRef.current.send(JSON.stringify({ type: 'clear_terminal_logs' }));
|
|
1137
|
+
}
|
|
1138
|
+
}, []);
|
|
1139
|
+
|
|
1140
|
+
// Clear chat messages (both local state and database)
|
|
1141
|
+
// Uses direct WebSocket send to avoid dependency on sendRequest (which is defined later)
|
|
1142
|
+
const clearChatMessages = useCallback(() => {
|
|
1143
|
+
setChatMessages([]);
|
|
1144
|
+
// Also clear from database via direct WebSocket send
|
|
1145
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
1146
|
+
wsRef.current.send(JSON.stringify({ type: 'clear_chat_messages', session_id: 'default' }));
|
|
1147
|
+
}
|
|
1148
|
+
}, []);
|
|
1149
|
+
|
|
1150
|
+
// Derive current workflow's node statuses (n8n pattern)
|
|
1151
|
+
// This provides a flat Record<nodeId, NodeStatus> for the current workflow
|
|
1152
|
+
// IMPORTANT: Use currentWorkflowId state directly, not ref, to ensure re-render on workflow switch
|
|
1153
|
+
const nodeStatuses = useMemo(() => {
|
|
1154
|
+
if (!currentWorkflowId) return {};
|
|
1155
|
+
return allNodeStatuses[currentWorkflowId] || {};
|
|
1156
|
+
}, [allNodeStatuses, currentWorkflowId]);
|
|
1157
|
+
|
|
1158
|
+
// Derive current workflow's variables (n8n pattern)
|
|
1159
|
+
// This provides a flat Record<varName, value> for the current workflow
|
|
1160
|
+
// IMPORTANT: Use currentWorkflowId state directly, not ref, to ensure re-render on workflow switch
|
|
1161
|
+
const variables = useMemo(() => {
|
|
1162
|
+
if (!currentWorkflowId) return {};
|
|
1163
|
+
return allVariables[currentWorkflowId] || {};
|
|
1164
|
+
}, [allVariables, currentWorkflowId]);
|
|
1165
|
+
|
|
1166
|
+
// =========================================================================
|
|
1167
|
+
// Core Request/Response Pattern
|
|
1168
|
+
// =========================================================================
|
|
1169
|
+
|
|
1170
|
+
// Send a request and wait for response
|
|
1171
|
+
// timeoutMs: undefined/0 = use default, negative = no timeout (for trigger nodes)
|
|
1172
|
+
const sendRequest = useCallback(async <T = any>(
|
|
1173
|
+
type: string,
|
|
1174
|
+
data?: Record<string, any>,
|
|
1175
|
+
timeoutMs?: number
|
|
1176
|
+
): Promise<T> => {
|
|
1177
|
+
return new Promise((resolve, reject) => {
|
|
1178
|
+
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
|
|
1179
|
+
reject(new Error('WebSocket not connected'));
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const requestId = generateRequestId();
|
|
1184
|
+
const useTimeout = timeoutMs === undefined || timeoutMs >= 0;
|
|
1185
|
+
const actualTimeout = timeoutMs && timeoutMs > 0 ? timeoutMs : REQUEST_TIMEOUT;
|
|
1186
|
+
|
|
1187
|
+
let timeout: NodeJS.Timeout | null = null;
|
|
1188
|
+
if (useTimeout && timeoutMs !== -1) {
|
|
1189
|
+
timeout = setTimeout(() => {
|
|
1190
|
+
pendingRequestsRef.current.delete(requestId);
|
|
1191
|
+
reject(new Error(`Request timeout: ${type}`));
|
|
1192
|
+
}, actualTimeout);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
pendingRequestsRef.current.set(requestId, { resolve, reject, timeout });
|
|
1196
|
+
|
|
1197
|
+
wsRef.current.send(JSON.stringify({
|
|
1198
|
+
type,
|
|
1199
|
+
request_id: requestId,
|
|
1200
|
+
...data
|
|
1201
|
+
}));
|
|
1202
|
+
});
|
|
1203
|
+
}, []);
|
|
1204
|
+
|
|
1205
|
+
// =========================================================================
|
|
1206
|
+
// Chat Message Operations
|
|
1207
|
+
// =========================================================================
|
|
1208
|
+
|
|
1209
|
+
// Send chat message (triggers chatTrigger nodes and saves to database)
|
|
1210
|
+
// nodeId: optional specific chatTrigger node to target
|
|
1211
|
+
const sendChatMessageAsync = useCallback(async (message: string, nodeId?: string): Promise<void> => {
|
|
1212
|
+
const timestamp = new Date().toISOString();
|
|
1213
|
+
const chatMessage: ChatMessage = {
|
|
1214
|
+
role: 'user',
|
|
1215
|
+
message,
|
|
1216
|
+
timestamp
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
// Add to local messages immediately for UI feedback
|
|
1220
|
+
setChatMessages(prev => [...prev, chatMessage]);
|
|
1221
|
+
|
|
1222
|
+
// Send to backend to dispatch to chatTrigger nodes (also saves to database)
|
|
1223
|
+
try {
|
|
1224
|
+
await sendRequest('send_chat_message', {
|
|
1225
|
+
message,
|
|
1226
|
+
role: 'user',
|
|
1227
|
+
node_id: nodeId, // Target specific chatTrigger node if specified
|
|
1228
|
+
session_id: 'default',
|
|
1229
|
+
timestamp
|
|
1230
|
+
});
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
console.error('[WebSocket] Failed to send chat message:', error);
|
|
1233
|
+
throw error;
|
|
1234
|
+
}
|
|
1235
|
+
}, [sendRequest]);
|
|
1236
|
+
|
|
1237
|
+
// =========================================================================
|
|
1238
|
+
// Node Parameters Operations
|
|
1239
|
+
// =========================================================================
|
|
1240
|
+
|
|
1241
|
+
const getNodeParametersAsync = useCallback(async (nodeId: string): Promise<NodeParameters | null> => {
|
|
1242
|
+
try {
|
|
1243
|
+
const response = await sendRequest<any>('get_node_parameters', { node_id: nodeId });
|
|
1244
|
+
if (response.parameters) {
|
|
1245
|
+
const params: NodeParameters = {
|
|
1246
|
+
parameters: response.parameters,
|
|
1247
|
+
version: response.version || 0,
|
|
1248
|
+
timestamp: response.timestamp
|
|
1249
|
+
};
|
|
1250
|
+
// Update local cache
|
|
1251
|
+
setNodeParameters(prev => ({ ...prev, [nodeId]: params }));
|
|
1252
|
+
return params;
|
|
1253
|
+
}
|
|
1254
|
+
return null;
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
console.error('[WebSocket] Failed to get node parameters:', error);
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
}, [sendRequest]);
|
|
1260
|
+
|
|
1261
|
+
const getAllNodeParametersAsync = useCallback(async (nodeIds: string[]): Promise<Record<string, NodeParameters>> => {
|
|
1262
|
+
if (!nodeIds.length) return {};
|
|
1263
|
+
try {
|
|
1264
|
+
const response = await sendRequest<any>('get_all_node_parameters', { node_ids: nodeIds });
|
|
1265
|
+
const result: Record<string, NodeParameters> = {};
|
|
1266
|
+
|
|
1267
|
+
if (response.parameters) {
|
|
1268
|
+
for (const [nodeId, data] of Object.entries(response.parameters as Record<string, any>)) {
|
|
1269
|
+
result[nodeId] = {
|
|
1270
|
+
parameters: data.parameters || {},
|
|
1271
|
+
version: data.version || 0,
|
|
1272
|
+
timestamp: response.timestamp
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
// Update local cache with all parameters
|
|
1276
|
+
setNodeParameters(prev => ({ ...prev, ...result }));
|
|
1277
|
+
}
|
|
1278
|
+
return result;
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
console.error('[WebSocket] Failed to get all node parameters:', error);
|
|
1281
|
+
return {};
|
|
1282
|
+
}
|
|
1283
|
+
}, [sendRequest]);
|
|
1284
|
+
|
|
1285
|
+
const saveNodeParametersAsync = useCallback(async (
|
|
1286
|
+
nodeId: string,
|
|
1287
|
+
parameters: Record<string, any>,
|
|
1288
|
+
version?: number
|
|
1289
|
+
): Promise<boolean> => {
|
|
1290
|
+
try {
|
|
1291
|
+
const currentVersion = nodeParameters[nodeId]?.version || version || 0;
|
|
1292
|
+
const response = await sendRequest<any>('save_node_parameters', {
|
|
1293
|
+
node_id: nodeId,
|
|
1294
|
+
parameters,
|
|
1295
|
+
version: currentVersion
|
|
1296
|
+
});
|
|
1297
|
+
if (response.success !== false) {
|
|
1298
|
+
// Update local cache
|
|
1299
|
+
setNodeParameters(prev => ({
|
|
1300
|
+
...prev,
|
|
1301
|
+
[nodeId]: {
|
|
1302
|
+
parameters: response.parameters || parameters,
|
|
1303
|
+
version: response.version || currentVersion + 1,
|
|
1304
|
+
timestamp: response.timestamp
|
|
1305
|
+
}
|
|
1306
|
+
}));
|
|
1307
|
+
return true;
|
|
1308
|
+
}
|
|
1309
|
+
return false;
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
console.error('[WebSocket] Failed to save node parameters:', error);
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
}, [sendRequest, nodeParameters]);
|
|
1315
|
+
|
|
1316
|
+
const deleteNodeParametersAsync = useCallback(async (nodeId: string): Promise<boolean> => {
|
|
1317
|
+
try {
|
|
1318
|
+
await sendRequest<any>('delete_node_parameters', { node_id: nodeId });
|
|
1319
|
+
setNodeParameters(prev => {
|
|
1320
|
+
const updated = { ...prev };
|
|
1321
|
+
delete updated[nodeId];
|
|
1322
|
+
return updated;
|
|
1323
|
+
});
|
|
1324
|
+
return true;
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
console.error('[WebSocket] Failed to delete node parameters:', error);
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
}, [sendRequest]);
|
|
1330
|
+
|
|
1331
|
+
// =========================================================================
|
|
1332
|
+
// Node Execution Operations
|
|
1333
|
+
// =========================================================================
|
|
1334
|
+
|
|
1335
|
+
const executeNodeAsync = useCallback(async (
|
|
1336
|
+
nodeId: string,
|
|
1337
|
+
nodeType: string,
|
|
1338
|
+
parameters: Record<string, any>,
|
|
1339
|
+
nodes?: any[],
|
|
1340
|
+
edges?: any[]
|
|
1341
|
+
): Promise<any> => {
|
|
1342
|
+
try {
|
|
1343
|
+
// Trigger nodes wait indefinitely for events - no timeout
|
|
1344
|
+
const isTriggerNode = TRIGGER_NODE_TYPES.includes(nodeType);
|
|
1345
|
+
const timeoutMs = isTriggerNode ? -1 : undefined; // -1 = no timeout
|
|
1346
|
+
|
|
1347
|
+
const response = await sendRequest<any>('execute_node', {
|
|
1348
|
+
node_id: nodeId,
|
|
1349
|
+
node_type: nodeType,
|
|
1350
|
+
parameters,
|
|
1351
|
+
nodes,
|
|
1352
|
+
edges,
|
|
1353
|
+
workflow_id: currentWorkflowId // Include workflow_id for per-workflow status scoping
|
|
1354
|
+
}, timeoutMs);
|
|
1355
|
+
return response;
|
|
1356
|
+
} catch (error) {
|
|
1357
|
+
console.error('[WebSocket] Failed to execute node:', error);
|
|
1358
|
+
throw error;
|
|
1359
|
+
}
|
|
1360
|
+
}, [sendRequest, currentWorkflowId]);
|
|
1361
|
+
|
|
1362
|
+
const getNodeOutputAsync = useCallback(async (
|
|
1363
|
+
nodeId: string,
|
|
1364
|
+
outputName?: string
|
|
1365
|
+
): Promise<any> => {
|
|
1366
|
+
try {
|
|
1367
|
+
const response = await sendRequest<any>('get_node_output', {
|
|
1368
|
+
node_id: nodeId,
|
|
1369
|
+
output_name: outputName || 'output_0'
|
|
1370
|
+
});
|
|
1371
|
+
if (response.success) {
|
|
1372
|
+
return response.data;
|
|
1373
|
+
}
|
|
1374
|
+
return null;
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
console.error('[WebSocket] Failed to get node output:', error);
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
1379
|
+
}, [sendRequest]);
|
|
1380
|
+
|
|
1381
|
+
// Cancel event wait (for trigger nodes)
|
|
1382
|
+
const cancelEventWaitAsync = useCallback(async (
|
|
1383
|
+
nodeId: string,
|
|
1384
|
+
waiterId?: string
|
|
1385
|
+
): Promise<{ success: boolean; cancelled_count?: number }> => {
|
|
1386
|
+
try {
|
|
1387
|
+
const response = await sendRequest<{ success: boolean; cancelled_count?: number }>('cancel_event_wait', {
|
|
1388
|
+
node_id: nodeId,
|
|
1389
|
+
waiter_id: waiterId
|
|
1390
|
+
});
|
|
1391
|
+
return response;
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
console.error('[WebSocket] Failed to cancel event wait:', error);
|
|
1394
|
+
return { success: false };
|
|
1395
|
+
}
|
|
1396
|
+
}, [sendRequest]);
|
|
1397
|
+
|
|
1398
|
+
const executeWorkflowAsync = useCallback(async (
|
|
1399
|
+
nodes: any[],
|
|
1400
|
+
edges: any[],
|
|
1401
|
+
sessionId?: string
|
|
1402
|
+
): Promise<any> => {
|
|
1403
|
+
try {
|
|
1404
|
+
const response = await sendRequest<any>('execute_workflow', {
|
|
1405
|
+
nodes: nodes.map(node => ({
|
|
1406
|
+
id: node.id,
|
|
1407
|
+
type: node.type || '',
|
|
1408
|
+
data: node.data || {}
|
|
1409
|
+
})),
|
|
1410
|
+
edges: edges.map(edge => ({
|
|
1411
|
+
id: edge.id,
|
|
1412
|
+
source: edge.source,
|
|
1413
|
+
target: edge.target,
|
|
1414
|
+
sourceHandle: edge.sourceHandle || undefined,
|
|
1415
|
+
targetHandle: edge.targetHandle || undefined
|
|
1416
|
+
})),
|
|
1417
|
+
session_id: sessionId || 'default'
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
return response;
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
console.error('[WebSocket] Failed to execute workflow:', error);
|
|
1423
|
+
throw error;
|
|
1424
|
+
}
|
|
1425
|
+
}, [sendRequest]);
|
|
1426
|
+
|
|
1427
|
+
// =========================================================================
|
|
1428
|
+
// Deployment Operations
|
|
1429
|
+
// =========================================================================
|
|
1430
|
+
|
|
1431
|
+
const deployWorkflowAsync = useCallback(async (
|
|
1432
|
+
workflowId: string,
|
|
1433
|
+
nodes: any[],
|
|
1434
|
+
edges: any[],
|
|
1435
|
+
sessionId?: string
|
|
1436
|
+
): Promise<any> => {
|
|
1437
|
+
try {
|
|
1438
|
+
const response = await sendRequest<any>('deploy_workflow', {
|
|
1439
|
+
workflow_id: workflowId,
|
|
1440
|
+
nodes: nodes.map(node => ({
|
|
1441
|
+
id: node.id,
|
|
1442
|
+
type: node.type || '',
|
|
1443
|
+
data: node.data || {}
|
|
1444
|
+
})),
|
|
1445
|
+
edges: edges.map(edge => ({
|
|
1446
|
+
id: edge.id,
|
|
1447
|
+
source: edge.source,
|
|
1448
|
+
target: edge.target,
|
|
1449
|
+
sourceHandle: edge.sourceHandle || undefined,
|
|
1450
|
+
targetHandle: edge.targetHandle || undefined
|
|
1451
|
+
})),
|
|
1452
|
+
session_id: sessionId || 'default'
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
return response;
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
console.error('[WebSocket] Failed to start deployment:', error);
|
|
1458
|
+
throw error;
|
|
1459
|
+
}
|
|
1460
|
+
}, [sendRequest]);
|
|
1461
|
+
|
|
1462
|
+
const cancelDeploymentAsync = useCallback(async (workflowId?: string): Promise<any> => {
|
|
1463
|
+
try {
|
|
1464
|
+
const response = await sendRequest<any>('cancel_deployment', {
|
|
1465
|
+
workflow_id: workflowId
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
// Reset deployment status only if the cancelled workflow matches current
|
|
1469
|
+
if (!workflowId || workflowId === deploymentStatus.workflow_id) {
|
|
1470
|
+
setDeploymentStatus(defaultDeploymentStatus);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
return response;
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
console.error('[WebSocket] Failed to cancel deployment:', error);
|
|
1476
|
+
throw error;
|
|
1477
|
+
}
|
|
1478
|
+
}, [sendRequest, deploymentStatus.workflow_id]);
|
|
1479
|
+
|
|
1480
|
+
const getDeploymentStatusAsync = useCallback(async (workflowId?: string): Promise<{ isRunning: boolean; activeRuns: number; settings?: any; workflow_id?: string }> => {
|
|
1481
|
+
try {
|
|
1482
|
+
const response = await sendRequest<any>('get_deployment_status', { workflow_id: workflowId });
|
|
1483
|
+
return {
|
|
1484
|
+
isRunning: response.is_running || false,
|
|
1485
|
+
activeRuns: response.active_runs || 0,
|
|
1486
|
+
settings: response.settings,
|
|
1487
|
+
workflow_id: response.workflow_id
|
|
1488
|
+
};
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
console.error('[WebSocket] Failed to get deployment status:', error);
|
|
1491
|
+
return { isRunning: false, activeRuns: 0 };
|
|
1492
|
+
}
|
|
1493
|
+
}, [sendRequest]);
|
|
1494
|
+
|
|
1495
|
+
// =========================================================================
|
|
1496
|
+
// AI Operations
|
|
1497
|
+
// =========================================================================
|
|
1498
|
+
|
|
1499
|
+
const executeAiNodeAsync = useCallback(async (
|
|
1500
|
+
nodeId: string,
|
|
1501
|
+
nodeType: string,
|
|
1502
|
+
parameters: Record<string, any>,
|
|
1503
|
+
model: string,
|
|
1504
|
+
workflowId: string,
|
|
1505
|
+
nodes: any[],
|
|
1506
|
+
edges: any[]
|
|
1507
|
+
): Promise<any> => {
|
|
1508
|
+
try {
|
|
1509
|
+
const response = await sendRequest<any>('execute_ai_node', {
|
|
1510
|
+
node_id: nodeId,
|
|
1511
|
+
node_type: nodeType,
|
|
1512
|
+
parameters,
|
|
1513
|
+
model,
|
|
1514
|
+
workflow_id: workflowId,
|
|
1515
|
+
nodes,
|
|
1516
|
+
edges
|
|
1517
|
+
});
|
|
1518
|
+
return response;
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
console.error('[WebSocket] Failed to execute AI node:', error);
|
|
1521
|
+
throw error;
|
|
1522
|
+
}
|
|
1523
|
+
}, [sendRequest]);
|
|
1524
|
+
|
|
1525
|
+
const getAiModelsAsync = useCallback(async (provider: string, apiKey: string): Promise<string[]> => {
|
|
1526
|
+
try {
|
|
1527
|
+
const response = await sendRequest<any>('get_ai_models', {
|
|
1528
|
+
provider,
|
|
1529
|
+
api_key: apiKey
|
|
1530
|
+
});
|
|
1531
|
+
return response.models || [];
|
|
1532
|
+
} catch (error) {
|
|
1533
|
+
console.error('[WebSocket] Failed to get AI models:', error);
|
|
1534
|
+
return [];
|
|
1535
|
+
}
|
|
1536
|
+
}, [sendRequest]);
|
|
1537
|
+
|
|
1538
|
+
// =========================================================================
|
|
1539
|
+
// API Key Operations
|
|
1540
|
+
// =========================================================================
|
|
1541
|
+
|
|
1542
|
+
const validateApiKeyAsync = useCallback(async (
|
|
1543
|
+
provider: string,
|
|
1544
|
+
apiKey: string
|
|
1545
|
+
): Promise<{ valid: boolean; message?: string; models?: string[] }> => {
|
|
1546
|
+
try {
|
|
1547
|
+
const response = await sendRequest<any>('validate_api_key', {
|
|
1548
|
+
provider,
|
|
1549
|
+
api_key: apiKey
|
|
1550
|
+
});
|
|
1551
|
+
const result = {
|
|
1552
|
+
valid: response.valid || false,
|
|
1553
|
+
message: response.message,
|
|
1554
|
+
models: response.models
|
|
1555
|
+
};
|
|
1556
|
+
|
|
1557
|
+
// Update apiKeyStatuses on successful validation
|
|
1558
|
+
if (result.valid) {
|
|
1559
|
+
setApiKeyStatuses(prev => ({
|
|
1560
|
+
...prev,
|
|
1561
|
+
[provider]: { hasKey: true, valid: true, models: result.models }
|
|
1562
|
+
}));
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
return result;
|
|
1566
|
+
} catch (error) {
|
|
1567
|
+
console.error('[WebSocket] Failed to validate API key:', error);
|
|
1568
|
+
return { valid: false, message: 'Validation failed' };
|
|
1569
|
+
}
|
|
1570
|
+
}, [sendRequest]);
|
|
1571
|
+
|
|
1572
|
+
const getStoredApiKeyAsync = useCallback(async (
|
|
1573
|
+
provider: string
|
|
1574
|
+
): Promise<{ hasKey: boolean; apiKey?: string; models?: string[] }> => {
|
|
1575
|
+
try {
|
|
1576
|
+
const response = await sendRequest<any>('get_stored_api_key', { provider });
|
|
1577
|
+
const result = {
|
|
1578
|
+
hasKey: response.has_key || false,
|
|
1579
|
+
apiKey: response.api_key,
|
|
1580
|
+
models: response.models
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
// Update apiKeyStatuses with stored models
|
|
1584
|
+
if (result.hasKey) {
|
|
1585
|
+
setApiKeyStatuses(prev => ({
|
|
1586
|
+
...prev,
|
|
1587
|
+
[provider]: { hasKey: true, valid: true, models: result.models }
|
|
1588
|
+
}));
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
return result;
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
console.error('[WebSocket] Failed to get stored API key:', error);
|
|
1594
|
+
return { hasKey: false };
|
|
1595
|
+
}
|
|
1596
|
+
}, [sendRequest]);
|
|
1597
|
+
|
|
1598
|
+
const saveApiKeyAsync = useCallback(async (
|
|
1599
|
+
provider: string,
|
|
1600
|
+
apiKey: string,
|
|
1601
|
+
models?: string[]
|
|
1602
|
+
): Promise<boolean> => {
|
|
1603
|
+
try {
|
|
1604
|
+
const response = await sendRequest<any>('save_api_key', {
|
|
1605
|
+
provider,
|
|
1606
|
+
api_key: apiKey,
|
|
1607
|
+
models
|
|
1608
|
+
});
|
|
1609
|
+
const success = response.success !== false;
|
|
1610
|
+
|
|
1611
|
+
// Update apiKeyStatuses on successful save
|
|
1612
|
+
if (success) {
|
|
1613
|
+
setApiKeyStatuses(prev => ({
|
|
1614
|
+
...prev,
|
|
1615
|
+
[provider]: { hasKey: true, valid: true, models }
|
|
1616
|
+
}));
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
return success;
|
|
1620
|
+
} catch (error) {
|
|
1621
|
+
console.error('[WebSocket] Failed to save API key:', error);
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
}, [sendRequest]);
|
|
1625
|
+
|
|
1626
|
+
const deleteApiKeyAsync = useCallback(async (provider: string): Promise<boolean> => {
|
|
1627
|
+
try {
|
|
1628
|
+
await sendRequest<any>('delete_api_key', { provider });
|
|
1629
|
+
|
|
1630
|
+
// Remove from apiKeyStatuses on successful delete
|
|
1631
|
+
setApiKeyStatuses(prev => {
|
|
1632
|
+
const newStatuses = { ...prev };
|
|
1633
|
+
delete newStatuses[provider];
|
|
1634
|
+
return newStatuses;
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
return true;
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
console.error('[WebSocket] Failed to delete API key:', error);
|
|
1640
|
+
return false;
|
|
1641
|
+
}
|
|
1642
|
+
}, [sendRequest]);
|
|
1643
|
+
|
|
1644
|
+
// =========================================================================
|
|
1645
|
+
// Android Operations
|
|
1646
|
+
// =========================================================================
|
|
1647
|
+
|
|
1648
|
+
const getAndroidDevicesAsync = useCallback(async (): Promise<string[]> => {
|
|
1649
|
+
try {
|
|
1650
|
+
const response = await sendRequest<any>('get_android_devices', {});
|
|
1651
|
+
return response.devices || [];
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
console.error('[WebSocket] Failed to get Android devices:', error);
|
|
1654
|
+
return [];
|
|
1655
|
+
}
|
|
1656
|
+
}, [sendRequest]);
|
|
1657
|
+
|
|
1658
|
+
const executeAndroidActionAsync = useCallback(async (
|
|
1659
|
+
serviceId: string,
|
|
1660
|
+
action: string,
|
|
1661
|
+
parameters: Record<string, any>,
|
|
1662
|
+
deviceId?: string
|
|
1663
|
+
): Promise<any> => {
|
|
1664
|
+
try {
|
|
1665
|
+
const response = await sendRequest<any>('execute_android_action', {
|
|
1666
|
+
service_id: serviceId,
|
|
1667
|
+
action,
|
|
1668
|
+
parameters,
|
|
1669
|
+
device_id: deviceId
|
|
1670
|
+
});
|
|
1671
|
+
return response;
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
console.error('[WebSocket] Failed to execute Android action:', error);
|
|
1674
|
+
throw error;
|
|
1675
|
+
}
|
|
1676
|
+
}, [sendRequest]);
|
|
1677
|
+
|
|
1678
|
+
// =========================================================================
|
|
1679
|
+
// Maps Operations
|
|
1680
|
+
// =========================================================================
|
|
1681
|
+
|
|
1682
|
+
const validateMapsKeyAsync = useCallback(async (
|
|
1683
|
+
apiKey: string
|
|
1684
|
+
): Promise<{ valid: boolean; message?: string }> => {
|
|
1685
|
+
try {
|
|
1686
|
+
const response = await sendRequest<any>('validate_maps_key', { api_key: apiKey });
|
|
1687
|
+
return {
|
|
1688
|
+
valid: response.valid || false,
|
|
1689
|
+
message: response.message
|
|
1690
|
+
};
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
console.error('[WebSocket] Failed to validate Maps key:', error);
|
|
1693
|
+
return { valid: false, message: 'Validation failed' };
|
|
1694
|
+
}
|
|
1695
|
+
}, [sendRequest]);
|
|
1696
|
+
|
|
1697
|
+
// =========================================================================
|
|
1698
|
+
// WhatsApp Operations
|
|
1699
|
+
// =========================================================================
|
|
1700
|
+
|
|
1701
|
+
const getWhatsAppStatusAsync = useCallback(async (): Promise<{ connected: boolean; deviceId?: string; data?: any }> => {
|
|
1702
|
+
try {
|
|
1703
|
+
const response = await sendRequest<any>('whatsapp_status', {});
|
|
1704
|
+
return {
|
|
1705
|
+
connected: response.connected || false,
|
|
1706
|
+
deviceId: response.device_id,
|
|
1707
|
+
data: response.data
|
|
1708
|
+
};
|
|
1709
|
+
} catch (error) {
|
|
1710
|
+
console.error('[WebSocket] Failed to get WhatsApp status:', error);
|
|
1711
|
+
return { connected: false };
|
|
1712
|
+
}
|
|
1713
|
+
}, [sendRequest]);
|
|
1714
|
+
|
|
1715
|
+
const getWhatsAppQRAsync = useCallback(async (): Promise<{ connected: boolean; qr?: string; message?: string }> => {
|
|
1716
|
+
try {
|
|
1717
|
+
const response = await sendRequest<any>('whatsapp_qr', {});
|
|
1718
|
+
return {
|
|
1719
|
+
connected: response.connected || false,
|
|
1720
|
+
qr: response.qr,
|
|
1721
|
+
message: response.message
|
|
1722
|
+
};
|
|
1723
|
+
} catch (error) {
|
|
1724
|
+
console.error('[WebSocket] Failed to get WhatsApp QR:', error);
|
|
1725
|
+
return { connected: false, message: 'Failed to get QR code' };
|
|
1726
|
+
}
|
|
1727
|
+
}, [sendRequest]);
|
|
1728
|
+
|
|
1729
|
+
const sendWhatsAppMessageAsync = useCallback(async (
|
|
1730
|
+
phone: string,
|
|
1731
|
+
message: string
|
|
1732
|
+
): Promise<{ success: boolean; messageId?: string; error?: string }> => {
|
|
1733
|
+
try {
|
|
1734
|
+
const response = await sendRequest<any>('whatsapp_send', { phone, message });
|
|
1735
|
+
return {
|
|
1736
|
+
success: response.success || false,
|
|
1737
|
+
messageId: response.messageId,
|
|
1738
|
+
error: response.error
|
|
1739
|
+
};
|
|
1740
|
+
} catch (error: any) {
|
|
1741
|
+
console.error('[WebSocket] Failed to send WhatsApp message:', error);
|
|
1742
|
+
return { success: false, error: error.message || 'Send failed' };
|
|
1743
|
+
}
|
|
1744
|
+
}, [sendRequest]);
|
|
1745
|
+
|
|
1746
|
+
const startWhatsAppConnectionAsync = useCallback(async (): Promise<{ success: boolean; message?: string }> => {
|
|
1747
|
+
try {
|
|
1748
|
+
const response = await sendRequest<any>('whatsapp_start', {});
|
|
1749
|
+
return {
|
|
1750
|
+
success: response.success !== false,
|
|
1751
|
+
message: response.message
|
|
1752
|
+
};
|
|
1753
|
+
} catch (error: any) {
|
|
1754
|
+
console.error('[WebSocket] Failed to start WhatsApp connection:', error);
|
|
1755
|
+
return { success: false, message: error.message || 'Failed to start' };
|
|
1756
|
+
}
|
|
1757
|
+
}, [sendRequest]);
|
|
1758
|
+
|
|
1759
|
+
const restartWhatsAppConnectionAsync = useCallback(async (): Promise<{ success: boolean; message?: string }> => {
|
|
1760
|
+
try {
|
|
1761
|
+
const response = await sendRequest<any>('whatsapp_restart', {});
|
|
1762
|
+
return {
|
|
1763
|
+
success: response.success !== false,
|
|
1764
|
+
message: response.message
|
|
1765
|
+
};
|
|
1766
|
+
} catch (error: any) {
|
|
1767
|
+
console.error('[WebSocket] Failed to restart WhatsApp connection:', error);
|
|
1768
|
+
return { success: false, message: error.message || 'Failed to restart' };
|
|
1769
|
+
}
|
|
1770
|
+
}, [sendRequest]);
|
|
1771
|
+
|
|
1772
|
+
const getWhatsAppGroupsAsync = useCallback(async (): Promise<{ success: boolean; groups: Array<{ jid: string; name: string; topic?: string; size?: number; is_community?: boolean }>; error?: string }> => {
|
|
1773
|
+
try {
|
|
1774
|
+
const response = await sendRequest<any>('whatsapp_groups', {});
|
|
1775
|
+
return {
|
|
1776
|
+
success: response.success !== false,
|
|
1777
|
+
groups: response.groups || [],
|
|
1778
|
+
error: response.error
|
|
1779
|
+
};
|
|
1780
|
+
} catch (error: any) {
|
|
1781
|
+
console.error('[WebSocket] Failed to get WhatsApp groups:', error);
|
|
1782
|
+
return { success: false, groups: [], error: error.message || 'Failed to get groups' };
|
|
1783
|
+
}
|
|
1784
|
+
}, [sendRequest]);
|
|
1785
|
+
|
|
1786
|
+
const getWhatsAppGroupInfoAsync = useCallback(async (groupId: string): Promise<{ success: boolean; participants: Array<{ phone: string; name: string; jid: string; is_admin?: boolean }>; name?: string; error?: string }> => {
|
|
1787
|
+
try {
|
|
1788
|
+
const response = await sendRequest<any>('whatsapp_group_info', { group_id: groupId });
|
|
1789
|
+
return {
|
|
1790
|
+
success: response.success !== false,
|
|
1791
|
+
participants: response.participants || [],
|
|
1792
|
+
name: response.name,
|
|
1793
|
+
error: response.error
|
|
1794
|
+
};
|
|
1795
|
+
} catch (error: any) {
|
|
1796
|
+
console.error('[WebSocket] Failed to get WhatsApp group info:', error);
|
|
1797
|
+
return { success: false, participants: [], error: error.message || 'Failed to get group info' };
|
|
1798
|
+
}
|
|
1799
|
+
}, [sendRequest]);
|
|
1800
|
+
|
|
1801
|
+
const getWhatsAppRateLimitConfigAsync = useCallback(async (): Promise<{ success: boolean; config?: RateLimitConfig; stats?: RateLimitStats; error?: string }> => {
|
|
1802
|
+
try {
|
|
1803
|
+
const response = await sendRequest<any>('whatsapp_rate_limit_get', {});
|
|
1804
|
+
return {
|
|
1805
|
+
success: response.success !== false,
|
|
1806
|
+
config: response.config,
|
|
1807
|
+
stats: response.stats,
|
|
1808
|
+
error: response.error
|
|
1809
|
+
};
|
|
1810
|
+
} catch (error: any) {
|
|
1811
|
+
console.error('[WebSocket] Failed to get WhatsApp rate limit config:', error);
|
|
1812
|
+
return { success: false, error: error.message || 'Failed to get rate limit config' };
|
|
1813
|
+
}
|
|
1814
|
+
}, [sendRequest]);
|
|
1815
|
+
|
|
1816
|
+
const setWhatsAppRateLimitConfigAsync = useCallback(async (config: Partial<RateLimitConfig>): Promise<{ success: boolean; config?: RateLimitConfig; error?: string }> => {
|
|
1817
|
+
try {
|
|
1818
|
+
const response = await sendRequest<any>('whatsapp_rate_limit_set', { config });
|
|
1819
|
+
return {
|
|
1820
|
+
success: response.success !== false,
|
|
1821
|
+
config: response.config,
|
|
1822
|
+
error: response.error
|
|
1823
|
+
};
|
|
1824
|
+
} catch (error: any) {
|
|
1825
|
+
console.error('[WebSocket] Failed to set WhatsApp rate limit config:', error);
|
|
1826
|
+
return { success: false, error: error.message || 'Failed to set rate limit config' };
|
|
1827
|
+
}
|
|
1828
|
+
}, [sendRequest]);
|
|
1829
|
+
|
|
1830
|
+
const getWhatsAppRateLimitStatsAsync = useCallback(async (): Promise<{ success: boolean; stats?: RateLimitStats; error?: string }> => {
|
|
1831
|
+
try {
|
|
1832
|
+
const response = await sendRequest<any>('whatsapp_rate_limit_stats', {});
|
|
1833
|
+
return {
|
|
1834
|
+
success: response.success !== false,
|
|
1835
|
+
stats: response.stats || response,
|
|
1836
|
+
error: response.error
|
|
1837
|
+
};
|
|
1838
|
+
} catch (error: any) {
|
|
1839
|
+
console.error('[WebSocket] Failed to get WhatsApp rate limit stats:', error);
|
|
1840
|
+
return { success: false, error: error.message || 'Failed to get rate limit stats' };
|
|
1841
|
+
}
|
|
1842
|
+
}, [sendRequest]);
|
|
1843
|
+
|
|
1844
|
+
const unpauseWhatsAppRateLimitAsync = useCallback(async (): Promise<{ success: boolean; stats?: RateLimitStats; error?: string }> => {
|
|
1845
|
+
try {
|
|
1846
|
+
const response = await sendRequest<any>('whatsapp_rate_limit_unpause', {});
|
|
1847
|
+
return {
|
|
1848
|
+
success: response.success !== false,
|
|
1849
|
+
stats: response.stats,
|
|
1850
|
+
error: response.error
|
|
1851
|
+
};
|
|
1852
|
+
} catch (error: any) {
|
|
1853
|
+
console.error('[WebSocket] Failed to unpause WhatsApp rate limit:', error);
|
|
1854
|
+
return { success: false, error: error.message || 'Failed to unpause rate limit' };
|
|
1855
|
+
}
|
|
1856
|
+
}, [sendRequest]);
|
|
1857
|
+
|
|
1858
|
+
// =========================================================================
|
|
1859
|
+
// Memory and Skill Operations
|
|
1860
|
+
// =========================================================================
|
|
1861
|
+
|
|
1862
|
+
const clearMemoryAsync = useCallback(async (
|
|
1863
|
+
sessionId: string,
|
|
1864
|
+
clearLongTerm = false
|
|
1865
|
+
): Promise<{ success: boolean; default_content?: string; cleared_vector_store?: boolean; error?: string }> => {
|
|
1866
|
+
try {
|
|
1867
|
+
const response = await sendRequest<any>('clear_memory', {
|
|
1868
|
+
session_id: sessionId,
|
|
1869
|
+
clear_long_term: clearLongTerm
|
|
1870
|
+
});
|
|
1871
|
+
return {
|
|
1872
|
+
success: response.success !== false,
|
|
1873
|
+
default_content: response.default_content,
|
|
1874
|
+
cleared_vector_store: response.cleared_vector_store,
|
|
1875
|
+
error: response.error
|
|
1876
|
+
};
|
|
1877
|
+
} catch (error: any) {
|
|
1878
|
+
console.error('[WebSocket] Failed to clear memory:', error);
|
|
1879
|
+
return { success: false, error: error.message || 'Failed to clear memory' };
|
|
1880
|
+
}
|
|
1881
|
+
}, [sendRequest]);
|
|
1882
|
+
|
|
1883
|
+
const resetSkillAsync = useCallback(async (
|
|
1884
|
+
skillName: string
|
|
1885
|
+
): Promise<{ success: boolean; original_content?: string; is_builtin?: boolean; error?: string }> => {
|
|
1886
|
+
try {
|
|
1887
|
+
const response = await sendRequest<any>('reset_skill', {
|
|
1888
|
+
skill_name: skillName
|
|
1889
|
+
});
|
|
1890
|
+
return {
|
|
1891
|
+
success: response.success !== false,
|
|
1892
|
+
original_content: response.original_content,
|
|
1893
|
+
is_builtin: response.is_builtin,
|
|
1894
|
+
error: response.error
|
|
1895
|
+
};
|
|
1896
|
+
} catch (error: any) {
|
|
1897
|
+
console.error('[WebSocket] Failed to reset skill:', error);
|
|
1898
|
+
return { success: false, error: error.message || 'Failed to reset skill' };
|
|
1899
|
+
}
|
|
1900
|
+
}, [sendRequest]);
|
|
1901
|
+
|
|
1902
|
+
// Track if component is mounted to prevent state updates after unmount
|
|
1903
|
+
const isMountedRef = useRef(true);
|
|
1904
|
+
|
|
1905
|
+
// Connect only when authenticated (not during auth loading)
|
|
1906
|
+
useEffect(() => {
|
|
1907
|
+
isMountedRef.current = true;
|
|
1908
|
+
|
|
1909
|
+
// Don't connect if still loading auth or not authenticated
|
|
1910
|
+
if (authLoading || !isAuthenticated) {
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
// Skip if already connected
|
|
1915
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// Small delay to avoid React Strict Mode double-connection issues
|
|
1920
|
+
const connectTimeout = setTimeout(() => {
|
|
1921
|
+
if (isMountedRef.current && isAuthenticated && !wsRef.current) {
|
|
1922
|
+
connect();
|
|
1923
|
+
}
|
|
1924
|
+
}, 100);
|
|
1925
|
+
|
|
1926
|
+
return () => {
|
|
1927
|
+
clearTimeout(connectTimeout);
|
|
1928
|
+
};
|
|
1929
|
+
}, [connect, isAuthenticated, authLoading]);
|
|
1930
|
+
|
|
1931
|
+
// Handle logout - separate effect to avoid reconnect loops
|
|
1932
|
+
useEffect(() => {
|
|
1933
|
+
if (!isAuthenticated && wsRef.current) {
|
|
1934
|
+
wsRef.current.close(1000, 'User logged out');
|
|
1935
|
+
wsRef.current = null;
|
|
1936
|
+
setIsConnected(false);
|
|
1937
|
+
}
|
|
1938
|
+
}, [isAuthenticated]);
|
|
1939
|
+
|
|
1940
|
+
// Cleanup on unmount only
|
|
1941
|
+
useEffect(() => {
|
|
1942
|
+
return () => {
|
|
1943
|
+
isMountedRef.current = false;
|
|
1944
|
+
if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
|
|
1945
|
+
if (pingIntervalRef.current) clearInterval(pingIntervalRef.current);
|
|
1946
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
1947
|
+
wsRef.current.close(1000, 'Component unmounted');
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
}, []);
|
|
1951
|
+
|
|
1952
|
+
const value: WebSocketContextValue = {
|
|
1953
|
+
// Connection state
|
|
1954
|
+
isConnected,
|
|
1955
|
+
reconnecting,
|
|
1956
|
+
|
|
1957
|
+
// Status data
|
|
1958
|
+
androidStatus,
|
|
1959
|
+
whatsappStatus,
|
|
1960
|
+
whatsappMessages,
|
|
1961
|
+
lastWhatsAppMessage,
|
|
1962
|
+
apiKeyStatuses,
|
|
1963
|
+
consoleLogs,
|
|
1964
|
+
terminalLogs,
|
|
1965
|
+
chatMessages,
|
|
1966
|
+
nodeStatuses,
|
|
1967
|
+
nodeParameters,
|
|
1968
|
+
variables,
|
|
1969
|
+
workflowStatus,
|
|
1970
|
+
deploymentStatus,
|
|
1971
|
+
workflowLock,
|
|
1972
|
+
|
|
1973
|
+
// Status getters
|
|
1974
|
+
getNodeStatus,
|
|
1975
|
+
getApiKeyStatus,
|
|
1976
|
+
getVariable,
|
|
1977
|
+
requestStatus,
|
|
1978
|
+
clearNodeStatus,
|
|
1979
|
+
clearWhatsAppMessages,
|
|
1980
|
+
clearConsoleLogs,
|
|
1981
|
+
clearTerminalLogs,
|
|
1982
|
+
clearChatMessages,
|
|
1983
|
+
sendChatMessage: sendChatMessageAsync,
|
|
1984
|
+
|
|
1985
|
+
// Generic request method
|
|
1986
|
+
sendRequest,
|
|
1987
|
+
|
|
1988
|
+
// Node Parameters
|
|
1989
|
+
getNodeParameters: getNodeParametersAsync,
|
|
1990
|
+
getAllNodeParameters: getAllNodeParametersAsync,
|
|
1991
|
+
saveNodeParameters: saveNodeParametersAsync,
|
|
1992
|
+
deleteNodeParameters: deleteNodeParametersAsync,
|
|
1993
|
+
|
|
1994
|
+
// Node Execution
|
|
1995
|
+
executeNode: executeNodeAsync,
|
|
1996
|
+
executeWorkflow: executeWorkflowAsync,
|
|
1997
|
+
getNodeOutput: getNodeOutputAsync,
|
|
1998
|
+
|
|
1999
|
+
// Trigger/Event Waiting
|
|
2000
|
+
cancelEventWait: cancelEventWaitAsync,
|
|
2001
|
+
|
|
2002
|
+
// Deployment Operations
|
|
2003
|
+
deployWorkflow: deployWorkflowAsync,
|
|
2004
|
+
cancelDeployment: cancelDeploymentAsync,
|
|
2005
|
+
getDeploymentStatus: getDeploymentStatusAsync,
|
|
2006
|
+
|
|
2007
|
+
// AI Operations
|
|
2008
|
+
executeAiNode: executeAiNodeAsync,
|
|
2009
|
+
getAiModels: getAiModelsAsync,
|
|
2010
|
+
|
|
2011
|
+
// API Key Operations
|
|
2012
|
+
validateApiKey: validateApiKeyAsync,
|
|
2013
|
+
getStoredApiKey: getStoredApiKeyAsync,
|
|
2014
|
+
saveApiKey: saveApiKeyAsync,
|
|
2015
|
+
deleteApiKey: deleteApiKeyAsync,
|
|
2016
|
+
|
|
2017
|
+
// Android Operations
|
|
2018
|
+
getAndroidDevices: getAndroidDevicesAsync,
|
|
2019
|
+
executeAndroidAction: executeAndroidActionAsync,
|
|
2020
|
+
// Maps Operations
|
|
2021
|
+
validateMapsKey: validateMapsKeyAsync,
|
|
2022
|
+
|
|
2023
|
+
// WhatsApp Operations
|
|
2024
|
+
getWhatsAppStatus: getWhatsAppStatusAsync,
|
|
2025
|
+
getWhatsAppQR: getWhatsAppQRAsync,
|
|
2026
|
+
sendWhatsAppMessage: sendWhatsAppMessageAsync,
|
|
2027
|
+
startWhatsAppConnection: startWhatsAppConnectionAsync,
|
|
2028
|
+
restartWhatsAppConnection: restartWhatsAppConnectionAsync,
|
|
2029
|
+
getWhatsAppGroups: getWhatsAppGroupsAsync,
|
|
2030
|
+
getWhatsAppGroupInfo: getWhatsAppGroupInfoAsync,
|
|
2031
|
+
getWhatsAppRateLimitConfig: getWhatsAppRateLimitConfigAsync,
|
|
2032
|
+
setWhatsAppRateLimitConfig: setWhatsAppRateLimitConfigAsync,
|
|
2033
|
+
getWhatsAppRateLimitStats: getWhatsAppRateLimitStatsAsync,
|
|
2034
|
+
unpauseWhatsAppRateLimit: unpauseWhatsAppRateLimitAsync,
|
|
2035
|
+
|
|
2036
|
+
// Memory and Skill Operations
|
|
2037
|
+
clearMemory: clearMemoryAsync,
|
|
2038
|
+
resetSkill: resetSkillAsync
|
|
2039
|
+
};
|
|
2040
|
+
|
|
2041
|
+
return (
|
|
2042
|
+
<WebSocketContext.Provider value={value}>
|
|
2043
|
+
{children}
|
|
2044
|
+
</WebSocketContext.Provider>
|
|
2045
|
+
);
|
|
2046
|
+
};
|
|
2047
|
+
|
|
2048
|
+
// Hook to use WebSocket context
|
|
2049
|
+
export const useWebSocket = (): WebSocketContextValue => {
|
|
2050
|
+
const context = useContext(WebSocketContext);
|
|
2051
|
+
if (!context) {
|
|
2052
|
+
throw new Error('useWebSocket must be used within a WebSocketProvider');
|
|
2053
|
+
}
|
|
2054
|
+
return context;
|
|
2055
|
+
};
|
|
2056
|
+
|
|
2057
|
+
// Hook specifically for Android status
|
|
2058
|
+
export const useAndroidStatus = (): AndroidStatus & { isConnected: boolean } => {
|
|
2059
|
+
const { androidStatus, isConnected } = useWebSocket();
|
|
2060
|
+
return {
|
|
2061
|
+
...androidStatus,
|
|
2062
|
+
isConnected
|
|
2063
|
+
};
|
|
2064
|
+
};
|
|
2065
|
+
|
|
2066
|
+
// Hook specifically for node status
|
|
2067
|
+
export const useNodeStatus = (nodeId: string): NodeStatus | undefined => {
|
|
2068
|
+
const { getNodeStatus } = useWebSocket();
|
|
2069
|
+
return getNodeStatus(nodeId);
|
|
2070
|
+
};
|
|
2071
|
+
|
|
2072
|
+
// Hook specifically for workflow status
|
|
2073
|
+
export const useWorkflowStatus = (): WorkflowStatus => {
|
|
2074
|
+
const { workflowStatus } = useWebSocket();
|
|
2075
|
+
return workflowStatus;
|
|
2076
|
+
};
|
|
2077
|
+
|
|
2078
|
+
// Hook specifically for API key status
|
|
2079
|
+
export const useApiKeyStatus = (provider: string): ApiKeyStatus | undefined => {
|
|
2080
|
+
const { getApiKeyStatus } = useWebSocket();
|
|
2081
|
+
return getApiKeyStatus(provider);
|
|
2082
|
+
};
|
|
2083
|
+
|
|
2084
|
+
// Hook specifically for WhatsApp status
|
|
2085
|
+
export const useWhatsAppStatus = (): WhatsAppStatus => {
|
|
2086
|
+
const { whatsappStatus } = useWebSocket();
|
|
2087
|
+
return whatsappStatus;
|
|
2088
|
+
};
|
|
2089
|
+
|
|
2090
|
+
// Hook specifically for deployment status
|
|
2091
|
+
export const useDeploymentStatus = (): DeploymentStatus => {
|
|
2092
|
+
const { deploymentStatus } = useWebSocket();
|
|
2093
|
+
return deploymentStatus;
|
|
2094
|
+
};
|
|
2095
|
+
|
|
2096
|
+
// Hook specifically for workflow lock status
|
|
2097
|
+
export const useWorkflowLock = (): WorkflowLock => {
|
|
2098
|
+
const { workflowLock } = useWebSocket();
|
|
2099
|
+
return workflowLock;
|
|
2100
|
+
};
|
|
2101
|
+
|
|
2102
|
+
// Hook specifically for WhatsApp messages (for trigger nodes)
|
|
2103
|
+
export const useWhatsAppMessages = (): {
|
|
2104
|
+
messages: WhatsAppMessage[];
|
|
2105
|
+
lastMessage: WhatsAppMessage | null;
|
|
2106
|
+
clearMessages: () => void;
|
|
2107
|
+
} => {
|
|
2108
|
+
const { whatsappMessages, lastWhatsAppMessage, clearWhatsAppMessages } = useWebSocket();
|
|
2109
|
+
return {
|
|
2110
|
+
messages: whatsappMessages,
|
|
2111
|
+
lastMessage: lastWhatsAppMessage,
|
|
2112
|
+
clearMessages: clearWhatsAppMessages
|
|
2113
|
+
};
|
|
2114
|
+
};
|
|
2115
|
+
|
|
2116
|
+
// Hook to check if a tool is currently being executed by any AI Agent
|
|
2117
|
+
// Used by tool nodes to show spinning indicator when they're being used
|
|
2118
|
+
export const useIsToolExecuting = (toolName: string): boolean => {
|
|
2119
|
+
const { nodeStatuses } = useWebSocket();
|
|
2120
|
+
|
|
2121
|
+
// Debug: Log what we're checking
|
|
2122
|
+
if (toolName) {
|
|
2123
|
+
const statusCount = Object.keys(nodeStatuses).length;
|
|
2124
|
+
if (statusCount > 0) {
|
|
2125
|
+
console.log(`[useIsToolExecuting] Checking for tool '${toolName}', nodeStatuses count:`, statusCount, nodeStatuses);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// Scan all node statuses to find if any AI Agent is executing this tool
|
|
2130
|
+
// The status object contains phase and tool_name directly (not nested under data)
|
|
2131
|
+
for (const nodeId in nodeStatuses) {
|
|
2132
|
+
const status = nodeStatuses[nodeId] as Record<string, any>;
|
|
2133
|
+
if (status?.phase === 'executing_tool') {
|
|
2134
|
+
console.log(`[useIsToolExecuting] Found executing_tool phase for node ${nodeId}:`, status);
|
|
2135
|
+
if (status?.tool_name === toolName) {
|
|
2136
|
+
console.log(`[useIsToolExecuting] MATCH! Tool '${toolName}' is executing`);
|
|
2137
|
+
return true;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return false;
|
|
2142
|
+
};
|
|
2143
|
+
|
|
2144
|
+
export default WebSocketContext;
|