diff --git a/desktop/src/components/AgentOnboardingWizard.tsx b/desktop/src/components/AgentOnboardingWizard.tsx index 30496f5..c3693c8 100644 --- a/desktop/src/components/AgentOnboardingWizard.tsx +++ b/desktop/src/components/AgentOnboardingWizard.tsx @@ -18,6 +18,7 @@ import { Check, Loader2, AlertCircle, + LayoutGrid, } from 'lucide-react'; import { cn } from '../lib/utils'; import { useAgentStore, type CloneCreateOptions } from '../store/agentStore'; @@ -28,6 +29,12 @@ import type { Clone } from '../store/agentStore'; import { intelligenceClient } from '../lib/intelligence-client'; import { generateSoulContent, generateUserContent } from '../lib/personality-presets'; import { createLogger } from '../lib/logger'; +import { + type AgentTemplateAvailable, + type AgentTemplateFull, + saasClient, +} from '../lib/saas-client'; +import { useSaaSStore } from '../store/saasStore'; const log = createLogger('AgentOnboardingWizard'); @@ -72,6 +79,7 @@ const initialFormData: WizardFormData = { // === Step Configuration === const steps = [ + { id: 0, title: '行业模板', description: '选择预设或自定义', icon: LayoutGrid }, { id: 1, title: '认识用户', description: '让我们了解一下您', icon: User }, { id: 2, title: 'Agent 身份', description: '给助手起个名字', icon: Bot }, { id: 3, title: '人格风格', description: '选择沟通风格', icon: Sparkles }, @@ -82,19 +90,21 @@ const steps = [ // === Component === export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboardingWizardProps) { - const { createClone, updateClone, clones, isLoading, error, clearError } = useAgentStore(); - const [currentStep, setCurrentStep] = useState(1); + const { createClone, createFromTemplate, updateClone, clones, isLoading, error, clearError } = useAgentStore(); + const [currentStep, setCurrentStep] = useState(0); const [formData, setFormData] = useState(initialFormData); const [errors, setErrors] = useState>({}); const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle'); + const [selectedTemplate, setSelectedTemplate] = useState(null); // Reset form when modal opens useEffect(() => { if (isOpen) { setFormData(initialFormData); - setCurrentStep(1); + setCurrentStep(0); setErrors({}); setSubmitStatus('idle'); + setSelectedTemplate(null); clearError(); } }, [isOpen, clearError]); @@ -111,11 +121,33 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa } }; + // Handle template selection + const handleSelectTemplate = async (t: AgentTemplateAvailable) => { + try { + const full = await saasClient.fetchTemplateFull(t.id); + setSelectedTemplate(full); + setFormData(prev => ({ + ...prev, + agentName: full.name, + emoji: full.emoji || prev.emoji, + personality: full.personality || prev.personality, + scenarios: full.scenarios.length > 0 ? full.scenarios : prev.scenarios, + })); + setCurrentStep(1); + } catch { + // If fetch fails, still allow manual creation + setCurrentStep(1); + } + }; + // Validate current step const validateStep = useCallback((step: number): boolean => { const newErrors: Record = {}; switch (step) { + case 0: + // Template selection is always valid (blank agent is an option) + break; case 1: if (!formData.userName.trim()) { newErrors.userName = '请输入您的名字'; @@ -157,7 +189,7 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa // Navigate to previous step const prevStep = () => { - setCurrentStep((prev) => Math.max(prev - 1, 1)); + setCurrentStep((prev) => Math.max(prev - 1, 0)); }; // Handle form submission @@ -169,59 +201,76 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa 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); + // Template-based creation path + if (selectedTemplate && clones.length === 0) { + clone = await createFromTemplate(selectedTemplate); + + // Persist USER.md for template-created agents + if (clone) { + try { + const userContent = generateUserContent({ + userName: formData.userName, + userRole: formData.userRole, + scenarios: formData.scenarios, + }); + await intelligenceClient.identity.updateFile(clone.id, 'user_profile', userContent); + } catch (err) { + log.warn('Failed to persist USER.md for template agent:', err); + } + } } else { - const createOptions: CloneCreateOptions = { - ...personalityUpdates, - privacyOptIn: formData.privacyOptIn, + // Manual creation / update path + 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, }; - clone = await createClone(createOptions); + + 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, - }); + // Persist SOUL.md and USER.md to the identity system (manual path only) + if (!selectedTemplate) { + 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, - }); + 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); + await intelligenceClient.identity.updateFile(clone.id, 'soul', soulContent); + await intelligenceClient.identity.updateFile(clone.id, 'user_profile', userContent); - // Write USER.md (user profile) - await intelligenceClient.identity.updateFile(clone.id, 'user_profile', userContent); - - log.debug('SOUL.md and USER.md persisted for agent:', clone.id); - } catch (err) { - log.warn('Failed to persist identity files:', err); - // Don't fail the whole onboarding if identity persistence fails + log.debug('SOUL.md and USER.md persisted for agent:', clone.id); + } catch (err) { + log.warn('Failed to persist identity files:', err); + } } setSubmitStatus('success'); @@ -239,7 +288,7 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa if (!isOpen) return null; - const CurrentStepIcon = steps[currentStep - 1]?.icon || Bot; + const CurrentStepIcon = steps[currentStep]?.icon || Bot; return (
@@ -262,7 +311,7 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa 创建新 Agent

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

@@ -321,6 +370,36 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa transition={{ duration: 0.2 }} className="space-y-4" > + {/* Step 0: 行业模板 */} + {currentStep === 0 && ( +
+

选择一个行业预设快速开始,或创建空白 Agent

+
+ + {useSaaSStore.getState().availableTemplates.map(t => ( + + ))} +
+
+ )} + {/* Step 1: 认识用户 */} {currentStep === 1 && ( <> @@ -627,7 +706,7 @@ export function AgentOnboardingWizard({ isOpen, onClose, onSuccess }: AgentOnboa