machinaos 0.0.76 → 0.0.78
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/README.md +143 -107
- package/client/dist/assets/ActionBar-Du2MSFSz.js +1 -0
- package/client/dist/assets/ApiKeyInput-k2LBmBjb.js +1 -0
- package/client/dist/assets/ApiKeyPanel-C_bV9U0X.js +1 -0
- package/client/dist/assets/ApiUsageSection-CmVfwZzL.js +1 -0
- package/client/dist/assets/EmailPanel-CeKIMGu-.js +1 -0
- package/client/dist/assets/OAuthPanel-KA3t3Q2K.js +1 -0
- package/client/dist/assets/QrPairingPanel-NgNpJNuk.js +1 -0
- package/client/dist/assets/RateLimitSection-Du5YNVIA.js +1 -0
- package/client/dist/assets/StatusCard-DNLyayXc.js +1 -0
- package/client/dist/assets/index-DQ0nwhec.js +257 -0
- package/client/dist/assets/index-DxmbVskS.css +1 -0
- package/client/dist/assets/vendor-flow-CZmBvHRo.js +1 -0
- package/client/dist/assets/vendor-icons-CVrPjN2Q.js +22 -0
- package/client/dist/assets/vendor-markdown-CRou3yQ5.js +62 -0
- package/client/dist/assets/vendor-misc-C4VxKHs5.js +1 -0
- package/client/dist/assets/vendor-query-SzWcOU0G.js +1 -0
- package/client/dist/assets/vendor-radix-Dnos29jG.js +56 -0
- package/client/dist/assets/vendor-react-DvWIbVx0.js +1 -0
- package/client/dist/index.html +37 -3
- package/client/index.html +28 -1
- package/client/package.json +44 -40
- package/client/src/App.tsx +2 -0
- package/client/src/Dashboard.tsx +157 -45
- package/client/src/ParameterPanel.tsx +3 -5
- package/client/src/adapters/nodeSpecToDescription.ts +1 -0
- package/client/src/assets/icons/NodeIcon.tsx +32 -0
- package/client/src/assets/icons/index.ts +4 -0
- package/client/src/assets/icons/stripe.svg +1 -0
- package/client/src/assets/icons/themedGlyphs.ts +404 -0
- package/client/src/components/AIAgentNode.tsx +77 -53
- package/client/src/components/GenericNode.tsx +34 -52
- package/client/src/components/OutputPanel.tsx +64 -147
- package/client/src/components/ParameterRenderer.tsx +5 -3
- package/client/src/components/SkillEditorModal.tsx +9 -18
- package/client/src/components/SquareNode.tsx +97 -115
- package/client/src/components/StartNode.tsx +32 -42
- package/client/src/components/SvgFilterDefs.tsx +54 -0
- package/client/src/components/TeamMonitorNode.tsx +12 -14
- package/client/src/components/ToolkitNode.tsx +35 -60
- package/client/src/components/TriggerNode.tsx +43 -77
- package/client/src/components/__tests__/CredentialsModal.test.tsx +49 -45
- package/client/src/components/credentials/CredentialsModal.tsx +98 -30
- package/client/src/components/credentials/CredentialsPalette.tsx +73 -5
- package/client/src/components/credentials/catalogueAdapter.ts +17 -1
- package/client/src/components/credentials/panels/ApiKeyPanel.tsx +102 -37
- package/client/src/components/credentials/panels/EmailPanel.tsx +7 -19
- package/client/src/components/credentials/panels/OAuthPanel.tsx +5 -1
- package/client/src/components/credentials/panels/QrPairingPanel.tsx +1 -3
- package/client/src/components/credentials/primitives/ActionBar.tsx +7 -11
- package/client/src/components/credentials/primitives/OAuthConnect.tsx +19 -28
- package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +24 -3
- package/client/src/components/credentials/types.ts +12 -2
- package/client/src/components/credentials/useCredentialPanel.ts +43 -19
- package/client/src/components/icons/AIProviderIcons.tsx +16 -0
- package/client/src/components/onboarding/OnboardingWizard.tsx +23 -63
- package/client/src/components/onboarding/nodeRoleClasses.ts +23 -0
- package/client/src/components/onboarding/steps/CanvasStep.tsx +15 -21
- package/client/src/components/onboarding/steps/ConceptsStep.tsx +2 -11
- package/client/src/components/onboarding/steps/GetStartedStep.tsx +2 -10
- package/client/src/components/parameterPanel/InputSection.tsx +9 -7
- package/client/src/components/parameterPanel/MasterSkillEditor.tsx +84 -198
- package/client/src/components/parameterPanel/MiddleSection.tsx +57 -80
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +31 -25
- package/client/src/components/parameterPanel/__tests__/InputSection.test.tsx +7 -2
- package/client/src/components/ui/AIResultModal.tsx +1 -1
- package/client/src/components/ui/CollapsibleSection.tsx +9 -5
- package/client/src/components/ui/CommandPalette.tsx +147 -0
- package/client/src/components/ui/CommandPaletteHost.tsx +189 -0
- package/client/src/components/ui/ComponentItem.tsx +13 -7
- package/client/src/components/ui/ComponentPalette.tsx +24 -13
- package/client/src/components/ui/ConsolePanel.tsx +19 -11
- package/client/src/components/ui/DropCap.tsx +28 -0
- package/client/src/components/ui/EditableNodeLabel.tsx +10 -2
- package/client/src/components/ui/InputNodesPanel.tsx +1 -1
- package/client/src/components/ui/Modal.tsx +38 -6
- package/client/src/components/ui/OutputDisplayPanel.tsx +1 -1
- package/client/src/components/ui/SettingsPanel.tsx +42 -13
- package/client/src/components/ui/StatusBar.tsx +108 -0
- package/client/src/components/ui/ThemeSwitcher.tsx +109 -0
- package/client/src/components/ui/TopToolbar.tsx +42 -25
- package/client/src/components/ui/WorkflowSidebar.tsx +32 -16
- package/client/src/components/ui/action-button.tsx +40 -15
- package/client/src/components/ui/button.tsx +24 -1
- package/client/src/components/ui/dropdown-menu.tsx +24 -2
- package/client/src/components/ui/input.tsx +19 -2
- package/client/src/components/ui/select.tsx +15 -0
- package/client/src/components/ui/textarea.tsx +15 -2
- package/client/src/contexts/AuthContext.tsx +148 -109
- package/client/src/contexts/ThemeContext.tsx +93 -17
- package/client/src/contexts/WebSocketContext.tsx +373 -206
- package/client/src/contexts/__tests__/AuthContext.test.tsx +221 -0
- package/client/src/hooks/__tests__/useDragVariable.test.ts +7 -1
- package/client/src/hooks/__tests__/useWorkflowOpsListener.test.ts +142 -0
- package/client/src/hooks/useAppTheme.ts +209 -7
- package/client/src/hooks/useAutoSkillEdges.ts +7 -2
- package/client/src/hooks/useCatalogueQuery.ts +67 -1
- package/client/src/hooks/useDragVariable.ts +1 -1
- package/client/src/hooks/useNodeAllowlist.ts +115 -8
- package/client/src/hooks/useOnboarding.ts +20 -8
- package/client/src/hooks/useParameterPanel.ts +2 -1
- package/client/src/hooks/useReactFlowNodes.ts +2 -1
- package/client/src/hooks/useSound.ts +185 -0
- package/client/src/hooks/useWorkflowManagement.ts +6 -8
- package/client/src/hooks/useWorkflowOpsListener.ts +90 -0
- package/client/src/index.css +65 -3
- package/client/src/lib/__tests__/connectionConfig.test.ts +91 -0
- package/client/src/lib/aiModelProviders.ts +8 -0
- package/client/src/lib/connectionConfig.ts +107 -0
- package/client/src/lib/queryPersist.ts +13 -5
- package/client/src/lib/sound.ts +393 -0
- package/client/src/main.tsx +20 -0
- package/client/src/store/useAppStore.ts +26 -0
- package/client/src/styles/canvasAnimations.ts +37 -36
- package/client/src/styles/theme.ts +36 -20
- package/client/src/test/setup.ts +1 -0
- package/client/src/themes/atomic.css +253 -0
- package/client/src/themes/base.css +373 -0
- package/client/src/themes/cyber.css +890 -0
- package/client/src/themes/dark.css +70 -0
- package/client/src/themes/edo.css +246 -0
- package/client/src/themes/greek.css +293 -0
- package/client/src/themes/light.css +78 -0
- package/client/src/themes/plague.css +253 -0
- package/client/src/themes/renaissance.css +727 -0
- package/client/src/themes/rot.css +249 -0
- package/client/src/themes/steampunk.css +272 -0
- package/client/src/themes/surveillance.css +289 -0
- package/client/src/themes/wasteland.css +250 -0
- package/client/src/types/INodeProperties.ts +5 -0
- package/client/src/types/NodeTypes.ts +11 -1
- package/client/src/types/__tests__/cloudEvents.test.ts +99 -0
- package/client/src/types/cloudEvents.ts +78 -0
- package/client/src/vite-env.d.ts +7 -0
- package/client/tsconfig.json +1 -1
- package/client/vite.config.js +62 -2
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/machina/commands/build.py +51 -7
- package/machina/pyproject.toml +4 -0
- package/machina/supervisor.py +12 -2
- package/machina/tree.py +71 -21
- package/package.json +4 -4
- package/scripts/install.js +16 -1
- package/server/config/ai_cli_providers.json +54 -0
- package/server/config/credential_providers.json +109 -2
- package/server/config/llm_defaults.json +24 -0
- package/server/config/model_registry.json +338 -499
- package/server/config/node_allowlist.json +16 -1
- package/server/config/pricing.json +8 -0
- package/server/constants.py +38 -15
- package/server/core/container.py +2 -2
- package/server/core/credentials_database.py +35 -2
- package/server/core/logging.py +4 -3
- package/server/main.py +99 -13
- package/server/models/node_metadata.py +1 -0
- package/server/nodejs/package.json +8 -6
- package/server/nodejs/src/index.ts +22 -5
- package/server/nodes/README.md +31 -4
- package/server/nodes/agent/_inline.py +2 -0
- package/server/nodes/agent/_specialized.py +6 -3
- package/server/nodes/agent/ai_agent.py +13 -3
- package/server/nodes/agent/chat_agent.py +6 -3
- package/server/nodes/agent/claude_code_agent.py +287 -75
- package/server/nodes/agent/codex_agent.py +239 -0
- package/server/nodes/agent/deep_agent.py +3 -3
- package/server/nodes/agent/rlm_agent.py +3 -3
- package/server/nodes/android/__init__.py +31 -1
- package/server/nodes/android/_base.py +9 -5
- package/server/{services/android_service.py → nodes/android/_dispatcher.py} +2 -2
- package/server/nodes/android/_handlers.py +154 -0
- package/server/nodes/android/_option_loaders.py +44 -0
- package/server/nodes/android/_refresh.py +127 -0
- package/server/{services/android → nodes/android/_relay}/client.py +4 -4
- package/server/{routers/android.py → nodes/android/_router.py} +27 -8
- package/server/nodes/browser/browser.py +2 -2
- package/server/nodes/code/_base.py +6 -2
- package/server/nodes/code/_claude_code.py +134 -0
- package/server/nodes/document/embedding_generator.py +3 -3
- package/server/nodes/document/http_scraper.py +3 -3
- package/server/nodes/document/vector_store.py +5 -5
- package/server/nodes/email/__init__.py +11 -1
- package/server/nodes/email/_filters.py +21 -0
- package/server/{services/himalaya_service.py → nodes/email/_himalaya.py} +6 -10
- package/server/{services/email_service.py → nodes/email/_service.py} +9 -13
- package/server/nodes/email/email_read.py +1 -1
- package/server/nodes/email/email_receive.py +54 -5
- package/server/nodes/email/email_send.py +1 -1
- package/server/nodes/filesystem/shell.py +24 -1
- package/server/nodes/google/__init__.py +55 -1
- package/server/{services/handlers/google_auth.py → nodes/google/_auth_helper.py} +8 -5
- package/server/nodes/google/_base.py +2 -2
- package/server/nodes/google/_credentials.py +5 -5
- package/server/nodes/google/_filters.py +25 -0
- package/server/nodes/google/_handlers.py +57 -0
- package/server/{services/google_oauth.py → nodes/google/_oauth.py} +195 -162
- package/server/nodes/google/_option_loaders.py +107 -0
- package/server/nodes/google/_refresh.py +66 -0
- package/server/nodes/google/_router.py +131 -0
- package/server/nodes/google/gmail_receive.py +41 -4
- package/server/nodes/groups.py +1 -0
- package/server/nodes/location/_credentials.py +45 -1
- package/server/{services/maps.py → nodes/location/_service.py} +18 -3
- package/server/nodes/location/gmaps_create.py +4 -4
- package/server/nodes/location/gmaps_locations.py +4 -4
- package/server/nodes/location/gmaps_nearby_places.py +4 -4
- package/server/nodes/model/_base.py +8 -3
- package/server/nodes/model/_credentials.py +96 -8
- package/server/nodes/model/_local_validator.py +345 -0
- package/server/nodes/model/lmstudio_chat_model.py +23 -0
- package/server/nodes/model/ollama_chat_model.py +25 -0
- package/server/nodes/proxy/_usage.py +2 -2
- package/server/nodes/proxy/proxy_config.py +14 -14
- package/server/nodes/proxy/proxy_request.py +4 -4
- package/server/nodes/scraper/_credentials.py +29 -1
- package/server/nodes/scraper/apify_actor.py +9 -9
- package/server/nodes/scraper/crawlee_scraper.py +5 -5
- package/server/nodes/search/brave_search.py +4 -0
- package/server/nodes/search/perplexity_search.py +9 -0
- package/server/nodes/search/serper_search.py +3 -0
- package/server/nodes/skill/simple_memory.py +12 -0
- package/server/nodes/social/_base.py +2 -2
- package/server/nodes/stripe/__init__.py +46 -0
- package/server/nodes/stripe/_credentials.py +33 -0
- package/server/nodes/stripe/_handlers.py +270 -0
- package/server/nodes/stripe/_install.py +127 -0
- package/server/nodes/stripe/_source.py +174 -0
- package/server/nodes/stripe/stripe_action.py +81 -0
- package/server/nodes/stripe/stripe_receive.py +92 -0
- package/server/nodes/telegram/_credentials.py +52 -1
- package/server/nodes/telegram/_handlers.py +19 -18
- package/server/nodes/telegram/_service.py +134 -32
- package/server/nodes/telegram/telegram_send.py +5 -6
- package/server/nodes/text/file_handler.py +2 -2
- package/server/nodes/text/text_generator.py +2 -2
- package/server/nodes/tool/agent_builder.py +630 -0
- package/server/nodes/tool/task_manager.py +144 -2
- package/server/nodes/twitter/__init__.py +38 -1
- package/server/nodes/twitter/_base.py +7 -7
- package/server/nodes/twitter/_credentials.py +1 -1
- package/server/nodes/twitter/_filters.py +37 -0
- package/server/nodes/twitter/_handlers.py +77 -0
- package/server/nodes/twitter/_oauth.py +124 -0
- package/server/nodes/twitter/_refresh.py +78 -0
- package/server/nodes/twitter/_router.py +29 -0
- package/server/nodes/twitter/twitter_receive.py +4 -0
- package/server/nodes/visuals.json +64 -19
- package/server/nodes/whatsapp/__init__.py +45 -5
- package/server/nodes/whatsapp/_base.py +3 -3
- package/server/nodes/whatsapp/_filters.py +137 -0
- package/server/nodes/whatsapp/_handlers.py +167 -0
- package/server/nodes/whatsapp/_option_loaders.py +68 -0
- package/server/nodes/whatsapp/_refresh.py +62 -0
- package/server/nodes/whatsapp/_runtime.py +1 -1
- package/server/pyproject.toml +29 -7
- package/server/routers/schemas.py +2 -2
- package/server/routers/webhook.py +26 -9
- package/server/routers/websocket.py +149 -810
- package/server/services/ai.py +89 -8
- package/server/services/auth.py +220 -43
- package/server/services/claude_oauth.py +126 -100
- package/server/services/cli_agent/__init__.py +78 -0
- package/server/services/cli_agent/_handlers.py +237 -0
- package/server/services/cli_agent/config.py +112 -0
- package/server/services/cli_agent/factory.py +48 -0
- package/server/services/cli_agent/lockfile.py +141 -0
- package/server/services/cli_agent/mcp_server.py +482 -0
- package/server/services/cli_agent/protocol.py +173 -0
- package/server/services/cli_agent/providers/__init__.py +9 -0
- package/server/services/cli_agent/providers/anthropic_claude.py +419 -0
- package/server/services/cli_agent/providers/google_gemini.py +80 -0
- package/server/services/cli_agent/providers/openai_codex.py +310 -0
- package/server/services/cli_agent/service.py +607 -0
- package/server/services/cli_agent/session.py +618 -0
- package/server/services/cli_agent/types.py +227 -0
- package/server/services/cli_agent/workflow_tools.py +233 -0
- package/server/services/credential_registry.py +26 -1
- package/server/services/deployment/manager.py +26 -145
- package/server/services/deployment/poll_registry.py +59 -0
- package/server/services/event_waiter.py +76 -246
- package/server/services/events/__init__.py +54 -0
- package/server/services/events/cli.py +78 -0
- package/server/services/events/daemon.py +163 -0
- package/server/services/events/envelope.py +281 -0
- package/server/services/events/lifecycle.py +99 -0
- package/server/services/events/oauth_lifecycle.py +534 -0
- package/server/services/events/polling.py +60 -0
- package/server/services/events/push.py +36 -0
- package/server/services/events/source.py +63 -0
- package/server/services/events/triggers.py +118 -0
- package/server/services/events/verifiers/__init__.py +25 -0
- package/server/services/events/verifiers/base.py +28 -0
- package/server/services/events/verifiers/github.py +25 -0
- package/server/services/events/verifiers/hmac_basic.py +32 -0
- package/server/services/events/verifiers/standard_webhooks.py +47 -0
- package/server/services/events/verifiers/stripe.py +42 -0
- package/server/services/events/webhook.py +105 -0
- package/server/services/handlers/tools.py +28 -186
- package/server/services/llm/config.py +7 -0
- package/server/services/llm/factory.py +8 -2
- package/server/services/memory/__init__.py +52 -0
- package/server/services/memory/jsonl.py +80 -0
- package/server/services/memory/markdown.py +65 -0
- package/server/services/memory/state.py +112 -0
- package/server/services/memory/vector_store.py +40 -0
- package/server/services/model_registry.py +76 -0
- package/server/services/node_allowlist.py +71 -15
- package/server/services/node_executor.py +2 -2
- package/server/services/node_output_schemas.py +21 -10
- package/server/services/node_spec.py +1 -1
- package/server/services/oauth_utils.py +1 -1
- package/server/services/plugin/__init__.py +2 -0
- package/server/services/plugin/base.py +44 -2
- package/server/services/plugin/credential.py +288 -1
- package/server/services/plugin/deps.py +105 -0
- package/server/services/plugin/edge_walker.py +12 -4
- package/server/services/plugin/oauth.py +381 -0
- package/server/services/plugin/polling.py +247 -0
- package/server/services/plugin/registry.py +145 -0
- package/server/services/plugin/singleton.py +65 -0
- package/server/services/plugin/ws.py +81 -0
- package/server/services/process_service.py +31 -2
- package/server/services/status_broadcaster.py +155 -238
- package/server/services/temporal/workflow.py +7 -7
- package/server/services/workflow.py +21 -3
- package/server/services/ws_handler_registry.py +111 -28
- package/server/skills/GUIDE.md +16 -1
- package/server/skills/assistant/agent-builder-skill/SKILL.md +166 -0
- package/server/skills/payments_agent/stripe-skill/SKILL.md +306 -0
- package/server/tests/credentials/test_auth_service.py +16 -9
- package/server/tests/credentials/test_credential_broadcasts.py +219 -0
- package/server/tests/credentials/test_google_oauth.py +6 -6
- package/server/tests/credentials/test_oauth_utils.py +1 -1
- package/server/tests/credentials/test_twitter_oauth.py +2 -2
- package/server/tests/credentials/test_websocket_handlers.py +44 -20
- package/server/tests/llm/test_factory.py +1 -0
- package/server/tests/llm/test_wiring.py +5 -1
- package/server/tests/nodes/_compat.py +24 -24
- package/server/tests/nodes/test_agent_builder.py +439 -0
- package/server/tests/nodes/test_ai_tools.py +18 -14
- package/server/tests/nodes/test_code_fs_process.py +17 -8
- package/server/tests/nodes/test_email.py +10 -9
- package/server/tests/nodes/test_google_workspace.py +2 -2
- package/server/tests/nodes/test_specialized_agents.py +100 -53
- package/server/tests/nodes/test_stripe_plugin.py +293 -0
- package/server/tests/nodes/test_telegram_social.py +4 -4
- package/server/tests/nodes/test_twitter.py +1 -1
- package/server/tests/nodes/test_web_automation.py +2 -2
- package/server/tests/nodes/test_whatsapp.py +9 -9
- package/server/tests/services/cli_agent/__init__.py +0 -0
- package/server/tests/services/cli_agent/test_mcp_server.py +432 -0
- package/server/tests/services/cli_agent/test_providers.py +358 -0
- package/server/tests/services/cli_agent/test_service.py +298 -0
- package/server/tests/services/memory/__init__.py +0 -0
- package/server/tests/services/memory/test_jsonl.py +188 -0
- package/server/tests/services/test_events.py +333 -0
- package/server/tests/test_node_spec.py +56 -16
- package/server/tests/test_plugin_helpers.py +116 -0
- package/server/tests/test_plugin_self_containment.py +486 -0
- package/server/tests/test_status_broadcasts.py +425 -0
- package/workflows/{AI Assistant_workflow-1777421105154-0m4snkzjf.json → AI Assistant_workflow-1778504793388-ou1m1tz2x.json } +70 -266
- package/workflows/{AI Employee_workflow-1777720598005-u4cm858dv.json → AI Employee_example_workflow-1777720598005-u4cm858dv.json } +112 -112
- package/workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json +709 -0
- package/client/dist/assets/ActionBar-vzPpSR77.js +0 -1
- package/client/dist/assets/ApiKeyInput-Ds7AKFe8.js +0 -1
- package/client/dist/assets/ApiKeyPanel-gfblELep.js +0 -1
- package/client/dist/assets/ApiUsageSection-BMNWTe2r.js +0 -1
- package/client/dist/assets/EmailPanel-B1Om64p5.js +0 -1
- package/client/dist/assets/OAuthPanel-CXyQYGBz.js +0 -1
- package/client/dist/assets/QrPairingPanel-BgNuI1we.js +0 -1
- package/client/dist/assets/RateLimitSection-YYK8sx1T.js +0 -1
- package/client/dist/assets/StatusCard-DuYA5hJR.js +0 -1
- package/client/dist/assets/index-D9tZfgvi.js +0 -363
- package/client/dist/assets/index-al7snTkG.css +0 -1
- package/client/src/components/credentials/providers.tsx +0 -177
- package/server/routers/google.py +0 -277
- package/server/routers/maps.py +0 -142
- package/server/routers/twitter.py +0 -365
- package/server/services/claude_code_service.py +0 -106
- package/server/services/memory.py +0 -159
- package/server/services/node_option_loaders/__init__.py +0 -77
- package/server/services/node_option_loaders/android_loaders.py +0 -55
- package/server/services/node_option_loaders/google_loaders.py +0 -97
- package/server/services/node_option_loaders/whatsapp_loaders.py +0 -69
- package/server/services/twitter_oauth.py +0 -411
- package/server/services/websocket_client.py +0 -29
- /package/server/{services/android → nodes/android/_relay}/__init__.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/broadcaster.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/manager.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/protocol.py +0 -0
- /package/server/{services/browser_service.py → nodes/browser/_service.py} +0 -0
- /package/server/{services/whatsapp_service.py → nodes/whatsapp/_service.py} +0 -0
- /package/server/skills/{task_agent → assistant}/write-todos-skill/SKILL.md +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { memo, useState, useEffect, useMemo } from 'react';
|
|
2
2
|
import { nodePropsEqual } from './nodeMemoEquality';
|
|
3
3
|
import { Handle, Position, NodeProps } from 'reactflow';
|
|
4
|
-
import { NodeData } from '../types/NodeTypes';
|
|
4
|
+
import { NodeData, NodeStyle } from '../types/NodeTypes';
|
|
5
5
|
import { useAppStore } from '../store/useAppStore';
|
|
6
6
|
import AIAgentExecutionService from '../services/execution/aiAgentExecutionService';
|
|
7
7
|
import { useAppTheme } from '../hooks/useAppTheme';
|
|
@@ -9,6 +9,7 @@ import { useNodeStatus } from '../contexts/WebSocketContext';
|
|
|
9
9
|
import { dracula } from '../styles/theme';
|
|
10
10
|
import { useNodeSpec } from '../lib/nodeSpec';
|
|
11
11
|
import { NodeIcon } from '../assets/icons';
|
|
12
|
+
import { Badge } from '@/components/ui/badge';
|
|
12
13
|
|
|
13
14
|
// LangGraph phase icons and labels. Colors reference the dracula token
|
|
14
15
|
// constants so a future palette change in tokens.css propagates without
|
|
@@ -44,7 +45,7 @@ const REACT_POSITION: Record<SpecHandle['position'], Position> = {
|
|
|
44
45
|
|
|
45
46
|
const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
|
|
46
47
|
const theme = useAppTheme();
|
|
47
|
-
const
|
|
48
|
+
const setSelectedNode = useAppStore((s) => s.setSelectedNode);
|
|
48
49
|
const [_configValid, setConfigValid] = useState(true);
|
|
49
50
|
const [_configErrors, setConfigErrors] = useState<string[]>([]);
|
|
50
51
|
|
|
@@ -72,6 +73,15 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
72
73
|
const isExecuting = nodeStatus?.status === 'executing';
|
|
73
74
|
const currentPhase = nodeStatus?.data?.phase as string | undefined;
|
|
74
75
|
const phaseConfig = currentPhase ? PHASE_CONFIG[currentPhase] : null;
|
|
76
|
+
// Live LangGraph supervised-loop counter. Backed by the
|
|
77
|
+
// `agent_progress` CloudEvents broadcast (services/ai.py emits one
|
|
78
|
+
// per astream snapshot). `iteration` advances on each agent_node call;
|
|
79
|
+
// `max_iterations` mirrors LangGraph's recursion_limit
|
|
80
|
+
// (llm_defaults.json:agent.recursion_limit).
|
|
81
|
+
const iteration = nodeStatus?.data?.iteration as number | undefined;
|
|
82
|
+
const maxIterations = nodeStatus?.data?.max_iterations as number | undefined;
|
|
83
|
+
const showIterationBadge =
|
|
84
|
+
isExecuting && typeof iteration === 'number' && typeof maxIterations === 'number';
|
|
75
85
|
|
|
76
86
|
// Validate configuration whenever data changes
|
|
77
87
|
useEffect(() => {
|
|
@@ -91,60 +101,40 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
91
101
|
setSelectedNode({ id, type, data, position: { x: 0, y: 0 } });
|
|
92
102
|
};
|
|
93
103
|
|
|
94
|
-
const getBorderColor = () => {
|
|
95
|
-
if (isExecuting) {
|
|
96
|
-
if (theme.isDarkMode && phaseConfig) return phaseConfig.color;
|
|
97
|
-
return accentColor;
|
|
98
|
-
}
|
|
99
|
-
if (selected) return theme.colors.focus;
|
|
100
|
-
return theme.colors.border;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const getBoxShadow = () => {
|
|
104
|
-
if (isExecuting) {
|
|
105
|
-
if (theme.isDarkMode && phaseConfig) {
|
|
106
|
-
return `0 0 20px ${phaseConfig.color}80, 0 0 40px ${phaseConfig.color}40`;
|
|
107
|
-
}
|
|
108
|
-
return `0 0 0 3px ${accentColor}80, 0 4px 16px ${accentColor}60`;
|
|
109
|
-
}
|
|
110
|
-
if (selected) {
|
|
111
|
-
return `0 4px 12px ${theme.colors.focusRing}, 0 0 0 1px ${theme.colors.focusRing}`;
|
|
112
|
-
}
|
|
113
|
-
return `0 2px 4px ${theme.colors.shadow}`;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
104
|
const hasRightOutputs = rightOutputs.length > 0;
|
|
117
105
|
const hasLeftLabels = hasMainInput || leftInputs.length > 0;
|
|
118
106
|
|
|
119
107
|
return (
|
|
108
|
+
// `node` + `node-agent` + `selected` co-classes are the design-handoff
|
|
109
|
+
// structural hooks for per-theme decorations (Renaissance wax seal,
|
|
110
|
+
// Cyber neon underglow + corner LED blink, etc.).
|
|
120
111
|
<div
|
|
112
|
+
className={`node node-agent ${selected ? 'selected' : ''}`}
|
|
113
|
+
data-executing={isExecuting ? '' : undefined}
|
|
121
114
|
style={{
|
|
115
|
+
'--node-color': accentColor,
|
|
122
116
|
position: 'relative',
|
|
123
117
|
padding: theme.spacing.lg,
|
|
124
118
|
paddingLeft: hasLeftLabels ? '72px' : theme.spacing.lg,
|
|
125
119
|
paddingRight: hasRightOutputs ? '72px' : theme.spacing.lg,
|
|
126
|
-
|
|
120
|
+
// Width is fixed (not just min) so long titles like
|
|
121
|
+
// "PRODUCTIVITY AGENT" wrap inside the bordered card
|
|
122
|
+
// instead of growing the node and bleeding past the border.
|
|
123
|
+
width: `${width}px`,
|
|
127
124
|
minHeight: `${height}px`,
|
|
128
|
-
borderRadius: theme.borderRadius.lg,
|
|
129
|
-
background: theme.isDarkMode
|
|
130
|
-
? `linear-gradient(135deg, ${accentColor}20 0%, ${theme.colors.backgroundAlt} 100%)`
|
|
131
|
-
: `linear-gradient(145deg, #ffffff 0%, ${accentColor}08 100%)`,
|
|
132
|
-
border: `2px solid ${getBorderColor()}`,
|
|
133
125
|
color: theme.colors.text,
|
|
134
126
|
fontSize: theme.fontSize.sm,
|
|
135
127
|
fontWeight: theme.fontWeight.medium,
|
|
136
128
|
textAlign: 'center',
|
|
137
129
|
cursor: 'pointer',
|
|
138
130
|
transition: 'all 0.3s ease',
|
|
139
|
-
boxShadow: getBoxShadow(),
|
|
140
131
|
overflow: 'visible',
|
|
141
132
|
display: 'flex',
|
|
142
133
|
flexDirection: 'column',
|
|
143
134
|
alignItems: 'center',
|
|
144
135
|
justifyContent: 'center',
|
|
145
136
|
gap: theme.spacing.sm,
|
|
146
|
-
|
|
147
|
-
}}
|
|
137
|
+
} as NodeStyle}
|
|
148
138
|
>
|
|
149
139
|
{/* Main input (left, top area) — shown when the spec declares a main input */}
|
|
150
140
|
{hasMainInput && (
|
|
@@ -159,28 +149,50 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
159
149
|
type="target"
|
|
160
150
|
position={Position.Left}
|
|
161
151
|
isConnectable={isConnectable}
|
|
152
|
+
className="node-handle in"
|
|
162
153
|
style={{
|
|
163
154
|
position: 'absolute', left: '-6px', top: '30%', transform: 'translateY(-50%)',
|
|
164
155
|
width: theme.nodeSize.handle, height: theme.nodeSize.handle,
|
|
165
|
-
|
|
166
|
-
border: `2px solid ${theme.colors.textSecondary}`, borderRadius: '50%',
|
|
156
|
+
borderRadius: '50%',
|
|
167
157
|
}}
|
|
168
158
|
title="Input"
|
|
169
159
|
/>
|
|
170
160
|
</>
|
|
171
161
|
)}
|
|
172
162
|
|
|
163
|
+
{/* Live LangGraph iteration counter (e.g. "12 / 500"). shadcn
|
|
164
|
+
Badge primitive with `outline` variant — picks up `--border`
|
|
165
|
+
and `--foreground` from whichever theme is active. The
|
|
166
|
+
per-node-type accent rides via the `--node-color` custom
|
|
167
|
+
property already set on the parent at line ~105 (one of the
|
|
168
|
+
two grandfathered inline-style channels for canvas nodes per
|
|
169
|
+
CLAUDE.md). `tabular-nums` keeps "1 / 500" -> "12 / 500" from
|
|
170
|
+
jittering as digits widen. */}
|
|
171
|
+
{showIterationBadge && (
|
|
172
|
+
<Badge
|
|
173
|
+
variant="outline"
|
|
174
|
+
title={`Iteration ${iteration} of ${maxIterations} (LangGraph recursion_limit)`}
|
|
175
|
+
className="absolute top-1 left-1 z-20 tabular-nums pointer-events-none"
|
|
176
|
+
style={{
|
|
177
|
+
color: 'var(--node-color)',
|
|
178
|
+
borderColor: 'var(--node-color)',
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
{iteration} / {maxIterations}
|
|
182
|
+
</Badge>
|
|
183
|
+
)}
|
|
184
|
+
|
|
173
185
|
{/* Parameters gear */}
|
|
174
186
|
<button
|
|
175
187
|
onClick={handleParametersClick}
|
|
188
|
+
className="node-gear"
|
|
176
189
|
style={{
|
|
177
190
|
position: 'absolute', top: theme.spacing.xs, right: theme.spacing.xs,
|
|
178
191
|
width: theme.nodeSize.paramButton, height: theme.nodeSize.paramButton,
|
|
179
|
-
|
|
180
|
-
border: `1px solid ${theme.colors.border}`, cursor: 'pointer',
|
|
192
|
+
cursor: 'pointer',
|
|
181
193
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
182
|
-
fontSize: theme.fontSize.xs,
|
|
183
|
-
fontWeight: theme.fontWeight.normal,
|
|
194
|
+
fontSize: theme.fontSize.xs,
|
|
195
|
+
fontWeight: theme.fontWeight.normal, zIndex: 20,
|
|
184
196
|
}}
|
|
185
197
|
title="Edit Parameters"
|
|
186
198
|
>⚙️</button>
|
|
@@ -191,17 +203,31 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
191
203
|
<NodeIcon icon={spec?.icon} className="h-7 w-7 text-3xl" />
|
|
192
204
|
</div>
|
|
193
205
|
|
|
194
|
-
{/* Title
|
|
195
|
-
|
|
206
|
+
{/* Title — alignSelf:stretch + width:100% forces the flex child
|
|
207
|
+
to fill the parent's content area (parent is align-items:center
|
|
208
|
+
which would otherwise size the child to its content). With
|
|
209
|
+
overflowWrap + wordBreak, long display names like
|
|
210
|
+
"PRODUCTIVITY AGENT" wrap to multiple lines. */}
|
|
211
|
+
<div className="node-label" style={{
|
|
212
|
+
alignSelf: 'stretch',
|
|
213
|
+
width: '100%',
|
|
196
214
|
fontSize: theme.fontSize.base, fontWeight: theme.fontWeight.semibold,
|
|
197
215
|
color: theme.colors.text, lineHeight: '1.2', marginBottom: theme.spacing.xs,
|
|
216
|
+
overflowWrap: 'break-word', wordBreak: 'break-word', whiteSpace: 'normal',
|
|
217
|
+
textAlign: 'center',
|
|
198
218
|
}}>{title}</div>
|
|
199
219
|
|
|
200
|
-
{/* Subtitle
|
|
201
|
-
|
|
220
|
+
{/* Subtitle. Phase color is JS-driven (PHASE_CONFIG[phase].color) so
|
|
221
|
+
we keep the inline color while still exposing `node-sub` so themes
|
|
222
|
+
can style the resting subtitle. */}
|
|
223
|
+
<div className="node-sub" style={{
|
|
224
|
+
alignSelf: 'stretch',
|
|
225
|
+
width: '100%',
|
|
202
226
|
fontSize: theme.fontSize.xs, fontWeight: theme.fontWeight.normal,
|
|
203
227
|
color: isExecuting && phaseConfig ? phaseConfig.color : theme.colors.focus,
|
|
204
228
|
lineHeight: '1.2', marginBottom: theme.spacing.lg, transition: 'color 0.3s ease',
|
|
229
|
+
overflowWrap: 'break-word', wordBreak: 'break-word', whiteSpace: 'normal',
|
|
230
|
+
textAlign: 'center',
|
|
205
231
|
}}>{isExecuting && phaseConfig ? phaseConfig.label : subtitle}</div>
|
|
206
232
|
|
|
207
233
|
{/* Left inputs below the main one (Memory / Task / etc.) */}
|
|
@@ -218,11 +244,11 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
218
244
|
type="target"
|
|
219
245
|
position={REACT_POSITION[h.position]}
|
|
220
246
|
isConnectable={isConnectable}
|
|
247
|
+
className="node-handle in"
|
|
221
248
|
style={{
|
|
222
249
|
position: 'absolute', left: '-6px', top: h.offset || '50%',
|
|
223
250
|
width: theme.nodeSize.handle, height: theme.nodeSize.handle,
|
|
224
|
-
|
|
225
|
-
border: `2px solid ${theme.colors.textSecondary}`, borderRadius: '0',
|
|
251
|
+
borderRadius: '0',
|
|
226
252
|
transform: 'translateY(-50%) rotate(45deg)',
|
|
227
253
|
}}
|
|
228
254
|
title={h.label || h.name}
|
|
@@ -247,11 +273,11 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
247
273
|
type="target"
|
|
248
274
|
position={Position.Bottom}
|
|
249
275
|
isConnectable={isConnectable}
|
|
276
|
+
className="node-handle in"
|
|
250
277
|
style={{
|
|
251
278
|
position: 'absolute', bottom: '-6px', left: h.offset || '50%',
|
|
252
279
|
width: theme.nodeSize.handle, height: theme.nodeSize.handle,
|
|
253
|
-
|
|
254
|
-
border: `2px solid ${theme.colors.textSecondary}`, borderRadius: '0',
|
|
280
|
+
borderRadius: '0',
|
|
255
281
|
transform: 'translateX(-50%) rotate(45deg)',
|
|
256
282
|
}}
|
|
257
283
|
title={h.label || h.name}
|
|
@@ -265,11 +291,10 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
265
291
|
type="source"
|
|
266
292
|
position={Position.Top}
|
|
267
293
|
isConnectable={isConnectable}
|
|
294
|
+
className="node-handle out"
|
|
268
295
|
style={{
|
|
269
296
|
position: 'absolute', top: '-6px', left: '50%', transform: 'translateX(-50%)',
|
|
270
297
|
width: theme.nodeSize.handle, height: theme.nodeSize.handle,
|
|
271
|
-
backgroundColor: accentColor,
|
|
272
|
-
border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
|
|
273
298
|
borderRadius: '50%', zIndex: 20,
|
|
274
299
|
}}
|
|
275
300
|
title={topOutput.label || topOutput.name}
|
|
@@ -294,12 +319,11 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
294
319
|
type="source"
|
|
295
320
|
position={Position.Right}
|
|
296
321
|
isConnectable={isConnectable}
|
|
322
|
+
className="node-handle out"
|
|
297
323
|
style={{
|
|
298
324
|
position: 'absolute', right: '-6px', top: h.offset || '50%',
|
|
299
325
|
transform: 'translateY(-50%)',
|
|
300
326
|
width: theme.nodeSize.handle, height: theme.nodeSize.handle,
|
|
301
|
-
backgroundColor: accentColor,
|
|
302
|
-
border: `2px solid ${theme.isDarkMode ? theme.colors.background : '#ffffff'}`,
|
|
303
327
|
borderRadius: '50%', zIndex: 20,
|
|
304
328
|
}}
|
|
305
329
|
title={h.label || h.name}
|
|
@@ -316,11 +340,11 @@ const AIAgentNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
316
340
|
type="source"
|
|
317
341
|
position={Position.Right}
|
|
318
342
|
isConnectable={isConnectable}
|
|
343
|
+
className="node-handle out"
|
|
319
344
|
style={{
|
|
320
345
|
position: 'absolute', right: '-6px', top: '50%', transform: 'translateY(-50%)',
|
|
321
346
|
width: theme.nodeSize.handle, height: theme.nodeSize.handle,
|
|
322
|
-
|
|
323
|
-
border: `2px solid ${theme.colors.textSecondary}`, borderRadius: '50%',
|
|
347
|
+
borderRadius: '50%',
|
|
324
348
|
}}
|
|
325
349
|
title="Main Output"
|
|
326
350
|
/>
|
|
@@ -4,23 +4,24 @@ import { Handle, Position, NodeProps } from 'reactflow';
|
|
|
4
4
|
import { resolveNodeDescription } from '../lib/nodeSpec';
|
|
5
5
|
import { NodeIcon } from '../assets/icons';
|
|
6
6
|
import { useNodeSpec } from '../lib/nodeSpec';
|
|
7
|
-
import { NodeData } from '../types/NodeTypes';
|
|
7
|
+
import { NodeData, NodeStyle } from '../types/NodeTypes';
|
|
8
8
|
import { INodeInputDefinition, INodeOutputDefinition, NodeConnectionType } from '../types/INodeProperties';
|
|
9
9
|
import { useAppStore } from '../store/useAppStore';
|
|
10
|
-
import { useAppTheme } from '../hooks/useAppTheme';
|
|
11
10
|
import { useNodeStatus } from '../contexts/WebSocketContext';
|
|
12
11
|
import EditableNodeLabel from './ui/EditableNodeLabel';
|
|
13
12
|
|
|
14
13
|
const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnectable, selected }) => {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
14
|
+
const setSelectedNode = useAppStore((s) => s.setSelectedNode);
|
|
15
|
+
const setRenamingNodeId = useAppStore((s) => s.setRenamingNodeId);
|
|
16
|
+
const updateNodeData = useAppStore((s) => s.updateNodeData);
|
|
17
17
|
const isDisabled = data?.disabled === true;
|
|
18
18
|
|
|
19
|
-
// Per-id slice subscription so an unrelated node's status update
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
// Per-id slice subscription so an unrelated node's status update does
|
|
20
|
+
// not re-render this generic node. Wave 21: visual execution state
|
|
21
|
+
// moved to per-theme CSS via the `.executing` class on the wrapper —
|
|
22
|
+
// we keep the subscription so future state-driven CSS toggles can
|
|
23
|
+
// read it without refactoring.
|
|
24
|
+
useNodeStatus(id);
|
|
24
25
|
|
|
25
26
|
// Wave 6 Phase 3e: backend NodeSpec -> legacy fallback
|
|
26
27
|
const definition = type ? resolveNodeDescription(type) : null;
|
|
@@ -97,34 +98,22 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
97
98
|
const nodeInputs = getNodeInputs();
|
|
98
99
|
const nodeOutputs = getNodeOutputs();
|
|
99
100
|
|
|
100
|
-
//
|
|
101
|
+
// Node accent color — feeds the `--node-color` CSS custom property
|
|
102
|
+
// on the wrapper. Handle borders/backgrounds are now owned by
|
|
103
|
+
// `.node-handle` in base.css + per-theme overrides (Wave 26.C).
|
|
101
104
|
const getNodeColor = () => definition.defaults.color || '#9E9E9E';
|
|
102
|
-
const getBorderColor = () => {
|
|
103
|
-
const color = getNodeColor();
|
|
104
|
-
if (color.startsWith('#')) {
|
|
105
|
-
const hex = color.substring(1);
|
|
106
|
-
const r = Math.max(0, parseInt(hex.substring(0, 2), 16) - 40);
|
|
107
|
-
const g = Math.max(0, parseInt(hex.substring(2, 4), 16) - 40);
|
|
108
|
-
const b = Math.max(0, parseInt(hex.substring(4, 6), 16) - 40);
|
|
109
|
-
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
|
110
|
-
}
|
|
111
|
-
return color;
|
|
112
|
-
};
|
|
113
105
|
|
|
114
106
|
return (
|
|
107
|
+
// `node` + `selected` co-classes activate per-theme generic-node
|
|
108
|
+
// decorations (wax seal on Renaissance, neon LED on Cyber, etc.).
|
|
115
109
|
<div
|
|
110
|
+
className={`node ${selected ? 'selected' : ''}`}
|
|
116
111
|
style={{
|
|
112
|
+
'--node-color': getNodeColor(),
|
|
117
113
|
position: 'relative',
|
|
118
114
|
padding: '12px 32px 12px 16px',
|
|
119
115
|
minWidth: '160px',
|
|
120
116
|
minHeight: '60px',
|
|
121
|
-
borderRadius: '12px',
|
|
122
|
-
background: `linear-gradient(135deg, ${getNodeColor()} 0%, ${getBorderColor()} 100%)`,
|
|
123
|
-
border: `2px solid ${isExecuting
|
|
124
|
-
? (theme.isDarkMode ? theme.dracula.cyan : '#2563eb')
|
|
125
|
-
: selected
|
|
126
|
-
? '#3b82f6'
|
|
127
|
-
: getBorderColor()}`,
|
|
128
117
|
color: 'white',
|
|
129
118
|
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
130
119
|
fontSize: '14px',
|
|
@@ -132,19 +121,9 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
132
121
|
textAlign: 'center',
|
|
133
122
|
cursor: 'pointer',
|
|
134
123
|
transition: 'all 0.2s ease',
|
|
135
|
-
boxShadow: isExecuting
|
|
136
|
-
? theme.isDarkMode
|
|
137
|
-
? `0 4px 12px ${theme.dracula.cyan}66, 0 0 0 3px ${theme.dracula.cyan}4D`
|
|
138
|
-
: `0 0 0 3px rgba(37, 99, 235, 0.5), 0 4px 16px rgba(37, 99, 235, 0.35)`
|
|
139
|
-
: selected
|
|
140
|
-
? `0 8px 25px ${getNodeColor()}40, 0 0 0 2px ${theme.colors.focus}`
|
|
141
|
-
: theme.isDarkMode
|
|
142
|
-
? `0 4px 12px ${getNodeColor()}40`
|
|
143
|
-
: `0 2px 8px ${getNodeColor()}25, 0 4px 16px rgba(0, 0, 0, 0.08)`,
|
|
144
124
|
overflow: 'visible',
|
|
145
125
|
opacity: isDisabled ? 0.5 : 1,
|
|
146
|
-
|
|
147
|
-
}}
|
|
126
|
+
} as NodeStyle}
|
|
148
127
|
>
|
|
149
128
|
{/* Disabled Overlay */}
|
|
150
129
|
{isDisabled && (
|
|
@@ -165,12 +144,14 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
165
144
|
<span style={{ fontSize: '24px', opacity: 0.8 }}>||</span>
|
|
166
145
|
</div>
|
|
167
146
|
)}
|
|
168
|
-
{/* Input Handles - Multiple handles based on node definition
|
|
147
|
+
{/* Input Handles - Multiple handles based on node definition.
|
|
148
|
+
Visual styling (background, border) owned by `.node-handle.in`
|
|
149
|
+
in base.css + per-theme overrides. Inline keeps layout only. */}
|
|
169
150
|
{nodeInputs.map((input, index) => {
|
|
170
151
|
const totalInputs = nodeInputs.length;
|
|
171
|
-
const topPosition = totalInputs === 1 ? '50%' :
|
|
152
|
+
const topPosition = totalInputs === 1 ? '50%' :
|
|
172
153
|
`${20 + (60 * index) / Math.max(totalInputs - 1, 1)}%`;
|
|
173
|
-
|
|
154
|
+
|
|
174
155
|
return (
|
|
175
156
|
<Handle
|
|
176
157
|
key={`input-${input.name}-${index}`}
|
|
@@ -178,6 +159,7 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
178
159
|
type="target"
|
|
179
160
|
position={Position.Left}
|
|
180
161
|
isConnectable={isConnectable}
|
|
162
|
+
className="node-handle in"
|
|
181
163
|
style={{
|
|
182
164
|
position: 'absolute',
|
|
183
165
|
left: '-6px',
|
|
@@ -185,20 +167,20 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
185
167
|
transform: 'translateY(-50%)',
|
|
186
168
|
width: '12px',
|
|
187
169
|
height: '12px',
|
|
188
|
-
backgroundColor: 'rgba(255,255,255,0.9)',
|
|
189
|
-
border: `2px solid ${getBorderColor()}`,
|
|
190
170
|
borderRadius: '50%'
|
|
191
171
|
}}
|
|
192
172
|
title={`${input.displayName}: ${input.description}`}
|
|
193
173
|
/>
|
|
194
174
|
);
|
|
195
175
|
})}
|
|
196
|
-
|
|
197
176
|
|
|
198
|
-
|
|
177
|
+
|
|
178
|
+
{/* Parameters Button — visual styles (background, border, hover)
|
|
179
|
+
owned by `.node-gear` in base.css + per-theme overrides.
|
|
180
|
+
Inline keeps positioning + label color only. */}
|
|
199
181
|
<button
|
|
200
182
|
onClick={handleParametersClick}
|
|
201
|
-
className="absolute top-2 right-2 z-20 flex h-5 w-5 cursor-pointer items-center justify-center rounded-full
|
|
183
|
+
className="node-gear absolute top-2 right-2 z-20 flex h-5 w-5 cursor-pointer items-center justify-center rounded-full text-[10px] font-semibold transition-all duration-200 hover:scale-115"
|
|
202
184
|
style={{ color: getNodeColor() }}
|
|
203
185
|
title="Edit Parameters"
|
|
204
186
|
>
|
|
@@ -229,12 +211,13 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
229
211
|
</div>
|
|
230
212
|
|
|
231
213
|
|
|
232
|
-
{/* Output Handles - Multiple handles based on node definition
|
|
214
|
+
{/* Output Handles - Multiple handles based on node definition.
|
|
215
|
+
Visual styling owned by `.node-handle.out` in base.css. */}
|
|
233
216
|
{nodeOutputs.map((output, index) => {
|
|
234
217
|
const totalOutputs = nodeOutputs.length;
|
|
235
|
-
const topPosition = totalOutputs === 1 ? '50%' :
|
|
218
|
+
const topPosition = totalOutputs === 1 ? '50%' :
|
|
236
219
|
`${20 + (60 * index) / Math.max(totalOutputs - 1, 1)}%`;
|
|
237
|
-
|
|
220
|
+
|
|
238
221
|
return (
|
|
239
222
|
<Handle
|
|
240
223
|
key={`output-${output.name}-${index}`}
|
|
@@ -242,6 +225,7 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
242
225
|
type="source"
|
|
243
226
|
position={Position.Right}
|
|
244
227
|
isConnectable={isConnectable}
|
|
228
|
+
className="node-handle out"
|
|
245
229
|
style={{
|
|
246
230
|
position: 'absolute',
|
|
247
231
|
right: '-6px',
|
|
@@ -249,8 +233,6 @@ const GenericNode: React.FC<NodeProps<NodeData>> = ({ id, type, data, isConnecta
|
|
|
249
233
|
transform: 'translateY(-50%)',
|
|
250
234
|
width: '12px',
|
|
251
235
|
height: '12px',
|
|
252
|
-
backgroundColor: 'rgba(255,255,255,0.9)',
|
|
253
|
-
border: `2px solid ${getBorderColor()}`,
|
|
254
236
|
borderRadius: '50%'
|
|
255
237
|
}}
|
|
256
238
|
title={`${output.displayName}: ${output.description}`}
|