Speed up app initialization and improve refresh crashing (#3717)
This commit is contained in:
+31
-9
@@ -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
@@ -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
@@ -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) => {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user