Speed up app initialization and improve refresh crashing (#3717)

This commit is contained in:
Zane
2025-07-31 12:41:05 -07:00
committed by GitHub
parent 75a382bcee
commit 3f6a055790
5 changed files with 122 additions and 90 deletions
+31 -9
View File
@@ -8,20 +8,42 @@
// Initialize theme before any content loads // Initialize theme before any content loads
(function() { (function() {
function initializeTheme() { function initializeTheme() {
const useSystemTheme = localStorage.getItem('use_system_theme') === 'true'; try {
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (window.localStorage) {
const savedTheme = localStorage.getItem('theme'); const useSystemTheme = localStorage.getItem('use_system_theme') === 'true';
const isDark = useSystemTheme ? systemPrefersDark : (savedTheme ? savedTheme === 'dark' : systemPrefersDark); const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const savedTheme = localStorage.getItem('theme');
if (isDark) { const isDark = useSystemTheme ? systemPrefersDark : (savedTheme ? savedTheme === 'dark' : systemPrefersDark);
document.documentElement.classList.add('dark');
} else { if (isDark) {
document.documentElement.classList.remove('dark'); document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
} else {
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (systemPrefersDark) {
document.documentElement.classList.add('dark');
}
}
} catch (error) {
console.warn('Failed to initialize theme from localStorage, using system preference:', error);
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (systemPrefersDark) {
document.documentElement.classList.add('dark');
}
} }
} }
// Run immediately // Run immediately
initializeTheme(); initializeTheme();
// Retry after DOM is ready if initial attempt failed
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(initializeTheme, 50);
});
}
})(); })();
</script> </script>
<link href="./src/styles/main.css" rel="stylesheet" /> <link href="./src/styles/main.css" rel="stylesheet" />
+56 -48
View File
@@ -40,6 +40,7 @@ import {
validateConfig, validateConfig,
} from './api/sdk.gen'; } from './api/sdk.gen';
import PermissionSettingsView from './components/settings/permission/PermissionSetting'; import PermissionSettingsView from './components/settings/permission/PermissionSetting';
import { COST_TRACKING_ENABLED } from './updates';
import { type SessionDetails } from './sessions'; import { type SessionDetails } from './sessions';
import ExtensionsView, { ExtensionsViewOptions } from './components/extensions/ExtensionsView'; import ExtensionsView, { ExtensionsViewOptions } from './components/extensions/ExtensionsView';
@@ -855,53 +856,52 @@ export default function App() {
const initializeApp = async () => { const initializeApp = async () => {
try { try {
// Initialize cost database early to pre-load pricing data // Start cost database initialization early (non-blocking) - only if cost tracking is enabled
initializeCostDatabase().catch((error) => { const costDbPromise = COST_TRACKING_ENABLED
console.error('Failed to initialize cost database:', error); ? initializeCostDatabase().catch((error) => {
}); console.error('Failed to initialize cost database:', error);
})
: (() => {
console.log('Cost tracking disabled, skipping cost database initialization');
return Promise.resolve();
})();
await initConfig(); await initConfig();
try { try {
await readAllConfig({ throwOnError: true }); await readAllConfig({ throwOnError: true });
} catch (error) { } catch (error) {
console.warn('Initial config read failed, attempting recovery:', error);
const configVersion = localStorage.getItem('configVersion'); const configVersion = localStorage.getItem('configVersion');
const shouldMigrateExtensions = !configVersion || parseInt(configVersion, 10) < 3; const shouldMigrateExtensions = !configVersion || parseInt(configVersion, 10) < 3;
if (shouldMigrateExtensions) { if (shouldMigrateExtensions) {
await backupConfig({ throwOnError: true }); console.log('Performing extension migration...');
await initConfig();
} else {
// Config appears corrupted, try recovery
console.warn('Config file appears corrupted, attempting recovery...');
try { try {
// First try to validate the config await backupConfig({ throwOnError: true });
try { await initConfig();
await validateConfig({ throwOnError: true }); } catch (migrationError) {
// Config is valid but readAllConfig failed for another reason console.error('Migration failed:', migrationError);
throw new Error('Unable to read config file, it may be malformed'); // Continue with recovery attempts
} catch (validateError) { }
console.log('Config validation failed, attempting recovery...'); }
// Try to recover the config // Try recovery if migration didn't work or wasn't needed
try { console.log('Attempting config recovery...');
const recoveryResult = await recoverConfig({ throwOnError: true }); try {
console.log('Config recovery result:', recoveryResult); // Try to validate first (faster than recovery)
await validateConfig({ throwOnError: true });
// Try to read config again after recovery // If validation passes, try reading again
try { await readAllConfig({ throwOnError: true });
await readAllConfig({ throwOnError: true }); } catch (validateError) {
console.log('Config successfully recovered and loaded'); console.log('Config validation failed, attempting recovery...');
} catch (retryError) { try {
console.warn('Config still corrupted after recovery, reinitializing...'); await recoverConfig({ throwOnError: true });
await initConfig(); await readAllConfig({ throwOnError: true });
} } catch (recoverError) {
} catch (recoverError) { console.warn('Config recovery failed, reinitializing...');
console.warn('Config recovery failed, reinitializing...'); await initConfig();
await initConfig();
}
}
} catch (recoveryError) {
console.error('Config recovery process failed:', recoveryError);
throw new Error('Unable to read config file, it may be malformed');
} }
} }
} }
@@ -912,13 +912,21 @@ export default function App() {
if (provider && model) { if (provider && model) {
try { try {
await initializeSystem(provider as string, model as string, { // Initialize system in parallel with cost database (if enabled)
getExtensions, const initPromises = [
addExtension, initializeSystem(provider as string, model as string, {
}); getExtensions,
addExtension,
}),
];
// Check if we have a recipe config from a deeplink if (COST_TRACKING_ENABLED) {
// But skip navigation if we're ignoring recipe config changes (to prevent conflicts with new window creation) initPromises.push(costDbPromise);
}
await Promise.all(initPromises);
const recipeConfig = window.appConfig.get('recipe');
if ( if (
recipeConfig && recipeConfig &&
typeof recipeConfig === 'object' && typeof recipeConfig === 'object' &&
@@ -974,16 +982,16 @@ export default function App() {
} }
} }
} catch (error) { } catch (error) {
console.error('Error in initialization:', error); console.error('Error in system initialization:', error);
if (error instanceof MalformedConfigError) { if (error instanceof MalformedConfigError) {
throw error; throw error;
} }
// Navigate to welcome route window.location.hash = '#/welcome';
window.history.replaceState({}, '', '/welcome'); window.history.replaceState({}, '', '#/welcome');
} }
} else { } else {
// Navigate to welcome route window.location.hash = '#/welcome';
window.history.replaceState({}, '', '/welcome'); window.history.replaceState({}, '', '#/welcome');
} }
} catch (error) { } catch (error) {
console.error('Fatal error during initialization:', error); console.error('Fatal error during initialization:', error);
+17 -21
View File
@@ -668,34 +668,30 @@ const createChat = async (
// We need to wait for the window to load before we can access localStorage // We need to wait for the window to load before we can access localStorage
mainWindow.webContents.on('did-finish-load', () => { mainWindow.webContents.on('did-finish-load', () => {
const configStr = JSON.stringify(windowConfig).replace(/'/g, "\\'"); const configStr = JSON.stringify(windowConfig).replace(/'/g, "\\'");
// Add error handling and retry logic for localStorage access
mainWindow.webContents mainWindow.webContents
.executeJavaScript( .executeJavaScript(
` `
try { (function() {
if (typeof Storage !== 'undefined' && window.localStorage) { function setConfig() {
localStorage.setItem('gooseConfig', '${configStr}'); try {
} else { if (window.localStorage) {
console.warn('localStorage not available, retrying in 100ms');
setTimeout(() => {
try {
localStorage.setItem('gooseConfig', '${configStr}'); localStorage.setItem('gooseConfig', '${configStr}');
} catch (e) { return true;
console.error('Failed to set localStorage after retry:', e); }
} catch (e) {
console.warn('localStorage access failed:', e);
}
return false;
}
if (!setConfig()) {
setTimeout(() => {
if (!setConfig()) {
console.error('Failed to set localStorage after retry - continuing without localStorage config');
} }
}, 100); }, 100);
} }
} catch (e) { })();
console.error('Failed to access localStorage:', e);
// Retry after a short delay
setTimeout(() => {
try {
localStorage.setItem('gooseConfig', '${configStr}');
} catch (retryError) {
console.error('Failed to set localStorage after retry:', retryError);
}
}, 100);
}
` `
) )
.catch((error) => { .catch((error) => {
+5 -3
View File
@@ -126,9 +126,11 @@ const electronAPI: ElectronAPI = {
// Add fallback to localStorage if config from preload is empty or missing // Add fallback to localStorage if config from preload is empty or missing
if (!config || Object.keys(config).length === 0) { if (!config || Object.keys(config).length === 0) {
try { try {
const storedConfig = localStorage.getItem('gooseConfig'); if (window.localStorage) {
if (storedConfig) { const storedConfig = localStorage.getItem('gooseConfig');
return JSON.parse(storedConfig); if (storedConfig) {
return JSON.parse(storedConfig);
}
} }
} catch (e) { } catch (e) {
console.warn('Failed to parse stored config from localStorage:', e); console.warn('Failed to parse stored config from localStorage:', e);
+13 -9
View File
@@ -151,17 +151,20 @@ export const migrateExtensionsToSettingsV3 = async () => {
console.error('Failed to parse user settings:', error); console.error('Failed to parse user settings:', error);
} }
if (localStorageExtensions.length === 0) {
localStorage.setItem('configVersion', '3');
console.log('No extensions to migrate. Config version set to 3.');
return;
}
const migrationErrors: { name: string; error: unknown }[] = []; const migrationErrors: { name: string; error: unknown }[] = [];
for (const extension of localStorageExtensions) { // Process extensions in parallel for better performance
// NOTE: skip migrating builtin types since there was a format change const migrationPromises = localStorageExtensions
// instead we rely on initializeBundledExtensions & syncBundledExtensions .filter((extension) => extension.type !== 'builtin') // Skip builtins as before
// to handle updating / creating the new builtins to the config.yaml .map(async (extension) => {
// For all other extension types we migrate them to config.yaml
if (extension.type !== 'builtin') {
console.log(`Migrating extension ${extension.name} to config.yaml`); console.log(`Migrating extension ${extension.name} to config.yaml`);
try { try {
// manually import apiAddExtension to set throwOnError true
const query: ExtensionQuery = { const query: ExtensionQuery = {
name: extension.name, name: extension.name,
config: extension, config: extension,
@@ -178,8 +181,9 @@ export const migrateExtensionsToSettingsV3 = async () => {
error: `failed migration with ${JSON.stringify(err)}`, error: `failed migration with ${JSON.stringify(err)}`,
}); });
} }
} });
}
await Promise.allSettled(migrationPromises);
if (migrationErrors.length === 0) { if (migrationErrors.length === 0) {
localStorage.setItem('configVersion', '3'); localStorage.setItem('configVersion', '3');