Files
OllaHub/hooks/use-ollama-check.ts
2026-02-22 14:06:01 -03:00

224 lines
6.5 KiB
TypeScript

import { useState, useEffect, useRef, useCallback } from 'react';
import { invoke } from '@tauri-apps/api/core';
export type OllamaStatus = 'checking' | 'not_installed' | 'installed_stopped' | 'running';
interface UseOllamaCheckReturn {
status: OllamaStatus;
isChecking: boolean;
check: () => Promise<void>;
startPolling: () => void;
stopPolling: () => void;
reset: () => void;
elapsedTime: number;
}
const INITIAL_INTERVAL = 2000; // 2 segundos
const MAX_INTERVAL = 30000; // 30 segundos
const BACKOFF_MULTIPLIER = 1.5;
const TOTAL_TIMEOUT = 300000; // 5 minutos
export function useOllamaCheck(): UseOllamaCheckReturn {
const [status, setStatus] = useState<OllamaStatus>('not_installed');
const [isChecking, setIsChecking] = useState(false);
const [elapsedTime, setElapsedTime] = useState(0);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const startTimeRef = useRef<number | null>(null);
const currentIntervalRef = useRef<number>(INITIAL_INTERVAL);
const attemptCountRef = useRef<number>(0);
const isPollingActiveRef = useRef<boolean>(false);
const elapsedTimerRef = useRef<NodeJS.Timeout | null>(null);
const performCheck = useCallback(async (): Promise<boolean> => {
try {
const result = await invoke<{
installed: boolean;
running: boolean;
status: string;
}>('check_ollama_full');
const newStatus = result.status as OllamaStatus;
setStatus(newStatus);
// Se Ollama está rodando, polling deve parar
if (newStatus === 'running' && result.installed && result.running) {
return true; // Sucesso, parar polling
}
return false; // Continua polling
} catch (error) {
console.error('Failed to check ollama:', error);
// Se falhar, tentar verificação robusta como fallback
try {
const isInstalledAndRunning = await invoke<boolean>('verify_ollama_integrity_command');
if (isInstalledAndRunning) {
setStatus('running');
return true; // Sucesso, parar polling
}
// Se verify_ollama_integrity retornou false, pode ser instalado mas não rodando
// ou não instalado. Verificar separadamente para determinar status exato
const installed = await invoke<boolean>('check_ollama_installed');
if (!installed) {
setStatus('not_installed');
return false; // Continua polling (não instalado ainda)
}
const running = await invoke<boolean>('check_ollama_running');
if (!running) {
setStatus('installed_stopped');
return false; // Continua polling (instalado mas não rodando)
}
setStatus('running');
return true; // Sucesso, parar polling
} catch (fallbackError) {
console.error('Fallback check also failed:', fallbackError);
setStatus('not_installed');
return false; // Continua polling
}
}
}, []);
const startPolling = useCallback(() => {
if (isPollingActiveRef.current) {
return; // Já está rodando
}
isPollingActiveRef.current = true;
setIsChecking(true);
startTimeRef.current = Date.now();
attemptCountRef.current = 0;
currentIntervalRef.current = INITIAL_INTERVAL;
// Atualizar tempo decorrido a cada segundo
elapsedTimerRef.current = setInterval(() => {
if (startTimeRef.current) {
const elapsed = Math.floor((Date.now() - startTimeRef.current) / 1000);
setElapsedTime(elapsed);
// Verificar timeout total
if (elapsed * 1000 >= TOTAL_TIMEOUT) {
setIsChecking(false);
isPollingActiveRef.current = false;
if (elapsedTimerRef.current) {
clearInterval(elapsedTimerRef.current);
elapsedTimerRef.current = null;
}
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return;
}
}
}, 1000);
// Primeira verificação imediata
performCheck().then((shouldStop) => {
if (shouldStop) {
stopPolling();
return;
}
// Iniciar polling com exponential backoff
const scheduleNext = () => {
if (!isPollingActiveRef.current) {
return;
}
attemptCountRef.current += 1;
// Calcular próximo intervalo com exponential backoff
const nextInterval = Math.min(
INITIAL_INTERVAL * Math.pow(BACKOFF_MULTIPLIER, attemptCountRef.current - 1),
MAX_INTERVAL
);
currentIntervalRef.current = nextInterval;
intervalRef.current = setTimeout(async () => {
if (!isPollingActiveRef.current) {
return;
}
const shouldStop = await performCheck();
if (shouldStop) {
stopPolling();
} else {
scheduleNext();
}
}, nextInterval);
};
scheduleNext();
});
// Timeout total
timeoutRef.current = setTimeout(() => {
if (isPollingActiveRef.current) {
console.warn('Ollama check polling timeout após 5 minutos');
stopPolling();
}
}, TOTAL_TIMEOUT);
}, [performCheck]);
const stopPolling = useCallback(() => {
isPollingActiveRef.current = false;
setIsChecking(false);
if (intervalRef.current) {
clearTimeout(intervalRef.current);
intervalRef.current = null;
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
if (elapsedTimerRef.current) {
clearInterval(elapsedTimerRef.current);
elapsedTimerRef.current = null;
}
}, []);
const check = useCallback(async () => {
// Forçar verificação manual
await performCheck();
}, [performCheck]);
const reset = useCallback(() => {
stopPolling();
currentIntervalRef.current = INITIAL_INTERVAL;
attemptCountRef.current = 0;
setElapsedTime(0);
startTimeRef.current = null;
startPolling();
}, [stopPolling, startPolling]);
// Limpar polling ao desmontar
useEffect(() => {
return () => {
stopPolling();
};
}, [stopPolling]);
// Parar polling automaticamente quando status for 'running'
useEffect(() => {
if (status === 'running' && isPollingActiveRef.current) {
stopPolling();
}
}, [status, stopPolling]);
return {
status,
isChecking,
check,
startPolling,
stopPolling,
reset,
elapsedTime,
};
}