From 36b0d391025b249f8b1e7e66d468b9e3693da626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 22 Mar 2026 02:55:42 -0400 Subject: [PATCH] refactor(01-05): decompose CompanyOnboarding.tsx (686 lines) into 8 focused sub-components - Extract ConnectorReadiness.tsx (84 lines): connector status display - Extract CompanyStep.tsx (158 lines): company name, connector, workspace setup - Extract MissionStep.tsx (97 lines): goal title and description panel - Extract LaunchStep.tsx (91 lines): launch summary grid - Extract ClaudeSettingsDialog.tsx (85 lines): Claude Code settings modal - Extract LanguageStep.tsx (76 lines): language selection step - Extract OnboardingHero.tsx (97 lines): left hero panel with step indicators - OnboardingWizard.tsx (287 lines): main orchestrator with state and handlers - CompanyOnboarding.tsx becomes re-export barrel (2 lines) - All sub-components under 300 lines --- src/renderer/components/CompanyOnboarding.tsx | 688 +----------------- .../ClaudeSettingsDialog.tsx | 85 +++ .../CompanyOnboarding/CompanyStep.tsx | 158 ++++ .../CompanyOnboarding/ConnectorReadiness.tsx | 84 +++ .../CompanyOnboarding/LanguageStep.tsx | 76 ++ .../CompanyOnboarding/LaunchStep.tsx | 91 +++ .../CompanyOnboarding/MissionStep.tsx | 97 +++ .../CompanyOnboarding/OnboardingHero.tsx | 97 +++ .../CompanyOnboarding/OnboardingWizard.tsx | 287 ++++++++ 9 files changed, 977 insertions(+), 686 deletions(-) create mode 100644 src/renderer/components/CompanyOnboarding/ClaudeSettingsDialog.tsx create mode 100644 src/renderer/components/CompanyOnboarding/CompanyStep.tsx create mode 100644 src/renderer/components/CompanyOnboarding/ConnectorReadiness.tsx create mode 100644 src/renderer/components/CompanyOnboarding/LanguageStep.tsx create mode 100644 src/renderer/components/CompanyOnboarding/LaunchStep.tsx create mode 100644 src/renderer/components/CompanyOnboarding/MissionStep.tsx create mode 100644 src/renderer/components/CompanyOnboarding/OnboardingHero.tsx create mode 100644 src/renderer/components/CompanyOnboarding/OnboardingWizard.tsx diff --git a/src/renderer/components/CompanyOnboarding.tsx b/src/renderer/components/CompanyOnboarding.tsx index 9182f1f6..63898664 100644 --- a/src/renderer/components/CompanyOnboarding.tsx +++ b/src/renderer/components/CompanyOnboarding.tsx @@ -1,686 +1,2 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; -import { AnimatePresence, motion } from "framer-motion"; -import { - Buildings, - CheckCircle, - Cpu, - FolderOpen, - GearSix, - GlobeSimple, - Lightning, - Rocket, - Target, - WarningCircle, - X, - XCircle, -} from "@phosphor-icons/react"; -import { getConnectorExecutionReadinessIssue, isConnectorExecutionReady } from "@shared/connector-policy"; -import type { OnboardingBootstrapResult, ProfileSnapshot } from "@shared/types"; -import type { AppLocale } from "@shared/locale"; -import { APP_LOCALES, LOCALE_LABELS } from "@shared/locale"; -import { useT } from "../i18n"; -import { unwrap } from "../lib/desktop"; -import { ActionButton, Input, Select, TextArea } from "./ui"; - -type Step = "language" | "company" | "mission" | "launch"; - -export function CompanyOnboarding({ - connectors, - onDone, - canCancel = false, - onCancel, - currentLocale, - onLocaleChange, - isFirstLaunch = false, -}: { - connectors: ProfileSnapshot["connectors"]; - onDone: (summary: OnboardingBootstrapResult) => Promise; - canCancel?: boolean; - onCancel?: () => void; - currentLocale: AppLocale; - onLocaleChange: (locale: AppLocale) => void; - isFirstLaunch?: boolean; -}) { - const t = useT(); - const [step, setStep] = useState(isFirstLaunch ? "language" : "company"); - const [companyName, setCompanyName] = useState(""); - const [companyDescription, setCompanyDescription] = useState(""); - const [workspacePath, setWorkspacePath] = useState(""); - const [autoApproveHires, setAutoApproveHires] = useState(true); - const [goalTitle, setGoalTitle] = useState(""); - const [goalDescription, setGoalDescription] = useState(""); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [launchSummary, setLaunchSummary] = useState(null); - const [claudeSettingsOpen, setClaudeSettingsOpen] = useState(false); - const [claudeApiKey, setClaudeApiKey] = useState(""); - const [claudeBaseUrl, setClaudeBaseUrl] = useState(""); - const [claudeModel, setClaudeModel] = useState(""); - const [claudeSettingsSaving, setClaudeSettingsSaving] = useState(false); - - const executionReadyConnectors = useMemo( - () => connectors.filter((connector) => isConnectorExecutionReady(connector)), - [connectors], - ); - const [selectedConnectorId, setSelectedConnectorId] = useState(executionReadyConnectors[0]?.id ?? ""); - const hasRunnableConnector = executionReadyConnectors.length > 0; - const selectedConnector = - executionReadyConnectors.find((connector) => connector.id === selectedConnectorId) ?? executionReadyConnectors[0] ?? null; - - useEffect(() => { - if (!selectedConnectorId && executionReadyConnectors[0]) { - setSelectedConnectorId(executionReadyConnectors[0].id); - return; - } - if (selectedConnectorId && !executionReadyConnectors.some((connector) => connector.id === selectedConnectorId)) { - setSelectedConnectorId(executionReadyConnectors[0]?.id ?? ""); - } - }, [executionReadyConnectors, selectedConnectorId]); - - const openClaudeSettings = useCallback(() => { - const claude = connectors.find((c) => c.id === "claude_local"); - if (!claude) return; - const lines = claude.envBindingText.split(/\r?\n/); - setClaudeApiKey(lines.find((l) => l.startsWith("ANTHROPIC_API_KEY="))?.split("=").slice(1).join("=") ?? ""); - setClaudeBaseUrl(lines.find((l) => l.startsWith("ANTHROPIC_BASE_URL="))?.split("=").slice(1).join("=") ?? ""); - setClaudeModel(claude.model ?? ""); - setClaudeSettingsOpen(true); - }, [connectors]); - - const saveClaudeSettings = useCallback(async () => { - setClaudeSettingsSaving(true); - try { - const envLines: string[] = []; - if (claudeApiKey.trim()) envLines.push(`ANTHROPIC_API_KEY=${claudeApiKey.trim()}`); - if (claudeBaseUrl.trim()) envLines.push(`ANTHROPIC_BASE_URL=${claudeBaseUrl.trim()}`); - const claude = connectors.find((c) => c.id === "claude_local"); - if (!claude) return; - const existingLines = claude.envBindingText.split(/\r?\n/).filter((l) => { - const key = l.split("=")[0]?.trim(); - return key && key !== "ANTHROPIC_API_KEY" && key !== "ANTHROPIC_BASE_URL" && l.trim(); - }); - const newEnvText = [...envLines, ...existingLines].join("\n"); - unwrap(await window.agentCompany.saveConnector({ - id: "claude_local", - command: claude.command, - model: claudeModel.trim() || null, - envBindingText: newEnvText, - notes: claude.notes, - })); - setClaudeSettingsOpen(false); - } catch (err) { - setError(err instanceof Error ? err.message : String(err)); - } finally { - setClaudeSettingsSaving(false); - } - }, [claudeApiKey, claudeBaseUrl, claudeModel, connectors]); - - const stepLabels = [ - { label: t("onboarding.stepLabel1"), icon: Buildings }, - { label: t("onboarding.stepLabel2"), icon: Target }, - { label: t("onboarding.stepLabel3"), icon: Rocket }, - ] as const; - const stepIndex = step === "language" ? -1 : step === "company" ? 0 : step === "mission" ? 1 : 2; - - const pickDirectory = useCallback(async () => { - setError(null); - const result = await window.agentCompany.pickDirectory(); - if (!result.ok) { - setError(result.error.message); - return; - } - if (result.data) { - setWorkspacePath(result.data); - } - }, []); - - const bootstrap = useCallback(async () => { - const result = unwrap(await window.agentCompany.bootstrapOnboarding({ - companyName: companyName.trim(), - companyDescription: companyDescription.trim(), - workspacePath: workspacePath.trim(), - autoApproveHires, - goalTitle: goalTitle.trim(), - goalDescription: goalDescription.trim(), - connectorId: selectedConnector?.id, - })); - setLaunchSummary(result); - setStep("launch"); - }, [ - autoApproveHires, - companyDescription, - companyName, - goalDescription, - goalTitle, - selectedConnector?.id, - workspacePath, - ]); - - const continueToMission = useCallback(async () => { - if (loading) return; - if (!companyName.trim()) { - setError(t("onboarding.enterCompanyName")); - return; - } - if (!selectedConnector) { - setError(t("onboarding.connectorRequired")); - return; - } - setError(null); - setStep("mission"); - }, [companyName, loading, selectedConnector, t]); - - const launchCompany = useCallback(async () => { - if (loading) return; - if (!goalTitle.trim()) { - setError(t("onboarding.describeFirstGoal")); - return; - } - setLoading(true); - setError(null); - try { - await bootstrap(); - } catch (submitError) { - setError(submitError instanceof Error ? submitError.message : t("onboarding.unableToBootstrap")); - } finally { - setLoading(false); - } - }, [bootstrap, goalTitle, loading, t]); - - const finishLaunch = useCallback(async () => { - if (!launchSummary) return; - setLoading(true); - setError(null); - try { - await onDone(launchSummary); - } catch (submitError) { - setError(submitError instanceof Error ? submitError.message : t("onboarding.unableToOpenControlCenter")); - } finally { - setLoading(false); - } - }, [launchSummary, onDone, t]); - - return ( -
-
- -
- {t("onboarding.subtitle")} -
-

- {t("onboarding.title").split("\n").map((line, i) => ( - {line}{i === 0 ?
: null}
- ))} -

-

- {t("onboarding.description")} -

- - {step !== "language" ? ( -
- {stepLabels.map((entry, index) => ( -
- {index > 0 ? ( -
- ) : null} -
- {index < stepIndex ? : } - {entry.label} -
-
- ))} -
- ) : null} - -
-
- {t("onboarding.connectorReadiness")} -
-
- {connectors.map((connector) => { - const isChecking = connector.status === "detected"; - const isReady = connector.status === "ready"; - const isMissing = connector.status === "not_installed"; - const issue = !isChecking ? getConnectorExecutionReadinessIssue(connector) : null; - return ( -
- {isChecking ? ( - - ) : isReady ? ( - - ) : isMissing ? ( - - ) : ( - - )} -
-
- - {connector.label} - - - {isChecking ? (currentLocale === "zh" ? "检测中..." : "checking...") : connector.status.replaceAll("_", " ")} - -
- {issue ? ( -
- {issue} -
- ) : null} -
- {connector.id === "claude_local" ? ( - - ) : null} -
- ); - })} -
- {!hasRunnableConnector ? ( -
- {t("onboarding.connectorWarning")} -
- ) : null} -
- -
- -
-
- {canCancel && onCancel ? ( - - ) : null} - - - {step === "language" ? ( - -
- - {t("onboarding.languageSelection")} / Language -
-
- {t("onboarding.selectLanguage")} / Select Language -
-
- -
- {APP_LOCALES.map((locale) => ( - - ))} -
- -
- setStep("company")} - icon={GlobeSimple} - tone="accent" - /> -
- - ) : null} - - {step === "company" ? ( - -
- {t("onboarding.stepLabel1")} 1 / 3 -
-
- {t("onboarding.step1Title")} -
-
- -
- -