Files
DeskClaw/src/pages/Setup/index.tsx
Haze b8ab0208d0 feat(core): initialize project skeleton with Electron + React + TypeScript
Set up the complete project foundation for ClawX, a graphical AI assistant:

- Electron main process with IPC handlers, menu, tray, and gateway management
- React renderer with routing, layout components, and page scaffolding
- Zustand state management for gateway, settings, channels, skills, chat, and cron
- shadcn/ui components with Tailwind CSS and CSS variable theming
- Build tooling with Vite, electron-builder, and TypeScript configuration
- Testing setup with Vitest and Playwright
- Development configurations (ESLint, Prettier, gitignore, env example)
2026-02-05 23:09:17 +08:00

324 lines
10 KiB
TypeScript

/**
* Setup Wizard Page
* First-time setup experience for new users
*/
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import { Check, ChevronLeft, ChevronRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
interface SetupStep {
id: string;
title: string;
description: string;
}
const steps: SetupStep[] = [
{
id: 'welcome',
title: 'Welcome to ClawX',
description: 'Your AI assistant is ready to be configured',
},
{
id: 'runtime',
title: 'Environment Check',
description: 'Verifying system requirements',
},
{
id: 'provider',
title: 'AI Provider',
description: 'Configure your AI service',
},
{
id: 'channel',
title: 'Connect Channel',
description: 'Link a messaging app',
},
{
id: 'skills',
title: 'Choose Skills',
description: 'Select your skill bundles',
},
{
id: 'complete',
title: 'All Set!',
description: 'ClawX is ready to use',
},
];
export function Setup() {
const navigate = useNavigate();
const [currentStep, setCurrentStep] = useState(0);
const step = steps[currentStep];
const isFirstStep = currentStep === 0;
const isLastStep = currentStep === steps.length - 1;
const handleNext = () => {
if (isLastStep) {
// Complete setup and go to dashboard
navigate('/');
} else {
setCurrentStep((i) => i + 1);
}
};
const handleBack = () => {
setCurrentStep((i) => Math.max(i - 1, 0));
};
const handleSkip = () => {
navigate('/');
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800 text-white">
{/* Progress Indicator */}
<div className="flex justify-center pt-8">
<div className="flex items-center gap-2">
{steps.map((s, i) => (
<div key={s.id} className="flex items-center">
<div
className={cn(
'flex h-8 w-8 items-center justify-center rounded-full border-2 transition-colors',
i < currentStep
? 'border-primary bg-primary text-primary-foreground'
: i === currentStep
? 'border-primary text-primary'
: 'border-slate-600 text-slate-600'
)}
>
{i < currentStep ? (
<Check className="h-4 w-4" />
) : (
<span className="text-sm">{i + 1}</span>
)}
</div>
{i < steps.length - 1 && (
<div
className={cn(
'h-0.5 w-8 transition-colors',
i < currentStep ? 'bg-primary' : 'bg-slate-600'
)}
/>
)}
</div>
))}
</div>
</div>
{/* Step Content */}
<AnimatePresence mode="wait">
<motion.div
key={step.id}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="mx-auto max-w-2xl p-8"
>
<div className="text-center mb-8">
<h1 className="text-3xl font-bold mb-2">{step.title}</h1>
<p className="text-slate-400">{step.description}</p>
</div>
{/* Step-specific content */}
<div className="rounded-xl bg-white/10 backdrop-blur p-8 mb-8">
{currentStep === 0 && <WelcomeContent />}
{currentStep === 1 && <RuntimeContent />}
{currentStep === 2 && <ProviderContent />}
{currentStep === 3 && <ChannelContent />}
{currentStep === 4 && <SkillsContent />}
{currentStep === 5 && <CompleteContent />}
</div>
{/* Navigation */}
<div className="flex justify-between">
<div>
{!isFirstStep && (
<Button variant="ghost" onClick={handleBack}>
<ChevronLeft className="h-4 w-4 mr-2" />
Back
</Button>
)}
</div>
<div className="flex gap-2">
{!isLastStep && (
<Button variant="ghost" onClick={handleSkip}>
Skip Setup
</Button>
)}
<Button onClick={handleNext}>
{isLastStep ? (
'Get Started'
) : (
<>
Next
<ChevronRight className="h-4 w-4 ml-2" />
</>
)}
</Button>
</div>
</div>
</motion.div>
</AnimatePresence>
</div>
);
}
// Step content components (simplified versions)
function WelcomeContent() {
return (
<div className="text-center space-y-4">
<div className="text-6xl mb-4">🤖</div>
<h2 className="text-xl font-semibold">Welcome to ClawX</h2>
<p className="text-slate-300">
ClawX is a graphical interface for OpenClaw, making it easy to use AI
assistants across your favorite messaging platforms.
</p>
<ul className="text-left space-y-2 text-slate-300">
<li> Zero command-line required</li>
<li> Modern, beautiful interface</li>
<li> Pre-installed skill bundles</li>
<li> Cross-platform support</li>
</ul>
</div>
);
}
function RuntimeContent() {
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Checking Environment</h2>
<div className="space-y-3">
<div className="flex items-center justify-between p-3 rounded-lg bg-white/5">
<span>Node.js Runtime</span>
<span className="text-green-400"> Installed</span>
</div>
<div className="flex items-center justify-between p-3 rounded-lg bg-white/5">
<span>OpenClaw Package</span>
<span className="text-green-400"> Ready</span>
</div>
<div className="flex items-center justify-between p-3 rounded-lg bg-white/5">
<span>Gateway Service</span>
<span className="text-green-400"> Running</span>
</div>
</div>
</div>
);
}
function ProviderContent() {
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Select AI Provider</h2>
<p className="text-slate-300">
Choose your preferred AI model provider
</p>
<div className="grid grid-cols-3 gap-4">
{[
{ id: 'anthropic', name: 'Anthropic', model: 'Claude', icon: '🤖' },
{ id: 'openai', name: 'OpenAI', model: 'GPT-4', icon: '💚' },
{ id: 'google', name: 'Google', model: 'Gemini', icon: '🔷' },
].map((provider) => (
<button
key={provider.id}
className="p-4 rounded-lg bg-white/5 hover:bg-white/10 transition-colors text-center"
>
<span className="text-3xl">{provider.icon}</span>
<p className="font-medium mt-2">{provider.name}</p>
<p className="text-sm text-slate-400">{provider.model}</p>
</button>
))}
</div>
</div>
);
}
function ChannelContent() {
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Connect a Channel</h2>
<p className="text-slate-300">
Link a messaging app to start chatting with your AI
</p>
<div className="grid grid-cols-2 gap-4">
{[
{ type: 'whatsapp', name: 'WhatsApp', icon: '📱' },
{ type: 'telegram', name: 'Telegram', icon: '✈️' },
{ type: 'discord', name: 'Discord', icon: '🎮' },
{ type: 'slack', name: 'Slack', icon: '💼' },
].map((channel) => (
<button
key={channel.type}
className="p-4 rounded-lg bg-white/5 hover:bg-white/10 transition-colors flex items-center gap-3"
>
<span className="text-2xl">{channel.icon}</span>
<span className="font-medium">{channel.name}</span>
</button>
))}
</div>
<p className="text-sm text-slate-400 text-center">
You can add more channels later in Settings
</p>
</div>
);
}
function SkillsContent() {
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">Choose Skill Bundles</h2>
<p className="text-slate-300">
Select pre-configured skill packages
</p>
<div className="grid grid-cols-2 gap-4">
{[
{ id: 'productivity', name: 'Productivity', icon: '📋', recommended: true },
{ id: 'developer', name: 'Developer', icon: '💻', recommended: true },
{ id: 'smart-home', name: 'Smart Home', icon: '🏠' },
{ id: 'media', name: 'Media', icon: '🎨' },
].map((bundle) => (
<button
key={bundle.id}
className={cn(
'p-4 rounded-lg bg-white/5 hover:bg-white/10 transition-colors text-left relative',
bundle.recommended && 'ring-2 ring-primary'
)}
>
<span className="text-2xl">{bundle.icon}</span>
<p className="font-medium mt-2">{bundle.name}</p>
{bundle.recommended && (
<span className="absolute top-2 right-2 text-xs bg-primary px-2 py-0.5 rounded">
Recommended
</span>
)}
</button>
))}
</div>
</div>
);
}
function CompleteContent() {
return (
<div className="text-center space-y-4">
<div className="text-6xl mb-4">🎉</div>
<h2 className="text-xl font-semibold">Setup Complete!</h2>
<p className="text-slate-300">
ClawX is configured and ready to use. You can now start chatting with
your AI assistant.
</p>
<div className="space-y-2 text-slate-300">
<p> AI Provider configured</p>
<p> Channel connected</p>
<p> Skills enabled</p>
<p> Gateway running</p>
</div>
</div>
);
}
export default Setup;