import { useState, useCallback, useEffect, useRef, type ReactNode } from 'react'; import { Box, Typography, Button, CircularProgress, Alert, } from '@mui/material'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import GroupAddIcon from '@mui/icons-material/GroupAdd'; import LoginIcon from '@mui/icons-material/Login'; import RocketLaunchIcon from '@mui/icons-material/RocketLaunch'; import { useSessionStore } from '@/store/sessionStore'; import { useAgentStore } from '@/store/agentStore'; import { apiFetch } from '@/utils/api'; import { isInIframe, triggerLogin } from '@/hooks/useAuth'; import { useOrgMembership } from '@/hooks/useOrgMembership'; const HF_ORANGE = '#FF9D00'; const ORG_JOIN_URL = 'https://huggingface.co/organizations/ml-agent-explorers/share/GzPMJUivoFPlfkvFtIqEouZKSytatKQSZT'; // --------------------------------------------------------------------------- // ChecklistStep sub-component // --------------------------------------------------------------------------- type StepStatus = 'completed' | 'active' | 'locked'; interface ChecklistStepProps { stepNumber: number; title: string; description: string; status: StepStatus; lockedReason?: string; actionLabel?: string; onAction?: () => void; actionIcon?: ReactNode; actionHref?: string; loading?: boolean; isLast?: boolean; } function StepIndicator({ status, stepNumber }: { status: StepStatus; stepNumber: number }) { if (status === 'completed') { return ; } return ( {stepNumber} ); } function ChecklistStep({ stepNumber, title, description, status, lockedReason, actionLabel, onAction, actionIcon, actionHref, loading = false, isLast = false, }: ChecklistStepProps) { const btnSx = { px: 3, py: 0.75, fontSize: '0.85rem', fontWeight: 700, textTransform: 'none' as const, borderRadius: '10px', whiteSpace: 'nowrap' as const, textDecoration: 'none', ...(status === 'active' ? { bgcolor: HF_ORANGE, color: '#000', boxShadow: '0 2px 12px rgba(255, 157, 0, 0.25)', '&:hover': { bgcolor: '#FFB340', boxShadow: '0 4px 20px rgba(255, 157, 0, 0.4)' }, } : { bgcolor: 'rgba(255,255,255,0.04)', color: 'var(--muted-text)', '&.Mui-disabled': { bgcolor: 'rgba(255,255,255,0.04)', color: 'var(--muted-text)' }, }), }; return ( {title} {status === 'locked' && lockedReason ? lockedReason : description} {status === 'completed' ? ( Done ) : actionLabel ? ( actionHref ? ( ) : ( ) ) : null} ); } // --------------------------------------------------------------------------- // WelcomeScreen // --------------------------------------------------------------------------- export default function WelcomeScreen() { const { createSession } = useSessionStore(); const { setPlan, clearPanel, user } = useAgentStore(); const [isCreating, setIsCreating] = useState(false); const [error, setError] = useState(null); const inIframe = isInIframe(); const isAuthenticated = !!user?.authenticated; const isDevUser = user?.username === 'dev'; // Iframe: localStorage-based org tracking (no auth token available) const [iframeOrgJoined, setIframeOrgJoined] = useState(() => { try { return localStorage.getItem('hf-agent-org-joined') === '1'; } catch { return false; } }); const joinLinkOpened = useRef(false); // Auto-advance when user returns from org join link (iframe only) useEffect(() => { if (!inIframe) return; const handleVisibility = () => { if (document.visibilityState !== 'visible' || !joinLinkOpened.current) return; joinLinkOpened.current = false; try { localStorage.setItem('hf-agent-org-joined', '1'); } catch { /* ignore */ } setIframeOrgJoined(true); }; document.addEventListener('visibilitychange', handleVisibility); return () => document.removeEventListener('visibilitychange', handleVisibility); }, [inIframe]); const isOrgMember = inIframe ? iframeOrgJoined : !!user?.orgMember; // Poll for org membership once authenticated (skipped in dev mode and iframe) const popupRef = useOrgMembership(isAuthenticated && !isDevUser && !inIframe && !isOrgMember); // ---- Actions ---- const handleJoinOrg = useCallback(() => { if (inIframe) { // Iframe: open link, track via visibilitychange + localStorage joinLinkOpened.current = true; window.open(ORG_JOIN_URL, '_blank', 'noopener,noreferrer'); return; } // Direct: open as popup, auto-close via polling const popup = window.open(ORG_JOIN_URL, 'hf-org-join', 'noopener'); if (popup) { popupRef.current = popup; } else { window.open(ORG_JOIN_URL, '_blank', 'noopener,noreferrer'); } }, [popupRef, inIframe]); const handleStartSession = useCallback(async () => { if (isCreating) return; setIsCreating(true); setError(null); try { const response = await apiFetch('/api/session', { method: 'POST' }); if (response.status === 503) { const data = await response.json(); setError(data.detail || 'Server is at capacity. Please try again later.'); return; } if (response.status === 401) { triggerLogin(); return; } if (!response.ok) { setError('Failed to create session. Please try again.'); return; } const data = await response.json(); createSession(data.session_id); setPlan([]); clearPanel(); } catch { // Redirect may throw — ignore } finally { setIsCreating(false); } }, [isCreating, createSession, setPlan, clearPanel]); // ---- Step status helpers ---- const signInStatus: StepStatus = isAuthenticated ? 'completed' : 'active'; const joinOrgStatus: StepStatus = isOrgMember ? 'completed' : isAuthenticated ? 'active' : 'locked'; const startStatus: StepStatus = isAuthenticated && isOrgMember ? 'active' : 'locked'; // Space URL for iframe "Open ML Intern" step const spaceHost = typeof window !== 'undefined' ? window.location.hostname.includes('.hf.space') ? window.location.origin : 'https://smolagents-ml-intern.hf.space' : ''; return ( {/* Logo */} {/* Title */} ML Intern {/* Description */} Your personal ML agent. It reads papers, finds datasets, trains models, and iterates until the numbers go up. Instructions in. Trained model out. {/* ── Checklist ──────────────────────────────────────────── */} {isDevUser ? ( /* Dev mode: single step */ } onAction={handleStartSession} loading={isCreating} isLast /> ) : inIframe ? ( /* Iframe: 2 steps */ <> } onAction={handleJoinOrg} /> } actionHref={spaceHost} isLast /> ) : ( /* Direct access: 3 steps */ <> } onAction={() => triggerLogin()} /> } onAction={handleJoinOrg} /> } onAction={handleStartSession} loading={isCreating} isLast /> )} {/* Polling hint when waiting for org join */} {isAuthenticated && !isOrgMember && !isDevUser && !inIframe && ( This page updates automatically when you join the organization. )} {/* Error */} {error && ( setError(null)} sx={{ mt: 3, maxWidth: 400, fontSize: '0.8rem', borderColor: HF_ORANGE, color: 'var(--text)', }} > {error} )} {/* Footnote */} Conversations are stored locally in your browser. ); }