machinaos 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.template +71 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/bin/cli.js +159 -0
- package/client/.dockerignore +45 -0
- package/client/Dockerfile +68 -0
- package/client/eslint.config.js +29 -0
- package/client/index.html +13 -0
- package/client/nginx.conf +66 -0
- package/client/package.json +48 -0
- package/client/src/App.tsx +27 -0
- package/client/src/Dashboard.tsx +1173 -0
- package/client/src/ParameterPanel.tsx +301 -0
- package/client/src/components/AIAgentNode.tsx +321 -0
- package/client/src/components/APIKeyValidator.tsx +118 -0
- package/client/src/components/ClaudeChatModelNode.tsx +18 -0
- package/client/src/components/ConditionalEdge.tsx +189 -0
- package/client/src/components/CredentialsModal.tsx +306 -0
- package/client/src/components/EdgeConditionEditor.tsx +443 -0
- package/client/src/components/GeminiChatModelNode.tsx +18 -0
- package/client/src/components/GenericNode.tsx +357 -0
- package/client/src/components/LocationParameterPanel.tsx +154 -0
- package/client/src/components/ModelNode.tsx +286 -0
- package/client/src/components/OpenAIChatModelNode.tsx +18 -0
- package/client/src/components/OutputPanel.tsx +471 -0
- package/client/src/components/ParameterRenderer.tsx +1874 -0
- package/client/src/components/SkillEditorModal.tsx +417 -0
- package/client/src/components/SquareNode.tsx +797 -0
- package/client/src/components/StartNode.tsx +250 -0
- package/client/src/components/ToolkitNode.tsx +365 -0
- package/client/src/components/TriggerNode.tsx +463 -0
- package/client/src/components/auth/LoginPage.tsx +247 -0
- package/client/src/components/auth/ProtectedRoute.tsx +59 -0
- package/client/src/components/base/BaseChatModelNode.tsx +271 -0
- package/client/src/components/icons/AIProviderIcons.tsx +50 -0
- package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
- package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
- package/client/src/components/maps/index.ts +26 -0
- package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
- package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
- package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
- package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
- package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
- package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
- package/client/src/components/parameterPanel/index.ts +42 -0
- package/client/src/components/shared/DataPanel.tsx +142 -0
- package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
- package/client/src/components/ui/AIResultModal.tsx +204 -0
- package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
- package/client/src/components/ui/CodeEditor.tsx +81 -0
- package/client/src/components/ui/CollapsibleSection.tsx +88 -0
- package/client/src/components/ui/ComponentItem.tsx +154 -0
- package/client/src/components/ui/ComponentPalette.tsx +321 -0
- package/client/src/components/ui/ConsolePanel.tsx +1074 -0
- package/client/src/components/ui/ErrorBoundary.tsx +196 -0
- package/client/src/components/ui/InputNodesPanel.tsx +204 -0
- package/client/src/components/ui/MapSelector.tsx +314 -0
- package/client/src/components/ui/Modal.tsx +149 -0
- package/client/src/components/ui/NodeContextMenu.tsx +192 -0
- package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
- package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
- package/client/src/components/ui/SettingsPanel.tsx +243 -0
- package/client/src/components/ui/TopToolbar.tsx +736 -0
- package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
- package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
- package/client/src/config/antdTheme.ts +186 -0
- package/client/src/config/api.ts +54 -0
- package/client/src/contexts/AuthContext.tsx +221 -0
- package/client/src/contexts/ThemeContext.tsx +42 -0
- package/client/src/contexts/WebSocketContext.tsx +1971 -0
- package/client/src/factories/baseChatModelFactory.ts +256 -0
- package/client/src/hooks/useAndroidOperations.ts +164 -0
- package/client/src/hooks/useApiKeyValidation.ts +107 -0
- package/client/src/hooks/useApiKeys.ts +238 -0
- package/client/src/hooks/useAppTheme.ts +17 -0
- package/client/src/hooks/useComponentPalette.ts +51 -0
- package/client/src/hooks/useCopyPaste.ts +155 -0
- package/client/src/hooks/useDragAndDrop.ts +124 -0
- package/client/src/hooks/useDragVariable.ts +88 -0
- package/client/src/hooks/useExecution.ts +313 -0
- package/client/src/hooks/useParameterPanel.ts +176 -0
- package/client/src/hooks/useReactFlowNodes.ts +189 -0
- package/client/src/hooks/useToolSchema.ts +209 -0
- package/client/src/hooks/useWhatsApp.ts +196 -0
- package/client/src/hooks/useWorkflowManagement.ts +46 -0
- package/client/src/index.css +315 -0
- package/client/src/main.tsx +19 -0
- package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
- package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
- package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
- package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
- package/client/src/nodeDefinitions/chatNodes.ts +135 -0
- package/client/src/nodeDefinitions/codeNodes.ts +54 -0
- package/client/src/nodeDefinitions/documentNodes.ts +379 -0
- package/client/src/nodeDefinitions/index.ts +15 -0
- package/client/src/nodeDefinitions/locationNodes.ts +463 -0
- package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
- package/client/src/nodeDefinitions/skillNodes.ts +211 -0
- package/client/src/nodeDefinitions/toolNodes.ts +198 -0
- package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
- package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
- package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
- package/client/src/nodeDefinitions.ts +104 -0
- package/client/src/schemas/workflowSchema.ts +264 -0
- package/client/src/services/dynamicParameterService.ts +96 -0
- package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
- package/client/src/services/executionService.ts +232 -0
- package/client/src/services/workflowApi.ts +91 -0
- package/client/src/store/useAppStore.ts +582 -0
- package/client/src/styles/theme.ts +508 -0
- package/client/src/styles/zIndex.ts +17 -0
- package/client/src/types/ComponentTypes.ts +39 -0
- package/client/src/types/EdgeCondition.ts +231 -0
- package/client/src/types/INodeProperties.ts +288 -0
- package/client/src/types/NodeTypes.ts +28 -0
- package/client/src/utils/formatters.ts +33 -0
- package/client/src/utils/googleMapsLoader.ts +140 -0
- package/client/src/utils/locationUtils.ts +85 -0
- package/client/src/utils/nodeUtils.ts +31 -0
- package/client/src/utils/workflow.ts +30 -0
- package/client/src/utils/workflowExport.ts +120 -0
- package/client/src/vite-env.d.ts +12 -0
- package/client/tailwind.config.js +60 -0
- package/client/tsconfig.json +25 -0
- package/client/tsconfig.node.json +11 -0
- package/client/vite.config.js +35 -0
- package/docker-compose.prod.yml +107 -0
- package/docker-compose.yml +104 -0
- package/docs-MachinaOs/README.md +85 -0
- package/docs-MachinaOs/deployment/docker.mdx +228 -0
- package/docs-MachinaOs/deployment/production.mdx +345 -0
- package/docs-MachinaOs/docs.json +75 -0
- package/docs-MachinaOs/faq.mdx +309 -0
- package/docs-MachinaOs/favicon.svg +5 -0
- package/docs-MachinaOs/installation.mdx +160 -0
- package/docs-MachinaOs/introduction.mdx +114 -0
- package/docs-MachinaOs/logo/dark.svg +6 -0
- package/docs-MachinaOs/logo/light.svg +6 -0
- package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
- package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
- package/docs-MachinaOs/nodes/android.mdx +411 -0
- package/docs-MachinaOs/nodes/overview.mdx +181 -0
- package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
- package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
- package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
- package/docs-MachinaOs/quickstart.mdx +119 -0
- package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
- package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
- package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
- package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
- package/nul +0 -0
- package/package.json +70 -0
- package/scripts/build.js +158 -0
- package/scripts/check-ports.ps1 +33 -0
- package/scripts/clean.js +40 -0
- package/scripts/docker.js +93 -0
- package/scripts/kill-port.ps1 +154 -0
- package/scripts/start.js +210 -0
- package/scripts/stop.js +325 -0
- package/server/.dockerignore +44 -0
- package/server/Dockerfile +45 -0
- package/server/constants.py +249 -0
- package/server/core/__init__.py +1 -0
- package/server/core/cache.py +461 -0
- package/server/core/config.py +128 -0
- package/server/core/container.py +99 -0
- package/server/core/database.py +1211 -0
- package/server/core/logging.py +314 -0
- package/server/main.py +289 -0
- package/server/middleware/__init__.py +5 -0
- package/server/middleware/auth.py +89 -0
- package/server/models/__init__.py +1 -0
- package/server/models/auth.py +52 -0
- package/server/models/cache.py +24 -0
- package/server/models/database.py +211 -0
- package/server/models/nodes.py +455 -0
- package/server/package.json +9 -0
- package/server/pyproject.toml +72 -0
- package/server/requirements.txt +83 -0
- package/server/routers/__init__.py +1 -0
- package/server/routers/android.py +294 -0
- package/server/routers/auth.py +203 -0
- package/server/routers/database.py +151 -0
- package/server/routers/maps.py +142 -0
- package/server/routers/nodejs_compat.py +289 -0
- package/server/routers/webhook.py +90 -0
- package/server/routers/websocket.py +2127 -0
- package/server/routers/whatsapp.py +761 -0
- package/server/routers/workflow.py +200 -0
- package/server/services/__init__.py +1 -0
- package/server/services/ai.py +2415 -0
- package/server/services/android/__init__.py +27 -0
- package/server/services/android/broadcaster.py +114 -0
- package/server/services/android/client.py +608 -0
- package/server/services/android/manager.py +78 -0
- package/server/services/android/protocol.py +165 -0
- package/server/services/android_service.py +588 -0
- package/server/services/auth.py +131 -0
- package/server/services/chat_client.py +160 -0
- package/server/services/deployment/__init__.py +12 -0
- package/server/services/deployment/manager.py +706 -0
- package/server/services/deployment/state.py +47 -0
- package/server/services/deployment/triggers.py +275 -0
- package/server/services/event_waiter.py +785 -0
- package/server/services/execution/__init__.py +77 -0
- package/server/services/execution/cache.py +769 -0
- package/server/services/execution/conditions.py +373 -0
- package/server/services/execution/dlq.py +132 -0
- package/server/services/execution/executor.py +1351 -0
- package/server/services/execution/models.py +531 -0
- package/server/services/execution/recovery.py +235 -0
- package/server/services/handlers/__init__.py +126 -0
- package/server/services/handlers/ai.py +355 -0
- package/server/services/handlers/android.py +260 -0
- package/server/services/handlers/code.py +278 -0
- package/server/services/handlers/document.py +598 -0
- package/server/services/handlers/http.py +193 -0
- package/server/services/handlers/polyglot.py +105 -0
- package/server/services/handlers/tools.py +845 -0
- package/server/services/handlers/triggers.py +107 -0
- package/server/services/handlers/utility.py +822 -0
- package/server/services/handlers/whatsapp.py +476 -0
- package/server/services/maps.py +289 -0
- package/server/services/memory_store.py +103 -0
- package/server/services/node_executor.py +375 -0
- package/server/services/parameter_resolver.py +218 -0
- package/server/services/polyglot_client.py +169 -0
- package/server/services/scheduler.py +155 -0
- package/server/services/skill_loader.py +417 -0
- package/server/services/status_broadcaster.py +826 -0
- package/server/services/temporal/__init__.py +23 -0
- package/server/services/temporal/activities.py +344 -0
- package/server/services/temporal/client.py +76 -0
- package/server/services/temporal/executor.py +147 -0
- package/server/services/temporal/worker.py +251 -0
- package/server/services/temporal/workflow.py +355 -0
- package/server/services/temporal/ws_client.py +236 -0
- package/server/services/text.py +111 -0
- package/server/services/user_auth.py +172 -0
- package/server/services/websocket_client.py +29 -0
- package/server/services/workflow.py +597 -0
- package/server/skills/android-skill/SKILL.md +82 -0
- package/server/skills/assistant-personality/SKILL.md +45 -0
- package/server/skills/code-skill/SKILL.md +140 -0
- package/server/skills/http-skill/SKILL.md +161 -0
- package/server/skills/maps-skill/SKILL.md +170 -0
- package/server/skills/memory-skill/SKILL.md +154 -0
- package/server/skills/scheduler-skill/SKILL.md +84 -0
- package/server/skills/whatsapp-skill/SKILL.md +283 -0
- package/server/uv.lock +2916 -0
- package/server/whatsapp-rpc/.dockerignore +30 -0
- package/server/whatsapp-rpc/Dockerfile +44 -0
- package/server/whatsapp-rpc/Dockerfile.web +17 -0
- package/server/whatsapp-rpc/README.md +139 -0
- package/server/whatsapp-rpc/cli.js +95 -0
- package/server/whatsapp-rpc/configs/config.yaml +7 -0
- package/server/whatsapp-rpc/docker-compose.yml +35 -0
- package/server/whatsapp-rpc/docs/API.md +410 -0
- package/server/whatsapp-rpc/go.mod +67 -0
- package/server/whatsapp-rpc/go.sum +203 -0
- package/server/whatsapp-rpc/package.json +30 -0
- package/server/whatsapp-rpc/schema.json +1294 -0
- package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
- package/server/whatsapp-rpc/scripts/cli.js +162 -0
- package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
- package/server/whatsapp-rpc/src/go/config/config.go +49 -0
- package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
- package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
- package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
- package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
- package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
- package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
- package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
- package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
- package/server/whatsapp-rpc/web/app.py +609 -0
- package/server/whatsapp-rpc/web/requirements.txt +6 -0
- package/server/whatsapp-rpc/web/rpc_client.py +427 -0
- package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
- package/server/whatsapp-rpc/web/templates/base.html +150 -0
- package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
- package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
- package/server/whatsapp-rpc/web/templates/groups.html +328 -0
- package/server/whatsapp-rpc/web/templates/messages.html +465 -0
- package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
- package/server/whatsapp-rpc/web/templates/send.html +259 -0
- package/server/whatsapp-rpc/web/templates/settings.html +459 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Google Maps service routes."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from core.container import container
|
|
9
|
+
from services.maps import MapsService
|
|
10
|
+
from services.status_broadcaster import get_status_broadcaster
|
|
11
|
+
from core.logging import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
router = APIRouter(prefix="/python", tags=["maps"])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GoogleMapsRequest(BaseModel):
|
|
18
|
+
node_id: str
|
|
19
|
+
node_type: str
|
|
20
|
+
parameters: Dict[str, Any]
|
|
21
|
+
api_key: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ApiKeyValidationRequest(BaseModel):
|
|
25
|
+
api_key: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@router.post("/maps/validate-key")
|
|
29
|
+
async def validate_google_maps_key(request: ApiKeyValidationRequest):
|
|
30
|
+
"""Validate Google Maps API key and broadcast status via WebSocket."""
|
|
31
|
+
broadcaster = get_status_broadcaster()
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
api_key = request.api_key.strip()
|
|
35
|
+
if not api_key:
|
|
36
|
+
await broadcaster.update_api_key_status(
|
|
37
|
+
provider="google_maps",
|
|
38
|
+
valid=False,
|
|
39
|
+
message="API key is required"
|
|
40
|
+
)
|
|
41
|
+
raise HTTPException(status_code=400, detail="API key is required")
|
|
42
|
+
|
|
43
|
+
# Test the API key with a simple geocoding request
|
|
44
|
+
async with httpx.AsyncClient() as client:
|
|
45
|
+
response = await client.get(
|
|
46
|
+
"https://maps.googleapis.com/maps/api/geocode/json",
|
|
47
|
+
params={
|
|
48
|
+
"address": "1600 Amphitheatre Parkway, Mountain View, CA",
|
|
49
|
+
"key": api_key
|
|
50
|
+
},
|
|
51
|
+
timeout=10.0
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
data = response.json()
|
|
55
|
+
|
|
56
|
+
if data.get("status") == "OK":
|
|
57
|
+
await broadcaster.update_api_key_status(
|
|
58
|
+
provider="google_maps",
|
|
59
|
+
valid=True,
|
|
60
|
+
message="API key validated successfully"
|
|
61
|
+
)
|
|
62
|
+
return {
|
|
63
|
+
"success": True,
|
|
64
|
+
"valid": True,
|
|
65
|
+
"message": "Google Maps API key is valid"
|
|
66
|
+
}
|
|
67
|
+
elif data.get("status") == "REQUEST_DENIED":
|
|
68
|
+
error_msg = data.get("error_message", "Invalid API key")
|
|
69
|
+
await broadcaster.update_api_key_status(
|
|
70
|
+
provider="google_maps",
|
|
71
|
+
valid=False,
|
|
72
|
+
message=error_msg
|
|
73
|
+
)
|
|
74
|
+
return {
|
|
75
|
+
"success": True,
|
|
76
|
+
"valid": False,
|
|
77
|
+
"message": error_msg
|
|
78
|
+
}
|
|
79
|
+
else:
|
|
80
|
+
# Other statuses like ZERO_RESULTS still mean the key works
|
|
81
|
+
await broadcaster.update_api_key_status(
|
|
82
|
+
provider="google_maps",
|
|
83
|
+
valid=True,
|
|
84
|
+
message="API key validated"
|
|
85
|
+
)
|
|
86
|
+
return {
|
|
87
|
+
"success": True,
|
|
88
|
+
"valid": True,
|
|
89
|
+
"message": f"API key is valid (status: {data.get('status')})"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
except httpx.TimeoutException:
|
|
93
|
+
await broadcaster.update_api_key_status(
|
|
94
|
+
provider="google_maps",
|
|
95
|
+
valid=False,
|
|
96
|
+
message="Validation request timed out"
|
|
97
|
+
)
|
|
98
|
+
raise HTTPException(status_code=504, detail="Validation request timed out")
|
|
99
|
+
except httpx.RequestError as e:
|
|
100
|
+
await broadcaster.update_api_key_status(
|
|
101
|
+
provider="google_maps",
|
|
102
|
+
valid=False,
|
|
103
|
+
message=f"Network error: {str(e)}"
|
|
104
|
+
)
|
|
105
|
+
raise HTTPException(status_code=503, detail=f"Network error: {str(e)}")
|
|
106
|
+
except HTTPException:
|
|
107
|
+
raise
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"API key validation error: {e}")
|
|
110
|
+
await broadcaster.update_api_key_status(
|
|
111
|
+
provider="google_maps",
|
|
112
|
+
valid=False,
|
|
113
|
+
message=f"Validation failed: {str(e)}"
|
|
114
|
+
)
|
|
115
|
+
raise HTTPException(status_code=500, detail=f"Validation failed: {str(e)}")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@router.post("/createmap/execute")
|
|
119
|
+
async def execute_createmap_node(
|
|
120
|
+
request: GoogleMapsRequest,
|
|
121
|
+
maps_service: MapsService = Depends(lambda: container.maps_service())
|
|
122
|
+
):
|
|
123
|
+
"""Execute Create Map node - Google Maps initialization."""
|
|
124
|
+
return await maps_service.create_map(request.node_id, request.parameters)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@router.post("/addlocations/execute")
|
|
128
|
+
async def execute_addlocations_node(
|
|
129
|
+
request: GoogleMapsRequest,
|
|
130
|
+
maps_service: MapsService = Depends(lambda: container.maps_service())
|
|
131
|
+
):
|
|
132
|
+
"""Execute Add Locations node - Google Maps Geocoding."""
|
|
133
|
+
return await maps_service.geocode_location(request.node_id, request.parameters)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@router.post("/shownearbyplaces/execute")
|
|
137
|
+
async def execute_shownearbyplaces_node(
|
|
138
|
+
request: GoogleMapsRequest,
|
|
139
|
+
maps_service: MapsService = Depends(lambda: container.maps_service())
|
|
140
|
+
):
|
|
141
|
+
"""Execute Show Nearby Places node - Google Places API."""
|
|
142
|
+
return await maps_service.find_nearby_places(request.node_id, request.parameters)
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""Node.js API compatibility routes for seamless migration."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
from fastapi import APIRouter, HTTPException, Depends
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from typing import Dict, Any, List, Optional
|
|
8
|
+
|
|
9
|
+
from core.container import container
|
|
10
|
+
from core.database import Database
|
|
11
|
+
from services.workflow import WorkflowService
|
|
12
|
+
from core.logging import get_logger
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
router = APIRouter(tags=["nodejs-compatibility"])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Pydantic models matching Node.js API
|
|
19
|
+
class NodeExecuteRequest(BaseModel):
|
|
20
|
+
nodeId: str
|
|
21
|
+
nodeType: str
|
|
22
|
+
parameters: Optional[Dict[str, Any]] = {}
|
|
23
|
+
nodes: Optional[List[Dict[str, Any]]] = []
|
|
24
|
+
edges: Optional[List[Dict[str, Any]]] = []
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WorkflowSaveRequest(BaseModel):
|
|
28
|
+
name: str
|
|
29
|
+
data: Dict[str, Any]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Health and status endpoints
|
|
33
|
+
@router.get("/")
|
|
34
|
+
async def root():
|
|
35
|
+
"""Node.js compatible root endpoint."""
|
|
36
|
+
return {
|
|
37
|
+
"message": "React Flow Project API Server",
|
|
38
|
+
"status": "running",
|
|
39
|
+
"version": "2.0.0-python",
|
|
40
|
+
"endpoints": {
|
|
41
|
+
"health": "/api/health",
|
|
42
|
+
"workflows": "/api/workflows",
|
|
43
|
+
"nodes": "/api/nodes",
|
|
44
|
+
"execute": "/api/nodes/execute"
|
|
45
|
+
},
|
|
46
|
+
"services": {
|
|
47
|
+
"main": "http://localhost:3010",
|
|
48
|
+
"python": "http://localhost:3010"
|
|
49
|
+
},
|
|
50
|
+
"timestamp": datetime.now().isoformat()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@router.get("/api/health")
|
|
55
|
+
async def health_check():
|
|
56
|
+
"""Node.js compatible health check."""
|
|
57
|
+
return {
|
|
58
|
+
"status": "OK",
|
|
59
|
+
"message": "React Flow Server is running",
|
|
60
|
+
"timestamp": datetime.now().isoformat()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Workflow management endpoints
|
|
65
|
+
@router.get("/api/workflows")
|
|
66
|
+
async def get_workflows(database: Database = Depends(lambda: container.database())):
|
|
67
|
+
"""Get all workflows (Node.js compatible)."""
|
|
68
|
+
try:
|
|
69
|
+
workflows = await database.get_all_workflows()
|
|
70
|
+
return {"workflows": workflows}
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error("Failed to get workflows", error=str(e))
|
|
73
|
+
return {"workflows": []}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@router.post("/api/workflows")
|
|
77
|
+
async def save_workflow(
|
|
78
|
+
request: WorkflowSaveRequest,
|
|
79
|
+
database: Database = Depends(lambda: container.database())
|
|
80
|
+
):
|
|
81
|
+
"""Save workflow (Node.js compatible)."""
|
|
82
|
+
try:
|
|
83
|
+
workflow_id = str(int(datetime.now().timestamp() * 1000)) # Node.js style ID
|
|
84
|
+
success = await database.save_workflow(
|
|
85
|
+
workflow_id=workflow_id,
|
|
86
|
+
name=request.name,
|
|
87
|
+
data=request.data
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if success:
|
|
91
|
+
return {
|
|
92
|
+
"success": True,
|
|
93
|
+
"id": workflow_id,
|
|
94
|
+
"message": "Workflow saved successfully"
|
|
95
|
+
}
|
|
96
|
+
else:
|
|
97
|
+
raise HTTPException(status_code=500, detail="Failed to save workflow")
|
|
98
|
+
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.error("Failed to save workflow", error=str(e))
|
|
101
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@router.get("/api/workflows/{workflow_id}")
|
|
105
|
+
async def get_workflow(
|
|
106
|
+
workflow_id: str,
|
|
107
|
+
database: Database = Depends(lambda: container.database())
|
|
108
|
+
):
|
|
109
|
+
"""Get workflow by ID (Node.js compatible)."""
|
|
110
|
+
try:
|
|
111
|
+
workflow = await database.get_workflow(workflow_id)
|
|
112
|
+
|
|
113
|
+
if workflow:
|
|
114
|
+
return {
|
|
115
|
+
"id": workflow.id,
|
|
116
|
+
"name": workflow.name,
|
|
117
|
+
"data": workflow.data
|
|
118
|
+
}
|
|
119
|
+
else:
|
|
120
|
+
return {
|
|
121
|
+
"id": workflow_id,
|
|
122
|
+
"name": "Sample Workflow",
|
|
123
|
+
"data": {"nodes": [], "edges": []}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.error("Failed to get workflow", workflow_id=workflow_id, error=str(e))
|
|
128
|
+
return {
|
|
129
|
+
"id": workflow_id,
|
|
130
|
+
"name": "Sample Workflow",
|
|
131
|
+
"data": {"nodes": [], "edges": []}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# Main execution endpoint - Node.js compatible
|
|
136
|
+
@router.post("/api/nodes/execute")
|
|
137
|
+
async def execute_node(
|
|
138
|
+
request: NodeExecuteRequest,
|
|
139
|
+
workflow_service: WorkflowService = Depends(lambda: container.workflow_service())
|
|
140
|
+
):
|
|
141
|
+
"""Execute a single node (Node.js compatible)."""
|
|
142
|
+
try:
|
|
143
|
+
if not request.nodeId or not request.nodeType:
|
|
144
|
+
raise HTTPException(status_code=400, detail="nodeId and nodeType are required")
|
|
145
|
+
|
|
146
|
+
execution_id = str(uuid4())
|
|
147
|
+
logger.info("Executing node", node_id=request.nodeId, execution_id=execution_id)
|
|
148
|
+
|
|
149
|
+
# Execute the node using workflow service
|
|
150
|
+
result = await workflow_service.execute_node(
|
|
151
|
+
node_id=request.nodeId,
|
|
152
|
+
node_type=request.nodeType,
|
|
153
|
+
parameters=request.parameters or {},
|
|
154
|
+
nodes=request.nodes or [],
|
|
155
|
+
edges=request.edges or [],
|
|
156
|
+
session_id="default"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Transform result to match Node.js format
|
|
160
|
+
if result.get("success"):
|
|
161
|
+
return {
|
|
162
|
+
"success": True,
|
|
163
|
+
"executionId": execution_id,
|
|
164
|
+
"nodeId": request.nodeId,
|
|
165
|
+
"nodeType": request.nodeType,
|
|
166
|
+
"result": result.get("result", {}),
|
|
167
|
+
"executionTime": result.get("execution_time", 0),
|
|
168
|
+
"timestamp": result.get("timestamp", datetime.now().isoformat())
|
|
169
|
+
}
|
|
170
|
+
else:
|
|
171
|
+
return {
|
|
172
|
+
"success": False,
|
|
173
|
+
"executionId": execution_id,
|
|
174
|
+
"nodeId": request.nodeId,
|
|
175
|
+
"nodeType": request.nodeType,
|
|
176
|
+
"error": result.get("error", "Unknown error"),
|
|
177
|
+
"executionTime": result.get("execution_time", 0),
|
|
178
|
+
"timestamp": result.get("timestamp", datetime.now().isoformat())
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.error("Node execution error", node_id=request.nodeId, error=str(e))
|
|
183
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# Execution status and output endpoints
|
|
187
|
+
@router.get("/api/executions/{execution_id}")
|
|
188
|
+
async def get_execution_status(execution_id: str):
|
|
189
|
+
"""Get execution status (Node.js compatible)."""
|
|
190
|
+
# For simplicity, return success status
|
|
191
|
+
# In a full implementation, you'd track execution status
|
|
192
|
+
return {
|
|
193
|
+
"success": True,
|
|
194
|
+
"executionId": execution_id,
|
|
195
|
+
"status": "completed",
|
|
196
|
+
"timestamp": datetime.now().isoformat()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@router.get("/api/nodes/{node_id}/output")
|
|
201
|
+
async def get_node_output(
|
|
202
|
+
node_id: str,
|
|
203
|
+
workflow_service: WorkflowService = Depends(lambda: container.workflow_service())
|
|
204
|
+
):
|
|
205
|
+
"""Get node output data (Node.js compatible)."""
|
|
206
|
+
try:
|
|
207
|
+
result = await workflow_service.get_workflow_node_output(node_id)
|
|
208
|
+
|
|
209
|
+
if result.get("success"):
|
|
210
|
+
return {
|
|
211
|
+
"success": True,
|
|
212
|
+
"nodeId": node_id,
|
|
213
|
+
"output": result.get("data", {})
|
|
214
|
+
}
|
|
215
|
+
else:
|
|
216
|
+
return {
|
|
217
|
+
"success": False,
|
|
218
|
+
"nodeId": node_id,
|
|
219
|
+
"error": result.get("error", "Node output not found")
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
logger.error("Get node output error", node_id=node_id, error=str(e))
|
|
224
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@router.post("/api/executions/clear")
|
|
228
|
+
async def clear_execution_cache():
|
|
229
|
+
"""Clear execution cache (Node.js compatible)."""
|
|
230
|
+
try:
|
|
231
|
+
# Clear workflow service cache if implemented
|
|
232
|
+
return {
|
|
233
|
+
"success": True,
|
|
234
|
+
"message": "Execution cache cleared"
|
|
235
|
+
}
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error("Clear cache error", error=str(e))
|
|
238
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# Legacy endpoint for backwards compatibility
|
|
242
|
+
@router.post("/api/execute/{node_id}")
|
|
243
|
+
async def legacy_execute_node(
|
|
244
|
+
node_id: str,
|
|
245
|
+
request: Dict[str, Any],
|
|
246
|
+
workflow_service: WorkflowService = Depends(lambda: container.workflow_service())
|
|
247
|
+
):
|
|
248
|
+
"""Legacy execution endpoint (Node.js compatible)."""
|
|
249
|
+
try:
|
|
250
|
+
node_type = request.get("nodeType")
|
|
251
|
+
if not node_type:
|
|
252
|
+
raise HTTPException(status_code=400, detail="nodeType is required")
|
|
253
|
+
|
|
254
|
+
execution_id = str(uuid4())
|
|
255
|
+
|
|
256
|
+
result = await workflow_service.execute_node(
|
|
257
|
+
node_id=node_id,
|
|
258
|
+
node_type=node_type,
|
|
259
|
+
parameters=request.get("parameters", {}),
|
|
260
|
+
nodes=request.get("nodes", []),
|
|
261
|
+
edges=request.get("edges", []),
|
|
262
|
+
session_id="default"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Return in Node.js format
|
|
266
|
+
if result.get("success"):
|
|
267
|
+
return {
|
|
268
|
+
"success": True,
|
|
269
|
+
"executionId": execution_id,
|
|
270
|
+
"nodeId": node_id,
|
|
271
|
+
"nodeType": node_type,
|
|
272
|
+
"result": result.get("result", {}),
|
|
273
|
+
"executionTime": result.get("execution_time", 0),
|
|
274
|
+
"timestamp": result.get("timestamp", datetime.now().isoformat())
|
|
275
|
+
}
|
|
276
|
+
else:
|
|
277
|
+
return {
|
|
278
|
+
"success": False,
|
|
279
|
+
"executionId": execution_id,
|
|
280
|
+
"nodeId": node_id,
|
|
281
|
+
"nodeType": node_type,
|
|
282
|
+
"error": result.get("error", "Unknown error"),
|
|
283
|
+
"executionTime": result.get("execution_time", 0),
|
|
284
|
+
"timestamp": result.get("timestamp", datetime.now().isoformat())
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
except Exception as e:
|
|
288
|
+
logger.error("Legacy execution error", node_id=node_id, error=str(e))
|
|
289
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Dynamic webhook endpoint router for incoming HTTP requests.
|
|
2
|
+
|
|
3
|
+
Works like WhatsApp trigger - uses broadcaster.send_custom_event() to dispatch
|
|
4
|
+
to event_waiter which resolves waiting trigger nodes.
|
|
5
|
+
"""
|
|
6
|
+
from fastapi import APIRouter, Request, HTTPException, Response
|
|
7
|
+
from fastapi.responses import JSONResponse
|
|
8
|
+
from typing import Dict, Any
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
from services.status_broadcaster import get_status_broadcaster
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
router = APIRouter(prefix="/webhook", tags=["webhook"])
|
|
16
|
+
|
|
17
|
+
# Pending responses: path -> asyncio.Future (for responseNode mode)
|
|
18
|
+
_pending_responses: Dict[str, asyncio.Future] = {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def resolve_webhook_response(node_id: str, response_data: dict):
|
|
22
|
+
"""Resolve a pending webhook response.
|
|
23
|
+
|
|
24
|
+
Called by webhookResponse node execution to send response back to caller.
|
|
25
|
+
Uses path from response_data to find the pending Future.
|
|
26
|
+
"""
|
|
27
|
+
# Find pending response by path (stored when we started waiting)
|
|
28
|
+
for path, future in list(_pending_responses.items()):
|
|
29
|
+
if not future.done():
|
|
30
|
+
future.set_result(response_data)
|
|
31
|
+
logger.info(f"[Webhook] Response resolved for path: {path}")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
logger.warning(f"[Webhook] No pending response found for node: {node_id}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
|
|
38
|
+
async def handle_webhook(path: str, request: Request):
|
|
39
|
+
"""Handle incoming webhook requests.
|
|
40
|
+
|
|
41
|
+
Dispatches webhook_received event to trigger waiting webhookTrigger nodes.
|
|
42
|
+
Similar to how WhatsApp events dispatch to whatsappReceive nodes.
|
|
43
|
+
"""
|
|
44
|
+
# Build webhook request data
|
|
45
|
+
body = await request.body()
|
|
46
|
+
json_body = None
|
|
47
|
+
content_type = request.headers.get("content-type", "")
|
|
48
|
+
|
|
49
|
+
if "application/json" in content_type and body:
|
|
50
|
+
try:
|
|
51
|
+
json_body = await request.json()
|
|
52
|
+
except:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
webhook_data = {
|
|
56
|
+
"method": request.method,
|
|
57
|
+
"path": path,
|
|
58
|
+
"headers": dict(request.headers),
|
|
59
|
+
"query": dict(request.query_params),
|
|
60
|
+
"body": body.decode('utf-8') if isinstance(body, bytes) else (body if body else ""),
|
|
61
|
+
"json": json_body
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
logger.info(f"[Webhook] Received: {request.method} /webhook/{path}")
|
|
65
|
+
|
|
66
|
+
# Dispatch event using broadcaster (same pattern as WhatsApp)
|
|
67
|
+
broadcaster = get_status_broadcaster()
|
|
68
|
+
await broadcaster.send_custom_event("webhook_received", webhook_data)
|
|
69
|
+
|
|
70
|
+
# For now, always return immediate response
|
|
71
|
+
# TODO: Support responseNode mode by storing Future and waiting
|
|
72
|
+
return JSONResponse(
|
|
73
|
+
content={
|
|
74
|
+
"status": "received",
|
|
75
|
+
"path": path,
|
|
76
|
+
"message": "Webhook received and dispatched to workflow"
|
|
77
|
+
},
|
|
78
|
+
status_code=200
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@router.get("/")
|
|
83
|
+
async def list_info():
|
|
84
|
+
"""Get webhook endpoint info."""
|
|
85
|
+
return {
|
|
86
|
+
"endpoint": "/webhook/{path}",
|
|
87
|
+
"description": "Send HTTP requests to trigger webhookTrigger nodes",
|
|
88
|
+
"usage": "Deploy a workflow with webhookTrigger node, then send requests to /webhook/{path}",
|
|
89
|
+
"example": "POST /webhook/my-webhook with JSON body"
|
|
90
|
+
}
|