/** * AgentOnboardingWizard - Guided Agent creation wizard * * A 5-step wizard for creating new Agents with personality settings. * Inspired by OpenClaw's quick configuration modal. */ import { useState, useCallback, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { X, User, Bot, Sparkles, Briefcase, Folder, ChevronLeft, ChevronRight, Check, Loader2, AlertCircle, } from 'lucide-react'; import { cn } from '../lib/utils'; import { useAgentStore, type CloneCreateOptions } from '../store/agentStore'; import { EmojiPicker } from './ui/EmojiPicker'; import { PersonalitySelector } from './PersonalitySelector'; import { ScenarioTags } from './ScenarioTags'; import type { Clone } from '../store/agentStore'; import { intelligenceClient } from '../lib/intelligence-client'; import { generateSoulContent, generateUserContent } from '../lib/personality-presets'; // === Types === interface WizardFormData { userName: string; userRole: string; agentName: string; agentRole: string; agentNickname: string; emoji: string; personality: string; scenarios: string[]; workspaceDir: string; restrictFiles: boolean; privacyOptIn: boolean; notes: string; } interface AgentOnboardingWizardProps { isOpen: boolean; onClose: () => void; onSuccess?: (clone: Clone) => void; } const initialFormData: WizardFormData = { userName: '', userRole: '', agentName: '', agentRole: '', agentNickname: '', emoji: '', personality: '', scenarios: [], workspaceDir: '', restrictFiles: true, privacyOptIn: false, notes: '', }; // === Step Configuration === const steps = [ { id: 1, title: '认识用户', description: '让我们了解一下您', icon: User }, { id: 2, title: 'Agent 身份', description: '给助手起个名字', icon: Bot }, { id: 3, title: '人格风格', description: '选择沟通风格', icon: Sparkles }, { id: 4, title: '使用场景', description: '选择应用场景', icon: Briefcase }, { id: 5, title: '工作环境', description: '配置工作目录', icon: Folder }, ]; // === Component === export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboardingWizardProps) { const { createClone, updateClone, clones, isLoading, error, clearError } = useAgentStore(); const [currentStep, setCurrentStep] = useState(1); const [formData, setFormData] = useState(initialFormData); const [errors, setErrors] = useState>({}); const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle'); // Reset form when modal opens useEffect(() => { if (isOpen) { setFormData(initialFormData); setCurrentStep(1); setErrors({}); setSubmitStatus('idle'); clearError(); } }, [isOpen, clearError]); // Update form field const updateField = (field: K, value: WizardFormData[K]) => { setFormData((prev) => ({ ...prev, [field]: value })); if (errors[field]) { setErrors((prev) => { const newErrors = { ...prev }; delete newErrors[field]; return newErrors; }); } }; // Validate current step const validateStep = useCallback((step: number): boolean => { const newErrors: Record = {}; switch (step) { case 1: if (!formData.userName.trim()) { newErrors.userName = '请输入您的名字'; } break; case 2: if (!formData.agentName.trim()) { newErrors.agentName = '请输入 Agent 名称'; } break; case 3: if (!formData.emoji) { newErrors.emoji = '请选择一个 Emoji'; } if (!formData.personality) { newErrors.personality = '请选择一个人格风格'; } break; case 4: if (formData.scenarios.length === 0) { newErrors.scenarios = '请至少选择一个使用场景'; } break; case 5: // Optional step, no validation break; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }, [formData]); // Navigate to next step const nextStep = () => { if (validateStep(currentStep)) { setCurrentStep((prev) => Math.min(prev + 1, steps.length)); } }; // Navigate to previous step const prevStep = () => { setCurrentStep((prev) => Math.max(prev - 1, 1)); }; // Handle form submission const handleSubmit = async () => { if (!validateStep(currentStep)) { return; } setSubmitStatus('idle'); try { const personalityUpdates = { name: formData.agentName, role: formData.agentRole || undefined, nickname: formData.agentNickname || undefined, userName: formData.userName, userRole: formData.userRole || undefined, scenarios: formData.scenarios, workspaceDir: formData.workspaceDir || undefined, restrictFiles: formData.restrictFiles, emoji: formData.emoji, personality: formData.personality, notes: formData.notes || undefined, }; let clone: Clone | undefined; // If there's an existing clone, update it instead of creating a new one if (clones && clones.length > 0) { clone = await updateClone(clones[0].id, personalityUpdates); } else { const createOptions: CloneCreateOptions = { ...personalityUpdates, privacyOptIn: formData.privacyOptIn, }; clone = await createClone(createOptions); } if (clone) { // Persist SOUL.md and USER.md to the identity system try { const soulContent = generateSoulContent({ agentName: formData.agentName, emoji: formData.emoji, personality: formData.personality, scenarios: formData.scenarios, }); const userContent = generateUserContent({ userName: formData.userName, userRole: formData.userRole, scenarios: formData.scenarios, }); // Write SOUL.md (agent personality) await intelligenceClient.identity.updateFile(clone.id, 'soul', soulContent); // Write USER.md (user profile) await intelligenceClient.identity.updateFile(clone.id, 'user_profile', userContent); console.log('[Onboarding] SOUL.md and USER.md persisted for agent:', clone.id); } catch (err) { console.warn('[Onboarding] Failed to persist identity files:', err); // Don't fail the whole onboarding if identity persistence fails } setSubmitStatus('success'); setTimeout(() => { onSuccess?.(clone); onClose(); }, 1500); } else { setSubmitStatus('error'); } } catch { setSubmitStatus('error'); } }; if (!isOpen) return null; const CurrentStepIcon = steps[currentStep - 1]?.icon || Bot; return (
{/* Backdrop */}
{/* Modal */}
{/* Header */}

创建新 Agent

步骤 {currentStep}/{steps.length}: {steps[currentStep - 1]?.title}

{/* Progress Bar */}
{steps.map((step, index) => { const StepIcon = step.icon; const isActive = currentStep === step.id; const isCompleted = currentStep > step.id; return (
{index < steps.length - 1 && (
)}
); })}
{/* Content */}
{/* Step 1: 认识用户 */} {currentStep === 1 && ( <>

让我们认识一下

请告诉我们您的名字,让助手更好地为您服务

updateField('userName', e.target.value)} placeholder="例如:张三" className={cn( 'w-full px-3 py-2 text-sm border rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary', errors.userName ? 'border-red-500' : 'border-gray-300 dark:border-gray-600' )} /> {errors.userName && (

{errors.userName}

)}
updateField('userRole', e.target.value)} placeholder="例如:产品经理、开发工程师" className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary" />
)} {/* Step 2: Agent 身份 */} {currentStep === 2 && ( <>

给您的助手起个名字

这将是您助手的身份标识

updateField('agentName', e.target.value)} placeholder="例如:小龙助手" className={cn( 'w-full px-3 py-2 text-sm border rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary', errors.agentName ? 'border-red-500' : 'border-gray-300 dark:border-gray-600' )} /> {errors.agentName && (

{errors.agentName}

)}
updateField('agentRole', e.target.value)} placeholder="例如:编程助手、写作顾问" className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary" />
updateField('agentNickname', e.target.value)} placeholder="例如:小龙" className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary" />
)} {/* Step 3: 人格风格 */} {currentStep === 3 && ( <>

选择人格风格

这决定了助手的沟通方式和性格特点

updateField('emoji', emoji)} /> {errors.emoji && (

{errors.emoji}

)}
updateField('personality', personality)} /> {errors.personality && (

{errors.personality}

)}
)} {/* Step 4: 使用场景 */} {currentStep === 4 && ( <>

选择使用场景

选择您希望 Agent 协助的领域(最多5个)

updateField('scenarios', scenarios)} maxSelections={5} /> {errors.scenarios && (

{errors.scenarios}

)} )} {/* Step 5: 工作环境 */} {currentStep === 5 && ( <>

配置工作环境

设置 Agent 的工作目录和权限

updateField('workspaceDir', e.target.value)} placeholder="例如:/home/user/projects/myproject" className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary font-mono" />

Agent 将在此目录下工作,留空则使用默认目录

限制文件访问

仅允许访问工作目录内的文件