diff --git a/src/renderer/components/company-onboarding/CompanyStep.tsx b/src/renderer/components/company-onboarding/CompanyStep.tsx index bdb105a8..5e4f95ab 100644 --- a/src/renderer/components/company-onboarding/CompanyStep.tsx +++ b/src/renderer/components/company-onboarding/CompanyStep.tsx @@ -1,53 +1,36 @@ import { motion } from "framer-motion"; import { Buildings, FolderOpen } from "@phosphor-icons/react"; -import type { ConnectorId, ProfileSnapshot } from "@shared/types"; -import type { AppLocale } from "@shared/locale"; +import type { ConnectorId } from "@shared/types"; import { ActionButton, Input, TextArea, Select } from "../ui"; import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero"; +import { useOnboarding } from "./OnboardingContext"; interface CompanyStepProps { - companyName: string; - setCompanyName: (v: string) => void; - companyDescription: string; - setCompanyDescription: (v: string) => void; - selectedConnectorId: ConnectorId | ""; - setSelectedConnectorId: (v: ConnectorId | "") => void; - selectedModel: string; - setSelectedModel: (v: string) => void; - executionReadyConnectors: ProfileSnapshot["connectors"]; - modelOptions: Array<{ id: string; label: string }>; - loadingModels: boolean; - workspacePath: string; - setWorkspacePath: (v: string) => void; - onPickDirectory: () => void; onContinue: () => void; onBack: () => void; - error: string | null; - currentLocale: AppLocale; - t: (key: string) => string; } -export function CompanyStep({ - companyName, - setCompanyName, - companyDescription, - setCompanyDescription, - selectedConnectorId, - setSelectedConnectorId, - selectedModel, - setSelectedModel, - executionReadyConnectors, - modelOptions, - loadingModels, - workspacePath, - setWorkspacePath, - onPickDirectory, - onContinue, - onBack, - error, - currentLocale, - t, -}: CompanyStepProps) { +export function CompanyStep({ onContinue, onBack }: CompanyStepProps) { + const { + companyName, + setCompanyName, + companyDescription, + setCompanyDescription, + selectedConnectorId, + setSelectedConnectorId, + selectedModel, + setSelectedModel, + executionReadyConnectors, + modelOptions, + loadingModels, + workspacePath, + setWorkspacePath, + onPickDirectory, + error, + currentLocale, + t, + } = useOnboarding(); + return ( void; onContinue: () => void; onBack: () => void; - t: (key: string) => string; } -export function ExecutionModeStep({ - executionMode, - setExecutionMode, - onContinue, - onBack, - t, -}: ExecutionModeStepProps) { +export function ExecutionModeStep({ onContinue, onBack }: ExecutionModeStepProps) { + const { executionMode, setExecutionMode, t } = useOnboarding(); + return ( void; - loading: boolean; - error: string | null; - t: (key: string) => string; } -export function LaunchStep({ - launchSummary, - selectedConnector, - onFinish, - loading, - error, - t, -}: LaunchStepProps) { +export function LaunchStep({ onFinish }: LaunchStepProps) { + const { launchSummary, selectedConnector, loading, error, t } = useOnboarding(); + + if (!launchSummary) return null; + return ( void; - goalDescription: string; - setGoalDescription: (v: string) => void; onLaunch: () => void; onBack: () => void; - loading: boolean; - error: string | null; - t: (key: string) => string; } -export function MissionStep({ - goalTitle, - setGoalTitle, - goalDescription, - setGoalDescription, - onLaunch, - onBack, - loading, - error, - t, -}: MissionStepProps) { +export function MissionStep({ onLaunch, onBack }: MissionStepProps) { + const { + goalTitle, + setGoalTitle, + goalDescription, + setGoalDescription, + loading, + error, + t, + } = useOnboarding(); + return ( ; - error: string | null; onBack: () => void; onContinue: () => void; - onToggleConnectorModel: (connectorId: ConnectorId, modelId: string) => void; - selectedConnectorId: ConnectorId | ""; - selectedConnectorEnabledModels: number; - totalEnabledModels: number; - t: (key: string) => string; } -export function ModelPoolStep({ - currentLocale, - connectorModelPools, - error, - onBack, - onContinue, - onToggleConnectorModel, - selectedConnectorId, - selectedConnectorEnabledModels, - totalEnabledModels, - t, -}: ModelPoolStepProps) { +export function ModelPoolStep({ onBack, onContinue }: ModelPoolStepProps) { + const { + currentLocale, + connectorModelPools, + error, + onToggleConnectorModel, + selectedConnectorId, + selectedConnectorEnabledModels, + totalEnabledModels, + t, + } = useOnboarding(); + const selectedConnectorIsRunnable = selectedConnectorEnabledModels > 0; const hasAnyEnabledModels = totalEnabledModels > 0; const canContinue = hasAnyEnabledModels && selectedConnectorIsRunnable; diff --git a/src/renderer/components/company-onboarding/NodeCheckStep.tsx b/src/renderer/components/company-onboarding/NodeCheckStep.tsx index 039a6153..a91506f7 100644 --- a/src/renderer/components/company-onboarding/NodeCheckStep.tsx +++ b/src/renderer/components/company-onboarding/NodeCheckStep.tsx @@ -9,15 +9,11 @@ import { import { ActionButton } from "../ui"; import type { TerminalCommandKey } from "@shared/contracts"; import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero"; +import { useOnboarding } from "./OnboardingContext"; interface NodeCheckStepProps { - nodeStatus: { installed: boolean; version: string | null } | null; - checking: boolean; - onCheck: () => void; - onInstall: (commandKey: TerminalCommandKey) => void; onContinue: () => void; onBack: () => void; - t: (key: string) => string; } const INSTALL_COMMANDS: Record = { @@ -48,15 +44,8 @@ node -v`, }, }; -export function NodeCheckStep({ - nodeStatus, - checking, - onCheck, - onInstall, - onContinue, - onBack, - t, -}: NodeCheckStepProps) { +export function NodeCheckStep({ onContinue, onBack }: NodeCheckStepProps) { + const { nodeStatus, nodeChecking, onCheckNode, onInstallCommand, t } = useOnboarding(); const platform = window.agentCompany.platform; const platformInfo = INSTALL_COMMANDS[platform] ?? INSTALL_COMMANDS.linux; @@ -79,7 +68,7 @@ export function NodeCheckStep({ {/* Status */}
- {checking ? ( + {nodeChecking ? ( ) : nodeStatus?.installed ? ( @@ -87,7 +76,7 @@ export function NodeCheckStep({ ) : null} - {checking ? ( + {nodeChecking ? ( {t("onboarding.providerChecking")}... ) : nodeStatus?.installed ? ( @@ -111,7 +100,7 @@ export function NodeCheckStep({ onInstall(platformInfo.commandKey)} + onClick={() => onInstallCommand(platformInfo.commandKey)} icon={Terminal} />
@@ -121,7 +110,7 @@ export function NodeCheckStep({
diff --git a/src/renderer/components/company-onboarding/OnboardingContext.tsx b/src/renderer/components/company-onboarding/OnboardingContext.tsx new file mode 100644 index 00000000..4ce5c420 --- /dev/null +++ b/src/renderer/components/company-onboarding/OnboardingContext.tsx @@ -0,0 +1,91 @@ +import { createContext, useContext, type ReactNode } from "react"; +import type { AppLocale } from "@shared/locale"; +import type { + ConnectorId, + ExecutionMode, + OnboardingBootstrapResult, + ProfileSnapshot, +} from "@shared/types"; +import type { ConnectorModelEntry } from "@shared/connector-models"; +import type { TerminalCommandKey } from "@shared/contracts"; +import type { TranslationFn } from "../../i18n"; + +export interface OnboardingContextValue { + currentLocale: AppLocale; + t: TranslationFn; + + error: string | null; + loading: boolean; + + nodeStatus: { installed: boolean; version: string | null } | null; + nodeChecking: boolean; + onCheckNode: () => void; + onInstallCommand: (commandKey: TerminalCommandKey) => void; + + connectors: ProfileSnapshot["connectors"]; + hasAtLeastOneReady: boolean; + onTestConnector: (connectorId: string) => void; + onSaveConnector: ( + connectorId: string, + envBindingText: string, + model: string | null, + notes?: string, + ) => void; + onLoginConnector: (connectorId: string) => void; + + companyName: string; + setCompanyName: (value: string) => void; + companyDescription: string; + setCompanyDescription: (value: string) => void; + + selectedConnectorId: ConnectorId | ""; + setSelectedConnectorId: (value: ConnectorId | "") => void; + selectedModel: string; + setSelectedModel: (value: string) => void; + selectedConnector: ProfileSnapshot["connectors"][number] | null; + + executionReadyConnectors: ProfileSnapshot["connectors"]; + connectorModelPools: Array<{ + connector: ProfileSnapshot["connectors"][number]; + models: ConnectorModelEntry[]; + }>; + modelOptions: Array<{ id: string; label: string }>; + loadingModels: boolean; + totalEnabledModels: number; + selectedConnectorEnabledModels: number; + onToggleConnectorModel: (connectorId: ConnectorId, modelId: string) => void; + + workspacePath: string; + setWorkspacePath: (value: string) => void; + onPickDirectory: () => void; + + executionMode: ExecutionMode; + setExecutionMode: (value: ExecutionMode) => void; + + goalTitle: string; + setGoalTitle: (value: string) => void; + goalDescription: string; + setGoalDescription: (value: string) => void; + + launchSummary: OnboardingBootstrapResult | null; +} + +const OnboardingContext = createContext(null); + +export function OnboardingProvider({ + value, + children, +}: { + value: OnboardingContextValue; + children: ReactNode; +}) { + return {children}; +} + +export function useOnboarding(): OnboardingContextValue { + const ctx = useContext(OnboardingContext); + if (!ctx) { + throw new Error("useOnboarding must be used within an OnboardingProvider"); + } + return ctx; +} diff --git a/src/renderer/components/company-onboarding/OnboardingStepPanel.tsx b/src/renderer/components/company-onboarding/OnboardingStepPanel.tsx index ee27fb44..ce772ddd 100644 --- a/src/renderer/components/company-onboarding/OnboardingStepPanel.tsx +++ b/src/renderer/components/company-onboarding/OnboardingStepPanel.tsx @@ -1,9 +1,4 @@ -import type { AppLocale } from "@shared/locale"; -import type { ConnectorId, OnboardingBootstrapResult, ProfileSnapshot } from "@shared/types"; -import type { ConnectorModelEntry } from "@shared/connector-models"; -import type { TerminalCommandKey } from "@shared/contracts"; import { AnimatePresence } from "framer-motion"; -import type { TranslationFn } from "../../i18n"; import { OnboardingHero, type OnboardingStep } from "./OnboardingHero"; import { NodeCheckStep } from "./NodeCheckStep"; import { ProvidersStep } from "./ProvidersStep"; @@ -12,54 +7,13 @@ import { ModelPoolStep } from "./ModelPoolStep"; import { ExecutionModeStep } from "./ExecutionModeStep"; import { MissionStep } from "./MissionStep"; import { LaunchStep } from "./LaunchStep"; +import { useOnboarding } from "./OnboardingContext"; interface OnboardingStepPanelProps { step: OnboardingStep; - currentLocale: AppLocale; - t: TranslationFn; canCancel: boolean; onCancel?: () => void; isFirstLaunch: boolean; - nodeStatus: { installed: boolean; version: string | null } | null; - nodeChecking: boolean; - onCheckNode: () => void; - onInstallCommand: (commandKey: TerminalCommandKey) => void; - connectors: ProfileSnapshot["connectors"]; - hasAtLeastOneReady: boolean; - onTestConnector: (connectorId: string) => void; - onSaveConnector: (connectorId: string, envBindingText: string, model: string | null, notes?: string) => void; - onLoginConnector: (connectorId: string) => void; - error: string | null; - companyName: string; - setCompanyName: (value: string) => void; - companyDescription: string; - setCompanyDescription: (value: string) => void; - selectedConnectorId: ConnectorId | ""; - setSelectedConnectorId: (value: ConnectorId | "") => void; - selectedModel: string; - setSelectedModel: (value: string) => void; - executionReadyConnectors: ProfileSnapshot["connectors"]; - connectorModelPools: Array<{ - connector: ProfileSnapshot["connectors"][number]; - models: ConnectorModelEntry[]; - }>; - modelOptions: Array<{ id: string; label: string }>; - loadingModels: boolean; - totalEnabledModels: number; - selectedConnectorEnabledModels: number; - onToggleConnectorModel: (connectorId: ConnectorId, modelId: string) => void; - workspacePath: string; - setWorkspacePath: (value: string) => void; - onPickDirectory: () => void; - executionMode: "ai_ceo" | "ai_cofounder"; - setExecutionMode: (value: "ai_ceo" | "ai_cofounder") => void; - goalTitle: string; - setGoalTitle: (value: string) => void; - goalDescription: string; - setGoalDescription: (value: string) => void; - loading: boolean; - launchSummary: OnboardingBootstrapResult | null; - selectedConnector: ProfileSnapshot["connectors"][number] | null; onBack: (step: OnboardingStep) => void; onContinueFromNodeCheck: () => void; onContinueFromProviders: () => void; @@ -72,48 +26,9 @@ interface OnboardingStepPanelProps { export function OnboardingStepPanel({ step, - currentLocale, - t, canCancel, onCancel, isFirstLaunch, - nodeStatus, - nodeChecking, - onCheckNode, - onInstallCommand, - connectors, - hasAtLeastOneReady, - onTestConnector, - onSaveConnector, - onLoginConnector, - error, - companyName, - setCompanyName, - companyDescription, - setCompanyDescription, - selectedConnectorId, - setSelectedConnectorId, - selectedModel, - setSelectedModel, - executionReadyConnectors, - connectorModelPools, - modelOptions, - loadingModels, - totalEnabledModels, - selectedConnectorEnabledModels, - onToggleConnectorModel, - workspacePath, - setWorkspacePath, - onPickDirectory, - executionMode, - setExecutionMode, - goalTitle, - setGoalTitle, - goalDescription, - setGoalDescription, - loading, - launchSummary, - selectedConnector, onBack, onContinueFromNodeCheck, onContinueFromProviders, @@ -123,6 +38,8 @@ export function OnboardingStepPanel({ onLaunchCompany, onFinishLaunch, }: OnboardingStepPanelProps) { + const { currentLocale, t, launchSummary } = useOnboarding(); + return ( <> {step === "nodecheck" ? ( onInstallCommand(commandKey)} onContinue={onContinueFromNodeCheck} onBack={() => onBack("welcome")} - t={t} /> ) : null} {step === "providers" ? ( onBack("nodecheck")} - error={error} - t={t} /> ) : null} {step === "company" ? ( onBack("providers")} - error={error} - currentLocale={currentLocale} - t={t} /> ) : null} {step === "modelpool" ? ( onBack("company")} onContinue={onContinueFromModelPool} - onToggleConnectorModel={onToggleConnectorModel} - selectedConnectorId={selectedConnectorId} - selectedConnectorEnabledModels={selectedConnectorEnabledModels} - totalEnabledModels={totalEnabledModels} - t={t} /> ) : null} {step === "executionmode" ? ( onBack("modelpool")} - t={t} /> ) : null} {step === "mission" ? ( onBack("executionmode")} - loading={loading} - error={error} - t={t} /> ) : null} {step === "launch" && launchSummary ? ( - + ) : null} diff --git a/src/renderer/components/company-onboarding/OnboardingWizard.tsx b/src/renderer/components/company-onboarding/OnboardingWizard.tsx index 2a7c67ec..40b6b7c3 100644 --- a/src/renderer/components/company-onboarding/OnboardingWizard.tsx +++ b/src/renderer/components/company-onboarding/OnboardingWizard.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import type { ConnectorId, ExecutionMode, OnboardingBootstrapResult, ProfileSnapshot } from "@shared/types"; import type { AppLocale } from "@shared/locale"; import { useT } from "../../i18n"; @@ -8,6 +8,7 @@ import { WelcomeStep } from "./WelcomeStep"; import { OnboardingStepPanel } from "./OnboardingStepPanel"; import { useConnectorCallbacks } from "./useConnectorCallbacks"; import { useConnectorModelSelection } from "./useConnectorModelSelection"; +import { OnboardingProvider, type OnboardingContextValue } from "./OnboardingContext"; export function CompanyOnboarding({ connectors, @@ -159,6 +160,85 @@ export function CompanyOnboarding({ const isWelcome = step === "welcome"; const heroStep: OnboardingStep = step as OnboardingStep; + const contextValue = useMemo( + () => ({ + currentLocale, + t, + error, + loading, + nodeStatus, + nodeChecking, + onCheckNode: () => void checkNode(), + onInstallCommand: (commandKey) => void handleInstallCommand(commandKey), + connectors, + hasAtLeastOneReady, + onTestConnector: (id) => void handleTestConnector(id), + onSaveConnector: (id, env, model, notes) => void handleSaveConnector(id, env, model, notes), + onLoginConnector: (id) => void handleLoginConnector(id), + companyName, + setCompanyName, + companyDescription, + setCompanyDescription, + selectedConnectorId, + setSelectedConnectorId, + selectedModel, + setSelectedModel, + selectedConnector, + executionReadyConnectors, + connectorModelPools, + modelOptions, + loadingModels, + totalEnabledModels, + selectedConnectorEnabledModels, + onToggleConnectorModel: (connectorId, modelId) => void toggleConnectorModel(connectorId, modelId), + workspacePath, + setWorkspacePath, + onPickDirectory: () => void pickDirectory(), + executionMode, + setExecutionMode, + goalTitle, + setGoalTitle, + goalDescription, + setGoalDescription, + launchSummary, + }), + [ + currentLocale, + t, + error, + loading, + nodeStatus, + nodeChecking, + checkNode, + handleInstallCommand, + connectors, + hasAtLeastOneReady, + handleTestConnector, + handleSaveConnector, + handleLoginConnector, + companyName, + companyDescription, + selectedConnectorId, + setSelectedConnectorId, + selectedModel, + setSelectedModel, + selectedConnector, + executionReadyConnectors, + connectorModelPools, + modelOptions, + loadingModels, + totalEnabledModels, + selectedConnectorEnabledModels, + toggleConnectorModel, + workspacePath, + pickDirectory, + executionMode, + goalTitle, + goalDescription, + launchSummary, + ], + ); + return (
) : ( - <> + void checkNode()} - onInstallCommand={(commandKey) => void handleInstallCommand(commandKey)} - connectors={connectors} - hasAtLeastOneReady={hasAtLeastOneReady} - onTestConnector={(id) => void handleTestConnector(id)} - onSaveConnector={(id, env, model, notes) => void handleSaveConnector(id, env, model, notes)} - onLoginConnector={(id) => void handleLoginConnector(id)} - error={error} - companyName={companyName} - setCompanyName={setCompanyName} - companyDescription={companyDescription} - setCompanyDescription={setCompanyDescription} - selectedConnectorId={selectedConnectorId} - setSelectedConnectorId={setSelectedConnectorId} - selectedModel={selectedModel} - setSelectedModel={setSelectedModel} - executionReadyConnectors={executionReadyConnectors} - connectorModelPools={connectorModelPools} - modelOptions={modelOptions} - loadingModels={loadingModels} - totalEnabledModels={totalEnabledModels} - selectedConnectorEnabledModels={selectedConnectorEnabledModels} - onToggleConnectorModel={(connectorId, modelId) => void toggleConnectorModel(connectorId, modelId)} - workspacePath={workspacePath} - setWorkspacePath={setWorkspacePath} - onPickDirectory={() => void pickDirectory()} - executionMode={executionMode} - setExecutionMode={setExecutionMode} - goalTitle={goalTitle} - setGoalTitle={setGoalTitle} - goalDescription={goalDescription} - setGoalDescription={setGoalDescription} - loading={loading} - launchSummary={launchSummary} - selectedConnector={selectedConnector} onBack={setStep} onContinueFromNodeCheck={() => { setError(null); setStep("providers"); }} onContinueFromProviders={() => { setError(null); setStep("company"); }} @@ -232,7 +273,7 @@ export function CompanyOnboarding({ onLaunchCompany={() => void launchCompany()} onFinishLaunch={() => void finishLaunch()} /> - + )}
); diff --git a/src/renderer/components/company-onboarding/ProvidersStep.tsx b/src/renderer/components/company-onboarding/ProvidersStep.tsx index 1f696b98..1f05f00a 100644 --- a/src/renderer/components/company-onboarding/ProvidersStep.tsx +++ b/src/renderer/components/company-onboarding/ProvidersStep.tsx @@ -1,8 +1,6 @@ import { useState } from "react"; import { motion } from "framer-motion"; import { ArrowRight, FloppyDisk, SignIn } from "@phosphor-icons/react"; -import type { ProfileSnapshot } from "@shared/types"; -import type { TerminalCommandKey } from "@shared/contracts"; import { ActionButton } from "../ui"; import { CompatProviderCard, @@ -12,22 +10,15 @@ import { INPUT_CLS, } from "./ProvidersStepShared"; import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero"; +import { useOnboarding } from "./OnboardingContext"; /* ------------------------------------------------------------------ */ /* Props */ /* ------------------------------------------------------------------ */ interface ProvidersStepProps { - connectors: ProfileSnapshot["connectors"]; - onTestConnector: (connectorId: string) => void; - onSaveConnector: (connectorId: string, envBindingText: string, model: string | null, notes?: string) => void; - onLoginConnector: (connectorId: string) => void; - onInstallCommand: (commandKey: TerminalCommandKey) => void; - hasAtLeastOneReady: boolean; onContinue: () => void; onBack: () => void; - error: string | null; - t: (key: string) => string; } const COMPAT_SLOTS = ["claude_compatible", "claude_compat_2", "claude_compat_3", "claude_compat_4", "claude_compat_5", "claude_compat_6", "claude_compat_7", "claude_compat_8", "claude_compat_9", "claude_compat_10"] as const; @@ -36,18 +27,18 @@ const COMPAT_SLOTS = ["claude_compatible", "claude_compat_2", "claude_compat_3", /* Main component */ /* ------------------------------------------------------------------ */ -export function ProvidersStep({ - connectors, - onTestConnector, - onSaveConnector, - onLoginConnector, - onInstallCommand, - hasAtLeastOneReady, - onContinue, - onBack, - error, - t, -}: ProvidersStepProps) { +export function ProvidersStep({ onContinue, onBack }: ProvidersStepProps) { + const { + connectors, + onTestConnector, + onSaveConnector, + onLoginConnector, + onInstallCommand, + hasAtLeastOneReady, + error, + t, + } = useOnboarding(); + const [expandedCard, setExpandedCard] = useState("claude"); const claudeConnector = connectors.find((c) => c.id === "claude_local");