github-actions[bot]
Deploy demo from GitHub Actions - 2025-12-24 02:23:20
6cdce85
'use client';
import { useState, useEffect, useCallback, useRef } from 'react';
export type WarmupStatus = 'idle' | 'checking' | 'warming' | 'ready' | 'error';
interface WarmupState {
status: WarmupStatus;
message: string;
workers: {
idle: number;
ready?: number;
running: number;
initializing: number;
} | null;
}
interface UseWarmupReturn extends WarmupState {
triggerWarmup: () => Promise<void>;
checkStatus: () => Promise<void>;
}
let warmupInProgress = false;
let lastWarmupTime = 0;
const WARMUP_COOLDOWN = 5000;
/**
* Hook to pre-warm RunPod workers when user lands on the page.
* Automatically triggers warmup on mount and polls for readiness.
*/
export function useWarmup(autoWarmup = true): UseWarmupReturn {
const [state, setState] = useState<WarmupState>({
status: 'idle',
message: '',
workers: null,
});
const mountedRef = useRef(true);
const checkStatus = useCallback(async () => {
try {
const response = await fetch('/api/warmup', { method: 'GET' });
if (response.ok && mountedRef.current) {
const data = await response.json();
if (data.ready) {
setState({
status: 'ready',
message: 'Model ready',
workers: data.workers || null,
});
return;
}
if (data.warming) {
setState({
status: 'warming',
message: 'Model starting...',
workers: data.workers || null,
});
return;
}
if (data.busy) {
setState({
status: 'warming',
message: 'Model busy...',
workers: data.workers || null,
});
return;
}
setState((prev) => ({
...prev,
workers: data.workers || null,
}));
}
} catch {
// Silently fail status checks
}
}, []);
const triggerWarmup = useCallback(async () => {
// Prevent duplicate warmups
const now = Date.now();
if (warmupInProgress || (now - lastWarmupTime) < WARMUP_COOLDOWN) {
await checkStatus();
return;
}
warmupInProgress = true;
lastWarmupTime = now;
setState((prev) => ({ ...prev, status: 'checking', message: 'Checking model status...' }));
try {
const response = await fetch('/api/warmup', { method: 'POST' });
const data = await response.json();
if (!mountedRef.current) return;
if (data.status === 'ready') {
setState({
status: 'ready',
message: 'Model ready',
workers: data.workers || null,
});
} else if (data.status === 'warming') {
setState({
status: 'warming',
message: data.message || 'Starting model...',
workers: data.workers || null,
});
} else if (data.status === 'skipped') {
setState({
status: 'ready',
message: '',
workers: null,
});
} else if (data.status === 'error') {
setState({
status: 'error',
message: data.message || 'Warmup failed',
workers: null,
});
}
} catch (error) {
if (mountedRef.current) {
setState({
status: 'error',
message: error instanceof Error ? error.message : 'Warmup request failed',
workers: null,
});
}
} finally {
warmupInProgress = false;
}
}, [checkStatus]);
useEffect(() => {
mountedRef.current = true;
if (autoWarmup) {
triggerWarmup();
}
return () => {
mountedRef.current = false;
};
}, [autoWarmup, triggerWarmup]);
useEffect(() => {
if (state.status !== 'warming') return;
const pollInterval = setInterval(() => {
checkStatus();
}, 5000);
const timeout = setTimeout(() => {
clearInterval(pollInterval);
}, 180000);
return () => {
clearInterval(pollInterval);
clearTimeout(timeout);
};
}, [state.status, checkStatus]);
return {
...state,
triggerWarmup,
checkStatus,
};
}