@swarmclawai/swarmclaw 0.2.0
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 +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,940 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from 'react'
|
|
4
|
+
import { api } from '@/lib/api-client'
|
|
5
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
6
|
+
import type { ProviderType, Credential } from '@/types'
|
|
7
|
+
|
|
8
|
+
type WizardProvider =
|
|
9
|
+
| 'anthropic'
|
|
10
|
+
| 'openai'
|
|
11
|
+
| 'google'
|
|
12
|
+
| 'deepseek'
|
|
13
|
+
| 'groq'
|
|
14
|
+
| 'together'
|
|
15
|
+
| 'mistral'
|
|
16
|
+
| 'xai'
|
|
17
|
+
| 'fireworks'
|
|
18
|
+
| 'ollama'
|
|
19
|
+
| 'openclaw'
|
|
20
|
+
type CheckState = 'idle' | 'checking' | 'ok' | 'error'
|
|
21
|
+
|
|
22
|
+
interface WizardProviderOption {
|
|
23
|
+
id: WizardProvider
|
|
24
|
+
name: string
|
|
25
|
+
description: string
|
|
26
|
+
requiresKey: boolean
|
|
27
|
+
supportsEndpoint: boolean
|
|
28
|
+
defaultEndpoint?: string
|
|
29
|
+
keyUrl?: string
|
|
30
|
+
keyLabel?: string
|
|
31
|
+
keyPlaceholder?: string
|
|
32
|
+
optionalKey?: boolean
|
|
33
|
+
badge?: string
|
|
34
|
+
icon: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ProviderCheckResponse {
|
|
38
|
+
ok: boolean
|
|
39
|
+
message: string
|
|
40
|
+
normalizedEndpoint?: string
|
|
41
|
+
recommendedModel?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface SetupDoctorCheck {
|
|
45
|
+
id: string
|
|
46
|
+
label: string
|
|
47
|
+
status: 'pass' | 'warn' | 'fail'
|
|
48
|
+
detail: string
|
|
49
|
+
required?: boolean
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface SetupDoctorResponse {
|
|
53
|
+
ok: boolean
|
|
54
|
+
summary: string
|
|
55
|
+
checks: SetupDoctorCheck[]
|
|
56
|
+
actions?: string[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface SetupWizardProps {
|
|
60
|
+
onComplete: () => void
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const STARTER_AGENT_TOOLS = [
|
|
64
|
+
'memory',
|
|
65
|
+
'files',
|
|
66
|
+
'web_search',
|
|
67
|
+
'web_fetch',
|
|
68
|
+
'browser',
|
|
69
|
+
'manage_agents',
|
|
70
|
+
'manage_tasks',
|
|
71
|
+
'manage_schedules',
|
|
72
|
+
'manage_skills',
|
|
73
|
+
'manage_connectors',
|
|
74
|
+
'manage_sessions',
|
|
75
|
+
'manage_secrets',
|
|
76
|
+
'manage_documents',
|
|
77
|
+
'manage_webhooks',
|
|
78
|
+
'claude_code',
|
|
79
|
+
'codex_cli',
|
|
80
|
+
'opencode_cli',
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
const SWARMCLAW_ASSISTANT_PROMPT = `You are the default SwarmClaw assistant inside the SwarmClaw dashboard.
|
|
84
|
+
|
|
85
|
+
Primary objective:
|
|
86
|
+
- Help the user operate SwarmClaw itself before anything else.
|
|
87
|
+
|
|
88
|
+
When the user asks about SwarmClaw, prioritize concrete guidance with exact UI paths and commands:
|
|
89
|
+
- Sessions: create, configure provider/model, and run chats.
|
|
90
|
+
- Agents: create specialist agents/orchestrators, set provider/model, tools, and prompts.
|
|
91
|
+
- Providers: connect API keys/endpoints, troubleshoot auth/model issues.
|
|
92
|
+
- Tasks + Schedules: queue work and automate recurring runs.
|
|
93
|
+
- Skills + Connectors + Webhooks + Secrets + Memory: explain when to use each and how to configure safely.
|
|
94
|
+
|
|
95
|
+
Behavior:
|
|
96
|
+
- Be concise, direct, and action-oriented.
|
|
97
|
+
- If the request is ambiguous, ask one focused clarifying question.
|
|
98
|
+
- Prefer step-by-step instructions that can be executed immediately.
|
|
99
|
+
- When the user asks for direct execution (for example browsing, screenshots, research, or file edits), use available tools and return real results instead of only describing what to do.
|
|
100
|
+
- If a capability depends on provider/tool configuration, call that out explicitly.`
|
|
101
|
+
|
|
102
|
+
const PROVIDERS: WizardProviderOption[] = [
|
|
103
|
+
{
|
|
104
|
+
id: 'openai',
|
|
105
|
+
name: 'OpenAI',
|
|
106
|
+
description: 'Great default for most users. Fast, reliable GPT models.',
|
|
107
|
+
requiresKey: true,
|
|
108
|
+
supportsEndpoint: true,
|
|
109
|
+
defaultEndpoint: 'https://api.openai.com/v1',
|
|
110
|
+
keyUrl: 'https://platform.openai.com/api-keys',
|
|
111
|
+
keyLabel: 'platform.openai.com',
|
|
112
|
+
badge: 'Recommended',
|
|
113
|
+
icon: 'O',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'anthropic',
|
|
117
|
+
name: 'Anthropic',
|
|
118
|
+
description: 'Claude models — strong for coding, analysis, and long-form reasoning.',
|
|
119
|
+
requiresKey: true,
|
|
120
|
+
supportsEndpoint: false,
|
|
121
|
+
keyUrl: 'https://console.anthropic.com/settings/keys',
|
|
122
|
+
keyLabel: 'console.anthropic.com',
|
|
123
|
+
icon: 'A',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'google',
|
|
127
|
+
name: 'Google Gemini',
|
|
128
|
+
description: 'Gemini models with strong multimodal and coding support.',
|
|
129
|
+
requiresKey: true,
|
|
130
|
+
supportsEndpoint: false,
|
|
131
|
+
keyUrl: 'https://aistudio.google.com/app/apikey',
|
|
132
|
+
keyLabel: 'aistudio.google.com',
|
|
133
|
+
keyPlaceholder: 'AIza...',
|
|
134
|
+
icon: 'G',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'deepseek',
|
|
138
|
+
name: 'DeepSeek',
|
|
139
|
+
description: 'High-value reasoning and coding models from DeepSeek.',
|
|
140
|
+
requiresKey: true,
|
|
141
|
+
supportsEndpoint: false,
|
|
142
|
+
keyUrl: 'https://platform.deepseek.com/api_keys',
|
|
143
|
+
keyLabel: 'platform.deepseek.com',
|
|
144
|
+
icon: 'D',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'groq',
|
|
148
|
+
name: 'Groq',
|
|
149
|
+
description: 'Very fast inference with open and reasoning model options.',
|
|
150
|
+
requiresKey: true,
|
|
151
|
+
supportsEndpoint: false,
|
|
152
|
+
keyUrl: 'https://console.groq.com/keys',
|
|
153
|
+
keyLabel: 'console.groq.com',
|
|
154
|
+
icon: 'G',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 'together',
|
|
158
|
+
name: 'Together AI',
|
|
159
|
+
description: 'Broad catalog of open models with OpenAI-compatible APIs.',
|
|
160
|
+
requiresKey: true,
|
|
161
|
+
supportsEndpoint: false,
|
|
162
|
+
keyUrl: 'https://api.together.xyz/settings/api-keys',
|
|
163
|
+
keyLabel: 'api.together.xyz',
|
|
164
|
+
icon: 'T',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: 'mistral',
|
|
168
|
+
name: 'Mistral AI',
|
|
169
|
+
description: 'Efficient frontier models with strong latency and quality.',
|
|
170
|
+
requiresKey: true,
|
|
171
|
+
supportsEndpoint: false,
|
|
172
|
+
keyUrl: 'https://console.mistral.ai/api-keys/',
|
|
173
|
+
keyLabel: 'console.mistral.ai',
|
|
174
|
+
icon: 'M',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 'xai',
|
|
178
|
+
name: 'xAI (Grok)',
|
|
179
|
+
description: 'Grok models for fast answers, coding, and analysis.',
|
|
180
|
+
requiresKey: true,
|
|
181
|
+
supportsEndpoint: false,
|
|
182
|
+
keyUrl: 'https://console.x.ai',
|
|
183
|
+
keyLabel: 'console.x.ai',
|
|
184
|
+
icon: 'X',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'fireworks',
|
|
188
|
+
name: 'Fireworks AI',
|
|
189
|
+
description: 'Serverless and optimized open-model inference endpoints.',
|
|
190
|
+
requiresKey: true,
|
|
191
|
+
supportsEndpoint: false,
|
|
192
|
+
keyUrl: 'https://fireworks.ai/account/api-keys',
|
|
193
|
+
keyLabel: 'fireworks.ai',
|
|
194
|
+
icon: 'F',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: 'openclaw',
|
|
198
|
+
name: 'OpenClaw',
|
|
199
|
+
description: 'Connect to your local or remote OpenClaw gateway (multi-OpenClaw ready).',
|
|
200
|
+
requiresKey: false,
|
|
201
|
+
supportsEndpoint: true,
|
|
202
|
+
defaultEndpoint: 'http://localhost:18789/v1',
|
|
203
|
+
optionalKey: true,
|
|
204
|
+
badge: 'OpenClaw',
|
|
205
|
+
icon: 'C',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: 'ollama',
|
|
209
|
+
name: 'Ollama',
|
|
210
|
+
description: 'Run local open-source models. No API key required.',
|
|
211
|
+
requiresKey: false,
|
|
212
|
+
supportsEndpoint: true,
|
|
213
|
+
defaultEndpoint: 'http://localhost:11434',
|
|
214
|
+
badge: 'Local',
|
|
215
|
+
icon: 'L',
|
|
216
|
+
},
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
const DEFAULT_AGENTS: Record<WizardProvider, { name: string; description: string; systemPrompt: string; model: string; tools: string[] }> = {
|
|
220
|
+
anthropic: {
|
|
221
|
+
name: 'Assistant',
|
|
222
|
+
description: 'A helpful Claude-powered assistant.',
|
|
223
|
+
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
224
|
+
model: 'claude-sonnet-4-6',
|
|
225
|
+
tools: STARTER_AGENT_TOOLS,
|
|
226
|
+
},
|
|
227
|
+
openai: {
|
|
228
|
+
name: 'Assistant',
|
|
229
|
+
description: 'A helpful GPT-powered assistant.',
|
|
230
|
+
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
231
|
+
model: 'gpt-4o',
|
|
232
|
+
tools: STARTER_AGENT_TOOLS,
|
|
233
|
+
},
|
|
234
|
+
google: {
|
|
235
|
+
name: 'Assistant',
|
|
236
|
+
description: 'A helpful Gemini-powered assistant.',
|
|
237
|
+
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
238
|
+
model: 'gemini-2.5-pro',
|
|
239
|
+
tools: STARTER_AGENT_TOOLS,
|
|
240
|
+
},
|
|
241
|
+
deepseek: {
|
|
242
|
+
name: 'Assistant',
|
|
243
|
+
description: 'A helpful DeepSeek-powered assistant.',
|
|
244
|
+
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
245
|
+
model: 'deepseek-chat',
|
|
246
|
+
tools: STARTER_AGENT_TOOLS,
|
|
247
|
+
},
|
|
248
|
+
groq: {
|
|
249
|
+
name: 'Assistant',
|
|
250
|
+
description: 'A low-latency assistant powered by Groq.',
|
|
251
|
+
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
252
|
+
model: 'llama-3.3-70b-versatile',
|
|
253
|
+
tools: STARTER_AGENT_TOOLS,
|
|
254
|
+
},
|
|
255
|
+
together: {
|
|
256
|
+
name: 'Assistant',
|
|
257
|
+
description: 'A helpful assistant powered by Together AI.',
|
|
258
|
+
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
259
|
+
model: 'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8',
|
|
260
|
+
tools: STARTER_AGENT_TOOLS,
|
|
261
|
+
},
|
|
262
|
+
mistral: {
|
|
263
|
+
name: 'Assistant',
|
|
264
|
+
description: 'A helpful assistant powered by Mistral.',
|
|
265
|
+
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
266
|
+
model: 'mistral-large-latest',
|
|
267
|
+
tools: STARTER_AGENT_TOOLS,
|
|
268
|
+
},
|
|
269
|
+
xai: {
|
|
270
|
+
name: 'Assistant',
|
|
271
|
+
description: 'A helpful assistant powered by xAI Grok.',
|
|
272
|
+
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
273
|
+
model: 'grok-3',
|
|
274
|
+
tools: STARTER_AGENT_TOOLS,
|
|
275
|
+
},
|
|
276
|
+
fireworks: {
|
|
277
|
+
name: 'Assistant',
|
|
278
|
+
description: 'A helpful assistant powered by Fireworks AI.',
|
|
279
|
+
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
280
|
+
model: 'accounts/fireworks/models/deepseek-r1-0528',
|
|
281
|
+
tools: STARTER_AGENT_TOOLS,
|
|
282
|
+
},
|
|
283
|
+
ollama: {
|
|
284
|
+
name: 'Assistant',
|
|
285
|
+
description: 'A local assistant running through Ollama.',
|
|
286
|
+
systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
|
|
287
|
+
model: 'llama3',
|
|
288
|
+
tools: STARTER_AGENT_TOOLS,
|
|
289
|
+
},
|
|
290
|
+
openclaw: {
|
|
291
|
+
name: 'OpenClaw Operator',
|
|
292
|
+
description: 'A manager agent for talking to and coordinating OpenClaw instances.',
|
|
293
|
+
systemPrompt: 'You are an operator focused on reliable execution, clear status updates, and task completion.',
|
|
294
|
+
model: 'default',
|
|
295
|
+
tools: STARTER_AGENT_TOOLS,
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function SparkleIcon() {
|
|
300
|
+
return (
|
|
301
|
+
<div className="flex justify-center mb-6">
|
|
302
|
+
<div className="relative w-12 h-12">
|
|
303
|
+
<svg
|
|
304
|
+
width="48"
|
|
305
|
+
height="48"
|
|
306
|
+
viewBox="0 0 48 48"
|
|
307
|
+
fill="none"
|
|
308
|
+
className="text-accent-bright"
|
|
309
|
+
style={{ animation: 'sparkle-spin 8s linear infinite' }}
|
|
310
|
+
>
|
|
311
|
+
<path
|
|
312
|
+
d="M24 4L27.5 18.5L42 24L27.5 29.5L24 44L20.5 29.5L6 24L20.5 18.5L24 4Z"
|
|
313
|
+
fill="currentColor"
|
|
314
|
+
opacity="0.9"
|
|
315
|
+
/>
|
|
316
|
+
</svg>
|
|
317
|
+
<div className="absolute inset-0 blur-xl bg-accent-bright/20" />
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function StepDots({ current, total }: { current: number; total: number }) {
|
|
324
|
+
return (
|
|
325
|
+
<div className="flex items-center justify-center gap-2 mb-8">
|
|
326
|
+
{Array.from({ length: total }, (_, i) => (
|
|
327
|
+
<div
|
|
328
|
+
key={i}
|
|
329
|
+
className={`h-1.5 rounded-full transition-all duration-300 ${
|
|
330
|
+
i === current
|
|
331
|
+
? 'w-6 bg-accent-bright'
|
|
332
|
+
: i < current
|
|
333
|
+
? 'w-1.5 bg-accent-bright/50'
|
|
334
|
+
: 'w-1.5 bg-white/10'
|
|
335
|
+
}`}
|
|
336
|
+
/>
|
|
337
|
+
))}
|
|
338
|
+
</div>
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function SkipLink({ onClick }: { onClick: () => void }) {
|
|
343
|
+
return (
|
|
344
|
+
<button
|
|
345
|
+
onClick={onClick}
|
|
346
|
+
className="mt-8 text-[13px] text-text-3 hover:text-text-2 transition-colors cursor-pointer bg-transparent border-none"
|
|
347
|
+
>
|
|
348
|
+
Skip setup for now
|
|
349
|
+
</button>
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function ProviderBadge({ label }: { label?: string }) {
|
|
354
|
+
if (!label) return null
|
|
355
|
+
return (
|
|
356
|
+
<span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-accent-bright/15 text-accent-bright text-[10px] uppercase tracking-[0.08em] font-600">
|
|
357
|
+
{label}
|
|
358
|
+
</span>
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function getOpenClawErrorHint(message: string): string | null {
|
|
363
|
+
const lower = message.toLowerCase()
|
|
364
|
+
if (lower.includes('timeout') || lower.includes('timed out'))
|
|
365
|
+
return 'Ensure the port is open and reachable from this machine.'
|
|
366
|
+
if (lower.includes('401') || lower.includes('unauthorized'))
|
|
367
|
+
return 'Check your gateway auth token.'
|
|
368
|
+
if (lower.includes('405') || lower.includes('method not allowed'))
|
|
369
|
+
return 'Enable chatCompletions in your OpenClaw config: openclaw config set gateway.http.endpoints.chatCompletions.enabled true'
|
|
370
|
+
if (lower.includes('econnrefused') || lower.includes('connection refused') || lower.includes('connect econnrefused'))
|
|
371
|
+
return 'Verify that the OpenClaw gateway is running on the target host.'
|
|
372
|
+
return null
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function SetupWizard({ onComplete }: SetupWizardProps) {
|
|
376
|
+
const [step, setStep] = useState(0)
|
|
377
|
+
const [provider, setProvider] = useState<WizardProvider | null>(null)
|
|
378
|
+
const [endpoint, setEndpoint] = useState('')
|
|
379
|
+
const [apiKey, setApiKey] = useState('')
|
|
380
|
+
const [credentialId, setCredentialId] = useState<string | null>(null)
|
|
381
|
+
const [checkState, setCheckState] = useState<CheckState>('idle')
|
|
382
|
+
const [checkMessage, setCheckMessage] = useState('')
|
|
383
|
+
const [doctorState, setDoctorState] = useState<'idle' | 'checking' | 'done' | 'error'>('idle')
|
|
384
|
+
const [doctorError, setDoctorError] = useState('')
|
|
385
|
+
const [doctorReport, setDoctorReport] = useState<SetupDoctorResponse | null>(null)
|
|
386
|
+
const [saving, setSaving] = useState(false)
|
|
387
|
+
const [error, setError] = useState('')
|
|
388
|
+
|
|
389
|
+
const [agentName, setAgentName] = useState('')
|
|
390
|
+
const [agentDescription, setAgentDescription] = useState('')
|
|
391
|
+
const [agentPrompt, setAgentPrompt] = useState('')
|
|
392
|
+
const [agentModel, setAgentModel] = useState('')
|
|
393
|
+
|
|
394
|
+
const selectedProvider = useMemo(
|
|
395
|
+
() => PROVIDERS.find((p) => p.id === provider) || null,
|
|
396
|
+
[provider],
|
|
397
|
+
)
|
|
398
|
+
const totalSteps = 3
|
|
399
|
+
const requiresKey = selectedProvider?.requiresKey || false
|
|
400
|
+
const supportsEndpoint = selectedProvider?.supportsEndpoint || false
|
|
401
|
+
const keyIsOptional = selectedProvider?.optionalKey || false
|
|
402
|
+
const requiresVerifiedConnection = provider === 'openclaw'
|
|
403
|
+
|
|
404
|
+
const skip = async () => {
|
|
405
|
+
try {
|
|
406
|
+
await api('PUT', '/settings', { setupCompleted: true })
|
|
407
|
+
} catch {
|
|
408
|
+
// Continue anyway.
|
|
409
|
+
}
|
|
410
|
+
onComplete()
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const selectProvider = (next: WizardProvider) => {
|
|
414
|
+
const defaults = DEFAULT_AGENTS[next]
|
|
415
|
+
const meta = PROVIDERS.find((p) => p.id === next)
|
|
416
|
+
|
|
417
|
+
setProvider(next)
|
|
418
|
+
setEndpoint(meta?.defaultEndpoint || '')
|
|
419
|
+
setApiKey('')
|
|
420
|
+
setCredentialId(null)
|
|
421
|
+
setCheckState('idle')
|
|
422
|
+
setCheckMessage('')
|
|
423
|
+
setError('')
|
|
424
|
+
|
|
425
|
+
setAgentName(defaults.name)
|
|
426
|
+
setAgentDescription(defaults.description)
|
|
427
|
+
setAgentPrompt(defaults.systemPrompt)
|
|
428
|
+
setAgentModel(defaults.model)
|
|
429
|
+
|
|
430
|
+
setStep(1)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const runConnectionCheck = async (): Promise<boolean> => {
|
|
434
|
+
if (!provider || !selectedProvider) return false
|
|
435
|
+
if (requiresKey && !apiKey.trim()) {
|
|
436
|
+
setCheckState('error')
|
|
437
|
+
setCheckMessage('Please paste your API key first.')
|
|
438
|
+
return false
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
setCheckState('checking')
|
|
442
|
+
setCheckMessage('')
|
|
443
|
+
setError('')
|
|
444
|
+
try {
|
|
445
|
+
const result = await api<ProviderCheckResponse>('POST', '/setup/check-provider', {
|
|
446
|
+
provider,
|
|
447
|
+
apiKey: apiKey.trim() || undefined,
|
|
448
|
+
endpoint: supportsEndpoint ? endpoint.trim() || undefined : undefined,
|
|
449
|
+
model: agentModel.trim() || undefined,
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
if (result.normalizedEndpoint && supportsEndpoint) {
|
|
453
|
+
setEndpoint(result.normalizedEndpoint)
|
|
454
|
+
}
|
|
455
|
+
if (result.recommendedModel && provider) {
|
|
456
|
+
const currentModel = agentModel.trim()
|
|
457
|
+
const defaultModel = DEFAULT_AGENTS[provider].model
|
|
458
|
+
if (!currentModel || currentModel === defaultModel) {
|
|
459
|
+
setAgentModel(result.recommendedModel)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
setCheckState(result.ok ? 'ok' : 'error')
|
|
463
|
+
setCheckMessage(result.message || (result.ok ? 'Connected successfully.' : 'Connection failed.'))
|
|
464
|
+
return !!result.ok
|
|
465
|
+
} catch (err: any) {
|
|
466
|
+
setCheckState('error')
|
|
467
|
+
setCheckMessage(err?.message || 'Connection check failed.')
|
|
468
|
+
return false
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const runSetupDoctor = async () => {
|
|
473
|
+
setDoctorState('checking')
|
|
474
|
+
setDoctorError('')
|
|
475
|
+
try {
|
|
476
|
+
const report = await api<SetupDoctorResponse>('GET', '/setup/doctor')
|
|
477
|
+
setDoctorReport(report)
|
|
478
|
+
setDoctorState('done')
|
|
479
|
+
} catch (err: any) {
|
|
480
|
+
setDoctorState('error')
|
|
481
|
+
setDoctorReport(null)
|
|
482
|
+
setDoctorError(err?.message || 'Failed to run setup diagnostics.')
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const saveProviderAndContinue = async () => {
|
|
487
|
+
if (!provider || !selectedProvider) return
|
|
488
|
+
if (requiresKey && !apiKey.trim()) {
|
|
489
|
+
setError('This provider requires an API key.')
|
|
490
|
+
return
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
setSaving(true)
|
|
494
|
+
setError('')
|
|
495
|
+
try {
|
|
496
|
+
if (requiresVerifiedConnection && checkState !== 'ok') {
|
|
497
|
+
const ok = await runConnectionCheck()
|
|
498
|
+
if (!ok) {
|
|
499
|
+
setError('OpenClaw must pass connection verification before continuing.')
|
|
500
|
+
return
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
let nextCredentialId = credentialId
|
|
505
|
+
const shouldSaveCredential = !!apiKey.trim() && (requiresKey || keyIsOptional)
|
|
506
|
+
|
|
507
|
+
if (shouldSaveCredential && !nextCredentialId) {
|
|
508
|
+
const cred = await api<Credential>('POST', '/credentials', {
|
|
509
|
+
provider,
|
|
510
|
+
name: `${selectedProvider.name} key`,
|
|
511
|
+
apiKey: apiKey.trim(),
|
|
512
|
+
})
|
|
513
|
+
nextCredentialId = cred.id
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
setCredentialId(nextCredentialId || null)
|
|
517
|
+
setStep(2)
|
|
518
|
+
} catch (err: any) {
|
|
519
|
+
setError(err?.message || 'Failed to save provider setup.')
|
|
520
|
+
} finally {
|
|
521
|
+
setSaving(false)
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const createStarterAgent = async () => {
|
|
526
|
+
if (!provider || !agentName.trim()) return
|
|
527
|
+
if (requiresVerifiedConnection && checkState !== 'ok') {
|
|
528
|
+
setError('OpenClaw connection is not verified. Go back and run the connection check.')
|
|
529
|
+
setStep(1)
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
setSaving(true)
|
|
533
|
+
setError('')
|
|
534
|
+
try {
|
|
535
|
+
const payload: Record<string, unknown> = {
|
|
536
|
+
name: agentName.trim(),
|
|
537
|
+
description: agentDescription.trim(),
|
|
538
|
+
systemPrompt: agentPrompt.trim(),
|
|
539
|
+
provider: provider as ProviderType,
|
|
540
|
+
model: agentModel.trim() || DEFAULT_AGENTS[provider].model,
|
|
541
|
+
credentialId: credentialId || null,
|
|
542
|
+
tools: DEFAULT_AGENTS[provider].tools,
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (supportsEndpoint && endpoint.trim()) {
|
|
546
|
+
payload.apiEndpoint = endpoint.trim()
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const agents = await api<Record<string, { id: string }>>('GET', '/agents')
|
|
550
|
+
const canReuseDefault =
|
|
551
|
+
!!agents.default
|
|
552
|
+
&& Object.keys(agents).length === 1
|
|
553
|
+
&& agentName.trim().toLowerCase() === 'assistant'
|
|
554
|
+
|
|
555
|
+
const agentId = canReuseDefault
|
|
556
|
+
? 'default'
|
|
557
|
+
: (await api<{ id: string }>('POST', '/agents', payload)).id
|
|
558
|
+
|
|
559
|
+
if (canReuseDefault) {
|
|
560
|
+
await api('PUT', '/agents/default', payload)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const appState = useAppStore.getState()
|
|
564
|
+
const currentUser = appState.currentUser
|
|
565
|
+
if (currentUser && agentId) {
|
|
566
|
+
const sessionMap = await api<Record<string, { id: string; name: string; user: string }>>('GET', '/sessions')
|
|
567
|
+
const existingMain = Object.values(sessionMap).find((s) => s.name === '__main__' && s.user === currentUser)
|
|
568
|
+
const mainId = existingMain?.id || `main-${currentUser}`
|
|
569
|
+
const selectedModel = (payload.model as string) || DEFAULT_AGENTS[provider].model
|
|
570
|
+
const selectedEndpoint = supportsEndpoint ? (payload.apiEndpoint as string | undefined) || null : null
|
|
571
|
+
const selectedTools = Array.isArray(payload.tools) ? payload.tools as string[] : DEFAULT_AGENTS[provider].tools
|
|
572
|
+
|
|
573
|
+
if (!existingMain) {
|
|
574
|
+
await api('POST', '/sessions', {
|
|
575
|
+
id: mainId,
|
|
576
|
+
name: '__main__',
|
|
577
|
+
user: currentUser,
|
|
578
|
+
agentId,
|
|
579
|
+
provider,
|
|
580
|
+
model: selectedModel,
|
|
581
|
+
credentialId: credentialId || null,
|
|
582
|
+
apiEndpoint: selectedEndpoint,
|
|
583
|
+
tools: selectedTools,
|
|
584
|
+
heartbeatEnabled: true,
|
|
585
|
+
})
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
await api('PUT', `/sessions/${mainId}`, {
|
|
589
|
+
agentId,
|
|
590
|
+
provider,
|
|
591
|
+
model: selectedModel,
|
|
592
|
+
credentialId: credentialId || null,
|
|
593
|
+
apiEndpoint: selectedEndpoint,
|
|
594
|
+
tools: selectedTools,
|
|
595
|
+
heartbeatEnabled: true,
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
await appState.loadSessions()
|
|
599
|
+
appState.setCurrentSession(mainId)
|
|
600
|
+
}
|
|
601
|
+
await api('PUT', '/settings', { setupCompleted: true })
|
|
602
|
+
onComplete()
|
|
603
|
+
} catch (err: any) {
|
|
604
|
+
setError(err?.message || 'Failed to create starter assistant.')
|
|
605
|
+
} finally {
|
|
606
|
+
setSaving(false)
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return (
|
|
611
|
+
<div className="h-full flex flex-col items-center justify-center px-8 bg-bg relative overflow-hidden">
|
|
612
|
+
<div className="absolute inset-0 pointer-events-none">
|
|
613
|
+
<div
|
|
614
|
+
className="absolute top-[30%] left-[50%] -translate-x-1/2 -translate-y-1/2 w-[600px] h-[400px]"
|
|
615
|
+
style={{
|
|
616
|
+
background: 'radial-gradient(ellipse at center, rgba(99,102,241,0.06) 0%, transparent 70%)',
|
|
617
|
+
animation: 'glow-pulse 6s ease-in-out infinite',
|
|
618
|
+
}}
|
|
619
|
+
/>
|
|
620
|
+
</div>
|
|
621
|
+
|
|
622
|
+
<div
|
|
623
|
+
className="relative max-w-[520px] w-full text-center"
|
|
624
|
+
style={{ animation: 'fade-in 0.6s cubic-bezier(0.16, 1, 0.3, 1)' }}
|
|
625
|
+
>
|
|
626
|
+
<SparkleIcon />
|
|
627
|
+
<StepDots current={step} total={totalSteps} />
|
|
628
|
+
|
|
629
|
+
{step === 0 && (
|
|
630
|
+
<>
|
|
631
|
+
<h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
632
|
+
2-Minute Setup
|
|
633
|
+
</h1>
|
|
634
|
+
<p className="text-[15px] text-text-2 mb-2">
|
|
635
|
+
No coding required. Pick a provider, paste a key if needed, and start chatting.
|
|
636
|
+
</p>
|
|
637
|
+
<p className="text-[13px] text-text-3 mb-8">
|
|
638
|
+
You can change providers, models, and agent settings anytime later.
|
|
639
|
+
</p>
|
|
640
|
+
|
|
641
|
+
<div className="flex flex-col gap-3 max-h-[44vh] overflow-y-auto pr-1">
|
|
642
|
+
{PROVIDERS.map((p) => (
|
|
643
|
+
<button
|
|
644
|
+
key={p.id}
|
|
645
|
+
onClick={() => selectProvider(p.id)}
|
|
646
|
+
className="w-full px-5 py-4 rounded-[14px] border border-white/[0.08] bg-surface text-left
|
|
647
|
+
cursor-pointer hover:border-accent-bright/30 hover:bg-surface-hover transition-all duration-200
|
|
648
|
+
flex items-start gap-4"
|
|
649
|
+
>
|
|
650
|
+
<div className="w-10 h-10 rounded-[10px] bg-white/[0.04] border border-white/[0.06] flex items-center justify-center shrink-0 mt-0.5">
|
|
651
|
+
<span className="text-[16px] font-display font-700 text-accent-bright">
|
|
652
|
+
{p.icon}
|
|
653
|
+
</span>
|
|
654
|
+
</div>
|
|
655
|
+
<div>
|
|
656
|
+
<div className="text-[15px] font-display font-600 text-text mb-1">
|
|
657
|
+
{p.name}
|
|
658
|
+
<ProviderBadge label={p.badge} />
|
|
659
|
+
</div>
|
|
660
|
+
<div className="text-[13px] text-text-3 leading-relaxed">{p.description}</div>
|
|
661
|
+
{!p.requiresKey && (
|
|
662
|
+
<div className="mt-1.5 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-400 text-[11px] font-500">
|
|
663
|
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
|
|
664
|
+
No API key required
|
|
665
|
+
</div>
|
|
666
|
+
)}
|
|
667
|
+
</div>
|
|
668
|
+
</button>
|
|
669
|
+
))}
|
|
670
|
+
</div>
|
|
671
|
+
|
|
672
|
+
<div className="mt-4 text-left">
|
|
673
|
+
<button
|
|
674
|
+
onClick={runSetupDoctor}
|
|
675
|
+
disabled={doctorState === 'checking'}
|
|
676
|
+
className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-white/[0.02] text-[13px] text-text-2
|
|
677
|
+
cursor-pointer hover:bg-white/[0.05] transition-all duration-200 disabled:opacity-40"
|
|
678
|
+
>
|
|
679
|
+
{doctorState === 'checking' ? 'Running System Check...' : 'Run System Check'}
|
|
680
|
+
</button>
|
|
681
|
+
|
|
682
|
+
{doctorState === 'error' && doctorError && (
|
|
683
|
+
<p className="mt-2 text-[12px] text-red-300">{doctorError}</p>
|
|
684
|
+
)}
|
|
685
|
+
|
|
686
|
+
{doctorReport && doctorState === 'done' && (
|
|
687
|
+
<div className="mt-3 p-3 rounded-[12px] border border-white/[0.08] bg-surface">
|
|
688
|
+
<div className={`text-[12px] font-600 ${doctorReport.ok ? 'text-emerald-300' : 'text-amber-300'}`}>
|
|
689
|
+
{doctorReport.summary}
|
|
690
|
+
</div>
|
|
691
|
+
{doctorReport.checks.filter((c) => c.status !== 'pass').slice(0, 3).map((check) => (
|
|
692
|
+
<div key={check.id} className="mt-1 text-[11px] text-text-3">
|
|
693
|
+
- {check.label}: {check.detail}
|
|
694
|
+
</div>
|
|
695
|
+
))}
|
|
696
|
+
{!!doctorReport.actions?.length && (
|
|
697
|
+
<div className="mt-2 text-[11px] text-text-3/80">
|
|
698
|
+
Next: {doctorReport.actions.slice(0, 2).join(' ')}
|
|
699
|
+
</div>
|
|
700
|
+
)}
|
|
701
|
+
</div>
|
|
702
|
+
)}
|
|
703
|
+
</div>
|
|
704
|
+
|
|
705
|
+
<SkipLink onClick={skip} />
|
|
706
|
+
</>
|
|
707
|
+
)}
|
|
708
|
+
|
|
709
|
+
{step === 1 && provider && selectedProvider && (
|
|
710
|
+
<>
|
|
711
|
+
<h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
712
|
+
Connect {selectedProvider.name}
|
|
713
|
+
</h1>
|
|
714
|
+
<p className="text-[15px] text-text-2 mb-2">
|
|
715
|
+
Add only what is needed for this provider, then check connection.
|
|
716
|
+
</p>
|
|
717
|
+
<p className="text-[13px] text-text-3 mb-7">
|
|
718
|
+
{requiresVerifiedConnection
|
|
719
|
+
? 'OpenClaw must pass connection check before you can continue.'
|
|
720
|
+
: 'You can keep going even if the check fails and fix details later.'}
|
|
721
|
+
</p>
|
|
722
|
+
|
|
723
|
+
<div className="flex flex-col gap-3 text-left mb-4">
|
|
724
|
+
{supportsEndpoint && (
|
|
725
|
+
<div>
|
|
726
|
+
<label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">
|
|
727
|
+
Endpoint
|
|
728
|
+
</label>
|
|
729
|
+
<input
|
|
730
|
+
type="text"
|
|
731
|
+
value={endpoint}
|
|
732
|
+
onChange={(e) => { setEndpoint(e.target.value); setCheckState('idle'); setCheckMessage('') }}
|
|
733
|
+
placeholder={selectedProvider.defaultEndpoint || ''}
|
|
734
|
+
className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-surface
|
|
735
|
+
text-text text-[14px] font-mono outline-none transition-all duration-200
|
|
736
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
737
|
+
/>
|
|
738
|
+
{provider === 'openclaw' && (
|
|
739
|
+
<div className="mt-2 space-y-0.5">
|
|
740
|
+
<p className="text-[12px] text-text-3">Works with local (<code className="text-text-2">localhost:18789</code>) or remote OpenClaw instances.</p>
|
|
741
|
+
<p className="text-[12px] text-text-3">For remote: use your server's IP/domain with port (e.g. <code className="text-text-2">http://your-server:60924/v1</code>).</p>
|
|
742
|
+
<p className="text-[12px] text-text-3 mt-1">
|
|
743
|
+
<a href="/docs/openclaw-setup" target="_blank" rel="noopener noreferrer" className="text-accent-bright hover:underline">
|
|
744
|
+
Setup guide →
|
|
745
|
+
</a>
|
|
746
|
+
</p>
|
|
747
|
+
</div>
|
|
748
|
+
)}
|
|
749
|
+
</div>
|
|
750
|
+
)}
|
|
751
|
+
|
|
752
|
+
{(requiresKey || keyIsOptional) && (
|
|
753
|
+
<div>
|
|
754
|
+
<label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">
|
|
755
|
+
{keyIsOptional ? 'Token (optional)' : 'API key'}
|
|
756
|
+
</label>
|
|
757
|
+
<input
|
|
758
|
+
type="password"
|
|
759
|
+
value={apiKey}
|
|
760
|
+
onChange={(e) => { setApiKey(e.target.value); setCheckState('idle'); setCheckMessage(''); setError('') }}
|
|
761
|
+
placeholder={selectedProvider.keyPlaceholder || (provider === 'openclaw' ? 'Paste OpenClaw bearer token' : 'sk-...')}
|
|
762
|
+
className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-surface
|
|
763
|
+
text-text text-[14px] font-mono outline-none transition-all duration-200
|
|
764
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
765
|
+
/>
|
|
766
|
+
{selectedProvider.keyUrl && (
|
|
767
|
+
<p className="text-[11px] text-text-3 mt-1.5">
|
|
768
|
+
Get one at{' '}
|
|
769
|
+
<a
|
|
770
|
+
href={selectedProvider.keyUrl}
|
|
771
|
+
target="_blank"
|
|
772
|
+
rel="noopener noreferrer"
|
|
773
|
+
className="text-accent-bright hover:underline"
|
|
774
|
+
>
|
|
775
|
+
{selectedProvider.keyLabel}
|
|
776
|
+
</a>
|
|
777
|
+
</p>
|
|
778
|
+
)}
|
|
779
|
+
</div>
|
|
780
|
+
)}
|
|
781
|
+
</div>
|
|
782
|
+
|
|
783
|
+
{checkState !== 'idle' && (
|
|
784
|
+
<div
|
|
785
|
+
className={`mb-4 px-3 py-2 rounded-[10px] text-[12px] border ${
|
|
786
|
+
checkState === 'ok'
|
|
787
|
+
? 'bg-emerald-500/10 border-emerald-500/25 text-emerald-300'
|
|
788
|
+
: checkState === 'checking'
|
|
789
|
+
? 'bg-white/[0.03] border-white/[0.08] text-text-2'
|
|
790
|
+
: 'bg-red-500/10 border-red-500/25 text-red-300'
|
|
791
|
+
}`}
|
|
792
|
+
>
|
|
793
|
+
{checkState === 'checking' ? 'Checking connection...' : checkMessage}
|
|
794
|
+
{checkState === 'error' && provider === 'openclaw' && (() => {
|
|
795
|
+
const hint = getOpenClawErrorHint(checkMessage)
|
|
796
|
+
return hint ? <p className="mt-1.5 text-[11px] text-text-3">{hint}</p> : null
|
|
797
|
+
})()}
|
|
798
|
+
</div>
|
|
799
|
+
)}
|
|
800
|
+
|
|
801
|
+
{error && <p className="mb-4 text-[13px] text-red-400">{error}</p>}
|
|
802
|
+
|
|
803
|
+
<div className="flex items-center justify-center gap-3">
|
|
804
|
+
<button
|
|
805
|
+
onClick={() => { setStep(0); setError('') }}
|
|
806
|
+
className="px-6 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px]
|
|
807
|
+
font-display font-500 cursor-pointer hover:bg-white/[0.03] transition-all duration-200"
|
|
808
|
+
>
|
|
809
|
+
Back
|
|
810
|
+
</button>
|
|
811
|
+
<button
|
|
812
|
+
onClick={runConnectionCheck}
|
|
813
|
+
disabled={checkState === 'checking' || saving}
|
|
814
|
+
className="px-6 py-3.5 rounded-[14px] border border-white/[0.08] bg-white/[0.03] text-text text-[14px]
|
|
815
|
+
font-display font-600 cursor-pointer hover:bg-white/[0.06] transition-all duration-200 disabled:opacity-40"
|
|
816
|
+
>
|
|
817
|
+
{checkState === 'checking' ? 'Checking...' : 'Check Connection'}
|
|
818
|
+
</button>
|
|
819
|
+
<button
|
|
820
|
+
onClick={saveProviderAndContinue}
|
|
821
|
+
disabled={(requiresKey && !apiKey.trim()) || saving}
|
|
822
|
+
className="px-8 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-display font-600
|
|
823
|
+
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
824
|
+
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
825
|
+
>
|
|
826
|
+
{saving
|
|
827
|
+
? 'Saving...'
|
|
828
|
+
: requiresVerifiedConnection
|
|
829
|
+
? 'Verify & Continue'
|
|
830
|
+
: 'Save & Continue'}
|
|
831
|
+
</button>
|
|
832
|
+
</div>
|
|
833
|
+
|
|
834
|
+
<SkipLink onClick={skip} />
|
|
835
|
+
</>
|
|
836
|
+
)}
|
|
837
|
+
|
|
838
|
+
{step === 2 && provider && selectedProvider && (
|
|
839
|
+
<>
|
|
840
|
+
<h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
841
|
+
You're Ready
|
|
842
|
+
</h1>
|
|
843
|
+
<p className="text-[15px] text-text-2 mb-7">
|
|
844
|
+
We'll create a starter assistant so you can begin immediately.
|
|
845
|
+
</p>
|
|
846
|
+
|
|
847
|
+
<div className="mb-5 p-4 rounded-[14px] border border-white/[0.08] bg-surface text-left">
|
|
848
|
+
<div className="text-[12px] uppercase tracking-[0.08em] text-text-3 mb-2">Setup Summary</div>
|
|
849
|
+
<div className="text-[14px] text-text mb-1">Provider: {selectedProvider.name}</div>
|
|
850
|
+
{supportsEndpoint && endpoint.trim() && (
|
|
851
|
+
<div className="text-[12px] font-mono text-text-3 break-all">Endpoint: {endpoint.trim()}</div>
|
|
852
|
+
)}
|
|
853
|
+
{checkState === 'ok' && (
|
|
854
|
+
<div className="mt-2 text-[12px] text-emerald-300">{checkMessage}</div>
|
|
855
|
+
)}
|
|
856
|
+
{checkState === 'error' && (
|
|
857
|
+
<div className="mt-2 text-[12px] text-amber-300">Connection was not verified. You can still continue.</div>
|
|
858
|
+
)}
|
|
859
|
+
</div>
|
|
860
|
+
|
|
861
|
+
<details className="mb-6 text-left rounded-[14px] border border-white/[0.08] bg-surface px-4 py-3">
|
|
862
|
+
<summary className="cursor-pointer text-[13px] text-text-2 font-600">
|
|
863
|
+
Advanced agent settings (optional)
|
|
864
|
+
</summary>
|
|
865
|
+
<div className="mt-3 space-y-3">
|
|
866
|
+
<div>
|
|
867
|
+
<label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">Name</label>
|
|
868
|
+
<input
|
|
869
|
+
type="text"
|
|
870
|
+
value={agentName}
|
|
871
|
+
onChange={(e) => setAgentName(e.target.value)}
|
|
872
|
+
className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-bg
|
|
873
|
+
text-text text-[14px] outline-none transition-all duration-200
|
|
874
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
875
|
+
/>
|
|
876
|
+
</div>
|
|
877
|
+
<div>
|
|
878
|
+
<label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">Description</label>
|
|
879
|
+
<input
|
|
880
|
+
type="text"
|
|
881
|
+
value={agentDescription}
|
|
882
|
+
onChange={(e) => setAgentDescription(e.target.value)}
|
|
883
|
+
className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-bg
|
|
884
|
+
text-text text-[14px] outline-none transition-all duration-200
|
|
885
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
886
|
+
/>
|
|
887
|
+
</div>
|
|
888
|
+
<div>
|
|
889
|
+
<label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">System Prompt</label>
|
|
890
|
+
<textarea
|
|
891
|
+
value={agentPrompt}
|
|
892
|
+
onChange={(e) => setAgentPrompt(e.target.value)}
|
|
893
|
+
rows={3}
|
|
894
|
+
className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-bg
|
|
895
|
+
text-text text-[14px] outline-none transition-all duration-200 resize-none
|
|
896
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
897
|
+
/>
|
|
898
|
+
</div>
|
|
899
|
+
<div>
|
|
900
|
+
<label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">Model</label>
|
|
901
|
+
<input
|
|
902
|
+
type="text"
|
|
903
|
+
value={agentModel}
|
|
904
|
+
onChange={(e) => setAgentModel(e.target.value)}
|
|
905
|
+
className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-bg
|
|
906
|
+
text-text text-[14px] font-mono outline-none transition-all duration-200
|
|
907
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
908
|
+
/>
|
|
909
|
+
</div>
|
|
910
|
+
</div>
|
|
911
|
+
</details>
|
|
912
|
+
|
|
913
|
+
{error && <p className="mb-4 text-[13px] text-red-400">{error}</p>}
|
|
914
|
+
|
|
915
|
+
<div className="flex items-center justify-center gap-3">
|
|
916
|
+
<button
|
|
917
|
+
onClick={() => { setStep(1); setError('') }}
|
|
918
|
+
className="px-6 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px]
|
|
919
|
+
font-display font-500 cursor-pointer hover:bg-white/[0.03] transition-all duration-200"
|
|
920
|
+
>
|
|
921
|
+
Back
|
|
922
|
+
</button>
|
|
923
|
+
<button
|
|
924
|
+
onClick={createStarterAgent}
|
|
925
|
+
disabled={!agentName.trim() || saving}
|
|
926
|
+
className="px-10 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-display font-600
|
|
927
|
+
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
928
|
+
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
929
|
+
>
|
|
930
|
+
{saving ? 'Creating...' : 'Create Starter Assistant'}
|
|
931
|
+
</button>
|
|
932
|
+
</div>
|
|
933
|
+
|
|
934
|
+
<SkipLink onClick={skip} />
|
|
935
|
+
</>
|
|
936
|
+
)}
|
|
937
|
+
</div>
|
|
938
|
+
</div>
|
|
939
|
+
)
|
|
940
|
+
}
|