diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx index 88e16d09..8d746be7 100644 --- a/ui/src/components/OnboardingWizard.tsx +++ b/ui/src/components/OnboardingWizard.tsx @@ -53,7 +53,7 @@ import { X } from "lucide-react"; -type Step = 1 | 2 | 3 | 4; +type Step = 1 | 2 | 3 | 4 | 5 | 6; type AdapterType = | "claude_local" | "codex_local" @@ -65,7 +65,22 @@ type AdapterType = | "http" | "openclaw_gateway"; -const DEFAULT_TASK_DESCRIPTION = `Setup yourself as the CEO. Use the ceo persona found here: +const MISSION_PROMPT_CHIPS = [ + "Build a SaaS product", + "Scale a content business", + "Launch a marketplace" +]; + +function buildMissionFromQuestionnaire(q1: string, q2: string, q3: string, q4: string): string { + const parts: string[] = []; + if (q1.trim()) parts.push(q1.trim()); + if (q2.trim()) parts.push(`We serve ${q2.trim().toLowerCase()}.`); + if (q3.trim()) parts.push(`Our biggest challenge is ${q3.trim().toLowerCase()}.`); + if (q4.trim()) parts.push(`Success looks like ${q4.trim().toLowerCase()}.`); + return parts.join(" "); +} + +const DEFAULT_TASK_DESCRIPTION = `Setup yourself as the CEO. Use the ceo persona found here: https://github.com/paperclipai/companies/blob/main/default/ceo/AGENTS.md @@ -91,6 +106,13 @@ export function OnboardingWizard() { // Step 1 const [companyName, setCompanyName] = useState(""); const [companyGoal, setCompanyGoal] = useState(""); + const [missionPath, setMissionPath] = useState<"direct" | "questionnaire" | null>(null); + const [missionConfirmed, setMissionConfirmed] = useState(false); + // Questionnaire answers + const [q1, setQ1] = useState(""); // What do you do? + const [q2, setQ2] = useState(""); // Who do you serve? + const [q3, setQ3] = useState(""); // Biggest bottleneck? + const [q4, setQ4] = useState(""); // What would success look like? // Step 2 const [agentName, setAgentName] = useState("CEO"); @@ -158,8 +180,8 @@ export function OnboardingWizard() { // Resize textarea when step 3 is shown or description changes useEffect(() => { - if (step === 3) autoResizeTextarea(); - }, [step, taskDescription, autoResizeTextarea]); + // Auto-resize removed — task description textarea no longer used in onboarding + }, [step, autoResizeTextarea]); const { data: adapterModels, @@ -171,7 +193,7 @@ export function OnboardingWizard() { ? queryKeys.agents.adapterModels(createdCompanyId, adapterType) : ["agents", "none", "adapter-models", adapterType], queryFn: () => agentsApi.adapterModels(createdCompanyId!, adapterType), - enabled: Boolean(createdCompanyId) && onboardingOpen && step === 2 + enabled: Boolean(createdCompanyId) && onboardingOpen && step === 3 }); const isLocalAdapter = adapterType === "claude_local" || @@ -192,7 +214,7 @@ export function OnboardingWizard() { : "claude"); useEffect(() => { - if (step !== 2) return; + if (step !== 3) return; setAdapterEnvResult(null); setAdapterEnvError(null); }, [step, adapterType, cwd, model, command, args, url]); @@ -249,6 +271,12 @@ export function OnboardingWizard() { setError(null); setCompanyName(""); setCompanyGoal(""); + setMissionPath(null); + setMissionConfirmed(false); + setQ1(""); + setQ2(""); + setQ3(""); + setQ4(""); setAgentName("CEO"); setAdapterType("claude_local"); setCwd(""); @@ -351,20 +379,18 @@ export function OnboardingWizard() { setSelectedCompanyId(company.id); queryClient.invalidateQueries({ queryKey: queryKeys.companies.all }); - if (companyGoal.trim()) { - const parsedGoal = parseOnboardingGoalInput(companyGoal); - await goalsApi.create(company.id, { - title: parsedGoal.title, - ...(parsedGoal.description - ? { description: parsedGoal.description } - : {}), - level: "company", - status: "active" - }); - queryClient.invalidateQueries({ - queryKey: queryKeys.goals.list(company.id) - }); - } + const parsedGoal = parseOnboardingGoalInput(companyGoal); + await goalsApi.create(company.id, { + title: parsedGoal.title, + ...(parsedGoal.description + ? { description: parsedGoal.description } + : {}), + level: "company", + status: "active" + }); + queryClient.invalidateQueries({ + queryKey: queryKeys.goals.list(company.id) + }); setStep(2); } catch (err) { @@ -436,7 +462,7 @@ export function OnboardingWizard() { queryClient.invalidateQueries({ queryKey: queryKeys.agents.list(createdCompanyId) }); - setStep(3); + setStep(4); } catch (err) { setError(err instanceof Error ? err.message : "Failed to create agent"); } finally { @@ -539,10 +565,12 @@ export function OnboardingWizard() { function handleKeyDown(e: React.KeyboardEvent) { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); - if (step === 1 && companyName.trim()) handleStep1Next(); - else if (step === 2 && agentName.trim()) handleStep2Next(); - else if (step === 3 && taskTitle.trim()) handleStep3Next(); - else if (step === 4) handleLaunch(); + if (step === 1 && companyName.trim() && companyGoal.trim()) handleStep1Next(); + else if (step === 2) setStep(3); + else if (step === 3 && agentName.trim()) handleStep2Next(); + else if (step === 4) setStep(5); + else if (step === 5) setStep(6); + else if (step === 6) handleLaunch(); } } @@ -582,10 +610,12 @@ export function OnboardingWizard() {
{( [ - { step: 1 as Step, label: "Company", icon: Building2 }, - { step: 2 as Step, label: "Agent", icon: Bot }, - { step: 3 as Step, label: "Task", icon: ListTodo }, - { step: 4 as Step, label: "Launch", icon: Rocket } + { step: 1 as Step, label: "Mission", icon: Building2 }, + { step: 2 as Step, label: "Launch", icon: Rocket }, + { step: 3 as Step, label: "CEO", icon: Bot }, + { step: 4 as Step, label: "Chat", icon: Sparkles }, + { step: 5 as Step, label: "Plan", icon: ListTodo }, + { step: 6 as Step, label: "Hire", icon: Bot } ] as const ).map(({ step: s, label, icon: Icon }) => (
-

Name your company

+

Define your mission

- This is the organization your agents will work for. + Your mission drives everything — your CEO, your hires, + and the work your company will do.

@@ -638,37 +669,219 @@ export function OnboardingWizard() { autoFocus /> -
-
+ )} + + {/* Direct mission input */} + {missionPath === "direct" && ( +
+
+ +