mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-10 04:00:24 +00:00
update
This commit is contained in:
parent
25f4013a78
commit
aa3c39afd2
7 changed files with 94 additions and 126 deletions
|
|
@ -1301,58 +1301,21 @@ async function createWindow() {
|
|||
});
|
||||
});
|
||||
} else {
|
||||
// Installation is complete - ensure initState is set to 'done'
|
||||
log.info('Installation already complete - ensuring initState is done');
|
||||
|
||||
win.webContents.once('dom-ready', () => {
|
||||
if (!win || win.isDestroyed()) {
|
||||
log.warn('Window destroyed before DOM ready - skipping localStorage update');
|
||||
return;
|
||||
}
|
||||
log.info('DOM ready - checking and updating auth-storage to done state');
|
||||
win.webContents.executeJavaScript(`
|
||||
(function() {
|
||||
try {
|
||||
const authStorage = localStorage.getItem('auth-storage');
|
||||
console.log('[ELECTRON DEBUG] Current auth-storage:', authStorage);
|
||||
if (authStorage) {
|
||||
const parsed = JSON.parse(authStorage);
|
||||
console.log('[ELECTRON DEBUG] Parsed state:', parsed.state);
|
||||
if (parsed.state && parsed.state.initState !== 'done') {
|
||||
console.log('[ELECTRON] Updating initState from', parsed.state.initState, 'to done');
|
||||
// Only update the initState field, preserve all other data
|
||||
const updatedStorage = {
|
||||
...parsed,
|
||||
state: {
|
||||
...parsed.state,
|
||||
initState: 'done'
|
||||
}
|
||||
};
|
||||
localStorage.setItem('auth-storage', JSON.stringify(updatedStorage));
|
||||
console.log('[ELECTRON] initState updated to done, reloading page...');
|
||||
return true; // Signal that we need to reload
|
||||
} else {
|
||||
console.log('[ELECTRON DEBUG] initState already done or state missing');
|
||||
}
|
||||
} else {
|
||||
console.log('[ELECTRON DEBUG] No auth-storage found in localStorage');
|
||||
}
|
||||
return false; // No reload needed
|
||||
} catch (e) {
|
||||
console.error('[ELECTRON] Failed to update initState:', e);
|
||||
// Don't modify localStorage if there's an error to prevent data corruption
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
`).then(needsReload => {
|
||||
if (needsReload && win && !win.isDestroyed()) {
|
||||
log.info('Reloading window after localStorage update');
|
||||
win.reload();
|
||||
}
|
||||
}).catch(err => {
|
||||
log.error('Failed to inject script:', err);
|
||||
});
|
||||
});
|
||||
// REMOVED: Previously this block would directly set initState='done' when installation
|
||||
// was already complete, bypassing the backend readiness check.
|
||||
//
|
||||
// This caused a critical bug where:
|
||||
// 1. Frontend would show immediately (initState='done')
|
||||
// 2. Backend would still be starting (10-15 seconds)
|
||||
// 3. Users could interact before backend was ready, causing connection errors
|
||||
//
|
||||
// The proper flow is now handled by useInstallationSetup.ts with dual-check mechanism:
|
||||
// 1. Installation complete event → installationCompleted.current = true
|
||||
// 2. Backend ready event → backendReady.current = true
|
||||
// 3. Only when BOTH are true → setInitState('done')
|
||||
//
|
||||
// This ensures frontend never shows before backend is ready.
|
||||
log.info('Installation already complete - letting useInstallationSetup handle state transitions');
|
||||
}
|
||||
|
||||
// Load content
|
||||
|
|
@ -1386,6 +1349,10 @@ async function createWindow() {
|
|||
}
|
||||
log.info("[DEPS INSTALL] Dependency Success: ", res.message);
|
||||
|
||||
// IMPORTANT: Wait a bit to ensure React components have mounted and registered event listeners
|
||||
// This prevents race condition where events are sent before listeners are ready
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// IMPORTANT: Always send install-dependencies-complete event when installation check succeeds
|
||||
// This includes both cases: actual installation completed AND installation was skipped (already installed)
|
||||
// The frontend needs this event to properly transition from installation screen to main app
|
||||
|
|
|
|||
|
|
@ -49,9 +49,17 @@ export function update(win: Electron.BrowserWindow) {
|
|||
autoUpdater.setFeedURL(feed)
|
||||
if (!app.isPackaged) {
|
||||
console.log('[DEV] setFeedURL:', feed)
|
||||
autoUpdater.checkForUpdates()
|
||||
// In development, check for updates but don't fail if it errors
|
||||
autoUpdater.checkForUpdates().catch(err => {
|
||||
console.log('[DEV] Update check failed (expected in dev environment):', err.message)
|
||||
})
|
||||
}
|
||||
|
||||
// Handle errors globally to prevent crashes
|
||||
autoUpdater.on('error', (error: Error) => {
|
||||
console.error('[AutoUpdater] Update error:', error.message)
|
||||
// Don't crash the app on update errors
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export const CarouselStep: React.FC = () => {
|
|||
const [currentSlide, setCurrentSlide] = useState(0);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [api, setApi] = useState<any>(null);
|
||||
const [isDismissed, setIsDismissed] = useState(false);
|
||||
const videoRefs = useRef<(HTMLVideoElement | null)[]>([]);
|
||||
// listen to carousel change
|
||||
useEffect(() => {
|
||||
|
|
@ -93,6 +94,14 @@ export const CarouselStep: React.FC = () => {
|
|||
}
|
||||
}
|
||||
}, [currentSlide, api]);
|
||||
|
||||
// If carousel is dismissed, don't show anything
|
||||
// The actual transition to 'done' will be handled by useInstallationSetup
|
||||
// when both installation and backend are ready
|
||||
if (isDismissed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-lg w-[1120px] max-lg:w-[100%]">
|
||||
<div className="flex flex-col gap-md ">
|
||||
|
|
@ -136,7 +145,7 @@ export const CarouselStep: React.FC = () => {
|
|||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
<div className="flex justify-between items-center gap-sm">
|
||||
<div className="flex justify-center items-center gap-sm">
|
||||
<div className="flex justify-center items-center gap-6">
|
||||
{carouselItems.map((item, index) => (
|
||||
<div
|
||||
|
|
@ -150,29 +159,6 @@ export const CarouselStep: React.FC = () => {
|
|||
></div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-center items-center gap-sm">
|
||||
<Button
|
||||
onClick={() => setInitState("done")}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
>
|
||||
skip
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (currentSlide < carouselItems.length - 1) {
|
||||
api?.scrollNext(); // not last page, switch to next page
|
||||
} else {
|
||||
setInitState("done"); // last page, execute done logic
|
||||
}
|
||||
}}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
>
|
||||
<div>Next</div>
|
||||
<ArrowRight size={24} className="text-white-100%" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export const InstallDependencies: React.FC = () => {
|
|||
latestLog,
|
||||
error,
|
||||
isInstalling,
|
||||
installationState,
|
||||
retryInstallation,
|
||||
exportLog,
|
||||
} = useInstallationUI();
|
||||
|
|
@ -37,31 +38,20 @@ export const InstallDependencies: React.FC = () => {
|
|||
{/* {isInstalling.toString()} */}
|
||||
<div>
|
||||
<ProgressInstall
|
||||
value={isInstalling ? progress : 100}
|
||||
value={isInstalling || installationState === 'waiting-backend' ? progress : 100}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="flex items-center gap-2 justify-between">
|
||||
<div className="text-text-label text-xs font-normal leading-tight ">
|
||||
{isInstalling ? "System Installing ..." : ""}
|
||||
{isInstalling ? "System Installing ..." : installationState === 'waiting-backend' ? "Starting backend service..." : ""}
|
||||
<span className="pl-2">{latestLog?.data}</span>
|
||||
</div>
|
||||
<TooltipSimple content={`Cannot retry because state is ${error}`} hidden={true}>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
className="mt-1"
|
||||
onClick={retryInstallation}
|
||||
disabled={isInstalling}
|
||||
>
|
||||
<RefreshCcw className="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipSimple>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{initState === "permissions" && <Permissions />}
|
||||
{initState === "carousel" && <CarouselStep />}
|
||||
{initState === "carousel" && installationState !== 'waiting-backend' && <CarouselStep />}
|
||||
</div>
|
||||
</div>
|
||||
{/* error dialog */}
|
||||
|
|
|
|||
|
|
@ -82,9 +82,10 @@ const Layout = () => {
|
|||
// Show install screen if either:
|
||||
// 1. The installation store says to show it (isVisible && not completed)
|
||||
// 2. OR if initState is not 'done' (meaning permissions or carousel should show)
|
||||
const actualShouldShowInstallScreen = shouldShowInstallScreen || initState !== 'done';
|
||||
// 3. OR if waiting for backend (installationState === 'waiting-backend')
|
||||
const actualShouldShowInstallScreen = shouldShowInstallScreen || initState !== 'done' || installationState === 'waiting-backend';
|
||||
|
||||
// Only show main content when installation is complete (initState === 'done')
|
||||
// Only show main content when installation is complete (initState === 'done' AND not waiting for backend)
|
||||
const shouldShowMainContent = !actualShouldShowInstallScreen;
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -11,9 +11,8 @@ export const useInstallationSetup = () => {
|
|||
|
||||
// Use ref to track if initial check is done to prevent repeated checks
|
||||
const hasCheckedOnMount = useRef(false);
|
||||
const isInstalling = useRef(false); // Prevent concurrent installations
|
||||
|
||||
// Track installation and backend readiness
|
||||
// Track installation and backend readiness states
|
||||
const installationCompleted = useRef(false);
|
||||
const backendReady = useRef(false);
|
||||
|
||||
|
|
@ -23,6 +22,12 @@ export const useInstallationSetup = () => {
|
|||
const addLog = useInstallationStore(state => state.addLog);
|
||||
const setSuccess = useInstallationStore(state => state.setSuccess);
|
||||
const setError = useInstallationStore(state => state.setError);
|
||||
const setWaitingBackend = useInstallationStore(state => state.setWaitingBackend);
|
||||
|
||||
// REMOVED: Don't reset initState from 'done' to 'carousel'
|
||||
// Instead, we'll use installationState to control visibility in Layout component
|
||||
// When tools are already installed, we set installationState to 'waiting-backend'
|
||||
// which will show progress bar + text without showing carousel slides
|
||||
|
||||
// Check tool installation status on mount - but only during setup phase
|
||||
useEffect(() => {
|
||||
|
|
@ -37,18 +42,18 @@ export const useInstallationSetup = () => {
|
|||
try {
|
||||
const result = await window.ipcRenderer.invoke("check-tool-installed");
|
||||
|
||||
// Only perform tool check during setup phase (permissions or carousel)
|
||||
// Once user is in 'done' state (main app), don't check again
|
||||
// This prevents unexpected navigation away from the main app
|
||||
if (initState !== 'done') {
|
||||
if (result.success) {
|
||||
// REMOVED: Don't automatically set to 'done' even if tools are installed
|
||||
// We need to wait for proper installation complete + backend ready events
|
||||
// if (result.isInstalled && initState === "carousel") {
|
||||
// console.log('[useInstallationSetup] Tools installed but initState is carousel, setting to done');
|
||||
// setInitState("done");
|
||||
// }
|
||||
if (result.success) {
|
||||
// If tools are already installed, mark installation as completed
|
||||
// This handles the app restart scenario where tools were installed previously
|
||||
if (result.isInstalled) {
|
||||
console.log('[useInstallationSetup] Tools already installed, waiting for backend');
|
||||
installationCompleted.current = true;
|
||||
setWaitingBackend(); // Show "waiting for backend" state (progress bar + text, no carousel)
|
||||
}
|
||||
|
||||
// Only perform state transitions during setup phase (permissions or carousel)
|
||||
// Once user is in 'done' state (main app), don't change initState
|
||||
if (initState !== 'done') {
|
||||
if (!result.isInstalled && initState === "permissions") {
|
||||
// If tools are NOT installed and we're in permissions state, set to carousel
|
||||
console.log('[useInstallationSetup] Tools not installed and initState is permissions, setting to carousel');
|
||||
|
|
@ -72,15 +77,12 @@ export const useInstallationSetup = () => {
|
|||
startInstallation();
|
||||
} else if (initState !== 'done' && toolResult) {
|
||||
// Use the tool result from the previous check to avoid duplicate API calls
|
||||
if (toolResult.success && !toolResult.isInstalled && !isInstalling.current) {
|
||||
if (toolResult.success && !toolResult.isInstalled) {
|
||||
console.log('[useInstallationSetup] Tools missing and not installing. Starting installation...');
|
||||
isInstalling.current = true; // Set flag to prevent concurrent installations
|
||||
try {
|
||||
await performInstallation();
|
||||
} catch (installError) {
|
||||
console.error('[useInstallationSetup] Installation failed:', installError);
|
||||
} finally {
|
||||
isInstalling.current = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +103,7 @@ export const useInstallationSetup = () => {
|
|||
|
||||
// Setup Electron IPC listeners (only once)
|
||||
useEffect(() => {
|
||||
// Helper function to check if both conditions are met
|
||||
// Helper function to check if both installation and backend are ready
|
||||
const checkAndSetDone = () => {
|
||||
console.log('[useInstallationSetup] Checking readiness - Installation:', installationCompleted.current, 'Backend:', backendReady.current);
|
||||
|
||||
|
|
@ -113,13 +115,10 @@ export const useInstallationSetup = () => {
|
|||
|
||||
// Electron IPC event handlers
|
||||
const handleInstallStart = () => {
|
||||
if (!isInstalling.current) {
|
||||
isInstalling.current = true;
|
||||
// Reset states when installation starts
|
||||
installationCompleted.current = false;
|
||||
backendReady.current = false;
|
||||
startInstallation();
|
||||
}
|
||||
// Reset flags when installation starts
|
||||
installationCompleted.current = false;
|
||||
backendReady.current = false;
|
||||
startInstallation();
|
||||
};
|
||||
|
||||
const handleInstallLog = (data: { type: string; data: string }) => {
|
||||
|
|
@ -132,13 +131,16 @@ export const useInstallationSetup = () => {
|
|||
|
||||
const handleInstallComplete = (data: { success: boolean; code?: number; error?: string }) => {
|
||||
console.log('[useInstallationSetup] Installation complete event received:', data);
|
||||
isInstalling.current = false;
|
||||
|
||||
if (data.success) {
|
||||
setSuccess();
|
||||
installationCompleted.current = true;
|
||||
console.log('[useInstallationSetup] Installation marked as completed');
|
||||
// Check if we can transition to done
|
||||
|
||||
// Don't call setSuccess() yet if we're still waiting for backend
|
||||
// setSuccess() will be called in handleBackendReady when backend is ready
|
||||
// This prevents installationState from changing from 'waiting-backend' to 'completed' prematurely
|
||||
|
||||
// Only set initState to done if backend is also ready
|
||||
checkAndSetDone();
|
||||
} else {
|
||||
setError(data.error || 'Installation failed');
|
||||
|
|
@ -149,9 +151,14 @@ export const useInstallationSetup = () => {
|
|||
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 on port:', data.port);
|
||||
// Check if we can transition to done
|
||||
console.log('[useInstallationSetup] Backend marked as ready');
|
||||
|
||||
// Mark installation as completed (changes state from 'waiting-backend' to 'completed')
|
||||
setSuccess();
|
||||
|
||||
// Only set initState to done if installation is also completed
|
||||
checkAndSetDone();
|
||||
} else {
|
||||
console.error('[useInstallationSetup] Backend failed to start:', data.error);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ import { create } from 'zustand';
|
|||
import { subscribeWithSelector } from 'zustand/middleware';
|
||||
|
||||
// Define all possible installation states
|
||||
export type InstallationState =
|
||||
export type InstallationState =
|
||||
| 'idle'
|
||||
| 'checking-permissions'
|
||||
| 'showing-carousel'
|
||||
| 'showing-carousel'
|
||||
| 'installing'
|
||||
| 'waiting-backend' // New state: tools installed, waiting for backend to be ready
|
||||
| 'error'
|
||||
| 'completed';
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ interface InstallationStoreState {
|
|||
addLog: (log: InstallationLog) => void;
|
||||
setSuccess: () => void;
|
||||
setError: (error: string) => void;
|
||||
setWaitingBackend: () => void;
|
||||
retryInstallation: () => void;
|
||||
completeSetup: () => void;
|
||||
updateProgress: (progress: number) => void;
|
||||
|
|
@ -96,7 +98,14 @@ export const useInstallationStore = create<InstallationStoreState>()(
|
|||
},
|
||||
],
|
||||
})),
|
||||
|
||||
|
||||
setWaitingBackend: () =>
|
||||
set({
|
||||
state: 'waiting-backend',
|
||||
progress: 80,
|
||||
isVisible: true,
|
||||
}),
|
||||
|
||||
retryInstallation: () => {
|
||||
set({
|
||||
...initialState,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue