import { useEffect, useState, useMemo, useRef, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { useConfig } from './ConfigContext'; import { SetupModal } from './SetupModal'; import { startOpenRouterSetup } from '../utils/openRouterSetup'; import { startTetrateSetup } from '../utils/tetrateSetup'; import { startChatGptCodexSetup } from '../utils/chatgptCodexSetup'; import WelcomeGooseLogo from './WelcomeGooseLogo'; import { toastService } from '../toasts'; import { OllamaSetup } from './OllamaSetup'; import ApiKeyTester from './ApiKeyTester'; import { SwitchModelModal } from './settings/models/subcomponents/SwitchModelModal'; import { createNavigationHandler } from '../utils/navigationUtils'; import TelemetrySettings from './settings/app/TelemetrySettings'; import { trackOnboardingStarted, trackOnboardingProviderSelected, trackOnboardingCompleted, trackOnboardingAbandoned, trackOnboardingSetupFailed, } from '../utils/analytics'; import { Goose, OpenRouter, Tetrate, ChatGPT } from './icons'; interface ProviderGuardProps { didSelectProvider: boolean; children: React.ReactNode; } export default function ProviderGuard({ didSelectProvider, children }: ProviderGuardProps) { const { read, upsert } = useConfig(); const navigate = useNavigate(); const [isChecking, setIsChecking] = useState(true); const [hasProvider, setHasProvider] = useState(false); const [showFirstTimeSetup, setShowFirstTimeSetup] = useState(false); const [showOllamaSetup, setShowOllamaSetup] = useState(false); const [userInActiveSetup, setUserInActiveSetup] = useState(false); const [showSwitchModelModal, setShowSwitchModelModal] = useState(false); const [switchModelProvider, setSwitchModelProvider] = useState(null); const onboardingTracked = useRef(false); const scrollContainerRef = useRef(null); const [showScrollIndicator, setShowScrollIndicator] = useState(true); const checkScrollPosition = useCallback(() => { const container = scrollContainerRef.current; if (!container) return; const { scrollTop, scrollHeight, clientHeight } = container; const isNearBottom = scrollTop + clientHeight >= scrollHeight - 50; const canScroll = scrollHeight > clientHeight; setShowScrollIndicator(canScroll && !isNearBottom); }, []); const setView = useMemo(() => createNavigationHandler(navigate), [navigate]); const [openRouterSetupState, setOpenRouterSetupState] = useState<{ show: boolean; title: string; message: string; showRetry: boolean; autoClose?: number; } | null>(null); const [tetrateSetupState, setTetrateSetupState] = useState<{ show: boolean; title: string; message: string; showRetry: boolean; autoClose?: number; } | null>(null); const [chatgptCodexSetupState, setChatgptCodexSetupState] = useState<{ show: boolean; title: string; message: string; showRetry: boolean; autoClose?: number; } | null>(null); const handleTetrateSetup = async () => { trackOnboardingProviderSelected('tetrate'); try { const result = await startTetrateSetup(); if (result.success) { setSwitchModelProvider('tetrate'); setShowSwitchModelModal(true); } else { trackOnboardingSetupFailed('tetrate', result.message); setTetrateSetupState({ show: true, title: 'Setup Failed', message: result.message, showRetry: true, }); } } catch (error) { console.error('Tetrate setup error:', error); trackOnboardingSetupFailed('tetrate', 'unexpected_error'); setTetrateSetupState({ show: true, title: 'Setup Error', message: 'An unexpected error occurred during setup.', showRetry: true, }); } }; const handleChatGptCodexSetup = async () => { trackOnboardingProviderSelected('chatgpt_codex'); try { const result = await startChatGptCodexSetup(); if (result.success) { setSwitchModelProvider('chatgpt_codex'); setShowSwitchModelModal(true); } else { trackOnboardingSetupFailed('chatgpt_codex', result.message); setChatgptCodexSetupState({ show: true, title: 'Setup Failed', message: result.message, showRetry: true, }); } } catch (error) { console.error('ChatGPT Codex setup error:', error); trackOnboardingSetupFailed('chatgpt_codex', 'unexpected_error'); setChatgptCodexSetupState({ show: true, title: 'Setup Error', message: 'An unexpected error occurred during setup.', showRetry: true, }); } }; const handleApiKeySuccess = async (provider: string, _model: string, apiKey: string) => { trackOnboardingProviderSelected('api_key'); const keyName = `${provider.toUpperCase()}_API_KEY`; await upsert(keyName, apiKey, true); await upsert('GOOSE_PROVIDER', provider, false); setSwitchModelProvider(provider); setShowSwitchModelModal(true); }; const handleModelSelected = (model: string) => { if (switchModelProvider) { trackOnboardingCompleted(switchModelProvider, model); } setShowSwitchModelModal(false); setUserInActiveSetup(false); setShowFirstTimeSetup(false); setHasProvider(true); navigate('/', { replace: true }); }; const handleSwitchModelClose = () => { setShowSwitchModelModal(false); }; const handleOpenRouterSetup = async () => { trackOnboardingProviderSelected('openrouter'); try { const result = await startOpenRouterSetup(); if (result.success) { setSwitchModelProvider('openrouter'); setShowSwitchModelModal(true); } else { trackOnboardingSetupFailed('openrouter', result.message); setOpenRouterSetupState({ show: true, title: 'Setup Failed', message: result.message, showRetry: true, }); } } catch (error) { console.error('OpenRouter setup error:', error); trackOnboardingSetupFailed('openrouter', 'unexpected_error'); setOpenRouterSetupState({ show: true, title: 'Setup Error', message: 'An unexpected error occurred during setup.', showRetry: true, }); } }; const handleOllamaComplete = () => { trackOnboardingCompleted('ollama'); setShowOllamaSetup(false); setShowFirstTimeSetup(false); setHasProvider(true); navigate('/', { replace: true }); }; const handleOllamaCancel = () => { trackOnboardingAbandoned('ollama_setup'); setShowOllamaSetup(false); }; const handleRetrySetup = (setupType: 'openrouter' | 'tetrate' | 'chatgpt_codex') => { if (setupType === 'openrouter') { setOpenRouterSetupState(null); handleOpenRouterSetup(); } else if (setupType === 'tetrate') { setTetrateSetupState(null); handleTetrateSetup(); } else { setChatgptCodexSetupState(null); handleChatGptCodexSetup(); } }; const closeSetupModal = (setupType: 'openrouter' | 'tetrate' | 'chatgpt_codex') => { if (setupType === 'openrouter') { setOpenRouterSetupState(null); } else if (setupType === 'tetrate') { setTetrateSetupState(null); } else { setChatgptCodexSetupState(null); } }; useEffect(() => { const checkProvider = async () => { try { const provider = ((await read('GOOSE_PROVIDER', false)) as string) || ''; const hasConfiguredProvider = provider.trim() !== ''; // If user is actively testing keys, don't redirect if (userInActiveSetup) { setHasProvider(false); setShowFirstTimeSetup(true); } else if (hasConfiguredProvider || didSelectProvider) { setHasProvider(true); setShowFirstTimeSetup(false); } else { setHasProvider(false); setShowFirstTimeSetup(true); } } catch (error) { console.error('Error checking provider:', error); toastService.error({ title: 'Configuration Error', msg: 'Failed to check provider configuration.', traceback: error instanceof Error ? error.stack || '' : '', }); setHasProvider(false); setShowFirstTimeSetup(true); } finally { setIsChecking(false); } }; checkProvider(); }, [read, didSelectProvider, userInActiveSetup]); useEffect(() => { if (!isChecking && !hasProvider && showFirstTimeSetup && !onboardingTracked.current) { trackOnboardingStarted(); onboardingTracked.current = true; } }, [isChecking, hasProvider, showFirstTimeSetup]); useEffect(() => { if (!isChecking && !hasProvider && showFirstTimeSetup) { // Check scroll position after content renders const timer = setTimeout(checkScrollPosition, 100); return () => clearTimeout(timer); } return undefined; }, [isChecking, hasProvider, showFirstTimeSetup, checkScrollPosition]); if (isChecking) { return (
); } if (showOllamaSetup) { return ; } if (!hasProvider && showFirstTimeSetup) { return (
{/* Header section */}

Welcome to Goose

Since it’s your first time here, let’s get you set up with an AI provider so goose can work its magic.

{ setUserInActiveSetup(true); }} /> {/* ChatGPT Subscription Card - Full Width */}
Recommended if you have ChatGPT Plus/Pro
ChatGPT Subscription

Use your ChatGPT Plus/Pro subscription for GPT-5 Codex models.

{/* Tetrate Card - Full Width */}
Recommended for new users
Agent Router by Tetrate

Access multiple AI models with automatic setup. Sign up to receive $10 credit.

{/* OpenRouter Card - Full Width */}
{/* Subtle shimmer effect */}
OpenRouter

Access 200+ models with one API. Pay-per-use pricing.

{/* Other providers section */}

Other Providers

Set up additional providers manually through settings.

{/* Scroll indicator - fixed at bottom, hides when scrolled to bottom */} {showScrollIndicator && (
More options below
)} {/* Setup Modals */} {openRouterSetupState?.show && ( handleRetrySetup('openrouter')} onClose={() => closeSetupModal('openrouter')} autoClose={openRouterSetupState.autoClose} /> )} {tetrateSetupState?.show && ( handleRetrySetup('tetrate')} onClose={() => closeSetupModal('tetrate')} autoClose={tetrateSetupState.autoClose} /> )} {chatgptCodexSetupState?.show && ( handleRetrySetup('chatgpt_codex')} onClose={() => closeSetupModal('chatgpt_codex')} autoClose={chatgptCodexSetupState.autoClose} /> )} {showSwitchModelModal && ( )}
); } return <>{children}; }