refactor(setup): remove channel step from setup wizard
- Remove channel connection step from onboarding flow (6 steps -> 5 steps) - Users can now start using ClawX immediately - Channel configuration moved to Settings > Channels (future) - Update architecture doc to reflect simplified setup flow - Reduces onboarding friction for new users
This commit is contained in:
@@ -576,13 +576,13 @@ clawx/
|
|||||||
│ │ │ ├── index.tsx
|
│ │ │ ├── index.tsx
|
||||||
│ │ │ ├── GeneralSettings.tsx
|
│ │ │ ├── GeneralSettings.tsx
|
||||||
│ │ │ ├── ProviderSettings.tsx
|
│ │ │ ├── ProviderSettings.tsx
|
||||||
|
│ │ │ ├── ChannelsSettings.tsx # 通道连接配置 (从安装向导移出)
|
||||||
│ │ │ └── AdvancedSettings.tsx
|
│ │ │ └── AdvancedSettings.tsx
|
||||||
│ │ └── Setup/ # 安装向导
|
│ │ └── Setup/ # 安装向导 (简化版,不含通道连接)
|
||||||
│ │ ├── index.tsx
|
│ │ ├── index.tsx
|
||||||
│ │ ├── WelcomeStep.tsx
|
│ │ ├── WelcomeStep.tsx
|
||||||
│ │ ├── RuntimeStep.tsx
|
│ │ ├── RuntimeStep.tsx
|
||||||
│ │ ├── ProviderStep.tsx
|
│ │ ├── ProviderStep.tsx
|
||||||
│ │ ├── ChannelStep.tsx
|
|
||||||
│ │ └── SkillStep.tsx
|
│ │ └── SkillStep.tsx
|
||||||
│ │
|
│ │
|
||||||
│ ├── components/ # 通用组件
|
│ ├── components/ # 通用组件
|
||||||
@@ -812,12 +812,8 @@ const steps: SetupStep[] = [
|
|||||||
description: '配置您的 AI 服务提供商',
|
description: '配置您的 AI 服务提供商',
|
||||||
component: ProviderStep,
|
component: ProviderStep,
|
||||||
},
|
},
|
||||||
{
|
// NOTE: Channel step removed - 通道连接移至 Settings > Channels 页面
|
||||||
id: 'channel',
|
// 用户可在完成初始设置后自行配置消息通道
|
||||||
title: '连接消息应用',
|
|
||||||
description: '绑定 WhatsApp、Telegram 等',
|
|
||||||
component: ChannelStep,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'skills',
|
id: 'skills',
|
||||||
title: '选择技能包',
|
title: '选择技能包',
|
||||||
@@ -4042,13 +4038,15 @@ ClawX 版本: X.Y.Z[-prerelease]
|
|||||||
| Node.js 自动检测/安装 | P0 | ⬜ |
|
| Node.js 自动检测/安装 | P0 | ⬜ |
|
||||||
| openclaw npm 安装 | P0 | ⬜ |
|
| openclaw npm 安装 | P0 | ⬜ |
|
||||||
| Provider 配置 (API Key) | P0 | ⬜ |
|
| Provider 配置 (API Key) | P0 | ⬜ |
|
||||||
| 首个通道连接 (WhatsApp QR) | P1 | ⬜ |
|
|
||||||
| 错误处理与提示 | P1 | ⬜ |
|
| 错误处理与提示 | P1 | ⬜ |
|
||||||
|
|
||||||
|
> **注意**: 通道连接功能 (WhatsApp/Telegram 等) 已从安装向导移至 Settings > Channels 页面。
|
||||||
|
> 用户可在完成初始设置后,根据需要自行配置消息通道,降低首次使用门槛。
|
||||||
|
|
||||||
**交付物**:
|
**交付物**:
|
||||||
- 完整安装向导流程
|
- 简化版安装向导流程 (不含通道连接)
|
||||||
- 支持 macOS (Apple Silicon + Intel)
|
- 支持 macOS (Apple Silicon + Intel)
|
||||||
- 可配置 Anthropic/OpenAI
|
- 可配置 Anthropic/OpenAI/OpenRouter
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
67
build_process/commit_13_remove_channel_setup.md
Normal file
67
build_process/commit_13_remove_channel_setup.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Commit 13: Remove Channel Setup Step
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Simplified the setup wizard by removing the channel connection step. Users can now start using ClawX immediately and configure messaging channels later in Settings.
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
- Channel connection (WhatsApp, Telegram, etc.) is complex and requires external platform configuration
|
||||||
|
- Not required for core functionality - users can use the built-in chat interface directly
|
||||||
|
- Reduces onboarding friction for new users
|
||||||
|
- Progressive disclosure - advanced features available when needed
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### 1. Setup Wizard (`src/pages/Setup/index.tsx`)
|
||||||
|
|
||||||
|
**Steps reduced from 6 to 5:**
|
||||||
|
|
||||||
|
| Before | After |
|
||||||
|
|--------|-------|
|
||||||
|
| 0: Welcome | 0: Welcome |
|
||||||
|
| 1: Runtime | 1: Runtime |
|
||||||
|
| 2: Provider | 2: Provider |
|
||||||
|
| 3: Channel | (removed) |
|
||||||
|
| 4: Skills | 3: Skills |
|
||||||
|
| 5: Complete | 4: Complete |
|
||||||
|
|
||||||
|
**Code changes:**
|
||||||
|
- Removed `channel` step from `steps` array
|
||||||
|
- Updated `currentStep` indices for content rendering
|
||||||
|
- Updated `useEffect` for `canProceed` validation
|
||||||
|
- Removed `selectedChannel` state variable
|
||||||
|
- Removed `ChannelContent` component and `Channel` interface
|
||||||
|
- Updated `CompleteContent` to remove channel row
|
||||||
|
- Added note about configuring channels in Settings
|
||||||
|
|
||||||
|
### 2. Architecture Document (`ClawX-项目架构与版本大纲.md`)
|
||||||
|
|
||||||
|
- Updated section 2.4.2 setup wizard steps (removed ChannelStep)
|
||||||
|
- Updated directory structure (added ChannelsSettings.tsx to Settings, removed ChannelStep.tsx)
|
||||||
|
- Updated v0.5.0 milestone to note channel connection is deferred
|
||||||
|
|
||||||
|
## User Experience
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```
|
||||||
|
Welcome → Runtime → Provider → Channel → Skills → Complete
|
||||||
|
↑
|
||||||
|
(complex, often skipped)
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```
|
||||||
|
Welcome → Runtime → Provider → Skills → Complete
|
||||||
|
↓
|
||||||
|
"Configure channels in Settings"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
- `src/pages/Setup/index.tsx` - Removed channel step (-140 lines)
|
||||||
|
- `ClawX-项目架构与版本大纲.md` - Updated documentation
|
||||||
|
|
||||||
|
## Future Work
|
||||||
|
- Implement Settings > Channels page with:
|
||||||
|
- WhatsApp QR code scanning
|
||||||
|
- Telegram bot token configuration
|
||||||
|
- Discord/Slack OAuth flows
|
||||||
|
- Connection status indicators
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
* [commit_10] Cron tasks - Create/edit dialog, schedule presets, improved UI
|
* [commit_10] Cron tasks - Create/edit dialog, schedule presets, improved UI
|
||||||
* [commit_11] OpenClaw submodule fix - GitHub URL, auto-generated token, WebSocket auth
|
* [commit_11] OpenClaw submodule fix - GitHub URL, auto-generated token, WebSocket auth
|
||||||
* [commit_12] Real API key validation - OpenRouter support, actual API calls to verify keys
|
* [commit_12] Real API key validation - OpenRouter support, actual API calls to verify keys
|
||||||
|
* [commit_13] Remove channel setup step - Simplified onboarding, channels moved to Settings
|
||||||
|
|
||||||
### Plan:
|
### Plan:
|
||||||
1. ~~Initialize project structure~~ ✅
|
1. ~~Initialize project structure~~ ✅
|
||||||
@@ -46,6 +47,7 @@ All core features have been implemented:
|
|||||||
- Cron tasks management for scheduled automation
|
- Cron tasks management for scheduled automation
|
||||||
- OpenClaw submodule from official GitHub (v2026.2.3) with auto-token auth
|
- OpenClaw submodule from official GitHub (v2026.2.3) with auto-token auth
|
||||||
- Real API key validation via actual API calls (Anthropic, OpenAI, Google, OpenRouter)
|
- Real API key validation via actual API calls (Anthropic, OpenAI, Google, OpenRouter)
|
||||||
|
- Simplified setup wizard (channel connection deferred to Settings page)
|
||||||
|
|
||||||
## Version Milestones
|
## Version Milestones
|
||||||
|
|
||||||
|
|||||||
@@ -47,11 +47,7 @@ const steps: SetupStep[] = [
|
|||||||
title: 'AI Provider',
|
title: 'AI Provider',
|
||||||
description: 'Configure your AI service',
|
description: 'Configure your AI service',
|
||||||
},
|
},
|
||||||
{
|
// Channel step removed - users can configure channels later in Settings
|
||||||
id: 'channel',
|
|
||||||
title: 'Connect Channel',
|
|
||||||
description: 'Link a messaging app',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'skills',
|
id: 'skills',
|
||||||
title: 'Choose Skills',
|
title: 'Choose Skills',
|
||||||
@@ -80,20 +76,7 @@ const providers: Provider[] = [
|
|||||||
{ id: 'openrouter', name: 'OpenRouter', model: 'Multi-Model', icon: '🌐', placeholder: 'sk-or-...' },
|
{ id: 'openrouter', name: 'OpenRouter', model: 'Multi-Model', icon: '🌐', placeholder: 'sk-or-...' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Channel types
|
// NOTE: Channel types moved to Settings > Channels page
|
||||||
interface Channel {
|
|
||||||
type: string;
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channels: Channel[] = [
|
|
||||||
{ type: 'whatsapp', name: 'WhatsApp', icon: '📱', description: 'Connect via QR code scan' },
|
|
||||||
{ type: 'telegram', name: 'Telegram', icon: '✈️', description: 'Connect via bot token' },
|
|
||||||
{ type: 'discord', name: 'Discord', icon: '🎮', description: 'Connect via bot token' },
|
|
||||||
{ type: 'slack', name: 'Slack', icon: '💼', description: 'Connect via OAuth' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Skill bundle types
|
// Skill bundle types
|
||||||
interface SkillBundle {
|
interface SkillBundle {
|
||||||
@@ -146,7 +129,7 @@ export function Setup() {
|
|||||||
// Setup state
|
// Setup state
|
||||||
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
|
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
|
||||||
const [apiKey, setApiKey] = useState('');
|
const [apiKey, setApiKey] = useState('');
|
||||||
const [selectedChannel, setSelectedChannel] = useState<string | null>(null);
|
// Channel selection moved to Settings page
|
||||||
const [selectedBundles, setSelectedBundles] = useState<Set<string>>(new Set(['productivity', 'developer']));
|
const [selectedBundles, setSelectedBundles] = useState<Set<string>>(new Set(['productivity', 'developer']));
|
||||||
|
|
||||||
const step = steps[currentStep];
|
const step = steps[currentStep];
|
||||||
@@ -187,17 +170,14 @@ export function Setup() {
|
|||||||
case 2: // Provider
|
case 2: // Provider
|
||||||
setCanProceed(selectedProvider !== null && apiKey.length > 0);
|
setCanProceed(selectedProvider !== null && apiKey.length > 0);
|
||||||
break;
|
break;
|
||||||
case 3: // Channel
|
case 3: // Skills
|
||||||
setCanProceed(true); // Channel is optional
|
|
||||||
break;
|
|
||||||
case 4: // Skills
|
|
||||||
setCanProceed(selectedBundles.size > 0);
|
setCanProceed(selectedBundles.size > 0);
|
||||||
break;
|
break;
|
||||||
case 5: // Complete
|
case 4: // Complete
|
||||||
setCanProceed(true);
|
setCanProceed(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, [currentStep, selectedProvider, apiKey, selectedChannel, selectedBundles]);
|
}, [currentStep, selectedProvider, apiKey, selectedBundles]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800 text-white">
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800 text-white">
|
||||||
@@ -263,13 +243,6 @@ export function Setup() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentStep === 3 && (
|
{currentStep === 3 && (
|
||||||
<ChannelContent
|
|
||||||
channels={channels}
|
|
||||||
selectedChannel={selectedChannel}
|
|
||||||
onSelectChannel={setSelectedChannel}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{currentStep === 4 && (
|
|
||||||
<SkillsContent
|
<SkillsContent
|
||||||
bundles={skillBundles}
|
bundles={skillBundles}
|
||||||
selectedBundles={selectedBundles}
|
selectedBundles={selectedBundles}
|
||||||
@@ -284,10 +257,9 @@ export function Setup() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentStep === 5 && (
|
{currentStep === 4 && (
|
||||||
<CompleteContent
|
<CompleteContent
|
||||||
selectedProvider={selectedProvider}
|
selectedProvider={selectedProvider}
|
||||||
selectedChannel={selectedChannel}
|
|
||||||
selectedBundles={selectedBundles}
|
selectedBundles={selectedBundles}
|
||||||
bundles={skillBundles}
|
bundles={skillBundles}
|
||||||
/>
|
/>
|
||||||
@@ -315,7 +287,7 @@ export function Setup() {
|
|||||||
'Get Started'
|
'Get Started'
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{currentStep === 3 && !selectedChannel ? 'Skip' : 'Next'}
|
Next
|
||||||
<ChevronRight className="h-4 w-4 ml-2" />
|
<ChevronRight className="h-4 w-4 ml-2" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -703,112 +675,7 @@ function ProviderContent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChannelContentProps {
|
// NOTE: ChannelContent component moved to Settings > Channels page
|
||||||
channels: Channel[];
|
|
||||||
selectedChannel: string | null;
|
|
||||||
onSelectChannel: (type: string | null) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChannelContent({ channels, selectedChannel, onSelectChannel }: ChannelContentProps) {
|
|
||||||
const [connecting, setConnecting] = useState(false);
|
|
||||||
const [qrCode, setQrCode] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const handleConnect = async (channelType: string) => {
|
|
||||||
onSelectChannel(channelType);
|
|
||||||
setConnecting(true);
|
|
||||||
|
|
||||||
// Simulate QR code generation for WhatsApp
|
|
||||||
if (channelType === 'whatsapp') {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
// In real app, this would be a real QR code
|
|
||||||
setQrCode('placeholder');
|
|
||||||
}
|
|
||||||
|
|
||||||
setConnecting(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-2">Connect a Channel</h2>
|
|
||||||
<p className="text-slate-300">
|
|
||||||
Link a messaging app to start chatting with your AI
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!selectedChannel ? (
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
{channels.map((channel) => (
|
|
||||||
<button
|
|
||||||
key={channel.type}
|
|
||||||
onClick={() => handleConnect(channel.type)}
|
|
||||||
className="p-4 rounded-lg bg-white/5 hover:bg-white/10 transition-colors text-left"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="text-2xl">{channel.icon}</span>
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">{channel.name}</p>
|
|
||||||
<p className="text-sm text-slate-400">{channel.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
className="text-center space-y-4"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-center gap-2">
|
|
||||||
<span className="text-2xl">
|
|
||||||
{channels.find((c) => c.type === selectedChannel)?.icon}
|
|
||||||
</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{channels.find((c) => c.type === selectedChannel)?.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{connecting ? (
|
|
||||||
<div className="flex flex-col items-center gap-4">
|
|
||||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
|
||||||
<p>Generating QR code...</p>
|
|
||||||
</div>
|
|
||||||
) : selectedChannel === 'whatsapp' && qrCode ? (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="bg-white p-4 rounded-lg inline-block">
|
|
||||||
{/* Placeholder QR code */}
|
|
||||||
<div className="w-48 h-48 bg-gray-200 flex items-center justify-center text-gray-500">
|
|
||||||
QR Code Placeholder
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-slate-300">
|
|
||||||
Scan this QR code with WhatsApp to connect
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<p className="text-slate-300">
|
|
||||||
Follow the instructions to connect your {channels.find((c) => c.type === selectedChannel)?.name} account.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button variant="ghost" onClick={() => {
|
|
||||||
onSelectChannel(null);
|
|
||||||
setQrCode(null);
|
|
||||||
}}>
|
|
||||||
Choose different channel
|
|
||||||
</Button>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<p className="text-sm text-slate-400 text-center">
|
|
||||||
You can add more channels later in Settings
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SkillsContentProps {
|
interface SkillsContentProps {
|
||||||
bundles: SkillBundle[];
|
bundles: SkillBundle[];
|
||||||
@@ -865,16 +732,14 @@ function SkillsContent({ bundles, selectedBundles, onToggleBundle }: SkillsConte
|
|||||||
|
|
||||||
interface CompleteContentProps {
|
interface CompleteContentProps {
|
||||||
selectedProvider: string | null;
|
selectedProvider: string | null;
|
||||||
selectedChannel: string | null;
|
|
||||||
selectedBundles: Set<string>;
|
selectedBundles: Set<string>;
|
||||||
bundles: SkillBundle[];
|
bundles: SkillBundle[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function CompleteContent({ selectedProvider, selectedChannel, selectedBundles, bundles }: CompleteContentProps) {
|
function CompleteContent({ selectedProvider, selectedBundles, bundles }: CompleteContentProps) {
|
||||||
const gatewayStatus = useGatewayStore((state) => state.status);
|
const gatewayStatus = useGatewayStore((state) => state.status);
|
||||||
|
|
||||||
const providerData = providers.find((p) => p.id === selectedProvider);
|
const providerData = providers.find((p) => p.id === selectedProvider);
|
||||||
const channelData = channels.find((c) => c.type === selectedChannel);
|
|
||||||
const selectedBundleNames = bundles
|
const selectedBundleNames = bundles
|
||||||
.filter((b) => selectedBundles.has(b.id))
|
.filter((b) => selectedBundles.has(b.id))
|
||||||
.map((b) => b.name)
|
.map((b) => b.name)
|
||||||
@@ -896,12 +761,6 @@ function CompleteContent({ selectedProvider, selectedChannel, selectedBundles, b
|
|||||||
{providerData ? `${providerData.icon} ${providerData.name}` : '—'}
|
{providerData ? `${providerData.icon} ${providerData.name}` : '—'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between p-3 rounded-lg bg-white/5">
|
|
||||||
<span>Channel</span>
|
|
||||||
<span className={selectedChannel ? 'text-green-400' : 'text-slate-400'}>
|
|
||||||
{channelData ? `${channelData.icon} ${channelData.name}` : 'Skipped'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between p-3 rounded-lg bg-white/5">
|
<div className="flex items-center justify-between p-3 rounded-lg bg-white/5">
|
||||||
<span>Skills</span>
|
<span>Skills</span>
|
||||||
<span className="text-green-400">
|
<span className="text-green-400">
|
||||||
@@ -915,6 +774,10 @@ function CompleteContent({ selectedProvider, selectedChannel, selectedBundles, b
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p className="text-sm text-slate-400">
|
||||||
|
You can connect messaging channels later in Settings → Channels
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user