diff --git a/packages/ui/src/pages/home/App.tsx b/packages/ui/src/pages/home/App.tsx index 1de6c38e..c4863074 100644 --- a/packages/ui/src/pages/home/App.tsx +++ b/packages/ui/src/pages/home/App.tsx @@ -1259,8 +1259,7 @@ function App() { } useEffect(() => { - const providerFormVisible = providerAddOpen || (activeView === "onboarding" && onboardingStep === "provider"); - if (!window.ccr || !providerFormVisible) { + if (!window.ccr || !providerAddOpen) { return; } if (providerDraft.protocolDetectionMode === "manual") { diff --git a/packages/ui/src/pages/home/components/onboarding.tsx b/packages/ui/src/pages/home/components/onboarding.tsx index c3a85e6b..fff4f6be 100644 --- a/packages/ui/src/pages/home/components/onboarding.tsx +++ b/packages/ui/src/pages/home/components/onboarding.tsx @@ -1,12 +1,12 @@ import { AddProfileDraft, AddProviderDraft, AppConfig, Button, Check, ChevronLeft, - ChevronRight, CircleAlert, cn, GatewayProviderProbeResult, GatewayStatus, Gauge, getNextOnboardingStep, - isOnboardingProfileReady, isOnboardingProviderReady, Layers3, LucideIcon, motion, motionEase, - LoaderCircle, onboardingMascotSpriteUrl, OnboardingReadinessOptions, OnboardingStepId, onboardingStepOrder, ProviderConnectivityCheckReport, reducedMotionTransition, useAppText, useReducedMotion, + ChevronRight, cn, findProviderPreset, GatewayProviderProbeResult, GatewayStatus, Gauge, getNextOnboardingStep, + isOnboardingProfileReady, isOnboardingProviderReady, Layers3, LucideIcon, mergeProviderModelLists, motion, motionEase, + LoaderCircle, onboardingMascotSpriteUrl, OnboardingReadinessOptions, OnboardingStepId, onboardingStepOrder, providerDraftHasReadyCredentialPool, ProviderConnectivityCheckReport, reducedMotionTransition, splitLines, useAppText, useEffect, useReducedMotion, useState, UserRound, X } from "../shared/index"; -import { AddProviderForm } from "./providers"; +import { AddProviderForm, providerSetupStepIds, type ProviderSetupStepId } from "./providers"; import { AddProfileForm } from "./profiles"; type OnboardingMascotTone = "cyan" | "orange" | "violet"; @@ -108,6 +108,7 @@ export function OnboardingView({ const t = useAppText(); const shouldReduceMotion = useReducedMotion(); const [providerIconDetecting, setProviderIconDetecting] = useState(false); + const [providerSetupStep, setProviderSetupStep] = useState("provider"); const providerReady = isOnboardingProviderReady(config); const profileReady = isOnboardingProfileReady(config, readiness); const serviceReady = gatewayStatus.state === "running"; @@ -116,25 +117,72 @@ export function OnboardingView({ const activeDetails = onboardingStepDetails[activeStep]; const previousStep = onboardingStepOrder[activeIndex - 1]; const nextStep = getNextOnboardingStep(activeStep, config, readiness); + const localAgentProviderImport = providerDraft.providerPlugins.length > 0; + const providerIdentityReady = Boolean(findProviderPreset(providerDraft.presetId) || providerDraft.baseUrl.trim()); + const providerCredentialReady = localAgentProviderImport || Boolean( + providerDraft.credentialMode === "pool" + ? providerDraftHasReadyCredentialPool(providerDraft) + : providerDraft.apiKey.trim() + ); + const providerModelsReady = mergeProviderModelLists(providerDraft.selectedModels, splitLines(providerDraft.modelsText)).length > 0; + const providerSetupIndex = Math.max(0, providerSetupStepIds.indexOf(providerSetupStep)); + const previousProviderSetupStep = activeStep === "provider" ? providerSetupStepIds[providerSetupIndex - 1] : undefined; + const nextProviderSetupStep = activeStep === "provider" ? providerSetupStepIds[providerSetupIndex + 1] : undefined; const providerSubmitLoading = activeStep === "provider" && (providerProbeLoading || providerConnectivityLoading || providerIconDetecting); const nextDisabled = activeStep === "provider" - ? providerSubmitLoading || !(providerReady || canSubmitProvider) + ? providerSubmitLoading || (nextProviderSetupStep + ? !isProviderSetupStepReady(providerSetupStep) + : !(providerReady || canSubmitProvider)) : activeStep === "profile" ? !(profileReady || (providerReady && canSubmitProfile)) : !routeReady; - const nextGuidance = onboardingNextGuidance({ - activeStep, - canSubmitProfile, - canSubmitProvider, - profileReady, - providerReady, - providerSubmitLoading, - routeReady, - serviceReady - }); + + useEffect(() => { + if (activeStep !== "provider" || isProviderSetupStepUnlocked(providerSetupStep)) { + return; + } + setProviderSetupStep(getLatestUnlockedProviderSetupStep()); + }, [activeStep, providerCredentialReady, providerIdentityReady, providerModelsReady, providerSetupStep]); + + function isProviderSetupStepReady(step: ProviderSetupStepId): boolean { + switch (step) { + case "provider": + return providerIdentityReady; + case "credentials": + return providerCredentialReady; + case "models": + return providerModelsReady; + case "verify": + return true; + } + } + + function isProviderSetupStepUnlocked(step: ProviderSetupStepId): boolean { + switch (step) { + case "provider": + return true; + case "credentials": + return providerIdentityReady; + case "models": + return providerIdentityReady && providerCredentialReady; + case "verify": + return providerIdentityReady && providerCredentialReady && providerModelsReady; + } + } + + function getLatestUnlockedProviderSetupStep(): ProviderSetupStepId { + return [...providerSetupStepIds].reverse().find(isProviderSetupStepUnlocked) ?? "provider"; + } function goToPreviousStep() { + if (activeStep === "provider" && previousProviderSetupStep) { + setProviderSetupStep(previousProviderSetupStep); + return; + } if (previousStep) { + if (previousStep === "provider") { + setProviderSetupStep(getLatestUnlockedProviderSetupStep()); + } onSelectStep(previousStep); } } @@ -151,6 +199,12 @@ export function OnboardingView({ if (providerSubmitLoading) { return; } + if (nextProviderSetupStep) { + if (isProviderSetupStepReady(providerSetupStep)) { + setProviderSetupStep(nextProviderSetupStep); + } + return; + } if (canSubmitProvider) { const saved = await onSubmitProvider(); if (saved) { @@ -188,10 +242,10 @@ export function OnboardingView({ style={{ transformPerspective: 900 }} transition={shouldReduceMotion ? reducedMotionTransition : { duration: 0.28, ease: motionEase }} > - +
- {previousStep ? ( + {previousStep || previousProviderSetupStep ? (
-
@@ -230,10 +277,16 @@ export function OnboardingView({ connectivityProbe={providerConnectivityProbe} draft={providerDraft} error={providerError} + activeStep={providerSetupStep} mode={providerReady ? "edit" : "add"} onCheck={onCheckProvider} onChange={onChangeProvider} onIconDetectingChange={setProviderIconDetecting} + onSelectStep={(step) => { + if (isProviderSetupStepUnlocked(step)) { + setProviderSetupStep(step); + } + }} probe={providerProbe} probeLoading={providerProbeLoading} providerPlugins={config.providerPlugins ?? []} @@ -284,15 +337,7 @@ export function OnboardingView({
-
-
- {nextGuidance ? ( -
- - {t(nextGuidance)} -
- ) : null} -
+