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
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { Loader2 } from 'lucide-react';
|
|
8
|
-
import {
|
|
8
|
+
import { ActionButton } from '@/components/ui/action-button';
|
|
9
|
+
import type { ActionButtonIntent } from '@/components/ui/action-button';
|
|
9
10
|
|
|
10
11
|
export interface ActionDef {
|
|
11
12
|
key: string;
|
|
12
13
|
label: string;
|
|
13
|
-
/**
|
|
14
|
-
|
|
14
|
+
/** Semantic role consumed by `<ActionButton intent="...">`. */
|
|
15
|
+
intent: ActionButtonIntent;
|
|
15
16
|
onClick: () => void;
|
|
16
17
|
disabled?: boolean;
|
|
17
18
|
icon?: React.ReactNode;
|
|
@@ -31,20 +32,15 @@ const ActionBar: React.FC<Props> = ({ actions, loading }) => {
|
|
|
31
32
|
.map((a) => {
|
|
32
33
|
const isLoading = loading === a.key;
|
|
33
34
|
return (
|
|
34
|
-
<
|
|
35
|
+
<ActionButton
|
|
35
36
|
key={a.key}
|
|
36
|
-
|
|
37
|
+
intent={a.intent}
|
|
37
38
|
onClick={a.onClick}
|
|
38
39
|
disabled={a.disabled || isLoading}
|
|
39
|
-
style={{
|
|
40
|
-
backgroundColor: `${a.color}25`,
|
|
41
|
-
borderColor: `${a.color}60`,
|
|
42
|
-
color: a.color,
|
|
43
|
-
}}
|
|
44
40
|
>
|
|
45
41
|
{isLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : a.icon}
|
|
46
42
|
{a.label}
|
|
47
|
-
</
|
|
43
|
+
</ActionButton>
|
|
48
44
|
);
|
|
49
45
|
})}
|
|
50
46
|
</div>
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import React from 'react';
|
|
8
8
|
import { Loader2, RotateCcw } from 'lucide-react';
|
|
9
|
-
import { Button } from '@/components/ui/button';
|
|
10
9
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
11
|
-
import {
|
|
10
|
+
import { ActionButton } from '@/components/ui/action-button';
|
|
12
11
|
import FieldRenderer from './FieldRenderer';
|
|
13
12
|
import ActionBar, { type ActionDef } from './ActionBar';
|
|
14
13
|
import StatusCard from './StatusCard';
|
|
@@ -39,17 +38,22 @@ const OAuthConnect: React.FC<Props> = ({
|
|
|
39
38
|
config, form, connected, stored, loading, error, icon,
|
|
40
39
|
onSaveCredentials, onLogin, onLogout, onRefresh, extraSection,
|
|
41
40
|
}) => {
|
|
42
|
-
|
|
41
|
+
// Some providers (e.g. Stripe) delegate auth entirely to an external
|
|
42
|
+
// CLI tool — they have no MachinaOs-side credentials to paste, so
|
|
43
|
+
// there's nothing to "store" before Login is meaningful.
|
|
44
|
+
const hasFields = !!config.fields?.length;
|
|
43
45
|
|
|
44
46
|
const statusRows: StatusRowDef[] = [
|
|
45
47
|
{ label: 'Status', ok: () => connected, trueText: 'Connected', falseText: 'Not Connected' },
|
|
46
|
-
|
|
48
|
+
...(hasFields
|
|
49
|
+
? [{ label: 'Credentials', ok: () => stored, trueText: 'Configured', falseText: 'Not configured' } as StatusRowDef]
|
|
50
|
+
: []),
|
|
47
51
|
];
|
|
48
52
|
|
|
49
53
|
const actions: ActionDef[] = [
|
|
50
|
-
{ key: 'login', label: `Login with ${config.name}`,
|
|
51
|
-
{ key: 'logout', label: 'Disconnect',
|
|
52
|
-
{ key: 'refresh', label: 'Refresh',
|
|
54
|
+
{ key: 'login', label: `Login with ${config.name}`, intent: 'save', onClick: onLogin, disabled: hasFields && !stored, hidden: connected },
|
|
55
|
+
{ key: 'logout', label: 'Disconnect', intent: 'stop', onClick: onLogout, hidden: !connected },
|
|
56
|
+
{ key: 'refresh', label: 'Refresh', intent: 'save', onClick: onRefresh, icon: <RotateCcw className="h-4 w-4" /> },
|
|
53
57
|
];
|
|
54
58
|
|
|
55
59
|
const isSaving = loading === 'save';
|
|
@@ -61,19 +65,10 @@ const OAuthConnect: React.FC<Props> = ({
|
|
|
61
65
|
{!connected && config.fields && (
|
|
62
66
|
<div className="flex w-full flex-col gap-3">
|
|
63
67
|
<FieldRenderer fields={config.fields} form={form} />
|
|
64
|
-
<
|
|
65
|
-
variant="outline"
|
|
66
|
-
onClick={onSaveCredentials}
|
|
67
|
-
disabled={isSaving}
|
|
68
|
-
style={{
|
|
69
|
-
backgroundColor: `${theme.dracula.purple}25`,
|
|
70
|
-
borderColor: `${theme.dracula.purple}60`,
|
|
71
|
-
color: theme.dracula.purple,
|
|
72
|
-
}}
|
|
73
|
-
>
|
|
68
|
+
<ActionButton intent="secret" onClick={onSaveCredentials} disabled={isSaving}>
|
|
74
69
|
{isSaving && <Loader2 className="h-4 w-4 animate-spin" />}
|
|
75
70
|
Save Credentials
|
|
76
|
-
</
|
|
71
|
+
</ActionButton>
|
|
77
72
|
{config.instructions && (
|
|
78
73
|
<div className="text-xs leading-relaxed text-muted-foreground">
|
|
79
74
|
{config.instructions}
|
|
@@ -81,7 +76,7 @@ const OAuthConnect: React.FC<Props> = ({
|
|
|
81
76
|
<>
|
|
82
77
|
<br />
|
|
83
78
|
Callback URL:{' '}
|
|
84
|
-
<code className="text-
|
|
79
|
+
<code className="text-accent">{config.callbackUrl}</code>
|
|
85
80
|
</>
|
|
86
81
|
)}
|
|
87
82
|
</div>
|
|
@@ -95,17 +90,13 @@ const OAuthConnect: React.FC<Props> = ({
|
|
|
95
90
|
</Alert>
|
|
96
91
|
)}
|
|
97
92
|
|
|
98
|
-
<div
|
|
99
|
-
className="rounded-md border p-3"
|
|
100
|
-
style={{
|
|
101
|
-
backgroundColor: `${theme.dracula.cyan}10`,
|
|
102
|
-
borderColor: `${theme.dracula.cyan}30`,
|
|
103
|
-
}}
|
|
104
|
-
>
|
|
93
|
+
<div className="rounded-md border border-accent/30 bg-accent/10 p-3">
|
|
105
94
|
<div className="text-sm leading-relaxed text-muted-foreground">
|
|
106
95
|
{connected
|
|
107
|
-
?
|
|
108
|
-
|
|
96
|
+
? config.account_label
|
|
97
|
+
? `Connected as ${config.account_label}.`
|
|
98
|
+
: `Your ${config.name} account is connected.`
|
|
99
|
+
: (stored || !hasFields)
|
|
109
100
|
? 'Click Login to authorize.'
|
|
110
101
|
: 'Enter your credentials above to get started.'}
|
|
111
102
|
</div>
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
AccordionTrigger,
|
|
40
40
|
} from '@/components/ui/accordion';
|
|
41
41
|
import { useApiKeys, type ProviderDefaults, type ModelConstraints } from '../../../hooks/useApiKeys';
|
|
42
|
+
import { useWebSocket } from '../../../contexts/WebSocketContext';
|
|
42
43
|
|
|
43
44
|
const formSchema = z.object({
|
|
44
45
|
default_model: z.string().optional().default(''),
|
|
@@ -58,6 +59,14 @@ interface Props {
|
|
|
58
59
|
|
|
59
60
|
const ProviderDefaultsSection: React.FC<Props> = ({ providerId }) => {
|
|
60
61
|
const { getProviderDefaults, saveProviderDefaults, getStoredModels, getModelConstraints, isConnected } = useApiKeys();
|
|
62
|
+
// Subscribe to apiKeyStatuses[providerId]: WebSocketContext updates
|
|
63
|
+
// this reactively after a successful validate (handler at line 2175-
|
|
64
|
+
// 2180) AND on the backend's api_key_status broadcast (line 686-696
|
|
65
|
+
// -- fired after the validator stores fresh models). Adding it as a
|
|
66
|
+
// dep to the fetch effect below makes the model dropdown refresh
|
|
67
|
+
// immediately after Fetch instead of requiring a page reload.
|
|
68
|
+
const { apiKeyStatuses } = useWebSocket();
|
|
69
|
+
const apiKeyStatus = apiKeyStatuses[providerId];
|
|
61
70
|
|
|
62
71
|
const [models, setModels] = useState<string[]>([]);
|
|
63
72
|
const [constraints, setConstraints] = useState<ModelConstraints | null>(null);
|
|
@@ -71,9 +80,18 @@ const ProviderDefaultsSection: React.FC<Props> = ({ providerId }) => {
|
|
|
71
80
|
const thinkingEnabled = form.watch('thinking_enabled');
|
|
72
81
|
const { isDirty } = form.formState;
|
|
73
82
|
|
|
74
|
-
// Load defaults + models on mount.
|
|
83
|
+
// Load defaults + models on mount AND on providerId change.
|
|
84
|
+
// Reset models / constraints synchronously when the provider switches
|
|
85
|
+
// so a stale list from the previous panel can't bleed through. Without
|
|
86
|
+
// this, opening "OpenAI" then "LM Studio" left the OpenAI model list
|
|
87
|
+
// visible in the LM Studio dropdown — the dropdown only saw an
|
|
88
|
+
// explicit `setModels(m)` when the new fetch returned a non-empty
|
|
89
|
+
// list, so an empty `lmstudio` response (no Fetch clicked yet) was a
|
|
90
|
+
// no-op and the previous panel's state survived.
|
|
75
91
|
useEffect(() => {
|
|
76
92
|
if (!isConnected) return;
|
|
93
|
+
setModels([]);
|
|
94
|
+
setConstraints(null);
|
|
77
95
|
let cancelled = false;
|
|
78
96
|
(async () => {
|
|
79
97
|
setLoading(true);
|
|
@@ -84,14 +102,17 @@ const ProviderDefaultsSection: React.FC<Props> = ({ providerId }) => {
|
|
|
84
102
|
]);
|
|
85
103
|
if (!cancelled) {
|
|
86
104
|
form.reset((d as Partial<ProviderDefaults>) ?? {});
|
|
87
|
-
|
|
105
|
+
// Unconditional set: clears the dropdown when the fetch comes
|
|
106
|
+
// back empty (e.g. local-LLM panel before "Fetch" was clicked,
|
|
107
|
+
// or a freshly-removed key).
|
|
108
|
+
setModels(m ?? []);
|
|
88
109
|
}
|
|
89
110
|
} finally {
|
|
90
111
|
if (!cancelled) setLoading(false);
|
|
91
112
|
}
|
|
92
113
|
})();
|
|
93
114
|
return () => { cancelled = true; };
|
|
94
|
-
}, [providerId, isConnected]);
|
|
115
|
+
}, [providerId, isConnected, apiKeyStatus]);
|
|
95
116
|
|
|
96
117
|
// Refetch constraints when selected model changes.
|
|
97
118
|
useEffect(() => {
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* ALL conditional logic lives in config — panel components are pure renderers.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type { ActionButtonIntent } from '@/components/ui/action-button';
|
|
8
|
+
export type { ActionButtonIntent };
|
|
9
|
+
|
|
7
10
|
// ============================================================================
|
|
8
11
|
// Panel kinds — one renderer branch per kind
|
|
9
12
|
// ============================================================================
|
|
@@ -19,7 +22,11 @@ export interface FieldDef {
|
|
|
19
22
|
label: string;
|
|
20
23
|
secret?: boolean;
|
|
21
24
|
placeholder?: string;
|
|
25
|
+
/** Pre-fill value when nothing stored. See ServerFieldDef.default. */
|
|
26
|
+
default?: string;
|
|
22
27
|
required?: boolean;
|
|
28
|
+
/** Help text shown beneath the input (always visible). */
|
|
29
|
+
help?: string;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
// ============================================================================
|
|
@@ -43,8 +50,9 @@ export interface StatusRowDef {
|
|
|
43
50
|
export interface ActionDef {
|
|
44
51
|
key: string;
|
|
45
52
|
label: string;
|
|
46
|
-
/**
|
|
47
|
-
|
|
53
|
+
/** Semantic role consumed by `<ActionButton intent="...">`. Themes
|
|
54
|
+
* remap the underlying --action-X tokens without touching call sites. */
|
|
55
|
+
intent: ActionButtonIntent;
|
|
48
56
|
/** Return true to hide this action. */
|
|
49
57
|
hidden?: (status: any, stored: boolean) => boolean;
|
|
50
58
|
/** Return true to disable this action. */
|
|
@@ -115,6 +123,8 @@ export interface ProviderConfig {
|
|
|
115
123
|
usageService?: string;
|
|
116
124
|
/** Server-resolved: whether a key/token exists in the credentials DB. */
|
|
117
125
|
stored?: boolean;
|
|
126
|
+
/** Connected account identifier (email or display name) for OAuth providers. */
|
|
127
|
+
account_label?: string | null;
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
// ============================================================================
|
|
@@ -45,40 +45,48 @@ export function useCredentialPanel(config: ProviderConfig, visible: boolean) {
|
|
|
45
45
|
}, [config.id]);
|
|
46
46
|
|
|
47
47
|
const {
|
|
48
|
-
validateApiKey, saveApiKey,
|
|
48
|
+
validateApiKey, saveApiKey, removeApiKey,
|
|
49
49
|
getProviderDefaults, saveProviderDefaults,
|
|
50
50
|
getProviderUsageSummary, getAPIUsageSummary, getStoredModels, getModelConstraints,
|
|
51
51
|
isConnected,
|
|
52
52
|
} = useApiKeys();
|
|
53
53
|
const { sendRequest } = useWebSocket();
|
|
54
54
|
|
|
55
|
-
// Server-cached credential values.
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
|
|
55
|
+
// Server-cached credential values. Single RPC per field consolidates
|
|
56
|
+
// the previous hasStoredKey + getStoredApiKey pair (both went to
|
|
57
|
+
// ``get_stored_api_key`` anyway). The handler returns
|
|
58
|
+
// ``{hasKey, apiKey?}``; ``apiKey`` may carry the catalogue's
|
|
59
|
+
// ``default`` (e.g. local-LLM canonical Base URL) even when
|
|
60
|
+
// ``hasKey: false``, so the form renders a sensible value on a fresh
|
|
61
|
+
// install. ``hadStored`` tracks the real server state separately so
|
|
62
|
+
// the validated/connected badge stays honest — pre-filled defaults
|
|
63
|
+
// do NOT flip it to true.
|
|
64
|
+
const credentialValuesQuery = useQuery<{ values: CredentialFormValues; hadStored: boolean }, Error>({
|
|
59
65
|
queryKey: queryKeys.credentialValues.byProvider(config.id).queryKey,
|
|
60
66
|
queryFn: async () => {
|
|
61
|
-
if (!config.fields) return EMPTY_VALUES;
|
|
62
|
-
const
|
|
67
|
+
if (!config.fields) return { values: EMPTY_VALUES, hadStored: false };
|
|
68
|
+
const results = await Promise.all(
|
|
63
69
|
config.fields.map(async (field) => {
|
|
64
70
|
const storeKey = field.key === 'apiKey' ? config.id : field.key;
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return
|
|
71
|
+
const r = await sendRequest<{ hasKey: boolean; apiKey?: string }>(
|
|
72
|
+
'get_stored_api_key', { provider: storeKey },
|
|
73
|
+
);
|
|
74
|
+
return { key: field.key, hasKey: r.hasKey, apiKey: r.apiKey };
|
|
69
75
|
}),
|
|
70
76
|
);
|
|
71
77
|
const next: CredentialFormValues = {};
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
let hadStored = false;
|
|
79
|
+
for (const r of results) {
|
|
80
|
+
if (r.apiKey) next[r.key] = r.apiKey;
|
|
81
|
+
if (r.hasKey) hadStored = true;
|
|
74
82
|
}
|
|
75
|
-
return next;
|
|
83
|
+
return { values: next, hadStored };
|
|
76
84
|
},
|
|
77
85
|
enabled: visible && isConnected && !!config.fields,
|
|
78
86
|
staleTime: STALE_TIME.FOREVER,
|
|
79
87
|
});
|
|
80
88
|
|
|
81
|
-
const values = credentialValuesQuery.data ?? EMPTY_VALUES;
|
|
89
|
+
const values = credentialValuesQuery.data?.values ?? EMPTY_VALUES;
|
|
82
90
|
|
|
83
91
|
// Imperative form-like API kept for compat with existing panel code.
|
|
84
92
|
// Writes go through setQueryData on the provider's query so the cache
|
|
@@ -87,11 +95,23 @@ export function useCredentialPanel(config: ProviderConfig, visible: boolean) {
|
|
|
87
95
|
valuesRef.current = values;
|
|
88
96
|
|
|
89
97
|
const providerKey = config.id;
|
|
98
|
+
// The query stores `{values, hadStored}` (see queryFn above), NOT a flat
|
|
99
|
+
// CredentialFormValues. Earlier this called
|
|
100
|
+
// `setQueryData<CredentialFormValues>(...)` and the updater spread `prev`
|
|
101
|
+
// as if it were the inner `values` dict — at runtime `prev` was the whole
|
|
102
|
+
// envelope, so typed characters were merged at the envelope level next to
|
|
103
|
+
// `values` / `hadStored` and the input selector (which reads `.values[k]`)
|
|
104
|
+
// never saw them. Net effect: input felt unresponsive even though the
|
|
105
|
+
// setter was firing. Fix: update only the envelope's inner `values` and
|
|
106
|
+
// preserve `hadStored` verbatim.
|
|
90
107
|
const writeValues = useCallback(
|
|
91
108
|
(updater: (prev: CredentialFormValues) => CredentialFormValues) => {
|
|
92
|
-
qc.setQueryData<CredentialFormValues>(
|
|
109
|
+
qc.setQueryData<{ values: CredentialFormValues; hadStored: boolean }>(
|
|
93
110
|
queryKeys.credentialValues.byProvider(providerKey).queryKey,
|
|
94
|
-
(prev) =>
|
|
111
|
+
(prev) => ({
|
|
112
|
+
values: updater(prev?.values ?? EMPTY_VALUES),
|
|
113
|
+
hadStored: prev?.hadStored ?? false,
|
|
114
|
+
}),
|
|
95
115
|
);
|
|
96
116
|
},
|
|
97
117
|
[qc, providerKey],
|
|
@@ -118,8 +138,12 @@ export function useCredentialPanel(config: ProviderConfig, visible: boolean) {
|
|
|
118
138
|
);
|
|
119
139
|
|
|
120
140
|
// Sync stored from query on first load — if the backend already has
|
|
121
|
-
//
|
|
122
|
-
|
|
141
|
+
// mark stored=true based on the real server state (`hadStored`),
|
|
142
|
+
// NOT on whether the form happens to be populated. Pre-filled
|
|
143
|
+
// catalogue defaults (e.g. local-LLM Base URL) populate the form
|
|
144
|
+
// without being saved, so deriving from `Object.keys(values).length`
|
|
145
|
+
// would prematurely show the connected badge.
|
|
146
|
+
const queriedStored = credentialValuesQuery.data?.hadStored ?? false;
|
|
123
147
|
if (queriedStored && !stored) setStored(true);
|
|
124
148
|
|
|
125
149
|
// Generic action executor — replaces 19 duplicate handler functions.
|
|
@@ -12,6 +12,8 @@ import Cerebras from '@lobehub/icons/es/Cerebras';
|
|
|
12
12
|
import DeepSeek from '@lobehub/icons/es/DeepSeek';
|
|
13
13
|
import Kimi from '@lobehub/icons/es/Kimi';
|
|
14
14
|
import Mistral from '@lobehub/icons/es/Mistral';
|
|
15
|
+
import Ollama from '@lobehub/icons/es/Ollama';
|
|
16
|
+
import LmStudio from '@lobehub/icons/es/LmStudio';
|
|
15
17
|
import { dracula, solarized } from '../../styles/theme';
|
|
16
18
|
|
|
17
19
|
// Icon size constant for consistency
|
|
@@ -58,6 +60,16 @@ export const MistralIcon: React.FC<{ size?: number }> = ({ size = ICON_SIZE }) =
|
|
|
58
60
|
<Mistral.Color size={size} />
|
|
59
61
|
);
|
|
60
62
|
|
|
63
|
+
// Local LLM servers — Ollama exposes a Color variant; LmStudio's lobehub
|
|
64
|
+
// entry only ships an Avatar so we use that for parity.
|
|
65
|
+
export const OllamaIcon: React.FC<{ size?: number }> = ({ size = ICON_SIZE }) => (
|
|
66
|
+
<Ollama.Avatar size={size} />
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
export const LmStudioIcon: React.FC<{ size?: number }> = ({ size = ICON_SIZE }) => (
|
|
70
|
+
<LmStudio.Avatar size={size} />
|
|
71
|
+
);
|
|
72
|
+
|
|
61
73
|
// Map provider IDs to their icon components
|
|
62
74
|
export const AI_PROVIDER_ICONS: Record<string, React.FC<{ size?: number }>> = {
|
|
63
75
|
openai: OpenAIIcon,
|
|
@@ -69,6 +81,8 @@ export const AI_PROVIDER_ICONS: Record<string, React.FC<{ size?: number }>> = {
|
|
|
69
81
|
deepseek: DeepSeekIcon,
|
|
70
82
|
kimi: KimiIcon,
|
|
71
83
|
mistral: MistralIcon,
|
|
84
|
+
ollama: OllamaIcon,
|
|
85
|
+
lmstudio: LmStudioIcon,
|
|
72
86
|
};
|
|
73
87
|
|
|
74
88
|
// Centralized provider metadata (icon ref, theme color, display label).
|
|
@@ -86,6 +100,8 @@ export const AI_PROVIDER_META: Record<string, { iconRef: string; Icon: React.FC<
|
|
|
86
100
|
deepseek: { iconRef: 'lobehub:DeepSeek', Icon: DeepSeekIcon, color: dracula.cyan, label: 'DeepSeek' },
|
|
87
101
|
kimi: { iconRef: 'lobehub:Kimi', Icon: KimiIcon, color: dracula.purple, label: 'Kimi' },
|
|
88
102
|
mistral: { iconRef: 'lobehub:Mistral', Icon: MistralIcon, color: dracula.pink, label: 'Mistral' },
|
|
103
|
+
ollama: { iconRef: 'lobehub:Ollama', Icon: OllamaIcon, color: dracula.foreground, label: 'Ollama' },
|
|
104
|
+
lmstudio: { iconRef: 'lobehub:LmStudio', Icon: LmStudioIcon, color: solarized.cyan, label: 'LM Studio' },
|
|
89
105
|
};
|
|
90
106
|
|
|
91
107
|
// Get icon component by provider ID
|
|
@@ -2,10 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import { ArrowLeft, ArrowRight, Check } from 'lucide-react';
|
|
3
3
|
|
|
4
4
|
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { ActionButton } from '@/components/ui/action-button';
|
|
5
6
|
import { cn } from '@/lib/utils';
|
|
6
7
|
import Modal from '../ui/Modal';
|
|
7
8
|
import { useOnboarding } from '../../hooks/useOnboarding';
|
|
8
|
-
import { useAppTheme } from '../../hooks/useAppTheme';
|
|
9
9
|
import WelcomeStep from './steps/WelcomeStep';
|
|
10
10
|
import ConceptsStep from './steps/ConceptsStep';
|
|
11
11
|
import ApiKeyStep from './steps/ApiKeyStep';
|
|
@@ -17,42 +17,33 @@ interface OnboardingWizardProps {
|
|
|
17
17
|
reopenTrigger?: number;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{ title: '
|
|
25
|
-
{ title: '
|
|
20
|
+
// Single source of truth for the wizard's step list. Length feeds the
|
|
21
|
+
// hook's totalSteps and the progress indicator; renderer is dispatched
|
|
22
|
+
// by index. Adding a step is a one-line edit here.
|
|
23
|
+
const STEPS: { title: string; render: (props: { onOpenCredentials: () => void }) => React.ReactNode }[] = [
|
|
24
|
+
{ title: 'Welcome', render: () => <WelcomeStep /> },
|
|
25
|
+
{ title: 'Concepts', render: () => <ConceptsStep /> },
|
|
26
|
+
{ title: 'API Keys', render: ({ onOpenCredentials }) => <ApiKeyStep onOpenCredentials={onOpenCredentials} /> },
|
|
27
|
+
{ title: 'Canvas', render: () => <CanvasStep /> },
|
|
28
|
+
{ title: 'Get Started', render: () => <GetStartedStep /> },
|
|
26
29
|
];
|
|
27
30
|
|
|
28
31
|
const OnboardingWizard: React.FC<OnboardingWizardProps> = ({ onOpenCredentials, reopenTrigger }) => {
|
|
29
|
-
const theme = useAppTheme();
|
|
30
32
|
const {
|
|
31
33
|
isVisible,
|
|
32
34
|
currentStep,
|
|
33
35
|
isLoading,
|
|
34
36
|
hasChecked,
|
|
35
|
-
totalSteps,
|
|
36
37
|
nextStep,
|
|
37
38
|
prevStep,
|
|
38
39
|
skip,
|
|
39
40
|
complete,
|
|
40
|
-
} = useOnboarding(reopenTrigger);
|
|
41
|
+
} = useOnboarding(reopenTrigger, STEPS.length);
|
|
41
42
|
|
|
42
43
|
if (!isVisible || !hasChecked || isLoading) return null;
|
|
43
44
|
|
|
44
|
-
const isLastStep = currentStep >=
|
|
45
|
-
|
|
46
|
-
const renderStep = () => {
|
|
47
|
-
switch (currentStep) {
|
|
48
|
-
case 0: return <WelcomeStep />;
|
|
49
|
-
case 1: return <ConceptsStep />;
|
|
50
|
-
case 2: return <ApiKeyStep onOpenCredentials={onOpenCredentials} />;
|
|
51
|
-
case 3: return <CanvasStep />;
|
|
52
|
-
case 4: return <GetStartedStep />;
|
|
53
|
-
default: return <WelcomeStep />;
|
|
54
|
-
}
|
|
55
|
-
};
|
|
45
|
+
const isLastStep = currentStep >= STEPS.length - 1;
|
|
46
|
+
const safeIndex = Math.min(Math.max(currentStep, 0), STEPS.length - 1);
|
|
56
47
|
|
|
57
48
|
return (
|
|
58
49
|
<Modal
|
|
@@ -61,19 +52,14 @@ const OnboardingWizard: React.FC<OnboardingWizardProps> = ({ onOpenCredentials,
|
|
|
61
52
|
title="Welcome Guide"
|
|
62
53
|
maxWidth="95vw"
|
|
63
54
|
maxHeight="95vh"
|
|
64
|
-
autoHeight
|
|
65
55
|
>
|
|
66
|
-
<div
|
|
67
|
-
display: 'flex',
|
|
68
|
-
flexDirection: 'column',
|
|
69
|
-
padding: '16px 20px 12px',
|
|
70
|
-
}}>
|
|
56
|
+
<div className="flex flex-col px-5 pt-4 pb-3">
|
|
71
57
|
{/* Progress steps */}
|
|
72
58
|
<ol className="mb-4 flex w-full items-center">
|
|
73
|
-
{
|
|
59
|
+
{STEPS.map((item, idx) => {
|
|
74
60
|
const status: 'completed' | 'active' | 'upcoming' =
|
|
75
61
|
idx < currentStep ? 'completed' : idx === currentStep ? 'active' : 'upcoming';
|
|
76
|
-
const isLast = idx ===
|
|
62
|
+
const isLast = idx === STEPS.length - 1;
|
|
77
63
|
return (
|
|
78
64
|
<li key={item.title} className="flex flex-1 items-center gap-2">
|
|
79
65
|
<div
|
|
@@ -108,30 +94,16 @@ const OnboardingWizard: React.FC<OnboardingWizardProps> = ({ onOpenCredentials,
|
|
|
108
94
|
</ol>
|
|
109
95
|
|
|
110
96
|
{/* Step content */}
|
|
111
|
-
<div
|
|
112
|
-
|
|
113
|
-
minHeight: 500,
|
|
114
|
-
maxHeight: 'calc(95vh - 200px)',
|
|
115
|
-
paddingRight: 4,
|
|
116
|
-
}}>
|
|
117
|
-
{renderStep()}
|
|
97
|
+
<div className="overflow-y-auto pr-1 max-h-[calc(95vh-200px)]">
|
|
98
|
+
{STEPS[safeIndex].render({ onOpenCredentials })}
|
|
118
99
|
</div>
|
|
119
100
|
|
|
120
101
|
{/* Footer navigation */}
|
|
121
|
-
<div
|
|
122
|
-
display: 'flex',
|
|
123
|
-
justifyContent: 'space-between',
|
|
124
|
-
alignItems: 'center',
|
|
125
|
-
paddingTop: 12,
|
|
126
|
-
marginTop: 12,
|
|
127
|
-
borderTop: `1px solid ${theme.colors.border}`,
|
|
128
|
-
}}>
|
|
129
|
-
{/* Left: Skip */}
|
|
102
|
+
<div className="flex items-center justify-between border-t border-border pt-3 mt-3">
|
|
130
103
|
<Button variant="ghost" size="sm" onClick={skip} className="text-muted-foreground">
|
|
131
104
|
Skip for now
|
|
132
105
|
</Button>
|
|
133
106
|
|
|
134
|
-
{/* Right: Back + Next/Finish */}
|
|
135
107
|
<div className="flex items-center gap-2">
|
|
136
108
|
{currentStep > 0 && (
|
|
137
109
|
<Button variant="outline" onClick={prevStep}>
|
|
@@ -140,27 +112,15 @@ const OnboardingWizard: React.FC<OnboardingWizardProps> = ({ onOpenCredentials,
|
|
|
140
112
|
</Button>
|
|
141
113
|
)}
|
|
142
114
|
{isLastStep ? (
|
|
143
|
-
<
|
|
144
|
-
onClick={complete}
|
|
145
|
-
style={{
|
|
146
|
-
backgroundColor: theme.dracula.green,
|
|
147
|
-
borderColor: theme.dracula.green,
|
|
148
|
-
}}
|
|
149
|
-
>
|
|
115
|
+
<ActionButton intent="run" onClick={complete}>
|
|
150
116
|
<Check className="h-4 w-4" />
|
|
151
117
|
Start Building
|
|
152
|
-
</
|
|
118
|
+
</ActionButton>
|
|
153
119
|
) : (
|
|
154
|
-
<
|
|
155
|
-
onClick={nextStep}
|
|
156
|
-
style={{
|
|
157
|
-
backgroundColor: theme.dracula.purple,
|
|
158
|
-
borderColor: theme.dracula.purple,
|
|
159
|
-
}}
|
|
160
|
-
>
|
|
120
|
+
<ActionButton intent="tools" onClick={nextStep}>
|
|
161
121
|
Next
|
|
162
122
|
<ArrowRight className="h-4 w-4" />
|
|
163
|
-
</
|
|
123
|
+
</ActionButton>
|
|
164
124
|
)}
|
|
165
125
|
</div>
|
|
166
126
|
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared role-to-Tailwind-classes map for the onboarding step cards.
|
|
3
|
+
* Each role keys into the existing `--node-X` triplet (soft bg + border
|
|
4
|
+
* + accent text). Co-located with the onboarding folder because both
|
|
5
|
+
* consumers (ConceptsStep, GetStartedStep) live here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type NodeRole = 'model' | 'skill' | 'agent' | 'workflow' | 'trigger';
|
|
9
|
+
|
|
10
|
+
export interface NodeRoleClasses {
|
|
11
|
+
/** Tinted background + border for the card surface. */
|
|
12
|
+
card: string;
|
|
13
|
+
/** Accent text colour matching the card's role. */
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const NODE_ROLE_CLASSES: Record<NodeRole, NodeRoleClasses> = {
|
|
18
|
+
model: { card: 'bg-node-model-soft border-node-model-border', text: 'text-node-model' },
|
|
19
|
+
skill: { card: 'bg-node-skill-soft border-node-skill-border', text: 'text-node-skill' },
|
|
20
|
+
agent: { card: 'bg-node-agent-soft border-node-agent-border', text: 'text-node-agent' },
|
|
21
|
+
workflow: { card: 'bg-node-workflow-soft border-node-workflow-border', text: 'text-node-workflow' },
|
|
22
|
+
trigger: { card: 'bg-node-trigger-soft border-node-trigger-border', text: 'text-node-trigger' },
|
|
23
|
+
};
|
|
@@ -19,13 +19,19 @@ const CanvasStep: React.FC = () => {
|
|
|
19
19
|
</p>
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
|
-
{/*
|
|
22
|
+
{/*
|
|
23
|
+
* Visual layout diagram. Region tints mirror the live UI's
|
|
24
|
+
* accent colours via the predefined --node-X-soft tokens — no
|
|
25
|
+
* opacity arithmetic at the call site. Each region maps to a
|
|
26
|
+
* node-group colour: toolbar→workflow (orange), sidebar→model
|
|
27
|
+
* (cyan), canvas→agent (purple), palette→skill (green),
|
|
28
|
+
* console→trigger (pink). Decorative use of the dracula text
|
|
29
|
+
* colours is permitted (tutorial diagram intentionally mirrors
|
|
30
|
+
* the live UI palette).
|
|
31
|
+
*/}
|
|
23
32
|
<div className="mb-4 overflow-hidden rounded-lg border border-border">
|
|
24
33
|
{/* Toolbar */}
|
|
25
|
-
<div
|
|
26
|
-
className="flex items-center gap-1.5 border-b border-border px-2.5 py-1.5"
|
|
27
|
-
style={{ backgroundColor: 'hsl(var(--dracula-orange) / 0.12)' }}
|
|
28
|
-
>
|
|
34
|
+
<div className="flex items-center gap-1.5 border-b border-border bg-node-workflow-soft px-2.5 py-1.5">
|
|
29
35
|
<Wrench className="h-3 w-3 text-dracula-orange" />
|
|
30
36
|
<span className="text-xs font-semibold text-dracula-orange">Toolbar</span>
|
|
31
37
|
<div className="flex-1" />
|
|
@@ -37,10 +43,7 @@ const CanvasStep: React.FC = () => {
|
|
|
37
43
|
{/* Main area */}
|
|
38
44
|
<div className="flex h-[120px]">
|
|
39
45
|
{/* Sidebar */}
|
|
40
|
-
<div
|
|
41
|
-
className="flex w-20 flex-col gap-1 border-r border-border p-1.5"
|
|
42
|
-
style={{ backgroundColor: 'hsl(var(--dracula-cyan) / 0.08)' }}
|
|
43
|
-
>
|
|
46
|
+
<div className="flex w-20 flex-col gap-1 border-r border-border bg-node-model-soft p-1.5">
|
|
44
47
|
<span className="text-[9px] font-semibold text-dracula-cyan">Sidebar</span>
|
|
45
48
|
{['Workflow 1', 'Workflow 2'].map((w) => (
|
|
46
49
|
<div key={w} className="rounded-sm bg-muted px-1 py-0.5 text-[8px] text-muted-foreground">
|
|
@@ -50,10 +53,7 @@ const CanvasStep: React.FC = () => {
|
|
|
50
53
|
</div>
|
|
51
54
|
|
|
52
55
|
{/* Canvas */}
|
|
53
|
-
<div
|
|
54
|
-
className="relative flex flex-1 items-center justify-center"
|
|
55
|
-
style={{ backgroundColor: 'hsl(var(--dracula-purple) / 0.06)' }}
|
|
56
|
-
>
|
|
56
|
+
<div className="relative flex flex-1 items-center justify-center bg-node-agent-soft">
|
|
57
57
|
<div className="text-center">
|
|
58
58
|
<Layout className="mx-auto h-5 w-5 text-dracula-purple opacity-50" />
|
|
59
59
|
<div className="mt-0.5 text-[9px] text-dracula-purple">Canvas</div>
|
|
@@ -61,10 +61,7 @@ const CanvasStep: React.FC = () => {
|
|
|
61
61
|
</div>
|
|
62
62
|
|
|
63
63
|
{/* Palette */}
|
|
64
|
-
<div
|
|
65
|
-
className="flex w-20 flex-col gap-1 border-l border-border p-1.5"
|
|
66
|
-
style={{ backgroundColor: 'hsl(var(--dracula-green) / 0.08)' }}
|
|
67
|
-
>
|
|
64
|
+
<div className="flex w-20 flex-col gap-1 border-l border-border bg-node-skill-soft p-1.5">
|
|
68
65
|
<span className="text-[9px] font-semibold text-dracula-green">Palette</span>
|
|
69
66
|
{['AI Agents', 'AI Models', 'Skills'].map((c) => (
|
|
70
67
|
<div key={c} className="rounded-sm bg-muted px-1 py-0.5 text-[8px] text-muted-foreground">
|
|
@@ -75,10 +72,7 @@ const CanvasStep: React.FC = () => {
|
|
|
75
72
|
</div>
|
|
76
73
|
|
|
77
74
|
{/* Console */}
|
|
78
|
-
<div
|
|
79
|
-
className="flex items-center gap-1.5 border-t border-border px-2.5 py-1.5"
|
|
80
|
-
style={{ backgroundColor: 'hsl(var(--dracula-pink) / 0.08)' }}
|
|
81
|
-
>
|
|
75
|
+
<div className="flex items-center gap-1.5 border-t border-border bg-node-trigger-soft px-2.5 py-1.5">
|
|
82
76
|
<Terminal className="h-3 w-3 text-dracula-pink" />
|
|
83
77
|
<span className="text-xs font-semibold text-dracula-pink">Console</span>
|
|
84
78
|
<div className="flex-1" />
|