diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx index 62b5bfe2..a9041ebf 100644 --- a/ui/src/components/OnboardingWizard.tsx +++ b/ui/src/components/OnboardingWizard.tsx @@ -51,7 +51,11 @@ import { Loader2, FolderOpen, ChevronDown, - X + X, + Plus, + Pencil, + Trash2, + MessageSquare } from "lucide-react"; type Step = 1 | 2 | 3 | 4 | 5 | 6; @@ -81,6 +85,45 @@ function buildMissionFromQuestionnaire(q1: string, q2: string, q3: string, q4: s return parts.join(" "); } +interface HiringRole { + id: string; + name: string; + description: string; + enabled: boolean; + editing: boolean; +} + +let roleIdCounter = 0; +function nextRoleId(): string { + return `role-${++roleIdCounter}`; +} + +/** + * Parse a markdown hiring plan into structured roles. + * Looks for bullet points with bold role names: "- **Role Name**: description" + * Falls back to any bold text in bullet points. + */ +function parseHiringPlan(markdown: string): HiringRole[] { + const roles: HiringRole[] = []; + const lines = markdown.split("\n"); + for (const line of lines) { + // Match "- **Role Name**: description" or "- **Role Name** - description" + const match = line.match( + /^\s*[-*]\s+\*\*([^*]+)\*\*[:\s-]*(.*)$/ + ); + if (match) { + roles.push({ + id: nextRoleId(), + name: match[1].trim(), + description: match[2].trim(), + enabled: true, + editing: false, + }); + } + } + return roles; +} + 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 @@ -150,6 +193,8 @@ export function OnboardingWizard() { // Planning task + hiring plan const [planningTaskId, setPlanningTaskId] = useState(null); const [planContent, setPlanContent] = useState(null); + const [hiringRoles, setHiringRoles] = useState([]); + const [showRawPlan, setShowRawPlan] = useState(false); // Created entity IDs — pre-populate from existing company when skipping step 1 const [createdCompanyId, setCreatedCompanyId] = useState( @@ -284,6 +329,8 @@ export function OnboardingWizard() { setQ4(""); setPlanningTaskId(null); setPlanContent(null); + setHiringRoles([]); + setShowRawPlan(false); setAgentName("CEO"); setAdapterType("claude_local"); setCwd(""); @@ -551,33 +598,35 @@ export function OnboardingWizard() { setLoading(true); setError(null); try { - let issueRef = createdIssueRef; - if (!issueRef) { - const issue = await issuesApi.create(createdCompanyId, { - title: taskTitle.trim(), - ...(taskDescription.trim() - ? { description: taskDescription.trim() } - : {}), + // Create a hire task for each approved role + const approvedRoles = hiringRoles.filter( + (r) => r.enabled && r.name.trim() + ); + for (const role of approvedRoles) { + await issuesApi.create(createdCompanyId, { + title: `Hire: ${role.name}`, + description: role.description + ? `Hire a ${role.name} for the company.\n\n${role.description}` + : `Hire a ${role.name} for the company.`, assigneeAgentId: createdAgentId, status: "todo" }); - issueRef = issue.identifier ?? issue.id; - setCreatedIssueRef(issueRef); - queryClient.invalidateQueries({ - queryKey: queryKeys.issues.list(createdCompanyId) - }); } + queryClient.invalidateQueries({ + queryKey: queryKeys.issues.list(createdCompanyId) + }); + setSelectedCompanyId(createdCompanyId); reset(); closeOnboarding(); navigate( createdCompanyPrefix - ? `/${createdCompanyPrefix}/issues/${issueRef}` - : `/issues/${issueRef}` + ? `/${createdCompanyPrefix}/issues` + : `/issues` ); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to create task"); + setError(err instanceof Error ? err.message : "Failed to create hire tasks"); } finally { setLoading(false); } @@ -1394,9 +1443,9 @@ export function OnboardingWizard() { )} - {/* Step 5: Review hiring plan — placeholder */} + {/* Step 5: Review hiring plan */} {step === 5 && ( -
+
@@ -1404,16 +1453,209 @@ export function OnboardingWizard() {

Review your hiring plan

- Select which roles to hire. You can edit, add, or remove - roles before approving. + Select which roles to hire. Edit, add, or remove roles + before approving.

-
-

- Hiring plan review component coming soon. -

-
+ + {hiringRoles.length === 0 ? ( +
+

+ No roles parsed from the hiring plan yet. +

+ +
+ ) : ( +
+ {hiringRoles.map((role) => ( +
+ {role.editing ? ( +
+ + setHiringRoles((prev) => + prev.map((r) => + r.id === role.id + ? { ...r, name: e.target.value } + : r + ) + ) + } + autoFocus + /> +