refactor(onboarding): replace 50-prop drilling with OnboardingContext
OnboardingStepPanel previously forwarded ~50 props to seven step components, so adding a single wizard field meant touching four layers. Introduce a renderer-local React Context (OnboardingProvider / useOnboarding) that carries all wizard state and actions (connectors, node status, company inputs, model selection, execution mode, mission, launch summary, locale, t, error/loading). Step panels now receive only their navigation callbacks (onContinue / onBack / onLaunch / onFinish) and read everything else via useOnboarding(). CompanyOnboarding wraps the step panel tree in OnboardingProvider with a memoised value. Business logic is unchanged: each onContinueFrom* transition, the bootstrap call, and the welcome-step branch remain exactly as before; this refactor only moves the data-passing mechanism.
This commit is contained in:
1 parent
f3816c6115
commit
de77f5f38e
10 files changed
+256
-333
No files matched your search
@@ -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 (
|
||||
<motion.div
|
||||
key="company"
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRight, Brain, UserCircle, type Icon } from "@phosphor-icons/react";
|
||||
import type { ExecutionMode } from "@shared/types";
|
||||
import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero";
|
||||
import { useOnboarding } from "./OnboardingContext";
|
||||
|
||||
interface ExecutionModeStepProps {
|
||||
executionMode: ExecutionMode;
|
||||
setExecutionMode: (mode: ExecutionMode) => 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 (
|
||||
<motion.div
|
||||
key="executionmode"
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { CheckCircle, Rocket } from "@phosphor-icons/react";
|
||||
import type { OnboardingBootstrapResult, ProfileSnapshot } from "@shared/types";
|
||||
import { ActionButton } from "../ui";
|
||||
import { useOnboarding } from "./OnboardingContext";
|
||||
|
||||
interface LaunchStepProps {
|
||||
launchSummary: OnboardingBootstrapResult;
|
||||
selectedConnector: ProfileSnapshot["connectors"][number] | null;
|
||||
onFinish: () => 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 (
|
||||
<motion.div
|
||||
key="launch"
|
||||
|
||||
@@ -2,30 +2,24 @@ import { motion } from "framer-motion";
|
||||
import { Cpu, Lightning } from "@phosphor-icons/react";
|
||||
import { ActionButton, Input, TextArea } from "../ui";
|
||||
import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero";
|
||||
import { useOnboarding } from "./OnboardingContext";
|
||||
|
||||
interface MissionStepProps {
|
||||
goalTitle: string;
|
||||
setGoalTitle: (v: string) => 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 (
|
||||
<motion.div
|
||||
key="mission"
|
||||
|
||||
@@ -1,39 +1,26 @@
|
||||
import { motion } from "framer-motion";
|
||||
import type { ConnectorModelEntry } from "@shared/connector-models";
|
||||
import type { ConnectorId, ProfileSnapshot } from "@shared/types";
|
||||
import type { AppLocale } from "@shared/locale";
|
||||
import { ActionButton } from "../ui";
|
||||
import { ModelToggleList, StatusBadge } from "../execution/ModelManagementShared";
|
||||
import { getStepNumber, TOTAL_STEPS } from "./OnboardingHero";
|
||||
import { useOnboarding } from "./OnboardingContext";
|
||||
|
||||
interface ModelPoolStepProps {
|
||||
currentLocale: AppLocale;
|
||||
connectorModelPools: Array<{
|
||||
connector: ProfileSnapshot["connectors"][number];
|
||||
models: ConnectorModelEntry[];
|
||||
}>;
|
||||
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;
|
||||
|
||||
@@ -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<string, { label: string; code: string; commandKey: TerminalCommandKey }> = {
|
||||
@@ -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 */}
|
||||
<div className="mb-5 flex items-center gap-2.5 text-[13px]">
|
||||
{checking ? (
|
||||
{nodeChecking ? (
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-muted-foreground border-t-primary" />
|
||||
) : nodeStatus?.installed ? (
|
||||
<CheckCircle size={18} weight="fill" className="shrink-0 text-success" />
|
||||
@@ -87,7 +76,7 @@ export function NodeCheckStep({
|
||||
<XCircle size={18} weight="fill" className="shrink-0 text-destructive" />
|
||||
) : null}
|
||||
|
||||
{checking ? (
|
||||
{nodeChecking ? (
|
||||
<span className="text-muted-foreground">{t("onboarding.providerChecking")}...</span>
|
||||
) : nodeStatus?.installed ? (
|
||||
<span className="font-medium text-success">
|
||||
@@ -111,7 +100,7 @@ export function NodeCheckStep({
|
||||
</pre>
|
||||
<ActionButton
|
||||
label={t("onboarding.installNodeTerminal")}
|
||||
onClick={() => onInstall(platformInfo.commandKey)}
|
||||
onClick={() => onInstallCommand(platformInfo.commandKey)}
|
||||
icon={Terminal}
|
||||
/>
|
||||
</div>
|
||||
@@ -121,7 +110,7 @@ export function NodeCheckStep({
|
||||
<div className="mb-6">
|
||||
<ActionButton
|
||||
label={t("onboarding.checkAgain")}
|
||||
onClick={onCheck}
|
||||
onClick={onCheckNode}
|
||||
icon={ArrowCounterClockwise}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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<OnboardingContextValue | null>(null);
|
||||
|
||||
export function OnboardingProvider({
|
||||
value,
|
||||
children,
|
||||
}: {
|
||||
value: OnboardingContextValue;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return <OnboardingContext.Provider value={value}>{children}</OnboardingContext.Provider>;
|
||||
}
|
||||
|
||||
export function useOnboarding(): OnboardingContextValue {
|
||||
const ctx = useContext(OnboardingContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useOnboarding must be used within an OnboardingProvider");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
@@ -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 (
|
||||
<>
|
||||
<OnboardingHero
|
||||
@@ -146,103 +63,48 @@ export function OnboardingStepPanel({
|
||||
<AnimatePresence mode="wait">
|
||||
{step === "nodecheck" ? (
|
||||
<NodeCheckStep
|
||||
nodeStatus={nodeStatus}
|
||||
checking={nodeChecking}
|
||||
onCheck={onCheckNode}
|
||||
onInstall={(commandKey) => onInstallCommand(commandKey)}
|
||||
onContinue={onContinueFromNodeCheck}
|
||||
onBack={() => onBack("welcome")}
|
||||
t={t}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{step === "providers" ? (
|
||||
<ProvidersStep
|
||||
connectors={connectors}
|
||||
onTestConnector={onTestConnector}
|
||||
onSaveConnector={onSaveConnector}
|
||||
onLoginConnector={onLoginConnector}
|
||||
onInstallCommand={onInstallCommand}
|
||||
hasAtLeastOneReady={hasAtLeastOneReady}
|
||||
onContinue={onContinueFromProviders}
|
||||
onBack={() => onBack("nodecheck")}
|
||||
error={error}
|
||||
t={t}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{step === "company" ? (
|
||||
<CompanyStep
|
||||
companyName={companyName}
|
||||
setCompanyName={setCompanyName}
|
||||
companyDescription={companyDescription}
|
||||
setCompanyDescription={setCompanyDescription}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
setSelectedConnectorId={setSelectedConnectorId}
|
||||
selectedModel={selectedModel}
|
||||
setSelectedModel={setSelectedModel}
|
||||
executionReadyConnectors={executionReadyConnectors}
|
||||
modelOptions={modelOptions}
|
||||
loadingModels={loadingModels}
|
||||
workspacePath={workspacePath}
|
||||
setWorkspacePath={setWorkspacePath}
|
||||
onPickDirectory={onPickDirectory}
|
||||
onContinue={onContinueFromCompany}
|
||||
onBack={() => onBack("providers")}
|
||||
error={error}
|
||||
currentLocale={currentLocale}
|
||||
t={t}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{step === "modelpool" ? (
|
||||
<ModelPoolStep
|
||||
currentLocale={currentLocale}
|
||||
connectorModelPools={connectorModelPools}
|
||||
error={error}
|
||||
onBack={() => onBack("company")}
|
||||
onContinue={onContinueFromModelPool}
|
||||
onToggleConnectorModel={onToggleConnectorModel}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
selectedConnectorEnabledModels={selectedConnectorEnabledModels}
|
||||
totalEnabledModels={totalEnabledModels}
|
||||
t={t}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{step === "executionmode" ? (
|
||||
<ExecutionModeStep
|
||||
executionMode={executionMode}
|
||||
setExecutionMode={setExecutionMode}
|
||||
onContinue={onContinueFromExecutionMode}
|
||||
onBack={() => onBack("modelpool")}
|
||||
t={t}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{step === "mission" ? (
|
||||
<MissionStep
|
||||
goalTitle={goalTitle}
|
||||
setGoalTitle={setGoalTitle}
|
||||
goalDescription={goalDescription}
|
||||
setGoalDescription={setGoalDescription}
|
||||
onLaunch={onLaunchCompany}
|
||||
onBack={() => onBack("executionmode")}
|
||||
loading={loading}
|
||||
error={error}
|
||||
t={t}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{step === "launch" && launchSummary ? (
|
||||
<LaunchStep
|
||||
launchSummary={launchSummary}
|
||||
selectedConnector={selectedConnector}
|
||||
onFinish={onFinishLaunch}
|
||||
loading={loading}
|
||||
error={error}
|
||||
t={t}
|
||||
/>
|
||||
<LaunchStep onFinish={onFinishLaunch} />
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
@@ -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<OnboardingContextValue>(
|
||||
() => ({
|
||||
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 (
|
||||
<div
|
||||
className={`relative h-[100dvh] overflow-hidden bg-background ${
|
||||
@@ -178,51 +258,12 @@ export function CompanyOnboarding({
|
||||
t={t}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<OnboardingProvider value={contextValue}>
|
||||
<OnboardingStepPanel
|
||||
step={heroStep}
|
||||
currentLocale={currentLocale}
|
||||
t={t}
|
||||
canCancel={canCancel}
|
||||
onCancel={onCancel}
|
||||
isFirstLaunch={isFirstLaunch}
|
||||
nodeStatus={nodeStatus}
|
||||
nodeChecking={nodeChecking}
|
||||
onCheckNode={() => 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()}
|
||||
/>
|
||||
</>
|
||||
</OnboardingProvider>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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<string | null>("claude");
|
||||
|
||||
const claudeConnector = connectors.find((c) => c.id === "claude_local");
|
||||
|
||||
Reference in new issue
Block a user