mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-20 01:09:26 +00:00
248 lines
No EOL
9.6 KiB
TypeScript
248 lines
No EOL
9.6 KiB
TypeScript
import { useEffect, useRef, useCallback } from 'react';
|
|
import { useInstallationStore } from '@/store/installationStore';
|
|
import { useAuthStore } from '@/store/authStore';
|
|
|
|
/**
|
|
* Hook that sets up Electron IPC listeners and handles installation state synchronization
|
|
* This should be called once in your App component or Layout component
|
|
*/
|
|
export const useInstallationSetup = () => {
|
|
const { initState, setInitState, email } = useAuthStore();
|
|
|
|
const hasCheckedOnMount = useRef(false);
|
|
const installationCompleted = useRef(false);
|
|
const backendReady = useRef(false);
|
|
const startInstallation = useInstallationStore(state => state.startInstallation);
|
|
const performInstallation = useInstallationStore(state => state.performInstallation);
|
|
const addLog = useInstallationStore(state => state.addLog);
|
|
const setSuccess = useInstallationStore(state => state.setSuccess);
|
|
const setError = useInstallationStore(state => state.setError);
|
|
const setBackendError = useInstallationStore(state => state.setBackendError);
|
|
const setWaitingBackend = useInstallationStore(state => state.setWaitingBackend);
|
|
const needsBackendRestart = useInstallationStore(state => state.needsBackendRestart);
|
|
const setNeedsBackendRestart = useInstallationStore(state => state.setNeedsBackendRestart);
|
|
|
|
// Shared function to poll backend status
|
|
const startBackendPolling = useCallback(() => {
|
|
console.log('[useInstallationSetup] Starting backend polling');
|
|
|
|
// Immediately check backend status once
|
|
const checkBackendStatus = async () => {
|
|
try {
|
|
const backendPort = await window.electronAPI.getBackendPort();
|
|
if (backendPort && backendPort > 0) {
|
|
console.log('[useInstallationSetup] Backend immediately detected on port:', backendPort);
|
|
|
|
// Verify backend is actually responding
|
|
const response = await fetch(`http://localhost:${backendPort}/health`).catch(() => null);
|
|
if (response && response.ok) {
|
|
console.log('[useInstallationSetup] Backend health check passed immediately');
|
|
backendReady.current = true;
|
|
setSuccess();
|
|
setInitState('done');
|
|
setNeedsBackendRestart(false);
|
|
return true; // Backend is ready, no need to poll
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log('[useInstallationSetup] Initial backend check failed:', error);
|
|
}
|
|
return false; // Backend not ready, need to poll
|
|
};
|
|
|
|
// Check immediately, then start polling if needed
|
|
checkBackendStatus().then((isReady) => {
|
|
if (isReady) {
|
|
console.log('[useInstallationSetup] Backend already ready, skipping polling');
|
|
return;
|
|
}
|
|
|
|
console.log('[useInstallationSetup] Backend not ready, starting polling');
|
|
|
|
// Poll backend status every 2 seconds to ensure we catch when it's ready
|
|
// This is a fallback in case the backend-ready event is missed
|
|
const pollInterval = setInterval(async () => {
|
|
try {
|
|
const backendPort = await window.electronAPI.getBackendPort();
|
|
if (backendPort && backendPort > 0) {
|
|
console.log('[useInstallationSetup] Backend poll detected ready on port:', backendPort);
|
|
|
|
// Verify backend is actually responding
|
|
const response = await fetch(`http://localhost:${backendPort}/health`).catch(() => null);
|
|
if (response && response.ok) {
|
|
console.log('[useInstallationSetup] Backend health check passed');
|
|
clearInterval(pollInterval);
|
|
|
|
if (!backendReady.current) {
|
|
backendReady.current = true;
|
|
setSuccess();
|
|
setInitState('done');
|
|
// Clear the flag after backend is ready
|
|
setNeedsBackendRestart(false);
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log('[useInstallationSetup] Backend poll check failed:', error);
|
|
}
|
|
}, 2000);
|
|
|
|
// Clear polling after 30 seconds to prevent infinite polling
|
|
setTimeout(() => {
|
|
clearInterval(pollInterval);
|
|
}, 30000);
|
|
});
|
|
}, [setSuccess, setInitState, setNeedsBackendRestart]);
|
|
|
|
// Monitor for backend restart after logout
|
|
useEffect(() => {
|
|
// When user logs in after logout, needsBackendRestart will be true
|
|
if (needsBackendRestart && email !== null) {
|
|
console.log('[useInstallationSetup] Detected login after logout, waiting for backend restart');
|
|
|
|
// For account switching, tools are already installed, only backend needs restart
|
|
// So we mark installation as completed and only wait for backend
|
|
installationCompleted.current = true;
|
|
backendReady.current = false;
|
|
|
|
// Set to waiting-backend state
|
|
setWaitingBackend();
|
|
|
|
// Start polling for backend
|
|
startBackendPolling();
|
|
}
|
|
}, [needsBackendRestart, email, setWaitingBackend, startBackendPolling]);
|
|
|
|
|
|
useEffect(() => {
|
|
if (hasCheckedOnMount.current) {
|
|
return;
|
|
}
|
|
|
|
hasCheckedOnMount.current = true;
|
|
|
|
const checkToolInstalled = async () => {
|
|
try {
|
|
const result = await window.ipcRenderer.invoke("check-tool-installed");
|
|
|
|
if (result.success) {
|
|
if (result.isInstalled) {
|
|
console.log('[useInstallationSetup] Tools already installed, waiting for backend');
|
|
installationCompleted.current = true;
|
|
setWaitingBackend();
|
|
|
|
// Start polling for backend when tools are already installed
|
|
startBackendPolling();
|
|
}
|
|
|
|
if (initState !== 'done') {
|
|
if (!result.isInstalled && initState === "permissions") {
|
|
console.log('[useInstallationSetup] Tools not installed and initState is permissions, setting to carousel');
|
|
setInitState("carousel");
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
} catch (error) {
|
|
console.error("[useInstallationSetup] Tool installation check failed:", error);
|
|
return { success: false, error };
|
|
}
|
|
};
|
|
|
|
const checkBackendStatus = async(toolResult?: any) => {
|
|
try {
|
|
const installationStatus = await window.electronAPI.getInstallationStatus();
|
|
|
|
if (installationStatus.success && installationStatus.isInstalling) {
|
|
startInstallation();
|
|
} else if (initState !== 'done' && toolResult) {
|
|
if (toolResult.success && !toolResult.isInstalled) {
|
|
console.log('[useInstallationSetup] Tools missing and not installing. Starting installation...');
|
|
try {
|
|
await performInstallation();
|
|
} catch (installError) {
|
|
console.error('[useInstallationSetup] Installation failed:', installError);
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('[useInstallationSetup] Failed to check installation status:', err);
|
|
}
|
|
}
|
|
|
|
const runInitialChecks = async () => {
|
|
const toolResult = await checkToolInstalled();
|
|
await checkBackendStatus(toolResult);
|
|
};
|
|
|
|
runInitialChecks();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const checkAndSetDone = () => {
|
|
console.log('[useInstallationSetup] Checking readiness - Installation:', installationCompleted.current, 'Backend:', backendReady.current);
|
|
|
|
if (installationCompleted.current && backendReady.current) {
|
|
console.log('[useInstallationSetup] Both installation and backend are ready, setting initState to done');
|
|
setInitState('done');
|
|
}
|
|
};
|
|
|
|
const handleInstallStart = () => {
|
|
installationCompleted.current = false;
|
|
backendReady.current = false;
|
|
startInstallation();
|
|
};
|
|
|
|
const handleInstallLog = (data: { type: string; data: string }) => {
|
|
addLog({
|
|
type: data.type as 'stdout' | 'stderr',
|
|
data: data.data,
|
|
timestamp: new Date(),
|
|
});
|
|
};
|
|
|
|
const handleInstallComplete = (data: { success: boolean; code?: number; error?: string }) => {
|
|
console.log('[useInstallationSetup] Installation complete event received:', data);
|
|
|
|
if (data.success) {
|
|
installationCompleted.current = true;
|
|
console.log('[useInstallationSetup] Installation marked as completed');
|
|
|
|
// setSuccess() will be called in handleBackendReady to prevent premature state change
|
|
checkAndSetDone();
|
|
} else {
|
|
setError(data.error || 'Installation failed');
|
|
}
|
|
};
|
|
|
|
const handleBackendReady = (data: { success: boolean; port?: number; error?: string }) => {
|
|
console.log('[useInstallationSetup] Backend ready event received:', data);
|
|
|
|
if (data.success && data.port) {
|
|
console.log(`[useInstallationSetup] Backend is ready on port ${data.port}`);
|
|
backendReady.current = true;
|
|
console.log('[useInstallationSetup] Backend marked as ready');
|
|
|
|
setSuccess();
|
|
checkAndSetDone();
|
|
} else {
|
|
console.error('[useInstallationSetup] Backend failed to start:', data.error);
|
|
setBackendError(data.error || 'Backend startup failed');
|
|
}
|
|
};
|
|
|
|
window.electronAPI.onInstallDependenciesStart(handleInstallStart);
|
|
window.electronAPI.onInstallDependenciesLog(handleInstallLog);
|
|
window.electronAPI.onInstallDependenciesComplete(handleInstallComplete);
|
|
window.electronAPI.onBackendReady(handleBackendReady);
|
|
|
|
return () => {
|
|
window.electronAPI.removeAllListeners('install-dependencies-start');
|
|
window.electronAPI.removeAllListeners('install-dependencies-log');
|
|
window.electronAPI.removeAllListeners('install-dependencies-complete');
|
|
window.electronAPI.removeAllListeners('backend-ready');
|
|
};
|
|
}, [startInstallation, addLog, setSuccess, setError, setBackendError, setInitState]);
|
|
}; |