'use client'; import { useEffect, useState, useCallback } from 'react'; import { Loader2, Server, Cpu, Zap, Clock, AlertCircle } from 'lucide-react'; import { clsx } from 'clsx'; import type { StatusResponse } from '@/app/api/status/route'; type LoadingPhase = | 'sending' // Initial request sent | 'cold_start' // Workers starting up | 'initializing' // Model loading | 'processing' // Actively generating | 'streaming'; // Receiving response interface LoadingStatusProps { isLoading: boolean; hasStartedStreaming: boolean; onStatusChange?: (status: StatusResponse | null) => void; } const PHASE_CONFIG: Record = { sending: { icon: Zap, label: 'Sending request...', color: 'text-blue-400', pulseColor: 'bg-blue-400', }, cold_start: { icon: Server, label: 'Starting worker...', color: 'text-amber-400', pulseColor: 'bg-amber-400', }, initializing: { icon: Cpu, label: 'Loading model...', color: 'text-purple-400', pulseColor: 'bg-purple-400', }, processing: { icon: Loader2, label: 'Generating response...', color: 'text-teal-400', pulseColor: 'bg-teal-400', }, streaming: { icon: Zap, label: 'Receiving...', color: 'text-emerald-400', pulseColor: 'bg-emerald-400', }, }; export function LoadingStatus({ isLoading, hasStartedStreaming, onStatusChange }: LoadingStatusProps) { const [phase, setPhase] = useState('sending'); const [elapsedTime, setElapsedTime] = useState(0); const [estimatedWait, setEstimatedWait] = useState(); const [statusMessage, setStatusMessage] = useState(''); // Poll for status while loading const checkStatus = useCallback(async () => { try { const response = await fetch('/api/status'); if (response.ok) { const status: StatusResponse = await response.json(); onStatusChange?.(status); // Map status to phase if (status.status === 'cold_start') { setPhase('cold_start'); setStatusMessage(status.message); } else if (status.status === 'initializing') { setPhase('initializing'); setStatusMessage(status.message); } else if (status.status === 'processing') { setPhase('processing'); setStatusMessage(status.message); } if (status.estimatedWait) { setEstimatedWait(status.estimatedWait); } } } catch { // Silently fail, keep current phase } }, [onStatusChange]); // Start polling when loading starts useEffect(() => { if (!isLoading) { setPhase('sending'); setElapsedTime(0); setEstimatedWait(undefined); setStatusMessage(''); return; } // Initial status check checkStatus(); // Poll every 2 seconds while loading and not streaming const statusInterval = setInterval(() => { if (!hasStartedStreaming) { checkStatus(); } }, 2000); // Track elapsed time const timeInterval = setInterval(() => { setElapsedTime((prev) => prev + 1); }, 1000); return () => { clearInterval(statusInterval); clearInterval(timeInterval); }; }, [isLoading, hasStartedStreaming, checkStatus]); // Update phase based on streaming state useEffect(() => { if (hasStartedStreaming) { setPhase('streaming'); } }, [hasStartedStreaming]); // After 3 seconds without response, likely a cold start useEffect(() => { if (isLoading && !hasStartedStreaming && elapsedTime >= 3 && phase === 'sending') { setPhase('cold_start'); } }, [isLoading, hasStartedStreaming, elapsedTime, phase]); if (!isLoading) return null; const config = PHASE_CONFIG[phase]; const Icon = config.icon; const showEstimate = estimatedWait && phase !== 'streaming'; // Calculate progress percentage const progress = estimatedWait ? Math.min((elapsedTime / estimatedWait) * 100, 95) : undefined; return (
{/* Main status indicator */}
{/* Animated icon */}
{/* Pulse effect for cold start */} {(phase === 'cold_start' || phase === 'initializing') && ( )}
{/* Status text */}
{statusMessage || config.label}
{/* Time indicator */}
{formatTime(elapsedTime)} {showEstimate && ( <> ~{formatTime(Math.max(0, estimatedWait - elapsedTime))} remaining )}
{/* Progress bar for cold start */} {showEstimate && progress !== undefined && (
)} {/* Cold start explanation */} {phase === 'cold_start' && elapsedTime >= 5 && (

Cold start detected.{' '} The model is scaling up from zero. This typically takes 30-60 seconds on first request.

)}
); } function formatTime(seconds: number): string { if (seconds < 60) { return `${seconds}s`; } const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}m ${secs}s`; }